notification manager for bsky
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

build a reviewable Bluesky notification manager

Rebuild noti as a Bun + TypeScript code-mode prototype. The model writes
SDK mutation code against a narrow typed surface, the user reviews it,
and the server verifies the outcome against live Bluesky state.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

+2713 -5344
+4
.env.example
··· 1 1 ATPROTO_HANDLE=your.handle 2 2 ATPROTO_PASSWORD=your-app-password 3 3 ATPROTO_PDS=https://bsky.social 4 + ANTHROPIC_API_KEY=your-anthropic-api-key 5 + ANTHROPIC_MODEL=claude-sonnet-4-5 6 + SQLITE_PATH=data/noti-ts.db 7 + PORT=8000
+3 -4
.gitignore
··· 8 8 9 9 # Virtual environments 10 10 .venv 11 + node_modules 11 12 12 13 # Secrets 13 14 .env 14 - .session 15 - prompt.txt 16 - fixtures/ 17 15 .playwright-cli/ 18 - data/preferences.json 16 + .playwright-local/ 17 + playwright-local-runtime/ 19 18 data/ 20 19 notes/
-1
.python-version
··· 1 - 3.14
-111
DESIGN.md
··· 1 - # design 2 - 3 - ## thesis 4 - 5 - `noti` is an agentic inbox for Bluesky. 6 - 7 - The core move is simple: do not make the user mentally group a chronological notification pile every time they look at it. Poll the unread batch, compress it into a few higher-level feed items, and let the user jump back to Bluesky when they want the original thread or profile. 8 - 9 - ## architecture 10 - 11 - ```mermaid 12 - flowchart LR 13 - subgraph Bluesky 14 - API["notification + record APIs"] 15 - end 16 - 17 - subgraph noti 18 - POLL["Poller + normalizer"] 19 - PREFS["Preferences store"] 20 - GEN["Feed generator"] 21 - RENDER["Server-rendered UI"] 22 - end 23 - 24 - USER["Browser"] 25 - LLM["Claude"] 26 - 27 - API --> POLL 28 - PREFS --> POLL 29 - POLL -->|"unread notifications"| GEN 30 - PREFS --> GEN 31 - GEN -->|"0-4 feed items + briefing"| RENDER 32 - USER -->|"view + edit preferences"| RENDER 33 - RENDER --> USER 34 - GEN --> LLM 35 - LLM --> GEN 36 - USER -->|"open in Bluesky"| API 37 - ``` 38 - 39 - ## runtime flow 40 - 41 - 1. `poller.py` logs into Bluesky and polls notifications on an interval. 42 - 2. Notifications are normalized into plain dicts with actor info, text, URLs, thread keys, and resolved subject context for likes/reposts. 43 - 3. Preferences are loaded from disk and included in the regeneration key, so preference changes force a new feed. 44 - 4. `agent.py` sends the unread batch, preferences, and active-user context to Claude. 45 - 5. Claude returns a briefing and a small set of feed items. 46 - 6. The server validates the result, constrains links to real source URLs, derives avatar stacks, and renders the cards. 47 - 48 - ## what the model sees 49 - 50 - Each unread notification includes: 51 - 52 - - reason metadata like `reply`, `mention`, `like`, or `follow` 53 - - actor identity and profile URL 54 - - post text when present 55 - - subject text and subject URL for resolved likes/reposts 56 - - thread structure via `reply_root_uri`, `reply_parent_uri`, and `thread_key` 57 - - candidate URLs that are safe to link back to 58 - 59 - That gives the model enough structure to do more than just collapse identical events. It can turn a burst of related activity into one situation, or decide that one prolific poster should become a single “posting streak” item. 60 - 61 - ## responsibilities 62 - 63 - The model is responsible for: 64 - 65 - - grouping related notifications into a few meaningful situations 66 - - omitting low-value or explicitly unwanted activity 67 - - writing the top briefing 68 - - choosing internal priority for ordering 69 - - scaling the abstraction level to the size of the unread batch 70 - 71 - The server is responsible for: 72 - 73 - - polling and caching live Bluesky data 74 - - subject resolution and thread metadata 75 - - durable preferences 76 - - constraining links to real candidate URLs from the source notifications 77 - - limiting the UI to a small number of items 78 - - rendering the final feed 79 - - advancing the seen cursor when the user chooses `mark all read` 80 - - gating executable actions against Bluesky state before they are rendered 81 - 82 - ## current product boundaries 83 - 84 - - single-user: the deployed app is still backed by one account 85 - - generated inbox, not full client: history and detailed browsing stay in Bluesky 86 - - mostly read-oriented: `mark all read` advances the seen cursor 87 - - limited executable actions: account mute and activity unsubscribe are live because 88 - Bluesky exposes readable state for them; thread mute is intentionally not offered 89 - because there is no corresponding list/get endpoint for muted threads 90 - - no long-term semantic memory: each generation mostly reasons from the current unread batch 91 - 92 - ## why this shape 93 - 94 - Bluesky’s notification UI is chronological. That works at low volume, but once related activity piles up, the user has to do the grouping and prioritization mentally every time. 95 - 96 - `noti` tries to solve exactly one problem: turn the unread pile into something interpretable at a glance. 97 - 98 - That is also why the app does not try to recreate the whole Bluesky client. The system is better when it stays narrow: ingest, compress, hand off, and offer one clear inbox-clearing action. 99 - 100 - ## known weak spots 101 - 102 - - latency is still visible on cold generations and preference-triggered refreshes 103 - - canonical link selection is imperfect when one item spans several distinct posts 104 - - the current model can still be too literal under very noisy conditions 105 - - grouped items that genuinely span many destinations still do not have a great affordance 106 - 107 - ## obvious extensions 108 - 109 - 1. execute more suggested actions with explicit confirmation 110 - 2. improve grouping and link selection for very high-volume batches 111 - 3. add a lightweight sense of continuity between refreshes
-28
Dockerfile
··· 1 - # syntax=docker/dockerfile:1.9 2 - FROM python:3.14-slim AS builder 3 - 4 - COPY --from=ghcr.io/astral-sh/uv:latest /uv /usr/local/bin/uv 5 - 6 - ENV UV_LINK_MODE=copy \ 7 - UV_COMPILE_BYTECODE=1 \ 8 - UV_PYTHON_DOWNLOADS=never \ 9 - UV_PYTHON=python3.14 \ 10 - UV_PROJECT_ENVIRONMENT=/app 11 - 12 - WORKDIR /src 13 - COPY pyproject.toml uv.lock README.md ./ 14 - COPY src/ ./src/ 15 - 16 - RUN --mount=type=cache,target=/root/.cache/uv \ 17 - uv sync --frozen --no-dev --no-editable 18 - 19 - FROM python:3.14-slim 20 - COPY --from=builder /app /app 21 - COPY src/noti/templates/ /app/templates/ 22 - 23 - ENV PATH="/app/bin:$PATH" 24 - ENV PYTHONUNBUFFERED=1 25 - WORKDIR /app 26 - 27 - EXPOSE 8080 28 - CMD ["python", "-m", "uvicorn", "noti.main:app", "--host", "0.0.0.0", "--port", "8080"]
+26 -44
README.md
··· 1 - # [noti.fly.dev](https://noti.fly.dev) 1 + # noti 2 2 3 - `noti` is an agentic Bluesky inbox. Instead of rendering raw notification rows, it compresses the unread pile into `0..4` higher-level feed items. 3 + `noti` is a Bluesky notification manager. It looks at the live inbox, suggests a few actions to reduce noise, and lets the user ask for a specific action in plain language. 4 4 5 - ## what i built 5 + `noti` currently offers a small set of notification-management mutations against the live Bluesky API: 6 6 7 - - a live Bluesky notification app that polls unread notifications 8 - - a feed generator that turns them into a few situation-level cards 9 - - freeform preferences that steer what gets surfaced 10 - - one real inbox action: `mark all read` 7 + - mute or unmute an account 8 + - turn post notifications on or off for a specific account 9 + - turn reply notifications on or off for a specific account 10 + - mark notifications seen 11 11 12 - ## how to run it 12 + The main design choice is code mode. The server gives the model live state plus a constrained SDK surface, the model writes the mutation code, the user can review it, and the server re-reads Bluesky state afterward before claiming success. 13 13 14 - install `uv`: 14 + When a direct request names someone outside the current inbox context, `noti` first tries to resolve that actor host-side. If the name is too ambiguous, it asks for a handle instead of guessing. 15 15 16 - ```bash 17 - curl -LsSf https://astral.sh/uv/install.sh | sh 18 - ``` 19 - 20 - then: 16 + ## running it 21 17 22 18 ```bash 23 - uv sync 24 19 cp .env.example .env 25 20 just dev 26 21 ``` 27 22 28 - set these env vars in `.env`: 23 + Set these env vars in `.env`: 24 + 29 25 - `ATPROTO_HANDLE` 30 26 - `ATPROTO_PASSWORD` 31 27 - `ANTHROPIC_API_KEY` 32 28 33 29 `ATPROTO_PDS` is optional and defaults to `https://bsky.social`. 34 30 35 - ## deploy on fly 36 - 37 - create the app and persistent volume: 38 - 39 - ```bash 40 - fly apps create noti 41 - fly volumes create data --region ord --size 1 -a noti 42 - ``` 31 + The app serves on `http://127.0.0.1:8000`. 43 32 44 - set the required secrets: 33 + Other useful commands: 45 34 46 - ```bash 47 - fly secrets set \ 48 - ATPROTO_HANDLE=... \ 49 - ATPROTO_PASSWORD=... \ 50 - ANTHROPIC_API_KEY=... \ 51 - -a noti 52 - ``` 35 + - `just start` 36 + - `just check` 37 + - `just lint` 53 38 54 - then deploy: 39 + ## why this scope 55 40 56 - ```bash 57 - fly deploy 58 - ``` 41 + The assignment is about helping a user manage notifications, not about rebuilding the full Bluesky client. I aimed for one thing done well: a reviewable action queue that can actually change live notification state and report the result honestly. 59 42 60 - the volume is required because preferences are persisted at `/data/preferences.json`. 43 + That led to two constraints: 61 44 62 - ## why i scoped it this way 45 + - keep the mutation surface small enough to verify cleanly 46 + - keep the UI centered on concrete actions instead of feed narration 63 47 64 - i focused on one problem: making a noisy Bluesky inbox interpretable at a glance. the app stays narrow on purpose: poll, compress, suggest a next step, and hand the user back to Bluesky when they want the original thread or profile. 48 + ## next 65 49 66 - ## what i would do next 67 - 68 - - execute more suggested actions with explicit confirmation 69 - - improve grouping and link selection for very high-volume bursts 70 - - add a lightweight sense of continuity between refreshes 50 + - expand from per-actor activity subscriptions into the broader global notification-preferences surface 51 + - keep widening the code-mode SDK surface only when each added lever has a clear verification story 52 + - keep tightening the UI around clarity, hierarchy, and reversible controls
+12
biome.json
··· 1 + { 2 + "$schema": "https://biomejs.dev/schemas/2.4.12/schema.json", 3 + "files": { 4 + "includes": ["src/**/*.ts", "scripts/**/*.ts"] 5 + }, 6 + "linter": { 7 + "enabled": true, 8 + "rules": { 9 + "recommended": true 10 + } 11 + } 12 + }
bun.lockb

This is a binary file and will not be displayed.

-38
fly.toml
··· 1 - app = "noti" 2 - primary_region = "ord" 3 - 4 - [build] 5 - dockerfile = "Dockerfile" 6 - 7 - [deploy] 8 - strategy = "rolling" 9 - 10 - [[vm]] 11 - size = "shared-cpu-1x" 12 - memory = "512mb" 13 - 14 - [env] 15 - PORT = "8080" 16 - POLL_INTERVAL = "30" 17 - PREFERENCES_PATH = "/data/preferences.json" 18 - DEBUG = "true" 19 - 20 - [[mounts]] 21 - source = "data" 22 - destination = "/data" 23 - 24 - [http_service] 25 - internal_port = 8080 26 - force_https = true 27 - auto_stop_machines = false 28 - auto_start_machines = false 29 - min_machines_running = 1 30 - max_machines_running = 1 31 - 32 - [[http_service.checks]] 33 - interval = "30s" 34 - grace_period = "15s" 35 - method = "GET" 36 - path = "/health" 37 - protocol = "http" 38 - timeout = "10s"
+16 -5
justfile
··· 1 + set dotenv-load := true 2 + 3 + default: 4 + @just --list 5 + 6 + install: 7 + bun install 8 + 1 9 dev: 2 - uv run uvicorn noti.main:app --reload 10 + bun run dev 11 + 12 + start: 13 + bun run start 3 14 4 - run: 5 - uv run uvicorn noti.main:app 15 + check: 16 + bun run check 6 17 7 - deploy: 8 - fly deploy --remote-only 18 + lint: 19 + bun run lint
+22
package.json
··· 1 + { 2 + "name": "noti", 3 + "version": "0.0.0", 4 + "private": true, 5 + "type": "module", 6 + "scripts": { 7 + "dev": "bun --watch src/server.ts", 8 + "start": "bun src/server.ts", 9 + "check": "tsc --noEmit", 10 + "lint": "biome lint src scripts", 11 + "lint:fix": "biome check --write src scripts" 12 + }, 13 + "dependencies": { 14 + "@anthropic-ai/sdk": "^0.89.0", 15 + "@atproto/api": "^0.19.8" 16 + }, 17 + "devDependencies": { 18 + "@biomejs/biome": "^2.4.12", 19 + "@types/bun": "^1.3.12", 20 + "typescript": "^6.0.2" 21 + } 22 + }
-21
pyproject.toml
··· 1 - [project] 2 - name = "noti" 3 - version = "0.1.0" 4 - description = "notification triage agent for bluesky" 5 - readme = "README.md" 6 - authors = [ 7 - { name = "zzstoatzz", email = "thrast36@gmail.com" } 8 - ] 9 - requires-python = ">=3.14" 10 - dependencies = [ 11 - "atproto>=0.0.65", 12 - "fastapi>=0.135.3", 13 - "jinja2>=3.1.6", 14 - "pydantic-ai>=1.81.0", 15 - "pydantic-settings>=2.13.1", 16 - "uvicorn[standard]>=0.44.0", 17 - ] 18 - 19 - [build-system] 20 - requires = ["uv_build>=0.9.2,<0.10.0"] 21 - build-backend = "uv_build"
-275
scripts/activity_fixtures.py
··· 1 - #!/usr/bin/env python 2 - """Manage Bluesky activity-subscription fixtures for demo prep. 3 - 4 - Examples: 5 - uv run scripts/activity_fixtures.py status --file scripts/demo-accounts.txt 6 - uv run scripts/activity_fixtures.py status --all 7 - uv run scripts/activity_fixtures.py subscribe --file scripts/demo-accounts.txt --mode both 8 - uv run scripts/activity_fixtures.py subscribe --handles "alice.example,bob.example" --mode replies 9 - uv run scripts/activity_fixtures.py unsubscribe --file scripts/demo-accounts.txt 10 - """ 11 - 12 - from __future__ import annotations 13 - 14 - import argparse 15 - from dataclasses import dataclass 16 - from pathlib import Path 17 - 18 - from atproto import Client 19 - 20 - from noti.config import settings 21 - 22 - 23 - DEFAULT_FIXTURE_FILE = Path("scripts/demo-accounts.txt") 24 - 25 - 26 - @dataclass 27 - class ActivitySubscription: 28 - did: str 29 - handle: str 30 - display_name: str | None 31 - post: bool | None 32 - reply: bool | None 33 - 34 - 35 - def _normalize_handle(value: str) -> str: 36 - return value.strip().lstrip("@") 37 - 38 - 39 - def _load_handles(args: argparse.Namespace) -> list[str]: 40 - handles: list[str] = [] 41 - if args.handles: 42 - handles.extend(_normalize_handle(part) for part in args.handles.split(",")) 43 - if args.file: 44 - path = Path(args.file) 45 - if not path.exists(): 46 - raise SystemExit(f"fixture file not found: {path}") 47 - for line in path.read_text().splitlines(): 48 - stripped = line.strip() 49 - if not stripped or stripped.startswith("#"): 50 - continue 51 - handles.append(_normalize_handle(stripped)) 52 - unique: list[str] = [] 53 - seen: set[str] = set() 54 - for handle in handles: 55 - if handle and handle not in seen: 56 - seen.add(handle) 57 - unique.append(handle) 58 - return unique 59 - 60 - 61 - def _build_client() -> Client: 62 - client = Client(base_url=settings.atproto_pds) 63 - client.login(settings.atproto_handle, settings.atproto_password) 64 - return client 65 - 66 - 67 - def _resolve_did(client: Client, handle_or_did: str) -> str: 68 - if handle_or_did.startswith("did:"): 69 - return handle_or_did 70 - resolved = client.resolve_handle(handle_or_did) 71 - return resolved.did 72 - 73 - 74 - def _parse_subscription(profile: object) -> ActivitySubscription: 75 - subscription = ( 76 - getattr(profile, "activity_subscription", None) 77 - or getattr(profile, "activitySubscription", None) 78 - or {} 79 - ) 80 - post = getattr(subscription, "post", None) 81 - reply = getattr(subscription, "reply", None) 82 - if isinstance(subscription, dict): 83 - post = subscription.get("post") 84 - reply = subscription.get("reply") 85 - return ActivitySubscription( 86 - did=getattr(profile, "did", ""), 87 - handle=getattr(profile, "handle", ""), 88 - display_name=getattr(profile, "display_name", None) 89 - or getattr(profile, "displayName", None), 90 - post=post, 91 - reply=reply, 92 - ) 93 - 94 - 95 - def _list_subscriptions(client: Client, *, limit: int | None = None) -> list[ActivitySubscription]: 96 - subscriptions: list[ActivitySubscription] = [] 97 - cursor: str | None = None 98 - while True: 99 - params: dict[str, object] = {"limit": min(limit or 100, 100)} 100 - if cursor: 101 - params["cursor"] = cursor 102 - response = client.app.bsky.notification.list_activity_subscriptions(params=params) 103 - subscriptions.extend(_parse_subscription(profile) for profile in response.subscriptions) 104 - if limit is not None and len(subscriptions) >= limit: 105 - return subscriptions[:limit] 106 - cursor = getattr(response, "cursor", None) 107 - if not cursor: 108 - return subscriptions 109 - 110 - 111 - def _mode_flags(mode: str) -> tuple[bool, bool]: 112 - if mode == "both": 113 - return True, True 114 - if mode == "posts": 115 - return True, False 116 - if mode == "replies": 117 - return False, True 118 - raise SystemExit(f"unsupported mode: {mode}") 119 - 120 - 121 - def _print_rows(rows: list[ActivitySubscription], *, selected_dids: set[str] | None = None) -> None: 122 - if not rows: 123 - print("no matching activity subscriptions") 124 - return 125 - for row in rows: 126 - marker = "" 127 - if selected_dids is not None and row.did in selected_dids: 128 - marker = "* " 129 - mode = ( 130 - "posts+replies" 131 - if row.post and row.reply 132 - else "posts" 133 - if row.post 134 - else "replies" 135 - if row.reply 136 - else "off" 137 - ) 138 - label = row.display_name or row.handle or row.did 139 - suffix = f" (@{row.handle})" if row.display_name and row.handle else "" 140 - print(f"{marker}{label}{suffix} [{row.did}] -> {mode}") 141 - 142 - 143 - def _is_active(row: ActivitySubscription) -> bool: 144 - return bool(row.post or row.reply) 145 - 146 - 147 - def cmd_status(args: argparse.Namespace) -> int: 148 - client = _build_client() 149 - rows = _list_subscriptions(client, limit=args.limit if args.all else None) 150 - if not args.include_off: 151 - rows = [row for row in rows if _is_active(row)] 152 - if args.all: 153 - _print_rows(rows) 154 - return 0 155 - 156 - handles = _load_handles(args) 157 - if not handles: 158 - raise SystemExit("no handles selected; pass --handles, --file, or use --all") 159 - selected_dids = {_resolve_did(client, handle) for handle in handles} 160 - filtered = [row for row in rows if row.did in selected_dids] 161 - _print_rows(filtered, selected_dids=selected_dids) 162 - missing = selected_dids - {row.did for row in filtered} 163 - if missing: 164 - print(f"\n{len(missing)} selected account(s) have no active activity subscription") 165 - return 0 166 - 167 - 168 - def cmd_subscribe(args: argparse.Namespace) -> int: 169 - client = _build_client() 170 - handles = _load_handles(args) 171 - if not handles: 172 - raise SystemExit("no handles selected; pass --handles or --file") 173 - post, reply = _mode_flags(args.mode) 174 - for handle in handles: 175 - did = _resolve_did(client, handle) 176 - client.app.bsky.notification.put_activity_subscription( 177 - { 178 - "subject": did, 179 - "activitySubscription": {"post": post, "reply": reply}, 180 - } 181 - ) 182 - mode = "posts+replies" if post and reply else "posts" if post else "replies" 183 - print(f"subscribed @{handle} [{did}] -> {mode}") 184 - print("\ncurrent status:") 185 - return cmd_status( 186 - argparse.Namespace( 187 - handles=",".join(handles), 188 - file=None, 189 - all=False, 190 - limit=None, 191 - ) 192 - ) 193 - 194 - 195 - def cmd_unsubscribe(args: argparse.Namespace) -> int: 196 - client = _build_client() 197 - handles = _load_handles(args) 198 - if not handles: 199 - raise SystemExit("no handles selected; pass --handles or --file") 200 - for handle in handles: 201 - did = _resolve_did(client, handle) 202 - client.app.bsky.notification.put_activity_subscription( 203 - { 204 - "subject": did, 205 - "activitySubscription": {"post": False, "reply": False}, 206 - } 207 - ) 208 - print(f"unsubscribed @{handle} [{did}]") 209 - print("\ncurrent status:") 210 - return cmd_status( 211 - argparse.Namespace( 212 - handles=",".join(handles), 213 - file=None, 214 - all=False, 215 - limit=None, 216 - ) 217 - ) 218 - 219 - 220 - def build_parser() -> argparse.ArgumentParser: 221 - parser = argparse.ArgumentParser(description=__doc__) 222 - subparsers = parser.add_subparsers(dest="command", required=True) 223 - 224 - def add_handle_args(subparser: argparse.ArgumentParser) -> None: 225 - subparser.add_argument( 226 - "--handles", 227 - help="comma-separated handles or DIDs", 228 - ) 229 - subparser.add_argument( 230 - "--file", 231 - default=str(DEFAULT_FIXTURE_FILE), 232 - help=f"fixture file with one handle per line (default: {DEFAULT_FIXTURE_FILE})", 233 - ) 234 - 235 - status = subparsers.add_parser("status", help="show current activity-subscription state") 236 - add_handle_args(status) 237 - status.add_argument("--all", action="store_true", help="show all current subscriptions") 238 - status.add_argument( 239 - "--include-off", 240 - action="store_true", 241 - help="include disabled/off subscription records in the output", 242 - ) 243 - status.add_argument( 244 - "--limit", 245 - type=int, 246 - default=50, 247 - help="max rows when using --all (default: 50)", 248 - ) 249 - status.set_defaults(func=cmd_status) 250 - 251 - subscribe = subparsers.add_parser("subscribe", help="enable subscriptions for a fixture set") 252 - add_handle_args(subscribe) 253 - subscribe.add_argument( 254 - "--mode", 255 - choices=("both", "posts", "replies"), 256 - default="both", 257 - help="subscription mode to apply (default: both)", 258 - ) 259 - subscribe.set_defaults(func=cmd_subscribe) 260 - 261 - unsubscribe = subparsers.add_parser("unsubscribe", help="disable subscriptions for a fixture set") 262 - add_handle_args(unsubscribe) 263 - unsubscribe.set_defaults(func=cmd_unsubscribe) 264 - 265 - return parser 266 - 267 - 268 - def main() -> int: 269 - parser = build_parser() 270 - args = parser.parse_args() 271 - return int(args.func(args)) 272 - 273 - 274 - if __name__ == "__main__": 275 - raise SystemExit(main())
-86
scripts/replay.py
··· 1 - #!/usr/bin/env python 2 - """replay feed generation against a saved fixture. 3 - 4 - usage: 5 - uv run scripts/replay.py # run all fixtures 6 - uv run scripts/replay.py fixtures/unread-001.json # run one fixture 7 - """ 8 - 9 - import asyncio 10 - import json 11 - import sys 12 - from pathlib import Path 13 - 14 - import os 15 - 16 - from pydantic_settings import BaseSettings, SettingsConfigDict 17 - 18 - 19 - class ReplaySettings(BaseSettings): 20 - model_config = SettingsConfigDict( 21 - env_file=os.environ.get("ENV_FILE", ".env"), extra="ignore" 22 - ) 23 - anthropic_api_key: str 24 - 25 - 26 - _settings = ReplaySettings() # type: ignore 27 - os.environ["ANTHROPIC_API_KEY"] = _settings.anthropic_api_key 28 - 29 - from noti.agent import ActiveUser, generate_feed # noqa: E402 30 - from noti.preferences import load_preferences # noqa: E402 31 - 32 - 33 - async def replay(fixture_path: Path): 34 - notifications = [] 35 - for raw in json.loads(fixture_path.read_text()): 36 - sanitized = dict(raw) 37 - sanitized.pop("priority", None) 38 - sanitized.pop("summary", None) 39 - sanitized.pop("candidate_urls", None) 40 - notifications.append(sanitized) 41 - preferences = load_preferences() 42 - print(f"\n{'=' * 60}") 43 - print(f"fixture: {fixture_path.name} ({len(notifications)} notifications)") 44 - print(f"{'=' * 60}") 45 - print(f"want to see: {preferences.want_to_see or '(none)'}") 46 - print(f"dont want to see: {preferences.dont_want_to_see or '(none)'}") 47 - 48 - result = await generate_feed( 49 - notifications, 50 - preferences, 51 - ActiveUser(handle="zzstoatzz.io", display_name="nate"), 52 - ) 53 - if not result: 54 - print("feed generation failed!") 55 - return 56 - 57 - print(f"\nbriefing: {result.briefing}\n") 58 - for item in result.items: 59 - print(f" [{item.priority:6}] {item.title} :: {item.count_label}") 60 - print(f" {item.summary}") 61 - if item.target_url: 62 - print(f" -> {item.target_label}: {item.target_url}") 63 - if item.suggested_actions: 64 - print(f" actions: {', '.join(item.suggested_actions)}") 65 - print() 66 - 67 - 68 - async def main(): 69 - fixtures_dir = Path("fixtures") 70 - 71 - if len(sys.argv) > 1: 72 - paths = [Path(p) for p in sys.argv[1:]] 73 - else: 74 - paths = sorted(fixtures_dir.glob("*.json")) 75 - 76 - if not paths: 77 - print("no fixtures found. save one with:") 78 - print(" curl -sf https://noti.fly.dev/api/notifications > fixtures/unread-001.json") 79 - return 80 - 81 - for path in paths: 82 - await replay(path) 83 - 84 - 85 - if __name__ == "__main__": 86 - asyncio.run(main())
+37
src/actor-queries.ts
··· 1 + function cleanActorQuery(value: string) { 2 + return value 3 + .trim() 4 + .replace(/^@/, '') 5 + .replace(/[.?!,:;]+$/g, '') 6 + .replace(/(?:'s)?\s+(?:posts?|repl(?:y|ies)|notifications?)$/i, '') 7 + .trim() 8 + } 9 + 10 + export function extractActorQueries(request: string) { 11 + const out: string[] = [] 12 + const seen = new Set<string>() 13 + const add = (value: string | undefined) => { 14 + const cleaned = cleanActorQuery(value || '') 15 + if (!cleaned) return 16 + const key = cleaned.toLowerCase() 17 + if (seen.has(key)) return 18 + seen.add(key) 19 + out.push(cleaned) 20 + } 21 + 22 + for (const handle of request.match(/@[A-Za-z0-9._-]+(?:\.[A-Za-z0-9._-]+)+/g) || []) { 23 + add(handle) 24 + } 25 + 26 + const patterns = [ 27 + /(?:from|for)\s+(.+?)$/i, 28 + /(?:mute|unmute)\s+(.+?)$/i, 29 + /(?:subscribe to|unsubscribe from)\s+(.+?)$/i, 30 + ] 31 + for (const pattern of patterns) { 32 + const match = request.match(pattern) 33 + if (match?.[1]) add(match[1]) 34 + } 35 + 36 + return out 37 + }
+346
src/bluesky.ts
··· 1 + import { 2 + AppBskyFeedPost, 3 + BskyAgent, 4 + type AppBskyNotificationListNotifications, 5 + } from '@atproto/api' 6 + 7 + import {actorHistory, recordNotifications} from './state' 8 + import type {ActorRef, EvidenceRow, ManagementTarget, NormalizedNotification} from './types' 9 + 10 + const rawService = process.env.ATPROTO_PDS || 'https://bsky.social' 11 + const service = rawService.startsWith('http') ? rawService : `https://${rawService}` 12 + 13 + function chunk<T>(items: T[], size: number): T[][] { 14 + const out: T[][] = [] 15 + for (let i = 0; i < items.length; i += size) out.push(items.slice(i, i + size)) 16 + return out 17 + } 18 + 19 + function preview(text: string | undefined, limit = 120) { 20 + if (!text) return '' 21 + const collapsed = text.replace(/\s+/g, ' ').trim() 22 + if (collapsed.length <= limit) return collapsed 23 + return `${collapsed.slice(0, limit - 1).trimEnd()}…` 24 + } 25 + 26 + function requireEnv(name: 'ATPROTO_HANDLE' | 'ATPROTO_PASSWORD') { 27 + const value = process.env[name] 28 + if (!value) throw new Error(`missing required env var: ${name}`) 29 + return value 30 + } 31 + 32 + export function actorProfileUrl(identifier: string) { 33 + return `https://bsky.app/profile/${encodeURIComponent(identifier)}` 34 + } 35 + 36 + function actorRefFromProfile(profile: {did: string; handle?: string; displayName?: string}) { 37 + const handle = profile.handle || profile.did 38 + return { 39 + did: profile.did, 40 + handle, 41 + name: profile.displayName || handle, 42 + profileUrl: actorProfileUrl(handle), 43 + } satisfies ActorRef 44 + } 45 + 46 + export async function createAgent() { 47 + const agent = new BskyAgent({service}) 48 + await agent.login({ 49 + identifier: requireEnv('ATPROTO_HANDLE'), 50 + password: requireEnv('ATPROTO_PASSWORD'), 51 + }) 52 + return agent 53 + } 54 + 55 + async function fetchSubjectTexts( 56 + agent: BskyAgent, 57 + notifications: AppBskyNotificationListNotifications.Notification[], 58 + ) { 59 + const uris = [ 60 + ...new Set( 61 + notifications 62 + .map(n => n.reasonSubject) 63 + .filter((uri): uri is string => Boolean(uri?.includes('app.bsky.feed.post'))), 64 + ), 65 + ] 66 + const texts = new Map<string, string>() 67 + for (const batch of chunk(uris, 25)) { 68 + const res = await agent.app.bsky.feed.getPosts({uris: batch}) 69 + for (const post of res.data.posts) { 70 + if (AppBskyFeedPost.isRecord(post.record) && post.record.text) { 71 + texts.set(String(post.uri), String(post.record.text)) 72 + } 73 + } 74 + } 75 + return texts 76 + } 77 + 78 + export async function listNotifications(agent: BskyAgent): Promise<NormalizedNotification[]> { 79 + const res = await agent.listNotifications({limit: 50}) 80 + const subjectTexts = await fetchSubjectTexts(agent, res.data.notifications) 81 + 82 + const normalized = res.data.notifications.map(notification => { 83 + const record = notification.record 84 + const text = 85 + AppBskyFeedPost.isRecord(record) && typeof record.text === 'string' ? record.text : undefined 86 + const isReply = Boolean(AppBskyFeedPost.isRecord(record) && record.reply) 87 + return { 88 + uri: notification.uri, 89 + reason: notification.reason, 90 + reasonSubject: notification.reasonSubject, 91 + authorDid: notification.author.did, 92 + authorHandle: notification.author.handle, 93 + authorName: notification.author.displayName || notification.author.handle, 94 + indexedAt: notification.indexedAt, 95 + isRead: notification.isRead, 96 + text, 97 + subjectText: notification.reasonSubject ? subjectTexts.get(notification.reasonSubject) : undefined, 98 + isReply, 99 + } satisfies NormalizedNotification 100 + }) 101 + 102 + recordNotifications(normalized) 103 + return normalized 104 + } 105 + 106 + export type ActivitySubscription = { 107 + post: boolean 108 + reply: boolean 109 + actorHandle: string 110 + actorName: string 111 + } 112 + 113 + export async function getActorProfiles(agent: BskyAgent, actorDids: string[]): Promise<Map<string, ActorRef>> { 114 + const unique = [...new Set(actorDids.filter(Boolean))] 115 + const out = new Map<string, ActorRef>() 116 + for (const batch of chunk(unique, 25)) { 117 + const res = await agent.getProfiles({actors: batch}) 118 + for (const profile of res.data.profiles) { 119 + out.set(profile.did, actorRefFromProfile(profile)) 120 + } 121 + } 122 + for (const did of unique) { 123 + if (out.has(did)) continue 124 + out.set(did, { 125 + did, 126 + handle: did, 127 + name: did, 128 + profileUrl: actorProfileUrl(did), 129 + }) 130 + } 131 + return out 132 + } 133 + 134 + export async function resolveActor(agent: BskyAgent, query: string): Promise<ActorRef | undefined> { 135 + const trimmed = query.trim().replace(/^@/, '') 136 + if (!trimmed) return undefined 137 + 138 + if (trimmed.startsWith('did:')) { 139 + const profile = await agent.getProfile({actor: trimmed}) 140 + return actorRefFromProfile(profile.data) 141 + } 142 + 143 + if (trimmed.includes('.')) { 144 + try { 145 + const resolved = await agent.resolveHandle({handle: trimmed}) 146 + const profile = await agent.getProfile({actor: resolved.data.did}) 147 + return actorRefFromProfile(profile.data) 148 + } catch { 149 + // Fall through to typeahead. 150 + } 151 + } 152 + 153 + const typeahead = await agent.searchActorsTypeahead({q: trimmed, limit: 8}) 154 + const normalizedQuery = trimmed 155 + .toLowerCase() 156 + .normalize('NFKD') 157 + .replace(/[^\p{L}\p{N}]+/gu, ' ') 158 + .trim() 159 + const sorted = [...typeahead.data.actors].sort((a, b) => { 160 + const aHandle = a.handle.toLowerCase() 161 + const bHandle = b.handle.toLowerCase() 162 + const aName = (a.displayName || '').toLowerCase() 163 + const bName = (b.displayName || '').toLowerCase() 164 + const aExact = Number(aHandle === trimmed.toLowerCase() || aName === trimmed.toLowerCase()) 165 + const bExact = Number(bHandle === trimmed.toLowerCase() || bName === trimmed.toLowerCase()) 166 + if (bExact !== aExact) return bExact - aExact 167 + const aLoose = Number( 168 + aHandle.includes(trimmed.toLowerCase()) || 169 + aName.includes(trimmed.toLowerCase()) || 170 + normalizedQuery === 171 + (a.displayName || '') 172 + .toLowerCase() 173 + .normalize('NFKD') 174 + .replace(/[^\p{L}\p{N}]+/gu, ' ') 175 + .trim(), 176 + ) 177 + const bLoose = Number( 178 + bHandle.includes(trimmed.toLowerCase()) || 179 + bName.includes(trimmed.toLowerCase()) || 180 + normalizedQuery === 181 + (b.displayName || '') 182 + .toLowerCase() 183 + .normalize('NFKD') 184 + .replace(/[^\p{L}\p{N}]+/gu, ' ') 185 + .trim(), 186 + ) 187 + return bLoose - aLoose 188 + }) 189 + const best = sorted[0] 190 + return best ? actorRefFromProfile(best) : undefined 191 + } 192 + 193 + export async function searchActorSuggestions( 194 + agent: BskyAgent, 195 + query: string, 196 + limit = 6, 197 + ): Promise<ActorRef[]> { 198 + const trimmed = query.trim().replace(/^@/, '') 199 + if (!trimmed) return [] 200 + const res = await agent.searchActorsTypeahead({q: trimmed, limit}) 201 + return res.data.actors.map(actorRefFromProfile) 202 + } 203 + 204 + export async function listActivitySubscriptions(agent: BskyAgent) { 205 + const rows = new Map<string, ActivitySubscription>() 206 + let cursor: string | undefined 207 + do { 208 + const res = await agent.app.bsky.notification.listActivitySubscriptions({cursor}) 209 + for (const profile of res.data.subscriptions) { 210 + const sub = profile.viewer?.activitySubscription 211 + if (sub?.post || sub?.reply) { 212 + rows.set(profile.did, { 213 + post: Boolean(sub.post), 214 + reply: Boolean(sub.reply), 215 + actorHandle: profile.handle, 216 + actorName: profile.displayName || profile.handle, 217 + }) 218 + } 219 + } 220 + cursor = res.data.cursor 221 + } while (cursor) 222 + return rows 223 + } 224 + 225 + export async function listMutedActors(agent: BskyAgent) { 226 + const rows = new Set<string>() 227 + let cursor: string | undefined 228 + do { 229 + const res = await agent.app.bsky.graph.getMutes({cursor, limit: 100}) 230 + for (const profile of res.data.mutes) rows.add(profile.did) 231 + cursor = res.data.cursor 232 + } while (cursor) 233 + return rows 234 + } 235 + 236 + export type ActorState = { 237 + subscription: {post: boolean; reply: boolean} | null 238 + muted: boolean 239 + } 240 + 241 + export async function getActorStates(agent: BskyAgent, actorDids: string[]): Promise<Map<string, ActorState>> { 242 + const unique = [...new Set(actorDids.filter(Boolean))] 243 + const [subscriptions, mutedActors] = await Promise.all([ 244 + listActivitySubscriptions(agent), 245 + listMutedActors(agent), 246 + ]) 247 + const out = new Map<string, ActorState>() 248 + for (const did of unique) { 249 + const subscription = subscriptions.get(did) 250 + out.set(did, { 251 + subscription: subscription ? {post: subscription.post, reply: subscription.reply} : null, 252 + muted: mutedActors.has(did), 253 + }) 254 + } 255 + return out 256 + } 257 + 258 + export type ActorStateChange = 259 + | {actorDid: string; kind: 'mute'; muted: boolean} 260 + | {actorDid: string; kind: 'subscription'; subscription: {post: boolean; reply: boolean} | null} 261 + 262 + export function diffActorStates( 263 + before: Map<string, ActorState>, 264 + after: Map<string, ActorState>, 265 + ): ActorStateChange[] { 266 + const changes: ActorStateChange[] = [] 267 + for (const [did, afterState] of after) { 268 + const beforeState = before.get(did) 269 + if (!beforeState) continue 270 + if (beforeState.muted !== afterState.muted) { 271 + changes.push({actorDid: did, kind: 'mute', muted: afterState.muted}) 272 + } 273 + const bSub = beforeState.subscription 274 + const aSub = afterState.subscription 275 + if (JSON.stringify(bSub) !== JSON.stringify(aSub)) { 276 + changes.push({actorDid: did, kind: 'subscription', subscription: aSub}) 277 + } 278 + } 279 + return changes 280 + } 281 + 282 + function evidenceRows(notifications: NormalizedNotification[]): EvidenceRow[] { 283 + return notifications.slice(0, 3).map(notification => ({ 284 + uri: notification.uri, 285 + actor: notification.authorName, 286 + actorProfileUrl: actorProfileUrl(notification.authorHandle || notification.authorDid || notification.authorName), 287 + reason: notification.reason, 288 + snippet: preview( 289 + notification.text || notification.subjectText || notification.reasonSubject || notification.reason, 290 + ), 291 + })) 292 + } 293 + 294 + export function buildManagementTargets( 295 + notifications: NormalizedNotification[], 296 + subscriptionModes: Map<string, ActivitySubscription>, 297 + mutedActors: Set<string>, 298 + ): ManagementTarget[] { 299 + const unread = notifications.filter(n => !n.isRead && n.authorDid) 300 + const grouped = new Map<string, NormalizedNotification[]>() 301 + for (const notification of unread) { 302 + const actorDid = notification.authorDid 303 + if (!actorDid) continue 304 + grouped.set(actorDid, [...(grouped.get(actorDid) || []), notification]) 305 + } 306 + 307 + const actorDids = [...new Set([...grouped.keys(), ...subscriptionModes.keys()])] 308 + const history = actorHistory(actorDids) 309 + 310 + return actorDids 311 + .map(actorDid => { 312 + const actorNotifications = grouped.get(actorDid) || [] 313 + const latest = [...actorNotifications].sort((a, b) => b.indexedAt.localeCompare(a.indexedAt))[0] 314 + const modes = subscriptionModes.get(actorDid) 315 + const hist = history.get(actorDid) || {allTimeCount: 0, recent24hCount: 0, recent7dCount: 0} 316 + const reasons: Record<string, number> = {} 317 + for (const notification of actorNotifications) { 318 + reasons[notification.reason] = (reasons[notification.reason] || 0) + 1 319 + } 320 + const actorHandle = latest?.authorHandle || modes?.actorHandle || actorDid 321 + const actorName = latest?.authorName || modes?.actorName || actorHandle 322 + return { 323 + actorDid, 324 + actorHandle, 325 + actorName, 326 + sourceUris: actorNotifications.map(n => n.uri), 327 + currentUnreadCount: actorNotifications.length, 328 + recent24hCount: hist.recent24hCount, 329 + recent7dCount: hist.recent7dCount, 330 + allTimeCount: hist.allTimeCount, 331 + reasons, 332 + subscriptionPosts: Boolean(modes?.post), 333 + subscriptionReplies: Boolean(modes?.reply), 334 + muted: mutedActors.has(actorDid), 335 + examples: evidenceRows(actorNotifications), 336 + } satisfies ManagementTarget 337 + }) 338 + .sort((a, b) => { 339 + return ( 340 + Number(b.subscriptionPosts || b.subscriptionReplies) - Number(a.subscriptionPosts || a.subscriptionReplies) || 341 + b.currentUnreadCount - a.currentUnreadCount || 342 + b.recent24hCount - a.recent24hCount || 343 + b.allTimeCount - a.allTimeCount 344 + ) 345 + }) 346 + }
+418
src/client.ts
··· 1 + type ToastPayload = { 2 + description: string 3 + verified: boolean 4 + label?: string 5 + href?: string 6 + text: string 7 + } 8 + 9 + type ActorSuggestion = { 10 + did: string 11 + handle: string 12 + name: string 13 + profileUrl: string 14 + } 15 + 16 + type MentionQuery = { 17 + query: string 18 + start: number 19 + end: number 20 + } 21 + 22 + let mentionItems: ActorSuggestion[] = [] 23 + let mentionIndex = 0 24 + let mentionFetchToken = 0 25 + let mentionDebounce: number | undefined 26 + 27 + function cleanActorQuery(value: string) { 28 + return value 29 + .trim() 30 + .replace(/^@/, '') 31 + .replace(/[.?!,:;]+$/g, '') 32 + .replace(/(?:'s)?\s+(?:posts?|repl(?:y|ies)|notifications?)$/i, '') 33 + .trim() 34 + } 35 + 36 + function actorHint(message: string) { 37 + const handle = message.match(/@[A-Za-z0-9._-]+(?:\.[A-Za-z0-9._-]+)+/) 38 + if (handle) return cleanActorQuery(handle[0]) 39 + const patterns = [ 40 + /(?:from|for)\s+(.+?)$/i, 41 + /(?:mute|unmute)\s+(.+?)$/i, 42 + /(?:subscribe to|unsubscribe from)\s+(.+?)$/i, 43 + ] 44 + for (const pattern of patterns) { 45 + const match = message.match(pattern) 46 + if (match?.[1]) { 47 + const candidate = cleanActorQuery(match[1]) 48 + if (candidate) return candidate 49 + } 50 + } 51 + return '' 52 + } 53 + 54 + function feedbackRoot() { 55 + return document.getElementById('proposal-feedback') 56 + } 57 + 58 + function toastRoot() { 59 + return document.getElementById('toast-stack') 60 + } 61 + 62 + function mentionRoot() { 63 + return document.getElementById('actor-suggestions') 64 + } 65 + 66 + function messageTextarea() { 67 + const input = document.querySelector('#propose-form textarea[name="message"]') 68 + return input instanceof HTMLTextAreaElement ? input : null 69 + } 70 + 71 + function activeMention(textarea: HTMLTextAreaElement): MentionQuery | null { 72 + const caret = textarea.selectionStart 73 + const before = textarea.value.slice(0, caret) 74 + const match = before.match(/(^|\s)@([A-Za-z0-9._-]*)$/) 75 + if (!match) return null 76 + const query = match[2] || '' 77 + const start = caret - query.length - 1 78 + return {query, start, end: caret} 79 + } 80 + 81 + function hideMentionMenu() { 82 + const root = mentionRoot() 83 + mentionItems = [] 84 + mentionIndex = 0 85 + if (!root) return 86 + root.hidden = true 87 + root.replaceChildren() 88 + } 89 + 90 + function insertMention(textarea: HTMLTextAreaElement, mention: MentionQuery, actor: ActorSuggestion) { 91 + const next = `${textarea.value.slice(0, mention.start)}@${actor.handle} ${textarea.value.slice(mention.end)}` 92 + const cursor = mention.start + actor.handle.length + 2 93 + textarea.value = next 94 + textarea.focus() 95 + textarea.setSelectionRange(cursor, cursor) 96 + hideMentionMenu() 97 + } 98 + 99 + function renderMentionMenu(textarea: HTMLTextAreaElement, mention: MentionQuery, actors: ActorSuggestion[]) { 100 + const root = mentionRoot() 101 + if (!root || actors.length === 0) { 102 + hideMentionMenu() 103 + return 104 + } 105 + root.replaceChildren( 106 + ...actors.map((actor, index) => { 107 + const button = document.createElement('button') 108 + button.type = 'button' 109 + button.className = `mention-row${index === mentionIndex ? ' mention-row-active' : ''}` 110 + button.dataset.index = String(index) 111 + const name = document.createElement('span') 112 + name.className = 'mention-name' 113 + name.textContent = actor.name 114 + const handle = document.createElement('span') 115 + handle.className = 'mention-handle' 116 + handle.textContent = `@${actor.handle}` 117 + button.append(name, handle) 118 + button.addEventListener('mousedown', event => { 119 + event.preventDefault() 120 + insertMention(textarea, mention, actor) 121 + }) 122 + return button 123 + }), 124 + ) 125 + root.hidden = false 126 + } 127 + 128 + async function loadMentionSuggestions(mention: MentionQuery) { 129 + window.clearTimeout(mentionDebounce) 130 + if (!mention.query.trim()) { 131 + hideMentionMenu() 132 + return 133 + } 134 + const token = ++mentionFetchToken 135 + mentionDebounce = window.setTimeout(async () => { 136 + try { 137 + const res = await fetch(`/api/actors/typeahead?q=${encodeURIComponent(mention.query)}`) 138 + if (!res.ok) throw new Error('typeahead failed') 139 + const payload = (await res.json()) as {actors?: ActorSuggestion[]} 140 + if (token !== mentionFetchToken) return 141 + const textareaNow = messageTextarea() 142 + const active = textareaNow ? activeMention(textareaNow) : null 143 + if (!textareaNow || !active) { 144 + hideMentionMenu() 145 + return 146 + } 147 + mentionItems = Array.isArray(payload.actors) ? payload.actors : [] 148 + mentionIndex = 0 149 + renderMentionMenu(textareaNow, active, mentionItems) 150 + } catch { 151 + hideMentionMenu() 152 + } 153 + }, 120) 154 + } 155 + 156 + function showProposalError(message: string) { 157 + const feedback = feedbackRoot() 158 + if (!feedback) return 159 + const shell = document.createElement('div') 160 + shell.className = 'empty' 161 + shell.textContent = message 162 + feedback.replaceChildren(shell) 163 + } 164 + 165 + function showProposalProgress(message: string) { 166 + const feedback = feedbackRoot() 167 + if (!feedback) return () => {} 168 + const actor = actorHint(message) 169 + const shell = document.createElement('div') 170 + shell.className = 'loading-shell' 171 + const title = document.createElement('div') 172 + title.className = 'loading-title' 173 + title.textContent = actor ? `looking up ${actor}…` : 'thinking it through…' 174 + const copy = document.createElement('div') 175 + copy.className = 'loading-copy' 176 + copy.textContent = actor 177 + ? 'Finding the right account before suggesting a change.' 178 + : 'Choosing the smallest change that matches your request.' 179 + shell.append(title, copy) 180 + feedback.replaceChildren(shell) 181 + 182 + const timer = window.setTimeout(() => { 183 + title.textContent = 'deciding what to change…' 184 + copy.textContent = 'Putting together a suggestion you can review first.' 185 + }, 900) 186 + 187 + return () => window.clearTimeout(timer) 188 + } 189 + 190 + function showToast(payload: ToastPayload) { 191 + const root = toastRoot() 192 + if (!root) return 193 + 194 + const toast = document.createElement('div') 195 + toast.className = `toast ${payload.verified ? 'toast-ok' : 'toast-bad'}` 196 + 197 + const kicker = document.createElement('div') 198 + kicker.className = 'toast-kicker' 199 + kicker.textContent = payload.verified ? 'done' : 'needs attention' 200 + 201 + const title = document.createElement('div') 202 + title.className = 'toast-title' 203 + title.textContent = payload.description 204 + 205 + const copy = document.createElement('div') 206 + copy.className = 'toast-copy' 207 + if (payload.href && payload.label) { 208 + const link = document.createElement('a') 209 + link.className = 'actor-link' 210 + link.href = payload.href 211 + link.target = '_blank' 212 + link.rel = 'noopener noreferrer' 213 + link.textContent = payload.label 214 + copy.append(link, document.createTextNode(` ${payload.text}`)) 215 + } else { 216 + copy.textContent = payload.text 217 + } 218 + 219 + toast.append(kicker, title, copy) 220 + root.append(toast) 221 + window.setTimeout(() => toast.remove(), 3200) 222 + } 223 + 224 + function parseHtmlDocument(html: string) { 225 + return new DOMParser().parseFromString(html, 'text/html') 226 + } 227 + 228 + function replaceContents(id: string, nextDoc: Document) { 229 + const current = document.getElementById(id) 230 + const next = nextDoc.getElementById(id) 231 + if (current && next) current.innerHTML = next.innerHTML 232 + } 233 + 234 + function replaceMainSections(nextDoc: Document) { 235 + replaceContents('controls-root', nextDoc) 236 + replaceContents('ask-root', nextDoc) 237 + replaceContents('queue-root', nextDoc) 238 + replaceContents('subscriptions-root', nextDoc) 239 + 240 + const currentQueueCount = document.getElementById('queue-count') 241 + const nextQueueCount = nextDoc.getElementById('queue-count') 242 + if (currentQueueCount && nextQueueCount) { 243 + currentQueueCount.textContent = nextQueueCount.textContent 244 + } 245 + } 246 + 247 + async function refreshFromHome(toast?: ToastPayload) { 248 + const res = await fetch('/', {headers: {'x-noti-fragment': '1'}}) 249 + const html = await res.text() 250 + const nextDoc = parseHtmlDocument(html) 251 + replaceMainSections(nextDoc) 252 + if (toast) showToast(toast) 253 + } 254 + 255 + async function loadQueue() { 256 + const root = document.getElementById('queue-root') 257 + const count = document.getElementById('queue-count') 258 + if (!root || !count) return 259 + try { 260 + const res = await fetch('/queue', {headers: {'x-noti-fragment': '1'}}) 261 + if (!res.ok) throw new Error('failed to load suggestions') 262 + const html = await res.text() 263 + const nextCount = res.headers.get('x-noti-ready-count') 264 + root.innerHTML = html 265 + if (nextCount) count.textContent = `${nextCount} ready` 266 + } catch { 267 + root.innerHTML = '<div class="empty">could not load suggestions right now</div>' 268 + count.textContent = 'unavailable' 269 + } 270 + } 271 + 272 + async function handlePropose(form: HTMLFormElement) { 273 + const formData = new FormData(form) 274 + const message = String(formData.get('message') || '').trim() 275 + if (!message) return 276 + 277 + const submitter = form.querySelector('button[type="submit"]') 278 + const clearProgress = showProposalProgress(message) 279 + if (submitter instanceof HTMLButtonElement) submitter.disabled = true 280 + 281 + try { 282 + const res = await fetch(form.action, {method: 'POST', body: formData}) 283 + const html = await res.text() 284 + const nextDoc = parseHtmlDocument(html) 285 + replaceMainSections(nextDoc) 286 + await loadQueue() 287 + } catch { 288 + clearProgress() 289 + showProposalError('could not build a suggestion right now') 290 + } finally { 291 + if (submitter instanceof HTMLButtonElement) submitter.disabled = false 292 + } 293 + } 294 + 295 + async function handleApply(form: HTMLFormElement) { 296 + const submitter = form.querySelector('button[type="submit"]') 297 + if (submitter instanceof HTMLButtonElement) submitter.disabled = true 298 + try { 299 + const res = await fetch(form.action, { 300 + method: 'POST', 301 + body: new FormData(form), 302 + headers: {'x-noti-ajax': '1'}, 303 + }) 304 + if (!res.ok) throw new Error('apply failed') 305 + const payload = (await res.json()) as {toast: ToastPayload} 306 + await refreshFromHome(payload.toast) 307 + await loadQueue() 308 + } catch { 309 + showToast({ 310 + verified: false, 311 + description: 'Could not apply that change', 312 + text: 'Try again in a moment.', 313 + }) 314 + } finally { 315 + if (submitter instanceof HTMLButtonElement) submitter.disabled = false 316 + } 317 + } 318 + 319 + async function handleMarkAllRead(form: HTMLFormElement) { 320 + const confirmed = window.confirm('Mark all unread notifications as read in Bluesky?') 321 + if (!confirmed) return 322 + 323 + const submitter = form.querySelector('button[type="submit"]') 324 + if (submitter instanceof HTMLButtonElement) submitter.disabled = true 325 + try { 326 + const res = await fetch(form.action, {method: 'POST', body: new FormData(form)}) 327 + if (!res.ok) throw new Error('mark all read failed') 328 + await refreshFromHome({ 329 + verified: true, 330 + description: 'Marked all notifications read', 331 + text: 'Your Bluesky notification badge is cleared.', 332 + }) 333 + await loadQueue() 334 + } catch { 335 + showToast({ 336 + verified: false, 337 + description: 'Could not mark notifications read', 338 + text: 'Try again in a moment.', 339 + }) 340 + } finally { 341 + if (submitter instanceof HTMLButtonElement) submitter.disabled = false 342 + } 343 + } 344 + 345 + document.addEventListener('input', event => { 346 + const target = event.target 347 + if (!(target instanceof HTMLTextAreaElement) || target.name !== 'message') return 348 + const mention = activeMention(target) 349 + if (!mention) { 350 + hideMentionMenu() 351 + return 352 + } 353 + void loadMentionSuggestions(mention) 354 + }) 355 + 356 + document.addEventListener('click', event => { 357 + const root = mentionRoot() 358 + if (!root || root.hidden) return 359 + const target = event.target 360 + if (target instanceof Node && (root.contains(target) || target === messageTextarea())) return 361 + hideMentionMenu() 362 + }) 363 + 364 + document.addEventListener('keydown', event => { 365 + const textarea = messageTextarea() 366 + const root = mentionRoot() 367 + if (!(textarea instanceof HTMLTextAreaElement) || !root || root.hidden || document.activeElement !== textarea) { 368 + return 369 + } 370 + if (!mentionItems.length) return 371 + if (event.key === 'ArrowDown') { 372 + event.preventDefault() 373 + mentionIndex = (mentionIndex + 1) % mentionItems.length 374 + const mention = activeMention(textarea) 375 + if (mention) renderMentionMenu(textarea, mention, mentionItems) 376 + return 377 + } 378 + if (event.key === 'ArrowUp') { 379 + event.preventDefault() 380 + mentionIndex = (mentionIndex - 1 + mentionItems.length) % mentionItems.length 381 + const mention = activeMention(textarea) 382 + if (mention) renderMentionMenu(textarea, mention, mentionItems) 383 + return 384 + } 385 + if (event.key === 'Escape') { 386 + hideMentionMenu() 387 + return 388 + } 389 + if (event.key === 'Enter' && !event.shiftKey) { 390 + const mention = activeMention(textarea) 391 + if (!mention) return 392 + event.preventDefault() 393 + insertMention(textarea, mention, mentionItems[mentionIndex] || mentionItems[0]) 394 + } 395 + }) 396 + 397 + document.addEventListener('submit', event => { 398 + const form = event.target 399 + if (!(form instanceof HTMLFormElement)) return 400 + 401 + const action = new URL(form.action, window.location.href).pathname 402 + if (action === '/propose') { 403 + event.preventDefault() 404 + void handlePropose(form) 405 + return 406 + } 407 + if (action === '/apply') { 408 + event.preventDefault() 409 + void handleApply(form) 410 + return 411 + } 412 + if (action === '/mark-all-read') { 413 + event.preventDefault() 414 + void handleMarkAllRead(form) 415 + } 416 + }) 417 + 418 + void loadQueue()
+129
src/code-mode.ts
··· 1 + const METHOD_DEFS = [ 2 + { 3 + fq: 'agent.app.bsky.graph.muteActor', 4 + doc: 'Mute an account by DID.', 5 + inputType: 'MuteActorInput', 6 + input: `interface MuteActorInput {\n actor: Did\n}`, 7 + signature: `muteActor(input: MuteActorInput): Promise<void>`, 8 + }, 9 + { 10 + fq: 'agent.app.bsky.graph.unmuteActor', 11 + doc: 'Undo a previous account mute by DID.', 12 + inputType: 'UnmuteActorInput', 13 + input: `interface UnmuteActorInput {\n actor: Did\n}`, 14 + signature: `unmuteActor(input: UnmuteActorInput): Promise<void>`, 15 + }, 16 + { 17 + fq: 'agent.app.bsky.notification.putActivitySubscription', 18 + doc: 'Change activity-subscription settings for one actor DID.', 19 + inputType: 'PutActivitySubscriptionInput', 20 + input: [ 21 + 'interface PutActivitySubscriptionInput {', 22 + ' subject: Did', 23 + ' activitySubscription: {', 24 + ' post: boolean', 25 + ' reply: boolean', 26 + ' }', 27 + '}', 28 + ].join('\n'), 29 + signature: `putActivitySubscription(input: PutActivitySubscriptionInput): Promise<void>`, 30 + }, 31 + { 32 + fq: 'agent.app.bsky.notification.updateSeen', 33 + doc: 'Advance the global notification seen cursor.', 34 + inputType: 'UpdateSeenInput', 35 + input: `interface UpdateSeenInput {\n seenAt: string\n}`, 36 + signature: `updateSeen(input: UpdateSeenInput): Promise<void>`, 37 + }, 38 + ] as const 39 + 40 + const ALLOWED_METHODS = METHOD_DEFS.map(def => def.fq) 41 + 42 + export class CodeValidationError extends Error {} 43 + 44 + export const SDK_SURFACE = [ 45 + 'type Did = string', 46 + '', 47 + 'interface EvidenceNotification {', 48 + ' uri: string', 49 + ' reason: string', 50 + ' authorDid?: Did', 51 + ' authorHandle: string', 52 + ' authorName: string', 53 + ' indexedAt: string', 54 + ' text?: string', 55 + ' subjectText?: string', 56 + ' isReply: boolean', 57 + '}', 58 + '', 59 + 'interface Ctx {', 60 + ' sourceUris: string[]', 61 + ' notifications: EvidenceNotification[]', 62 + '}', 63 + '', 64 + ...METHOD_DEFS.flatMap(def => ['', `/** ${def.doc} */`, def.input]), 65 + '', 66 + 'interface BlueskyAgentSurface {', 67 + ' app: {', 68 + ' bsky: {', 69 + ' graph: {', 70 + ...METHOD_DEFS.filter(def => def.fq.includes('.graph.')).map(def => ` ${def.signature}`), 71 + ' }', 72 + ' notification: {', 73 + ...METHOD_DEFS.filter(def => def.fq.includes('.notification.')).map(def => ` ${def.signature}`), 74 + ' }', 75 + ' }', 76 + ' }', 77 + '}', 78 + '', 79 + 'Write exactly (plain JavaScript, no type annotations):', 80 + 'async function run(agent, ctx) {', 81 + ' // your code', 82 + '}', 83 + ].join('\n') 84 + 85 + export function validateCode(code: string) { 86 + const trimmed = code.trim() 87 + if (!trimmed) throw new CodeValidationError('empty code proposal') 88 + if (!trimmed.includes('async function run(')) { 89 + throw new CodeValidationError('proposal must define async function run(agent, ctx)') 90 + } 91 + 92 + const blocked = [ 93 + 'import ', 94 + 'require(', 95 + 'process.', 96 + 'Bun.', 97 + 'fetch(', 98 + 'XMLHttpRequest', 99 + 'WebSocket', 100 + 'setTimeout(', 101 + 'setInterval(', 102 + ] 103 + for (const token of blocked) { 104 + if (trimmed.includes(token)) { 105 + throw new CodeValidationError(`blocked token: ${token}`) 106 + } 107 + } 108 + 109 + const matches = [...trimmed.matchAll(/agent(?:\.[A-Za-z_$][\w$]*)+/g)].map(m => m[0]) 110 + for (const match of matches) { 111 + const isAllowed = ALLOWED_METHODS.some(method => match.startsWith(method)) 112 + if (!isAllowed) { 113 + throw new CodeValidationError(`blocked agent method: ${match}`) 114 + } 115 + } 116 + } 117 + 118 + export async function executeCode<TCtx extends object>(code: string, agent: unknown, ctx: TCtx) { 119 + validateCode(code) 120 + const AsyncFunction = Object.getPrototypeOf(async () => {}).constructor as new ( 121 + ...args: string[] 122 + ) => (...fnArgs: unknown[]) => Promise<unknown> 123 + const runner = new AsyncFunction( 124 + 'agent', 125 + 'ctx', 126 + `"use strict";\n${code}\nreturn await run(agent, ctx);`, 127 + ) 128 + return await runner(agent, ctx) 129 + }
src/noti/__init__.py

This is a binary file and will not be displayed.

-66
src/noti/actions.py
··· 1 - """Finite feed action registry backed by real Bluesky mutations.""" 2 - 3 - from typing import Literal 4 - 5 - ActionId = Literal["mute_account", "unsubscribe_activity"] 6 - 7 - ACTION_LABELS: dict[ActionId, str] = { 8 - "mute_account": "mute account", 9 - "unsubscribe_activity": "unsubscribe", 10 - } 11 - 12 - ACTION_CONFIRMATIONS: dict[ActionId, str] = { 13 - "mute_account": "Mute this account in Bluesky?", 14 - "unsubscribe_activity": "Unsubscribe from this account's posts and replies?", 15 - } 16 - 17 - ACTION_SUCCESS_MESSAGES: dict[ActionId, str] = { 18 - "mute_account": "account muted.", 19 - "unsubscribe_activity": "unsubscribed from activity.", 20 - } 21 - 22 - 23 - def _display_actor(notifications: list[dict]) -> str | None: 24 - names = { 25 - n.get("author_name") or n.get("author_handle") 26 - for n in notifications 27 - if n.get("author_name") or n.get("author_handle") 28 - } 29 - if len(names) == 1: 30 - return next(iter(names)) 31 - return None 32 - 33 - 34 - def action_label(action_id: ActionId, notifications: list[dict]) -> str: 35 - if action_id == "unsubscribe_activity": 36 - return "unsubscribe posts & replies" 37 - if action_id == "mute_account": 38 - actor = _display_actor(notifications) 39 - return f"mute {actor}" if actor else ACTION_LABELS[action_id] 40 - return ACTION_LABELS[action_id] 41 - 42 - 43 - def action_confirmation(action_id: ActionId, notifications: list[dict]) -> str: 44 - actor = _display_actor(notifications) 45 - if action_id == "unsubscribe_activity": 46 - if actor: 47 - return f"Stop notifications for {actor}'s posts and replies?" 48 - return ACTION_CONFIRMATIONS[action_id] 49 - if action_id == "mute_account": 50 - if actor: 51 - return f"Mute {actor} in Bluesky?" 52 - return ACTION_CONFIRMATIONS[action_id] 53 - return ACTION_CONFIRMATIONS[action_id] 54 - 55 - 56 - def candidate_action_ids(notifications: list[dict]) -> set[ActionId]: 57 - candidate_ids: set[ActionId] = set() 58 - author_dids = {n.get("author_did") for n in notifications if n.get("author_did")} 59 - reasons = {n.get("reason") for n in notifications} 60 - 61 - if len(author_dids) == 1: 62 - candidate_ids.add("mute_account") 63 - if reasons == {"subscribed-post"} and len(author_dids) == 1: 64 - candidate_ids.add("unsubscribe_activity") 65 - 66 - return candidate_ids
-531
src/noti/agent.py
··· 1 - """notification feed agent — synthesize unread notifications into feed items.""" 2 - 3 - from __future__ import annotations 4 - 5 - import json 6 - import logging 7 - import re 8 - from typing import Literal 9 - 10 - from pydantic import BaseModel, Field 11 - from pydantic_ai import Agent 12 - 13 - from noti.actions import ActionId, candidate_action_ids 14 - from noti.config import settings 15 - from noti.preferences import UserPreferences 16 - 17 - logger = logging.getLogger("noti") 18 - 19 - 20 - class FeedActor(BaseModel): 21 - """A visible actor associated with a synthesized feed item.""" 22 - 23 - name: str 24 - profile_url: str | None = None 25 - avatar_url: str | None = None 26 - 27 - 28 - class FeedItem(BaseModel): 29 - """A synthesized unread feed item backed by one or more notifications.""" 30 - 31 - source_uris: list[str] = Field( 32 - description="notification AT-URIs included in this item, each used exactly once" 33 - ) 34 - priority: Literal["high", "medium", "low"] = Field( 35 - description="high, medium, or low. internal ordering only, never user-facing" 36 - ) 37 - title: str = Field(description="short headline, under 8 words") 38 - count_label: str = Field( 39 - description="very short metadata, like '3 replies' or '8 likes, 2 replies'" 40 - ) 41 - summary: str = Field( 42 - description="1-2 concise sentences explaining what happened and why it matters" 43 - ) 44 - target_url: str | None = Field( 45 - default=None, 46 - description="best URL to open for this item, chosen from the notification data", 47 - ) 48 - target_label: str = Field( 49 - default="open", 50 - description="short button label like 'open thread' or 'view profile'", 51 - ) 52 - suggested_actions: list[ActionId] = Field( 53 - default_factory=list, 54 - description="0-3 action ids chosen only from: mute_account, unsubscribe_activity", 55 - ) 56 - actors: list[FeedActor] = Field(default_factory=list) 57 - actor_count: int = Field(default=0) 58 - 59 - 60 - class FeedPlan(BaseModel): 61 - """Structured unread feed.""" 62 - 63 - briefing: str = Field( 64 - description="1-2 sentences about the overall unread situation right now" 65 - ) 66 - items: list[FeedItem] 67 - 68 - 69 - class FeedResult(BaseModel): 70 - """Validated feed result used by the UI.""" 71 - 72 - briefing: str 73 - items: list[FeedItem] 74 - 75 - 76 - class ActiveUser(BaseModel): 77 - """The logged-in Bluesky account this feed is being generated for.""" 78 - 79 - handle: str 80 - display_name: str | None = None 81 - 82 - 83 - FEED_PROMPT = """\ 84 - you are generating an inbox feed for a bluesky user from their unread notifications. 85 - 86 - your job is not to repeat the raw notification list. instead, synthesize a smaller set \ 87 - of feed items representing the actual things happening. 88 - 89 - rules: 90 - - group notifications when a human would consider them part of the same thing 91 - - the feed must contain between 0 and 4 items total, never more 92 - - if there are 5 or more unread notifications, strongly prefer 3 main items or fewer 93 - - at high volume, use broad situation reports like posting streaks, ongoing discussions, 94 - bursts of engagement, or a catch-all bucket instead of itemizing individual records 95 - - if needed, the last item may be a catch-all like "more activity" or "assorted posting" 96 - - when there are only 1-4 unread notifications, stay concrete and close to the source material 97 - - at low volume, a card should be at least as informative as the native Bluesky notification 98 - - do not use vague phrases like "another discussion", "separate thread", "a thread", or 99 - "a conversation" when there are only a few unread items and you have the actual text 100 - - common good collapses: 101 - - many likes/reposts on the same post 102 - - multiple replies/mentions around the same post or thread 103 - - follow bursts or obvious spammy engagement clusters 104 - - if notifications share the same thread_key or reply_root_uri, prefer one discussion item unless 105 - there is a strong reason to keep them separate 106 - - if a mention, reply, and subscribed-post activity are clearly part of the same thread, prefer one 107 - discussion item with one best target_url 108 - - keep unrelated things separate 109 - - prefer fewer, more meaningful items over a long list of tiny ones 110 - - use the user's preferences to decide what deserves emphasis and what can be \ 111 - omitted entirely 112 - - it is valid to omit notifications that are low-value, repetitive, or explicitly unwanted 113 - - do not create "hidden", "suppressed", or "filtered" items 114 - - do not mention omitted notifications in the briefing or in visible item summaries 115 - - the briefing should summarize only the items you actually surfaced 116 - - do not mention the total unread count in the briefing 117 - - do not inventory the batch like a report; summarize the surfaced situations in natural language 118 - - if subject_text or subject metadata is available, use it 119 - - only include target_url when the item has one clear canonical destination 120 - - if a grouped item spans multiple distinct posts and there is no single canonical destination, 121 - leave target_url empty instead of inventing a catch-all link 122 - - priority is only for internal ordering. it will not be shown to the user 123 - - suggested_actions must only use these exact action ids: 124 - - mute_account 125 - - unsubscribe_activity 126 - - never invent new actions, synonyms, or freeform labels 127 - - only suggest an action when the item clearly maps to a real Bluesky mutation 128 - - for subscribed-post activity from one account, prefer unsubscribe_activity 129 - - for one clear actor, mute_account is valid 130 - - the input includes active_user. when you need to refer to whose post, thread, or account is involved, 131 - use the active user's real handle or display name from that object instead of generic placeholders 132 - - when an item is backed by a single reply or mention and notification text is available, 133 - the title and summary must include the actual topic or wording of that post 134 - - avoid generic summaries like "left a comment on a thread", "replied in a thread", 135 - "posted in a discussion", or "was active in a conversation" 136 - - for a single reply, prefer paraphrasing or briefly quoting the actual reply text 137 - - if an item is too small or too vague to be meaningfully better than the native notification, 138 - keep it very close to the actual post rather than abstracting it 139 - 140 - when referring to accounts, use the display name or full handle exactly as given. 141 - """ 142 - 143 - _feed_agent: Agent | None = None 144 - 145 - 146 - def _priority_rank(priority: str | None) -> int: 147 - return {"high": 0, "medium": 1, "low": 2}.get(priority or "", 3) 148 - 149 - 150 - def _normalize_text(value: str) -> str: 151 - value = value.lower().replace("ϕ", "phi").replace("φ", "phi") 152 - return re.sub(r"[^a-z0-9]+", " ", value).strip() 153 - 154 - 155 - def _preference_tokens(preferences: UserPreferences) -> set[str]: 156 - text = _normalize_text(preferences.dont_want_to_see) 157 - return {token for token in text.split() if len(token) >= 3} 158 - 159 - 160 - def _notification_actor_tokens(notification: dict) -> set[str]: 161 - values = [ 162 - notification.get("author_name", ""), 163 - notification.get("author_handle", ""), 164 - notification.get("author", ""), 165 - ] 166 - tokens: set[str] = set() 167 - for value in values: 168 - normalized = _normalize_text(str(value)) 169 - if not normalized: 170 - continue 171 - tokens.update(token for token in normalized.split() if len(token) >= 3) 172 - return tokens 173 - 174 - 175 - def _filter_notifications_for_preferences( 176 - notifications: list[dict], preferences: UserPreferences 177 - ) -> list[dict]: 178 - muted_tokens = _preference_tokens(preferences) 179 - if not muted_tokens: 180 - return notifications 181 - 182 - filtered: list[dict] = [] 183 - for notification in notifications: 184 - actor_tokens = _notification_actor_tokens(notification) 185 - if actor_tokens and actor_tokens.intersection(muted_tokens): 186 - continue 187 - filtered.append(notification) 188 - return filtered 189 - 190 - 191 - def _get_feed_agent() -> Agent: 192 - global _feed_agent 193 - if _feed_agent is None: 194 - _feed_agent = Agent( 195 - model=settings.feed_model, 196 - system_prompt=FEED_PROMPT, 197 - output_type=FeedPlan, 198 - ) 199 - return _feed_agent 200 - 201 - 202 - def _resolve_target_label(target_url: str | None, notifications: list[dict]) -> str: 203 - if not target_url: 204 - return "" 205 - 206 - thread_urls = {n.get("reply_root_url") for n in notifications if n.get("reply_root_url")} 207 - subject_urls = {n.get("subject_url") for n in notifications if n.get("subject_url")} 208 - notification_urls = { 209 - n.get("notification_url") for n in notifications if n.get("notification_url") 210 - } 211 - profile_urls = {n.get("profile_url") for n in notifications if n.get("profile_url")} 212 - 213 - if target_url in thread_urls: 214 - return "open thread" 215 - if target_url in subject_urls or target_url in notification_urls: 216 - return "open post" 217 - if target_url in profile_urls: 218 - return "view profile" 219 - return "open" 220 - 221 - 222 - def _resolve_group_target( 223 - source_uris: list[str], notifications_by_uri: dict[str, dict], requested_url: str | None 224 - ) -> str | None: 225 - notifications = [notifications_by_uri[uri] for uri in source_uris if uri in notifications_by_uri] 226 - if not notifications: 227 - return None 228 - 229 - thread_urls = {n.get("reply_root_url") for n in notifications if n.get("reply_root_url")} 230 - if len(thread_urls) == 1: 231 - 232 - if (shared_thread := next(iter(thread_urls))): 233 - return shared_thread 234 - 235 - subject_urls = {n.get("subject_url") for n in notifications if n.get("subject_url")} 236 - if len(subject_urls) == 1: 237 - 238 - if (shared_subject := next(iter(subject_urls))): 239 - return shared_subject 240 - 241 - notification_urls = { 242 - n.get("notification_url") for n in notifications if n.get("notification_url") 243 - } 244 - if len(notification_urls) == 1: 245 - if (shared_notification := next(iter(notification_urls))): 246 - return shared_notification 247 - 248 - profile_urls = {n.get("profile_url") for n in notifications if n.get("profile_url")} 249 - if len(profile_urls) == 1 and len(notifications) == 1: 250 - return next(iter(profile_urls)) 251 - 252 - if requested_url: 253 - candidate_sets = [ 254 - { 255 - candidate 256 - for candidate in notification.get("candidate_urls", []) 257 - if candidate 258 - } 259 - for notification in notifications 260 - ] 261 - if candidate_sets: 262 - shared_candidates = set.intersection(*candidate_sets) 263 - if requested_url in shared_candidates: 264 - return requested_url 265 - 266 - if len(notifications) == 1: 267 - return ( 268 - notifications[0].get("subject_url") 269 - or notifications[0].get("notification_url") 270 - or notifications[0].get("profile_url") 271 - ) 272 - 273 - return None 274 - 275 - 276 - def _reason_bucket(notification: dict) -> str: 277 - reason = notification.get("reason") 278 - if reason == "like": 279 - return "likes" 280 - if reason == "follow": 281 - return "follows" 282 - if reason == "mention": 283 - return "mentions" 284 - if notification.get("is_reply"): 285 - return "replies" 286 - return "posts" 287 - 288 - 289 - def _count_label_from_notifications(notifications: list[dict]) -> str: 290 - counts: dict[str, int] = {} 291 - for notification in notifications: 292 - bucket = _reason_bucket(notification) 293 - counts[bucket] = counts.get(bucket, 0) + 1 294 - 295 - ordered = ["posts", "replies", "likes", "mentions", "follows"] 296 - parts = [f"{counts[name]} {name}" for name in ordered if counts.get(name)] 297 - return ", ".join(parts[:3]) if parts else f"{len(notifications)} notifications" 298 - 299 - 300 - def _catch_all_summary(notifications: list[dict]) -> str: 301 - actor_names: list[str] = [] 302 - seen: set[str] = set() 303 - for notification in sorted( 304 - notifications, 305 - key=lambda notification: notification.get("indexed_at", ""), 306 - reverse=True, 307 - ): 308 - key = ( 309 - notification.get("profile_url") 310 - or notification.get("author_handle") 311 - or notification.get("author_name") 312 - or notification.get("uri") 313 - ) 314 - if key in seen: 315 - continue 316 - seen.add(key) 317 - actor_names.append( 318 - notification.get("author_name") 319 - or notification.get("author_handle") 320 - or "someone" 321 - ) 322 - if len(actor_names) >= 3: 323 - break 324 - 325 - if not actor_names: 326 - return "There is additional lower-signal activity in your unread pile." 327 - 328 - if len(actor_names) == 1: 329 - actors_text = actor_names[0] 330 - elif len(actor_names) == 2: 331 - actors_text = f"{actor_names[0]} and {actor_names[1]}" 332 - else: 333 - actors_text = f"{actor_names[0]}, {actor_names[1]}, and {actor_names[2]}" 334 - 335 - return f"{actors_text} are active, along with additional lower-signal posting and discussion." 336 - 337 - 338 - def _collapse_overflow_items( 339 - items: list[FeedItem], notifications_by_uri: dict[str, dict] 340 - ) -> list[FeedItem]: 341 - if len(items) <= 4: 342 - return items 343 - 344 - head = items[:3] 345 - tail = items[3:] 346 - overflow_uris: list[str] = [] 347 - for item in tail: 348 - overflow_uris.extend(item.source_uris) 349 - 350 - overflow_notifications = [ 351 - notifications_by_uri[uri] for uri in overflow_uris if uri in notifications_by_uri 352 - ] 353 - catch_all = FeedItem( 354 - source_uris=overflow_uris, 355 - priority="low", 356 - title="More activity", 357 - count_label=_count_label_from_notifications(overflow_notifications), 358 - summary=_catch_all_summary(overflow_notifications), 359 - target_url=None, 360 - target_label="", 361 - suggested_actions=[], 362 - actors=_derive_actors(overflow_uris, notifications_by_uri), 363 - actor_count=_count_actors(overflow_uris, notifications_by_uri), 364 - ) 365 - return head + [catch_all] 366 - 367 - 368 - def _sort_items(items: list[FeedItem], notifications_by_uri: dict[str, dict]) -> list[FeedItem]: 369 - def newest_indexed_at(item: FeedItem) -> str: 370 - timestamps = [ 371 - notifications_by_uri[uri].get("indexed_at", "") 372 - for uri in item.source_uris 373 - if uri in notifications_by_uri 374 - ] 375 - return max(timestamps, default="") 376 - 377 - items = sorted(items, key=newest_indexed_at, reverse=True) 378 - items = sorted(items, key=lambda item: _priority_rank(item.priority)) 379 - return items 380 - 381 - 382 - def _fallback_briefing(item_count: int, total_unread: int) -> str: 383 - if total_unread == 0: 384 - return "You are all caught up." 385 - if item_count == 0: 386 - return "Nothing in your unread pile looks worth surfacing right now." 387 - return "Here is what looks worth your attention right now." 388 - 389 - 390 - def _finalize_briefing( 391 - model_briefing: str | None, items: list[FeedItem], total_unread: int 392 - ) -> str: 393 - briefing = (model_briefing or "").strip() 394 - if briefing: 395 - return briefing 396 - return _fallback_briefing(len(items), total_unread) 397 - 398 - 399 - def _derive_actors( 400 - source_uris: list[str], notifications_by_uri: dict[str, dict], *, limit: int = 4 401 - ) -> list[FeedActor]: 402 - deduped: list[FeedActor] = [] 403 - seen: set[str] = set() 404 - notifications = sorted( 405 - (notifications_by_uri[uri] for uri in source_uris if uri in notifications_by_uri), 406 - key=lambda notification: notification.get("indexed_at", ""), 407 - reverse=True, 408 - ) 409 - for notification in notifications: 410 - key = ( 411 - notification.get("profile_url") 412 - or notification.get("author_handle") 413 - or notification.get("author_name") 414 - or notification.get("uri") 415 - ) 416 - if key in seen: 417 - continue 418 - seen.add(key) 419 - deduped.append( 420 - FeedActor( 421 - name=notification.get("author_name") 422 - or notification.get("author") 423 - or "someone", 424 - profile_url=notification.get("profile_url"), 425 - avatar_url=notification.get("avatar"), 426 - ) 427 - ) 428 - if len(deduped) >= limit: 429 - break 430 - return deduped 431 - 432 - 433 - def _count_actors(source_uris: list[str], notifications_by_uri: dict[str, dict]) -> int: 434 - seen: set[str] = set() 435 - for uri in source_uris: 436 - notification = notifications_by_uri.get(uri) 437 - if not notification: 438 - continue 439 - key = ( 440 - notification.get("profile_url") 441 - or notification.get("author_handle") 442 - or notification.get("author_name") 443 - or notification.get("uri") 444 - ) 445 - seen.add(key) 446 - return len(seen) 447 - 448 - 449 - async def generate_feed( 450 - notifications: list[dict], 451 - preferences: UserPreferences, 452 - active_user: ActiveUser, 453 - ) -> FeedResult | None: 454 - """Synthesize unread notifications into feed items.""" 455 - try: 456 - visible_notifications = _filter_notifications_for_preferences( 457 - notifications, preferences 458 - ) 459 - if not visible_notifications: 460 - return FeedResult( 461 - briefing=_finalize_briefing(None, [], len(notifications)), 462 - items=[], 463 - ) 464 - 465 - feed_input = json.dumps( 466 - { 467 - "preferences": { 468 - "want_to_see": preferences.want_to_see, 469 - "dont_want_to_see": preferences.dont_want_to_see, 470 - }, 471 - "active_user": active_user.model_dump(), 472 - "notifications": visible_notifications, 473 - } 474 - ) 475 - result = await _get_feed_agent().run(feed_input) 476 - plan = result.output 477 - notifications_by_uri = {n["uri"]: n for n in visible_notifications} 478 - used_source_uris: set[str] = set() 479 - validated_items: list[FeedItem] = [] 480 - 481 - for item in plan.items: 482 - source_uris = [] 483 - for uri in item.source_uris: 484 - if uri in notifications_by_uri and uri not in used_source_uris: 485 - source_uris.append(uri) 486 - used_source_uris.add(uri) 487 - if not source_uris: 488 - continue 489 - 490 - target_url = _resolve_group_target( 491 - source_uris, notifications_by_uri, item.target_url 492 - ) 493 - notifications = [ 494 - notifications_by_uri[uri] for uri in source_uris if uri in notifications_by_uri 495 - ] 496 - target_label = _resolve_target_label(target_url, notifications) 497 - 498 - validated_items.append( 499 - item.model_copy( 500 - update={ 501 - "source_uris": source_uris, 502 - "target_url": target_url, 503 - "target_label": target_label, 504 - "suggested_actions": [ 505 - action_id 506 - for action_id in item.suggested_actions[:3] 507 - if action_id in candidate_action_ids(notifications) 508 - ], 509 - "actors": _derive_actors(source_uris, notifications_by_uri), 510 - "actor_count": _count_actors(source_uris, notifications_by_uri), 511 - } 512 - ) 513 - ) 514 - 515 - validated_items = _sort_items(validated_items, notifications_by_uri) 516 - validated_items = _collapse_overflow_items(validated_items, notifications_by_uri) 517 - logger.info( 518 - "feed generation: %s notifications -> %s items", 519 - len(visible_notifications), 520 - len(validated_items), 521 - ) 522 - return FeedResult( 523 - briefing=_finalize_briefing( 524 - plan.briefing, validated_items, len(visible_notifications) 525 - ), 526 - items=validated_items, 527 - ) 528 - 529 - except Exception as e: 530 - logger.error(f"feed generation failed: {e}") 531 - return None
-30
src/noti/config.py
··· 1 - """settings loaded from env / .env file.""" 2 - 3 - import os 4 - 5 - from pydantic import field_validator 6 - from pydantic_settings import BaseSettings, SettingsConfigDict 7 - 8 - 9 - class Settings(BaseSettings): 10 - model_config = SettingsConfigDict( 11 - env_file=os.environ.get("ENV_FILE", ".env"), extra="ignore" 12 - ) 13 - 14 - atproto_handle: str 15 - atproto_password: str 16 - atproto_pds: str = "https://bsky.social" 17 - poll_interval: int = 30 18 - preferences_path: str = "data/preferences.json" 19 - feed_model: str = "anthropic:claude-haiku-4-5" 20 - debug: bool = False 21 - 22 - @field_validator("atproto_pds") 23 - @classmethod 24 - def _normalize_pds(cls, v: str) -> str: 25 - if not v.startswith("http"): 26 - return f"https://{v}" 27 - return v 28 - 29 - 30 - settings = Settings() # type: ignore
-218
src/noti/main.py
··· 1 - """noti — generated inbox for bluesky.""" 2 - 3 - import asyncio 4 - import json 5 - import logging 6 - from urllib.parse import parse_qs 7 - from contextlib import asynccontextmanager 8 - from pathlib import Path 9 - 10 - from fastapi import FastAPI, Request 11 - from fastapi.responses import HTMLResponse, JSONResponse, Response 12 - from jinja2 import Environment, FileSystemLoader 13 - 14 - from noti.actions import ( 15 - ACTION_SUCCESS_MESSAGES, 16 - action_confirmation, 17 - action_label, 18 - ) 19 - from noti.config import settings as _settings 20 - from noti.poller import NotificationPoller 21 - from noti.preferences import UserPreferences, load_preferences, save_preferences 22 - 23 - logging.basicConfig( 24 - level=logging.DEBUG if _settings.debug else logging.INFO, 25 - format="%(name)s: %(message)s", 26 - ) 27 - 28 - _TEMPLATE_DIRS = [ 29 - Path(__file__).parent / "templates", 30 - Path("/app/templates"), 31 - ] 32 - TEMPLATES = Environment( 33 - loader=FileSystemLoader([str(d) for d in _TEMPLATE_DIRS if d.exists()]), 34 - autoescape=True, 35 - ) 36 - 37 - poller = NotificationPoller() 38 - 39 - 40 - @asynccontextmanager 41 - async def lifespan(app: FastAPI): 42 - task = asyncio.create_task(poller.run()) 43 - yield 44 - task.cancel() 45 - 46 - 47 - app = FastAPI(title="noti", lifespan=lifespan) 48 - 49 - 50 - @app.get("/", response_class=HTMLResponse) 51 - async def index(): 52 - return TEMPLATES.get_template("index.html").render( 53 - preferences=load_preferences(), 54 - preferences_open=False, 55 - preferences_message=None, 56 - action_success_messages=ACTION_SUCCESS_MESSAGES, 57 - ) 58 - 59 - 60 - def _render_preferences_panel( 61 - preferences: UserPreferences, 62 - *, 63 - open_panel: bool, 64 - message: str | None = None, 65 - ) -> str: 66 - return TEMPLATES.get_template("partials/preferences.html").render( 67 - preferences=preferences, 68 - preferences_open=open_panel, 69 - preferences_message=message, 70 - ) 71 - 72 - 73 - @app.get("/partials/notifications", response_class=HTMLResponse) 74 - async def partials_notifications(): 75 - notifs, feed = await poller.get_state() 76 - unread = [n for n in notifs if not n["is_read"]] 77 - read = [n for n in notifs if n["is_read"]] 78 - unread_items = [] 79 - if feed: 80 - notifications_by_uri = {notification["uri"]: notification for notification in notifs} 81 - for item in feed.items: 82 - item_notifications = [ 83 - notifications_by_uri[uri] 84 - for uri in item.source_uris 85 - if uri in notifications_by_uri 86 - ] 87 - available_action_ids = await poller.available_actions( 88 - item.source_uris, list(item.suggested_actions) 89 - ) 90 - unread_items.append( 91 - { 92 - "item": item, 93 - "actions": [ 94 - { 95 - "id": action_id, 96 - "label": action_label(action_id, item_notifications), 97 - "confirmation": action_confirmation(action_id, item_notifications), 98 - } 99 - for action_id in available_action_ids 100 - ], 101 - } 102 - ) 103 - 104 - return TEMPLATES.get_template("partials/notifications.html").render( 105 - unread=unread_items, 106 - has_read=bool(read), 107 - bsky_notifications_url="https://bsky.app/notifications", 108 - briefing=feed.briefing if feed else None, 109 - unread_count=len(unread), 110 - feed_ready=feed is not None, 111 - ) 112 - 113 - 114 - @app.get("/partials/status", response_class=HTMLResponse) 115 - async def partials_status(): 116 - notifs, feed = await poller.get_state() 117 - unread = sum(1 for n in notifs if not n["is_read"]) 118 - return TEMPLATES.get_template("partials/status.html").render( 119 - unread=unread 120 - ) 121 - 122 - 123 - @app.post("/mark-all-read") 124 - async def mark_all_read(): 125 - changed = await poller.mark_all_read() 126 - response = Response(status_code=204) 127 - response.headers["HX-Trigger"] = json.dumps( 128 - { 129 - "notifications-updated": True, 130 - "mark-all-read-complete": changed, 131 - } 132 - ) 133 - return response 134 - 135 - 136 - @app.post("/actions/execute") 137 - async def execute_action(request: Request): 138 - form_data = parse_qs((await request.body()).decode()) 139 - action_id = form_data.get("action_id", [""])[0] 140 - source_uris = form_data.get("source_uris", []) 141 - changed = await poller.execute_action(action_id, source_uris) 142 - response = Response(status_code=204) 143 - response.headers["HX-Trigger"] = json.dumps( 144 - { 145 - "notifications-updated": True, 146 - "feed-action-complete": { 147 - "action": action_id, 148 - "changed": changed, 149 - }, 150 - } 151 - ) 152 - return response 153 - 154 - 155 - @app.post("/preferences", response_class=HTMLResponse) 156 - async def save_preferences_route(request: Request): 157 - form_data = parse_qs((await request.body()).decode()) 158 - preferences = save_preferences( 159 - UserPreferences( 160 - want_to_see=form_data.get("want_to_see", [""])[0], 161 - dont_want_to_see=form_data.get("dont_want_to_see", [""])[0], 162 - ) 163 - ) 164 - poller.schedule_refresh() 165 - 166 - response = HTMLResponse( 167 - _render_preferences_panel( 168 - preferences, 169 - open_panel=True, 170 - message="saved.", 171 - ) 172 - ) 173 - response.headers["HX-Trigger"] = json.dumps({"preferences-refresh-started": True}) 174 - return response 175 - 176 - 177 - @app.get("/api/refresh-status") 178 - async def refresh_status(): 179 - return { 180 - "refreshing": poller.is_refreshing(), 181 - "message": ( 182 - "preferences saved. refreshing feed..." 183 - if poller.is_refreshing() 184 - else "feed updated." 185 - ), 186 - } 187 - 188 - 189 - @app.get("/health") 190 - async def health(): 191 - return { 192 - "status": "ok" if poller._client else "connecting", 193 - "count": len(poller.notifications), 194 - "feed_ready": poller.feed is not None, 195 - } 196 - 197 - 198 - @app.get("/favicon.ico") 199 - async def favicon(): 200 - svg = """<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64"> 201 - <rect width="64" height="64" rx="16" fill="#0d1117"/> 202 - <circle cx="32" cy="32" r="23" fill="#161b22" stroke="#2b3744" stroke-width="2"/> 203 - <path d="M24 43V21h5l10 13V21h5v22h-4.8L29 29.9V43z" fill="#f0f6fc"/> 204 - </svg>""" 205 - return Response(content=svg, media_type="image/svg+xml") 206 - 207 - 208 - @app.get("/api/notifications") 209 - async def get_notifications(): 210 - notifs, _ = await poller.get_state() 211 - unread = [] 212 - for notification in notifs: 213 - if notification["is_read"]: 214 - continue 215 - unread.append( 216 - {key: value for key, value in notification.items() if key != "is_read"} 217 - ) 218 - return JSONResponse(unread)
-571
src/noti/poller.py
··· 1 - """notification polling + feed generation loop.""" 2 - 3 - import asyncio 4 - import json 5 - import logging 6 - from datetime import UTC, datetime 7 - 8 - from atproto import Client 9 - 10 - from noti.agent import ActiveUser, FeedResult, generate_feed 11 - from noti.config import settings 12 - from noti.preferences import load_preferences 13 - 14 - logger = logging.getLogger("noti") 15 - 16 - 17 - def _profile_url(handle: str | None) -> str | None: 18 - if not handle: 19 - return None 20 - return f"https://bsky.app/profile/{handle}" 21 - 22 - 23 - def _post_url(uri: str | None, handle: str | None = None) -> str | None: 24 - if not uri or not uri.startswith("at://"): 25 - return None 26 - parts = uri.removeprefix("at://").split("/", 2) 27 - if len(parts) != 3 or parts[1] != "app.bsky.feed.post": 28 - return None 29 - repo, _, rkey = parts 30 - profile = handle or repo 31 - return f"https://bsky.app/profile/{profile}/post/{rkey}" 32 - 33 - 34 - def _notifs_hash(notifs: list[dict]) -> str: 35 - """cheap fingerprint so we only regenerate when notifications change.""" 36 - return json.dumps( 37 - [ 38 - { 39 - "uri": n["uri"], 40 - "reason_subject": n.get("reason_subject"), 41 - "reply_root_uri": n.get("reply_root_uri"), 42 - "reply_parent_uri": n.get("reply_parent_uri"), 43 - "subject_text": n.get("subject_text", ""), 44 - } 45 - for n in notifs 46 - ], 47 - sort_keys=True, 48 - ) 49 - 50 - 51 - def _feed_key(notifs: list[dict]) -> str: 52 - preferences = load_preferences() 53 - return f"{preferences.fingerprint()}::{_notifs_hash(notifs)}" 54 - 55 - 56 - class NotificationPoller: 57 - def __init__(self): 58 - self.notifications: list[dict] = [] 59 - self.feed: FeedResult | None = None 60 - self._client: Client | None = None 61 - self._lock = asyncio.Lock() 62 - self._last_feed_hash: str = "" 63 - self._subject_cache: dict[str, dict[str, str]] = {} 64 - self._subject_cache_warmed = False 65 - self._activity_subscription_dids: set[str] = set() 66 - self._muted_actor_dids: set[str] = set() 67 - self._refresh_task: asyncio.Task[None] | None = None 68 - self._refresh_pending = False 69 - self._refreshing = False 70 - 71 - def _login(self) -> Client: 72 - client = Client(base_url=settings.atproto_pds) 73 - client.login(settings.atproto_handle, settings.atproto_password) 74 - return client 75 - 76 - def _active_user(self) -> ActiveUser: 77 - if self._client is not None: 78 - return ActiveUser( 79 - handle=self._client.me.handle, 80 - display_name=self._client.me.display_name or None, 81 - ) 82 - return ActiveUser(handle=settings.atproto_handle) 83 - 84 - @staticmethod 85 - def _preview_text(text: str, *, limit: int = 160) -> str: 86 - collapsed = " ".join(text.split()) 87 - if len(collapsed) <= limit: 88 - return collapsed 89 - return collapsed[: limit - 1].rstrip() + "…" 90 - 91 - @staticmethod 92 - def _parse_at_uri(uri: str) -> tuple[str, str, str] | None: 93 - if not uri.startswith("at://"): 94 - return None 95 - parts = uri.removeprefix("at://").split("/", 2) 96 - if len(parts) != 3: 97 - return None 98 - return parts[0], parts[1], parts[2] 99 - 100 - @classmethod 101 - def _subject_preview_from_record(cls, record: object) -> str | None: 102 - text = getattr(record, "text", None) 103 - if isinstance(text, str) and text.strip(): 104 - return cls._preview_text(text) 105 - return None 106 - 107 - def _subject_from_record( 108 - self, 109 - uri: str, 110 - record: object, 111 - *, 112 - author_name: str | None = None, 113 - author_handle: str | None = None, 114 - created_at: str | None = None, 115 - ) -> dict[str, str] | None: 116 - preview = self._subject_preview_from_record(record) 117 - if not preview: 118 - return None 119 - 120 - if self._client is not None: 121 - fallback_name = self._client.me.display_name or self._client.me.handle 122 - fallback_handle = self._client.me.handle 123 - else: 124 - fallback_name = "you" 125 - fallback_handle = "" 126 - 127 - return { 128 - "uri": uri, 129 - "text": preview, 130 - "author_name": author_name or fallback_name, 131 - "author_handle": author_handle or fallback_handle, 132 - "author_url": _profile_url(author_handle or fallback_handle), 133 - "url": _post_url(uri, author_handle or fallback_handle), 134 - "created_at": str(created_at or getattr(record, "created_at", "") or ""), 135 - } 136 - 137 - async def _warm_subject_cache(self): 138 - if self._client is None or self._subject_cache_warmed: 139 - return 140 - 141 - response = await asyncio.to_thread( 142 - self._client.com.atproto.repo.list_records, 143 - params={ 144 - "repo": self._client.me.did, 145 - "collection": "app.bsky.feed.post", 146 - "limit": 100, 147 - "reverse": True, 148 - }, 149 - ) 150 - for record in response.records: 151 - subject = self._subject_from_record(record.uri, getattr(record, "value", None)) 152 - if subject: 153 - self._subject_cache[record.uri] = subject 154 - self._subject_cache_warmed = True 155 - 156 - async def _refresh_activity_subscriptions(self): 157 - if self._client is None: 158 - return 159 - 160 - subscriptions: set[str] = set() 161 - cursor: str | None = None 162 - while True: 163 - params: dict[str, object] = {"limit": 100} 164 - if cursor: 165 - params["cursor"] = cursor 166 - response = await asyncio.to_thread( 167 - self._client.app.bsky.notification.list_activity_subscriptions, 168 - params=params, 169 - ) 170 - for profile in response.subscriptions: 171 - did = getattr(profile, "did", None) 172 - if did: 173 - subscriptions.add(did) 174 - cursor = getattr(response, "cursor", None) 175 - if not cursor: 176 - break 177 - 178 - self._activity_subscription_dids = subscriptions 179 - 180 - async def _refresh_muted_actors(self): 181 - if self._client is None: 182 - return 183 - 184 - muted: set[str] = set() 185 - cursor: str | None = None 186 - while True: 187 - params: dict[str, object] = {"limit": 100} 188 - if cursor: 189 - params["cursor"] = cursor 190 - response = await asyncio.to_thread( 191 - self._client.app.bsky.graph.get_mutes, 192 - params=params, 193 - ) 194 - for profile in response.mutes: 195 - did = getattr(profile, "did", None) 196 - if did: 197 - muted.add(did) 198 - cursor = getattr(response, "cursor", None) 199 - if not cursor: 200 - break 201 - 202 - self._muted_actor_dids = muted 203 - 204 - def _is_notification_surfaced(self, notification: dict) -> bool: 205 - author_did = notification.get("author_did") 206 - if author_did and author_did in self._muted_actor_dids: 207 - return False 208 - if ( 209 - notification.get("reason") == "subscribed-post" 210 - and author_did 211 - and author_did not in self._activity_subscription_dids 212 - ): 213 - return False 214 - return True 215 - 216 - async def _fetch_subject_posts(self, uris: list[str]): 217 - if self._client is None or not uris: 218 - return 219 - 220 - batch_size = 25 221 - for i in range(0, len(uris), batch_size): 222 - batch = uris[i : i + batch_size] 223 - response = await asyncio.to_thread( 224 - self._client.app.bsky.feed.get_posts, 225 - params={"uris": batch}, 226 - ) 227 - for post in response.posts: 228 - subject = self._subject_from_record( 229 - post.uri, 230 - getattr(post, "record", None), 231 - author_name=getattr(post.author, "display_name", None) 232 - or getattr(post.author, "handle", None), 233 - author_handle=getattr(post.author, "handle", None), 234 - created_at=getattr(post.record, "created_at", None), 235 - ) 236 - if subject: 237 - self._subject_cache[post.uri] = subject 238 - 239 - async def _fetch_subject_record(self, uri: str): 240 - if self._client is None: 241 - return 242 - 243 - parsed = self._parse_at_uri(uri) 244 - if parsed is None: 245 - return 246 - repo, collection, rkey = parsed 247 - response = await asyncio.to_thread( 248 - self._client.com.atproto.repo.get_record, 249 - params={ 250 - "repo": repo, 251 - "collection": collection, 252 - "rkey": rkey, 253 - }, 254 - ) 255 - record = getattr(response, "value", None) 256 - subject = self._subject_from_record(uri, record) 257 - if subject: 258 - self._subject_cache[uri] = subject 259 - elif collection == "app.bsky.feed.repost": 260 - self._subject_cache[uri] = { 261 - "uri": uri, 262 - "text": "your repost", 263 - "author_name": self._client.me.display_name or self._client.me.handle, 264 - "author_handle": self._client.me.handle, 265 - "author_url": _profile_url(self._client.me.handle), 266 - "url": None, 267 - "created_at": "", 268 - } 269 - else: 270 - self._subject_cache[uri] = {} 271 - 272 - async def _resolve_subjects(self, items: list[dict]): 273 - subjects = { 274 - item["reason_subject"] 275 - for item in items 276 - if item.get("reason_subject") 277 - } 278 - missing = [uri for uri in subjects if uri not in self._subject_cache] 279 - if missing: 280 - post_uris = [ 281 - uri 282 - for uri in missing 283 - if "/app.bsky.feed.post/" in uri 284 - ] 285 - if post_uris: 286 - await self._fetch_subject_posts(post_uris) 287 - 288 - for uri in missing: 289 - if uri not in self._subject_cache: 290 - await self._fetch_subject_record(uri) 291 - 292 - for item in items: 293 - subject_uri = item.get("reason_subject") 294 - subject = self._subject_cache.get(subject_uri or "") 295 - if subject: 296 - item["subject"] = subject 297 - item["subject_text"] = subject["text"] 298 - 299 - async def poll_once(self): 300 - """fetch notifications and regenerate the feed if unread state changed.""" 301 - if self._client is None: 302 - self._client = await asyncio.to_thread(self._login) 303 - logger.info(f"logged in as @{self._client.me.handle}") 304 - await self._warm_subject_cache() 305 - await self._refresh_activity_subscriptions() 306 - await self._refresh_muted_actors() 307 - 308 - response = await asyncio.to_thread( 309 - self._client.app.bsky.notification.list_notifications, 310 - params={"limit": 50}, 311 - ) 312 - await self._refresh_activity_subscriptions() 313 - await self._refresh_muted_actors() 314 - items = [] 315 - for n in response.notifications: 316 - reply = getattr(n.record, "reply", None) 317 - reply_root = getattr(getattr(reply, "root", None), "uri", None) 318 - reply_parent = getattr(getattr(reply, "parent", None), "uri", None) 319 - item = { 320 - "uri": n.uri, 321 - "reason": n.reason, 322 - "reason_subject": getattr(n, "reason_subject", None), 323 - "reply_root_uri": reply_root, 324 - "reply_parent_uri": reply_parent, 325 - "thread_key": reply_root, 326 - "author": n.author.handle, 327 - "author_did": getattr(n.author, "did", None), 328 - "author_name": n.author.display_name or n.author.handle, 329 - "author_handle": n.author.handle, 330 - "profile_url": _profile_url(n.author.handle), 331 - "avatar": getattr(n.author, "avatar", None), 332 - "is_read": n.is_read, 333 - "indexed_at": str(n.indexed_at), 334 - "created_at": str(getattr(n.record, "created_at", "")), 335 - "notification_url": _post_url(n.uri, n.author.handle), 336 - "text": "", 337 - "is_reply": getattr(n.record, "reply", None) is not None, 338 - "has_embed": getattr(n.record, "embed", None) is not None, 339 - } 340 - if hasattr(n.record, "text"): 341 - item["text"] = n.record.text 342 - logger.debug( 343 - f" {n.reason} from @{n.author.handle}" 344 - f"{' on ' + item['reason_subject'][:60] if item['reason_subject'] else ''}" 345 - f"{' [reply]' if item['is_reply'] else ''}" 346 - ) 347 - items.append(item) 348 - 349 - await self._resolve_subjects(items) 350 - for item in items: 351 - subject = item.get("subject") 352 - item["subject_url"] = subject.get("url") if subject else None 353 - item["reply_root_url"] = _post_url(item.get("reply_root_uri")) 354 - candidates = [ 355 - item.get("reply_root_url"), 356 - item.get("subject_url"), 357 - item.get("notification_url"), 358 - item.get("profile_url"), 359 - ] 360 - item["candidate_urls"] = [url for url in candidates if url] 361 - 362 - async with self._lock: 363 - self.notifications = items 364 - 365 - unread = sum(1 for i in items if not i["is_read"]) 366 - logger.info(f"polled {len(items)} notifications ({unread} unread)") 367 - 368 - # regenerate the feed if unread notifications changed 369 - unread_items = [i for i in items if not i["is_read"]] 370 - surfaced_unread_items = [ 371 - notification 372 - for notification in unread_items 373 - if self._is_notification_surfaced(notification) 374 - ] 375 - if not unread_items: 376 - async with self._lock: 377 - self.feed = None 378 - self._last_feed_hash = "" 379 - else: 380 - h = _feed_key(surfaced_unread_items) 381 - if h != self._last_feed_hash: 382 - feed = await generate_feed( 383 - surfaced_unread_items, 384 - load_preferences(), 385 - self._active_user(), 386 - ) 387 - if feed: 388 - async with self._lock: 389 - self.feed = feed 390 - self._last_feed_hash = h 391 - 392 - async def run(self): 393 - """poll loop — runs forever.""" 394 - while True: 395 - try: 396 - await self.poll_once() 397 - except Exception as e: 398 - logger.error(f"poll error: {e}") 399 - self._client = None 400 - self._subject_cache_warmed = False 401 - await asyncio.sleep(settings.poll_interval) 402 - 403 - async def get_state(self) -> tuple[list[dict], FeedResult | None]: 404 - """snapshot current notifications + generated feed for rendering.""" 405 - async with self._lock: 406 - return list(self.notifications), self.feed 407 - 408 - async def refresh_feed(self): 409 - """force a feed regeneration, used after preference edits.""" 410 - async with self._lock: 411 - unread_items = [n for n in self.notifications if not n["is_read"]] 412 - surfaced_unread_items = [ 413 - notification 414 - for notification in unread_items 415 - if self._is_notification_surfaced(notification) 416 - ] 417 - 418 - if not surfaced_unread_items: 419 - async with self._lock: 420 - self.feed = None 421 - self._last_feed_hash = _feed_key([]) 422 - return 423 - 424 - preferences = load_preferences() 425 - feed = await generate_feed( 426 - surfaced_unread_items, 427 - preferences, 428 - self._active_user(), 429 - ) 430 - if feed: 431 - async with self._lock: 432 - self.feed = feed 433 - self._last_feed_hash = _feed_key(surfaced_unread_items) 434 - 435 - async def mark_all_read(self) -> bool: 436 - """Advance the Bluesky seen cursor through the newest current unread item.""" 437 - if self._client is None: 438 - self._client = await asyncio.to_thread(self._login) 439 - logger.info(f"logged in as @{self._client.me.handle}") 440 - await self._warm_subject_cache() 441 - 442 - async with self._lock: 443 - unread_items = [n for n in self.notifications if not n["is_read"]] 444 - 445 - if not unread_items: 446 - return False 447 - 448 - seen_at = datetime.now(UTC).isoformat().replace("+00:00", "Z") 449 - 450 - await asyncio.to_thread( 451 - self._client.app.bsky.notification.update_seen, 452 - {"seenAt": seen_at}, 453 - ) 454 - logger.info("marked notifications seen through %s", seen_at) 455 - await self.poll_once() 456 - return True 457 - 458 - async def execute_action(self, action_id: str, source_uris: list[str]) -> bool: 459 - if self._client is None: 460 - self._client = await asyncio.to_thread(self._login) 461 - logger.info(f"logged in as @{self._client.me.handle}") 462 - await self._warm_subject_cache() 463 - 464 - async with self._lock: 465 - notifications_by_uri = {n["uri"]: n for n in self.notifications} 466 - 467 - notifications = [ 468 - notifications_by_uri[uri] for uri in source_uris if uri in notifications_by_uri 469 - ] 470 - if not notifications: 471 - return False 472 - 473 - if action_id == "mute_account": 474 - author_dids = { 475 - n.get("author_did") for n in notifications if n.get("author_did") 476 - } 477 - if len(author_dids) != 1: 478 - return False 479 - actor = next(iter(author_dids)) 480 - if not actor: 481 - return False 482 - await asyncio.to_thread( 483 - self._client.app.bsky.graph.mute_actor, 484 - {"actor": actor}, 485 - ) 486 - logger.info("muted account %s", actor) 487 - 488 - elif action_id == "unsubscribe_activity": 489 - author_dids = { 490 - n.get("author_did") for n in notifications if n.get("author_did") 491 - } 492 - reasons = {n.get("reason") for n in notifications} 493 - if len(author_dids) != 1 or reasons != {"subscribed-post"}: 494 - return False 495 - subject = next(iter(author_dids)) 496 - if not subject: 497 - return False 498 - await asyncio.to_thread( 499 - self._client.app.bsky.notification.put_activity_subscription, 500 - { 501 - "subject": subject, 502 - "activitySubscription": {"post": False, "reply": False}, 503 - }, 504 - ) 505 - logger.info("unsubscribed from activity for %s", subject) 506 - 507 - else: 508 - return False 509 - 510 - await self.poll_once() 511 - await self._refresh_activity_subscriptions() 512 - return True 513 - 514 - async def available_actions( 515 - self, source_uris: list[str], suggested_actions: list[str] 516 - ) -> list[str]: 517 - async with self._lock: 518 - notifications_by_uri = {n["uri"]: n for n in self.notifications} 519 - subscribed_dids = set(self._activity_subscription_dids) 520 - muted_actor_dids = set(self._muted_actor_dids) 521 - 522 - notifications = [ 523 - notifications_by_uri[uri] for uri in source_uris if uri in notifications_by_uri 524 - ] 525 - if not notifications: 526 - return [] 527 - 528 - available: list[str] = [] 529 - for action_id in suggested_actions: 530 - if action_id == "unsubscribe_activity": 531 - author_dids = { 532 - n.get("author_did") for n in notifications if n.get("author_did") 533 - } 534 - if len(author_dids) != 1: 535 - continue 536 - did = next(iter(author_dids)) 537 - if did not in subscribed_dids: 538 - continue 539 - if action_id == "mute_account": 540 - author_dids = { 541 - n.get("author_did") for n in notifications if n.get("author_did") 542 - } 543 - if len(author_dids) != 1: 544 - continue 545 - did = next(iter(author_dids)) 546 - if did in muted_actor_dids: 547 - continue 548 - available.append(action_id) 549 - return available 550 - 551 - async def _run_refresh_loop(self): 552 - self._refreshing = True 553 - try: 554 - while True: 555 - self._refresh_pending = False 556 - await self.refresh_feed() 557 - if not self._refresh_pending: 558 - break 559 - finally: 560 - self._refreshing = False 561 - self._refresh_task = None 562 - 563 - def schedule_refresh(self): 564 - """refresh the generated feed in the background without blocking the request path.""" 565 - if self._refresh_task is not None and not self._refresh_task.done(): 566 - self._refresh_pending = True 567 - return 568 - self._refresh_task = asyncio.create_task(self._run_refresh_loop()) 569 - 570 - def is_refreshing(self) -> bool: 571 - return self._refreshing
-63
src/noti/preferences.py
··· 1 - """simple persisted user preferences for feed generation.""" 2 - 3 - from __future__ import annotations 4 - 5 - import json 6 - from datetime import UTC, datetime 7 - from pathlib import Path 8 - from typing import Self 9 - 10 - from pydantic import BaseModel, Field 11 - 12 - from noti.config import settings 13 - 14 - 15 - class UserPreferences(BaseModel): 16 - want_to_see: str = Field(default="") 17 - dont_want_to_see: str = Field(default="") 18 - updated_at: str | None = None 19 - 20 - def normalized(self) -> Self: 21 - return self.model_copy( 22 - update={ 23 - "want_to_see": self.want_to_see.strip(), 24 - "dont_want_to_see": self.dont_want_to_see.strip(), 25 - } 26 - ) 27 - 28 - def fingerprint(self) -> str: 29 - normalized = self.normalized() 30 - return json.dumps( 31 - { 32 - "want_to_see": normalized.want_to_see, 33 - "dont_want_to_see": normalized.dont_want_to_see, 34 - }, 35 - sort_keys=True, 36 - ) 37 - 38 - 39 - def _preferences_path() -> Path: 40 - return Path(settings.preferences_path) 41 - 42 - 43 - def load_preferences() -> UserPreferences: 44 - path = _preferences_path() 45 - if not path.exists(): 46 - return UserPreferences() 47 - 48 - try: 49 - data = json.loads(path.read_text()) 50 - return UserPreferences.model_validate(data) 51 - except Exception: 52 - return UserPreferences() 53 - 54 - 55 - def save_preferences(preferences: UserPreferences) -> UserPreferences: 56 - path = _preferences_path() 57 - path.parent.mkdir(parents=True, exist_ok=True) 58 - 59 - saved = preferences.normalized().model_copy( 60 - update={"updated_at": datetime.now(UTC).isoformat()} 61 - ) 62 - path.write_text(saved.model_dump_json(indent=2) + "\n") 63 - return saved
src/noti/py.typed

This is a binary file and will not be displayed.

-608
src/noti/templates/index.html
··· 1 - <!DOCTYPE html> 2 - <html> 3 - <head> 4 - <meta charset="UTF-8"> 5 - <meta name="viewport" content="width=device-width, initial-scale=1.0"> 6 - <title>noti</title> 7 - <link rel="preconnect" href="https://fonts.googleapis.com"> 8 - <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin> 9 - <link href="https://fonts.googleapis.com/css2?family=Geist:wght@400;500;600;700&display=swap" rel="stylesheet"> 10 - <script src="https://unpkg.com/htmx.org@2.0.4"></script> 11 - <style> 12 - * { margin: 0; padding: 0; box-sizing: border-box; } 13 - body { 14 - font-family: "Geist", -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; 15 - background: #0d1117; color: #c9d1d9; 16 - max-width: 600px; margin: 0 auto; 17 - padding: 12px; 18 - -webkit-text-size-adjust: 100%; 19 - } 20 - header { 21 - display: flex; align-items: center; justify-content: space-between; 22 - padding: 8px 0 12px; 23 - border-bottom: 1px solid #21262d; 24 - margin-bottom: 12px; 25 - } 26 - h1 { font-size: 17px; font-weight: 600; } 27 - .meta { 28 - display: inline-flex; 29 - align-items: center; 30 - gap: 8px; 31 - } 32 - .status-pill { 33 - display: inline-flex; 34 - align-items: center; 35 - gap: 8px; 36 - padding: 7px 11px; 37 - border-radius: 999px; 38 - border: 1px solid #2b3744; 39 - background: #151b23; 40 - color: #8b949e; 41 - font-size: 12px; 42 - font-weight: 600; 43 - letter-spacing: 0.01em; 44 - line-height: 1; 45 - white-space: nowrap; 46 - } 47 - .status-pill::before { 48 - content: ""; 49 - width: 7px; 50 - height: 7px; 51 - border-radius: 999px; 52 - background: #6e7681; 53 - box-shadow: 0 0 0 4px rgba(110, 118, 129, 0.12); 54 - flex-shrink: 0; 55 - } 56 - .status-pill.has-unread { 57 - color: #f0f6fc; 58 - border-color: #304050; 59 - background: linear-gradient(180deg, #17212b 0%, #131a22 100%); 60 - } 61 - .status-pill.has-unread::before { 62 - background: #58a6ff; 63 - box-shadow: 0 0 0 4px rgba(88, 166, 255, 0.12); 64 - } 65 - .header-actions { display: flex; align-items: center; gap: 10px; } 66 - .header-action-button { 67 - border: 1px solid #2f3a47; 68 - background: linear-gradient(180deg, #161d26 0%, #10161d 100%); 69 - color: #c9d1d9; 70 - border-radius: 999px; 71 - padding: 7px 11px; 72 - font-size: 12px; 73 - font-weight: 600; 74 - line-height: 1; 75 - cursor: pointer; 76 - white-space: nowrap; 77 - transition: border-color 120ms ease, color 120ms ease, transform 120ms ease; 78 - } 79 - .header-action-button:hover { 80 - color: #f0f6fc; 81 - border-color: #4d637a; 82 - transform: translateY(-1px); 83 - } 84 - .header-action-button:active { 85 - transform: translateY(0); 86 - } 87 - .meta.htmx-request .header-action-button { 88 - opacity: 0.7; 89 - cursor: progress; 90 - pointer-events: none; 91 - } 92 - .settings-button { 93 - display: inline-flex; 94 - align-items: center; 95 - justify-content: center; 96 - width: 38px; 97 - height: 38px; 98 - border-radius: 999px; 99 - border: 1px solid #2d3642; 100 - background: 101 - radial-gradient(circle at 30% 30%, #222c39 0%, #161b22 45%, #0d1117 100%); 102 - color: #c9d1d9; 103 - cursor: pointer; 104 - box-shadow: 105 - inset 0 1px 0 rgba(255, 255, 255, 0.04), 106 - 0 8px 20px rgba(0, 0, 0, 0.28); 107 - transition: 108 - transform 120ms ease, 109 - border-color 120ms ease, 110 - box-shadow 120ms ease, 111 - color 120ms ease; 112 - } 113 - .settings-button:hover { 114 - border-color: #4c6177; 115 - color: #f0f6fc; 116 - transform: translateY(-1px); 117 - box-shadow: 118 - inset 0 1px 0 rgba(255, 255, 255, 0.05), 119 - 0 12px 26px rgba(0, 0, 0, 0.34); 120 - } 121 - .settings-button:active { 122 - transform: translateY(0); 123 - box-shadow: 124 - inset 0 1px 0 rgba(255, 255, 255, 0.03), 125 - 0 6px 14px rgba(0, 0, 0, 0.24); 126 - } 127 - .settings-button svg { 128 - width: 18px; 129 - height: 18px; 130 - stroke: currentColor; 131 - fill: none; 132 - stroke-width: 1.8; 133 - stroke-linecap: round; 134 - stroke-linejoin: round; 135 - } 136 - 137 - .briefing { 138 - background: #161b22; border: 1px solid #30363d; 139 - border-radius: 10px; padding: 12px 14px; 140 - margin-bottom: 12px; 141 - font-size: 14px; line-height: 1.5; 142 - } 143 - .preferences-panel { 144 - display: none; 145 - background: linear-gradient(180deg, #161b22 0%, #121821 100%); 146 - border: 1px solid #30363d; 147 - border-radius: 14px; 148 - padding: 14px; 149 - margin-bottom: 12px; 150 - } 151 - .preferences-panel.open { display: block; } 152 - .preferences-header { 153 - display: flex; 154 - align-items: flex-start; 155 - justify-content: space-between; 156 - gap: 12px; 157 - margin-bottom: 12px; 158 - } 159 - .preferences-title { font-size: 14px; font-weight: 700; } 160 - .preferences-copy { 161 - font-size: 12px; 162 - color: #8b949e; 163 - line-height: 1.4; 164 - margin-top: 2px; 165 - } 166 - .preferences-close { 167 - border: 0; 168 - background: none; 169 - color: #8b949e; 170 - font-size: 12px; 171 - cursor: pointer; 172 - } 173 - .preferences-label { 174 - display: block; 175 - font-size: 12px; 176 - font-weight: 600; 177 - color: #c9d1d9; 178 - margin: 10px 0 6px; 179 - } 180 - .preferences-input { 181 - width: 100%; 182 - min-height: 88px; 183 - resize: vertical; 184 - border: 1px solid #30363d; 185 - border-radius: 10px; 186 - background: #0d1117; 187 - color: #c9d1d9; 188 - padding: 10px 12px; 189 - font: inherit; 190 - line-height: 1.4; 191 - } 192 - .preferences-actions { 193 - display: flex; 194 - align-items: center; 195 - gap: 10px; 196 - margin-top: 12px; 197 - } 198 - .preferences-save { 199 - border: 1px solid #3fb95055; 200 - background: #2ea04322; 201 - color: #3fb950; 202 - padding: 8px 12px; 203 - border-radius: 999px; 204 - cursor: pointer; 205 - font-size: 13px; 206 - font-weight: 600; 207 - } 208 - .preferences-form.htmx-request .preferences-save { 209 - opacity: 0.72; 210 - cursor: progress; 211 - pointer-events: none; 212 - } 213 - .preferences-message { 214 - font-size: 12px; 215 - color: #8b949e; 216 - } 217 - .preferences-saving { 218 - display: none; 219 - } 220 - .preferences-form.htmx-request .preferences-saving { 221 - display: inline; 222 - } 223 - .preferences-form.htmx-request .preferences-message:not(.preferences-saving) { 224 - display: none; 225 - } 226 - .app-notice { 227 - position: sticky; 228 - top: 10px; 229 - z-index: 20; 230 - display: none; 231 - align-items: center; 232 - gap: 8px; 233 - margin-bottom: 12px; 234 - padding: 10px 12px; 235 - border-radius: 12px; 236 - border: 1px solid #2b3744; 237 - background: rgba(17, 24, 32, 0.92); 238 - color: #c9d1d9; 239 - backdrop-filter: blur(10px); 240 - box-shadow: 0 14px 28px rgba(0, 0, 0, 0.24); 241 - } 242 - .app-notice.visible { 243 - display: flex; 244 - } 245 - .app-notice::before { 246 - content: ""; 247 - width: 9px; 248 - height: 9px; 249 - border-radius: 999px; 250 - background: #58a6ff; 251 - box-shadow: 0 0 0 4px rgba(88, 166, 255, 0.12); 252 - flex-shrink: 0; 253 - } 254 - .app-notice.complete::before { 255 - background: #3fb950; 256 - box-shadow: 0 0 0 4px rgba(63, 185, 80, 0.12); 257 - } 258 - .app-notice-text { 259 - font-size: 13px; 260 - line-height: 1.35; 261 - } 262 - 263 - .feed-list { 264 - display: flex; 265 - flex-direction: column; 266 - gap: 12px; 267 - } 268 - .feed-item { 269 - background: linear-gradient(180deg, #141a22 0%, #10161d 100%); 270 - border: 1px solid #26303a; 271 - border-radius: 14px; 272 - padding: 14px; 273 - box-shadow: 0 10px 24px rgba(0, 0, 0, 0.16); 274 - } 275 - .feed-header { 276 - display: flex; 277 - align-items: flex-start; 278 - justify-content: space-between; 279 - gap: 12px; 280 - } 281 - .feed-actors { 282 - display: flex; 283 - align-items: center; 284 - flex-shrink: 0; 285 - padding-top: 2px; 286 - margin-right: 2px; 287 - } 288 - .feed-actor { 289 - width: 34px; 290 - height: 34px; 291 - border-radius: 999px; 292 - border: 2px solid #10161d; 293 - background: linear-gradient(180deg, #223040 0%, #17212b 100%); 294 - overflow: hidden; 295 - display: inline-flex; 296 - align-items: center; 297 - justify-content: center; 298 - color: #f0f6fc; 299 - font-size: 12px; 300 - font-weight: 700; 301 - text-decoration: none; 302 - box-shadow: 0 4px 12px rgba(0, 0, 0, 0.2); 303 - } 304 - .feed-actor + .feed-actor { 305 - margin-left: -10px; 306 - } 307 - .feed-actor:hover { 308 - transform: translateY(-1px); 309 - border-color: #3b82c4; 310 - } 311 - .feed-actor img { 312 - width: 100%; 313 - height: 100%; 314 - object-fit: cover; 315 - display: block; 316 - } 317 - .feed-actor-more { 318 - background: #202a36; 319 - color: #c9d1d9; 320 - font-size: 11px; 321 - } 322 - .feed-title-wrap { 323 - min-width: 0; 324 - flex: 1; 325 - } 326 - .feed-title { 327 - display: inline-block; 328 - font-size: 18px; 329 - font-weight: 700; 330 - color: #f0f6fc; 331 - line-height: 1.2; 332 - } 333 - .feed-title-link { 334 - text-decoration: none; 335 - } 336 - .feed-title-link:hover, 337 - .feed-open:hover { 338 - text-decoration: underline; 339 - } 340 - .feed-meta { 341 - margin-top: 4px; 342 - font-size: 12px; 343 - color: #8b949e; 344 - text-transform: uppercase; 345 - letter-spacing: 0.4px; 346 - } 347 - .feed-open { 348 - flex-shrink: 0; 349 - color: #58a6ff; 350 - text-decoration: none; 351 - font-size: 13px; 352 - font-weight: 600; 353 - margin-left: auto; 354 - } 355 - .feed-summary { 356 - margin-top: 10px; 357 - font-size: 15px; 358 - line-height: 1.5; 359 - color: #c9d1d9; 360 - } 361 - .feed-actions { 362 - display: flex; 363 - flex-wrap: wrap; 364 - gap: 8px; 365 - margin-top: 12px; 366 - } 367 - .feed-action-form { 368 - display: inline-flex; 369 - } 370 - .action-chip { 371 - display: inline-flex; 372 - align-items: center; 373 - padding: 5px 10px; 374 - border-radius: 999px; 375 - background: #1c2530; 376 - border: 1px solid #2b3744; 377 - color: #c9d1d9; 378 - font-size: 12px; 379 - font-weight: 600; 380 - font-family: inherit; 381 - cursor: pointer; 382 - transition: border-color 120ms ease, color 120ms ease, transform 120ms ease; 383 - } 384 - .action-chip:hover { 385 - color: #f0f6fc; 386 - border-color: #4d637a; 387 - transform: translateY(-1px); 388 - } 389 - .feed-action-form.htmx-request .action-chip { 390 - opacity: 0.7; 391 - cursor: progress; 392 - pointer-events: none; 393 - } 394 - @media (max-width: 520px) { 395 - .feed-header { 396 - flex-wrap: wrap; 397 - } 398 - .feed-open { 399 - width: 100%; 400 - margin-left: 0; 401 - } 402 - } 403 - 404 - .show-more { 405 - display: flex; 406 - align-items: center; 407 - justify-content: center; 408 - gap: 10px; 409 - width: fit-content; 410 - max-width: 100%; 411 - margin: 14px auto 0; 412 - padding: 9px 13px; 413 - border-radius: 999px; 414 - border: 1px solid #2f3a47; 415 - background: linear-gradient(180deg, #161d26 0%, #10161d 100%); 416 - box-shadow: 417 - inset 0 1px 0 rgba(255, 255, 255, 0.03), 418 - 0 8px 20px rgba(0, 0, 0, 0.18); 419 - color: #c9d1d9; 420 - font-size: 12px; 421 - font-weight: 600; 422 - cursor: pointer; 423 - text-decoration: none; 424 - line-height: 1; 425 - } 426 - .show-more:hover { 427 - color: #f0f6fc; 428 - border-color: #4d637a; 429 - transform: translateY(-1px); 430 - box-shadow: 431 - inset 0 1px 0 rgba(255, 255, 255, 0.04), 432 - 0 12px 26px rgba(0, 0, 0, 0.22); 433 - } 434 - .show-more-mark { 435 - width: 28px; 436 - height: 28px; 437 - border-radius: 999px; 438 - display: inline-flex; 439 - align-items: center; 440 - justify-content: center; 441 - flex-shrink: 0; 442 - overflow: hidden; 443 - box-shadow: 0 6px 14px rgba(39, 139, 255, 0.2); 444 - } 445 - .show-more-mark img { 446 - display: block; 447 - width: 100%; 448 - height: 100%; 449 - object-fit: cover; 450 - } 451 - .show-more-copy { 452 - display: flex; 453 - align-items: center; 454 - gap: 5px; 455 - min-width: 0; 456 - } 457 - .show-more-kicker { 458 - color: #8b949e; 459 - font-weight: 500; 460 - } 461 - .show-more-label { 462 - color: #f0f6fc; 463 - white-space: nowrap; 464 - } 465 - .show-more-arrow { 466 - width: 14px; 467 - height: 14px; 468 - stroke: #8b949e; 469 - fill: none; 470 - stroke-width: 1.8; 471 - stroke-linecap: round; 472 - stroke-linejoin: round; 473 - flex-shrink: 0; 474 - } 475 - .show-more:hover .show-more-arrow { stroke: #58a6ff; } 476 - 477 - #notif-container { min-height: 100px; } 478 - .loading { color: #8b949e; padding: 20px; text-align: center; font-size: 13px; } 479 - </style> 480 - </head> 481 - <body> 482 - <header> 483 - <h1>noti</h1> 484 - <div class="header-actions"> 485 - <span class="meta" 486 - hx-get="/partials/status" 487 - hx-trigger="load, every 30s, preferences-updated from:body, notifications-updated from:body" 488 - hx-swap="innerHTML"> 489 - connecting... 490 - </span> 491 - <button type="button" 492 - class="settings-button" 493 - aria-label="settings" 494 - onclick="togglePreferences()"> 495 - <svg viewBox="0 0 24 24" aria-hidden="true"> 496 - <circle cx="12" cy="12" r="3.25"></circle> 497 - <path d="M19.2 15a1 1 0 0 0 .2 1.1l.1.1a1.2 1.2 0 0 1 0 1.7l-1.4 1.4a1.2 1.2 0 0 1-1.7 0l-.1-.1a1 1 0 0 0-1.1-.2 1 1 0 0 0-.6.9V20a1.2 1.2 0 0 1-1.2 1.2h-2A1.2 1.2 0 0 1 10.2 20v-.2a1 1 0 0 0-.6-.9 1 1 0 0 0-1.1.2l-.1.1a1.2 1.2 0 0 1-1.7 0l-1.4-1.4a1.2 1.2 0 0 1 0-1.7l.1-.1a1 1 0 0 0 .2-1.1 1 1 0 0 0-.9-.6H4A1.2 1.2 0 0 1 2.8 13v-2A1.2 1.2 0 0 1 4 9.8h.2a1 1 0 0 0 .9-.6 1 1 0 0 0-.2-1.1l-.1-.1a1.2 1.2 0 0 1 0-1.7l1.4-1.4a1.2 1.2 0 0 1 1.7 0l.1.1a1 1 0 0 0 1.1.2 1 1 0 0 0 .6-.9V4A1.2 1.2 0 0 1 11 2.8h2A1.2 1.2 0 0 1 14.2 4v.2a1 1 0 0 0 .6.9 1 1 0 0 0 1.1-.2l.1-.1a1.2 1.2 0 0 1 1.7 0l1.4 1.4a1.2 1.2 0 0 1 0 1.7l-.1.1a1 1 0 0 0-.2 1.1 1 1 0 0 0 .9.6h.2A1.2 1.2 0 0 1 21.2 11v2A1.2 1.2 0 0 1 20 14.2h-.2a1 1 0 0 0-.6.8z"></path> 498 - </svg> 499 - </button> 500 - </div> 501 - </header> 502 - 503 - {% include "partials/preferences.html" %} 504 - 505 - <div id="app-notice" class="app-notice" aria-live="polite"> 506 - <div id="app-notice-text" class="app-notice-text"></div> 507 - </div> 508 - 509 - <div id="notif-container" 510 - hx-get="/partials/notifications" 511 - hx-trigger="load, every 30s, preferences-updated from:body, notifications-updated from:body" 512 - hx-swap="innerHTML"> 513 - <div class="loading">loading...</div> 514 - </div> 515 - 516 - <script> 517 - function togglePreferences(forceOpen) { 518 - const panel = document.getElementById('preferences-panel'); 519 - if (!panel) return; 520 - const shouldOpen = forceOpen === undefined 521 - ? !panel.classList.contains('open') 522 - : forceOpen; 523 - panel.classList.toggle('open', shouldOpen); 524 - } 525 - 526 - let refreshStatusPoll = null; 527 - let noticeTimeout = null; 528 - let refreshStatusGeneration = 0; 529 - 530 - function showNotice(message, complete = false) { 531 - const notice = document.getElementById('app-notice'); 532 - const text = document.getElementById('app-notice-text'); 533 - if (!notice || !text) return; 534 - text.textContent = message; 535 - notice.classList.add('visible'); 536 - notice.classList.toggle('complete', complete); 537 - if (noticeTimeout) { 538 - clearTimeout(noticeTimeout); 539 - noticeTimeout = null; 540 - } 541 - if (complete) { 542 - noticeTimeout = window.setTimeout(() => { 543 - notice.classList.remove('visible', 'complete'); 544 - }, 2200); 545 - } 546 - } 547 - 548 - async function pollRefreshStatus(generation) { 549 - if (generation !== refreshStatusGeneration) { 550 - return; 551 - } 552 - try { 553 - const response = await fetch('/api/refresh-status', { headers: { 'Accept': 'application/json' } }); 554 - if (!response.ok) return; 555 - const status = await response.json(); 556 - if (generation !== refreshStatusGeneration) { 557 - return; 558 - } 559 - showNotice(status.message, !status.refreshing); 560 - if (status.refreshing) { 561 - refreshStatusPoll = window.setTimeout(() => pollRefreshStatus(generation), 900); 562 - return; 563 - } 564 - htmx.trigger(document.body, 'preferences-updated'); 565 - refreshStatusPoll = null; 566 - } catch (error) { 567 - console.error('refresh status poll failed', error); 568 - if (generation !== refreshStatusGeneration) { 569 - return; 570 - } 571 - refreshStatusPoll = window.setTimeout(() => pollRefreshStatus(generation), 1500); 572 - } 573 - } 574 - 575 - document.body.addEventListener('preferences-refresh-started', () => { 576 - showNotice('preferences saved. refreshing feed...'); 577 - if (refreshStatusPoll) { 578 - clearTimeout(refreshStatusPoll); 579 - } 580 - refreshStatusGeneration += 1; 581 - const generation = refreshStatusGeneration; 582 - refreshStatusPoll = window.setTimeout(() => pollRefreshStatus(generation), 250); 583 - }); 584 - 585 - document.body.addEventListener('mark-all-read-complete', (event) => { 586 - if (event.detail && event.detail.value) { 587 - showNotice('marked all as read.', true); 588 - } else { 589 - showNotice('nothing new to mark as read.', true); 590 - } 591 - }); 592 - 593 - document.body.addEventListener('feed-action-complete', (event) => { 594 - const detail = event.detail && event.detail.value ? event.detail.value : event.detail; 595 - if (!detail || !detail.action) { 596 - showNotice('action finished.', true); 597 - return; 598 - } 599 - if (!detail.changed) { 600 - showNotice('action was not available for this card.', true); 601 - return; 602 - } 603 - const messages = {{ action_success_messages | tojson }}; 604 - showNotice(messages[detail.action] || 'action completed.', true); 605 - }); 606 - </script> 607 - </body> 608 - </html>
-93
src/noti/templates/partials/notifications.html
··· 1 - {% if briefing %} 2 - <div class="briefing">{{ briefing }}</div> 3 - {% endif %} 4 - 5 - {% if unread %} 6 - <div class="feed-list"> 7 - {% for entry in unread %} 8 - {% set item = entry.item %} 9 - <div class="feed-item"> 10 - <div class="feed-header"> 11 - {% if item.actors %} 12 - <div class="feed-actors" aria-label="{{ item.actor_count }} actors"> 13 - {% for actor in item.actors %} 14 - {% if actor.profile_url %} 15 - <a class="feed-actor" href="{{ actor.profile_url }}" target="_blank" rel="noopener noreferrer" title="{{ actor.name }}"> 16 - {% if actor.avatar_url %} 17 - <img src="{{ actor.avatar_url }}" alt="{{ actor.name }}"> 18 - {% else %} 19 - <span>{{ actor.name[:1] }}</span> 20 - {% endif %} 21 - </a> 22 - {% else %} 23 - <div class="feed-actor" title="{{ actor.name }}"> 24 - {% if actor.avatar_url %} 25 - <img src="{{ actor.avatar_url }}" alt="{{ actor.name }}"> 26 - {% else %} 27 - <span>{{ actor.name[:1] }}</span> 28 - {% endif %} 29 - </div> 30 - {% endif %} 31 - {% endfor %} 32 - {% if item.actor_count > item.actors | length %} 33 - <div class="feed-actor feed-actor-more">+{{ item.actor_count - (item.actors | length) }}</div> 34 - {% endif %} 35 - </div> 36 - {% endif %} 37 - <div class="feed-title-wrap"> 38 - {% if item.target_url %} 39 - <a class="feed-title feed-title-link" href="{{ item.target_url }}" target="_blank" rel="noopener noreferrer">{{ item.title }}</a> 40 - {% else %} 41 - <div class="feed-title feed-title-static">{{ item.title }}</div> 42 - {% endif %} 43 - <div class="feed-meta">{{ item.count_label }}</div> 44 - </div> 45 - {% if item.target_url %} 46 - <a class="feed-open" href="{{ item.target_url }}" target="_blank" rel="noopener noreferrer">{{ item.target_label or 'open' }}</a> 47 - {% endif %} 48 - </div> 49 - <div class="feed-summary">{{ item.summary }}</div> 50 - {% if entry.actions %} 51 - <div class="feed-actions"> 52 - {% for action in entry.actions %} 53 - <form class="feed-action-form" 54 - hx-post="/actions/execute" 55 - hx-swap="none" 56 - onsubmit='return confirm({{ action.confirmation | tojson }})'> 57 - <input type="hidden" name="action_id" value="{{ action.id }}"> 58 - {% for uri in item.source_uris %} 59 - <input type="hidden" name="source_uris" value="{{ uri }}"> 60 - {% endfor %} 61 - <button type="submit" class="action-chip">{{ action.label }}</button> 62 - </form> 63 - {% endfor %} 64 - </div> 65 - {% endif %} 66 - </div> 67 - {% endfor %} 68 - </div> 69 - {% else %} 70 - {% if unread_count and not feed_ready %} 71 - <div class="loading">building your feed... this can take a moment</div> 72 - {% elif unread_count and not briefing %} 73 - <div class="loading">nothing worth surfacing right now</div> 74 - {% elif not has_read %} 75 - <div class="loading">all caught up</div> 76 - {% endif %} 77 - {% endif %} 78 - 79 - {% if has_read %} 80 - <a class="show-more" href="{{ bsky_notifications_url }}" target="_blank" rel="noopener noreferrer"> 81 - <span class="show-more-mark" aria-hidden="true"> 82 - <img src="https://raw.githubusercontent.com/bluesky-social/social-app/main/assets/app-icons/android_icon_default_next.png" alt=""> 83 - </span> 84 - <span class="show-more-copy"> 85 - <span class="show-more-kicker">view in</span> 86 - <span class="show-more-label">Bluesky</span> 87 - </span> 88 - <svg class="show-more-arrow" viewBox="0 0 16 16" aria-hidden="true"> 89 - <path d="M5 11 11 5"></path> 90 - <path d="M6 5h5v5"></path> 91 - </svg> 92 - </a> 93 - {% endif %}
-33
src/noti/templates/partials/preferences.html
··· 1 - <div id="preferences-panel" class="preferences-panel{% if preferences_open %} open{% endif %}"> 2 - <form class="preferences-form" 3 - hx-post="/preferences" 4 - hx-target="#preferences-panel" 5 - hx-swap="outerHTML"> 6 - <div class="preferences-header"> 7 - <div> 8 - <div class="preferences-title">preferences</div> 9 - <div class="preferences-copy">tell noti what to surface and what to deprioritize.</div> 10 - </div> 11 - <button type="button" class="preferences-close" onclick="togglePreferences(false)">close</button> 12 - </div> 13 - 14 - <label class="preferences-label" for="want-to-see">what do you want to see?</label> 15 - <textarea id="want-to-see" 16 - name="want_to_see" 17 - class="preferences-input" 18 - placeholder="Replies from friends. Mentions asking me a question. Follows from researchers I know.">{{ preferences.want_to_see }}</textarea> 19 - 20 - <label class="preferences-label" for="dont-want-to-see">what do you not want to see?</label> 21 - <textarea id="dont-want-to-see" 22 - name="dont_want_to_see" 23 - class="preferences-input" 24 - placeholder="Routine likes. Follow spam. Low-context engagement from strangers.">{{ preferences.dont_want_to_see }}</textarea> 25 - <div class="preferences-actions"> 26 - <button type="submit" class="preferences-save">save</button> 27 - <span class="preferences-message preferences-saving" aria-live="polite">saving preferences...</span> 28 - {% if preferences_message %} 29 - <span class="preferences-message">{{ preferences_message }}</span> 30 - {% endif %} 31 - </div> 32 - </form> 33 - </div>
-16
src/noti/templates/partials/status.html
··· 1 - <span class="status-pill{% if unread %} has-unread{% endif %}"> 2 - {% if unread %} 3 - {{ unread }} unread 4 - {% else %} 5 - all caught up 6 - {% endif %} 7 - </span> 8 - {% if unread %} 9 - <button class="header-action-button" 10 - hx-post="/mark-all-read" 11 - hx-swap="none" 12 - hx-confirm="Mark all current notifications as read in Bluesky?" 13 - type="button"> 14 - mark all read 15 - </button> 16 - {% endif %}
+85
src/recommend-prompt.ts
··· 1 + import type {Tool} from '@anthropic-ai/sdk/resources/messages/messages' 2 + 3 + import {SDK_SURFACE} from './code-mode' 4 + 5 + export const SYSTEM = ` 6 + You help a Bluesky user manage their notifications. 7 + 8 + Propose concrete notification-management actions the user can review and apply. 9 + Focus on changes to notification state, not inbox summaries or feed narration. 10 + 11 + You will receive: 12 + - per-actor management targets with unread pressure, history, subscription state, and mute state 13 + - the typed SDK surface you may write code against 14 + 15 + Your job is to write code that calls the SDK to reduce notification noise. 16 + Each recommendation must include working code that defines \`async function run(agent, ctx)\`. 17 + 18 + Use the actor's DID (actorDid) in SDK calls, not their handle. 19 + Use their display name (actorName) or handle (actorHandle) in descriptions for the user. 20 + 21 + ${SDK_SURFACE} 22 + 23 + Prefer: 24 + - turning off replies for subscriptions that are too chatty 25 + - turning off posts and replies entirely for low-value subscriptions 26 + - muting clearly noisy accounts 27 + 28 + When writing user-facing text: 29 + - do not mention "selected notifications" or similar internal evidence-selection wording 30 + - do not cite raw rolling counters like "56 in 24h" or "56 in 7d" 31 + - prefer concrete actor names and the management action itself 32 + - do not mention DIDs, internal targets, code generation, or other implementation details 33 + 34 + For direct user requests: 35 + - it is valid to propose actor-level subscription changes even when there are zero unread notifications 36 + - when the action targets actor state rather than specific unread items, return actorDids and sourceUris may be empty 37 + - some actors may not allow subscriptions from this viewer; check subscriptionEligible, subscriptionPolicy, and subscriptionBlockedReason before proposing any subscription mutation 38 + - if the requested action is impossible or already blocked by current actor policy / relationship state, do not return code; explain clearly why it cannot be done 39 + ` 40 + 41 + export const recommendationTool = { 42 + name: 'emit_recommendations', 43 + description: 'Return up to 3 notification-management recommendations with code.', 44 + input_schema: { 45 + type: 'object', 46 + properties: { 47 + recommendations: { 48 + type: 'array', 49 + maxItems: 3, 50 + items: { 51 + type: 'object', 52 + properties: { 53 + sourceUris: {type: 'array', items: {type: 'string'}}, 54 + description: {type: 'string'}, 55 + why: {type: 'string'}, 56 + code: {type: 'string'}, 57 + }, 58 + required: ['sourceUris', 'description', 'why', 'code'], 59 + }, 60 + }, 61 + }, 62 + required: ['recommendations'] as string[], 63 + }, 64 + } satisfies Tool 65 + 66 + export const proposalTool = { 67 + name: 'emit_proposal', 68 + description: 'Return the proposed Bluesky mutation responding to the user request, or explain why it cannot be done.', 69 + input_schema: { 70 + type: 'object', 71 + properties: { 72 + message: {type: 'string', description: 'Brief explanation of what the proposed action will do.'}, 73 + actorDids: {type: 'array', items: {type: 'string'}, description: 'Actor DIDs this action will affect.'}, 74 + sourceUris: {type: 'array', items: {type: 'string'}, description: 'URIs of notifications relevant to this action.'}, 75 + description: {type: 'string', description: 'Short title for the action.'}, 76 + why: {type: 'string', description: 'Why this action helps.'}, 77 + code: {type: 'string', description: 'The async function run(agent, ctx) code to execute.'}, 78 + unavailableReason: { 79 + type: 'string', 80 + description: 'If the requested action cannot be done, explain why here and leave code empty.', 81 + }, 82 + }, 83 + required: ['message'] as string[], 84 + }, 85 + } satisfies Tool
+290
src/recommend.ts
··· 1 + import Anthropic from '@anthropic-ai/sdk' 2 + import type {Tool, ToolChoice} from '@anthropic-ai/sdk/resources/messages/messages' 3 + 4 + import {actorProfileUrl} from './bluesky' 5 + import {validateCode} from './code-mode' 6 + import {SYSTEM, proposalTool, recommendationTool} from './recommend-prompt' 7 + import type { 8 + ActionProposal, 9 + ActorRef, 10 + ManagementTarget, 11 + NormalizedNotification, 12 + Recommendation, 13 + } from './types' 14 + 15 + const anthropic = new Anthropic({apiKey: process.env.ANTHROPIC_API_KEY}) 16 + const model = process.env.ANTHROPIC_MODEL || 'claude-sonnet-4-6' 17 + 18 + type RecommendationToolResponse = { 19 + recommendations?: Array<{ 20 + sourceUris?: unknown 21 + description?: unknown 22 + why?: unknown 23 + code?: unknown 24 + }> 25 + } 26 + 27 + type ProposalToolResponse = { 28 + message?: unknown 29 + actorDids?: unknown 30 + sourceUris?: unknown 31 + description?: unknown 32 + why?: unknown 33 + code?: unknown 34 + unavailableReason?: unknown 35 + } 36 + 37 + const recommendationToolChoice: ToolChoice = {type: 'tool', name: recommendationTool.name} 38 + const proposalToolChoice: ToolChoice = {type: 'tool', name: proposalTool.name} 39 + 40 + async function toolResponse<TResponse>( 41 + prompt: string, 42 + tool: Tool, 43 + toolChoice: ToolChoice, 44 + ): Promise<TResponse> { 45 + const res = await anthropic.messages.create({ 46 + model, 47 + max_tokens: 1400, 48 + system: SYSTEM, 49 + messages: [{role: 'user', content: prompt}], 50 + tools: [tool], 51 + tool_choice: toolChoice, 52 + }) 53 + const block = res.content.find(item => item.type === 'tool_use' && item.name === tool.name) 54 + if (!block || block.type !== 'tool_use') { 55 + throw new Error(`missing tool response for ${tool.name}`) 56 + } 57 + return block.input as TResponse 58 + } 59 + 60 + function evidenceForUris( 61 + sourceUris: string[], 62 + notificationsByUri: Map<string, NormalizedNotification>, 63 + ) { 64 + return sourceUris 65 + .filter(uri => notificationsByUri.has(uri)) 66 + .slice(0, 3) 67 + .map(uri => { 68 + const notification = notificationsByUri.get(uri) 69 + if (!notification) { 70 + throw new Error(`missing notification for ${uri}`) 71 + } 72 + return { 73 + uri: notification.uri, 74 + actor: notification.authorName, 75 + actorProfileUrl: actorProfileUrl(notification.authorHandle || notification.authorDid || notification.authorName), 76 + reason: notification.reason, 77 + snippet: ( 78 + notification.text || 79 + notification.subjectText || 80 + notification.reasonSubject || 81 + notification.reason || 82 + '' 83 + ).slice(0, 140), 84 + } 85 + }) 86 + } 87 + 88 + function actorDidsForUris( 89 + sourceUris: string[], 90 + notificationsByUri: Map<string, NormalizedNotification>, 91 + ) { 92 + return [ 93 + ...new Set( 94 + sourceUris 95 + .map(uri => notificationsByUri.get(uri)?.authorDid) 96 + .filter((did): did is string => Boolean(did)), 97 + ), 98 + ] 99 + } 100 + 101 + function targetByDid(targets: ManagementTarget[]) { 102 + return new Map(targets.map(target => [target.actorDid, target])) 103 + } 104 + 105 + function actorRefsForDids( 106 + actorDids: string[], 107 + targetsByDid: Map<string, ManagementTarget>, 108 + notificationsByUri: Map<string, NormalizedNotification>, 109 + sourceUris: string[], 110 + ): ActorRef[] { 111 + return actorDids.map(did => { 112 + const target = targetsByDid.get(did) 113 + const notification = sourceUris 114 + .map(uri => notificationsByUri.get(uri)) 115 + .find(row => row?.authorDid === did) 116 + const handle = target?.actorHandle || notification?.authorHandle || did 117 + const name = target?.actorName || notification?.authorName || handle 118 + return { 119 + did, 120 + handle, 121 + name, 122 + profileUrl: actorProfileUrl(handle), 123 + } 124 + }) 125 + } 126 + 127 + function targetPayload(targets: ManagementTarget[]) { 128 + return targets.slice(0, 16).map(target => ({ 129 + actorDid: target.actorDid, 130 + actorHandle: target.actorHandle, 131 + actorName: target.actorName, 132 + currentUnreadCount: target.currentUnreadCount, 133 + recent24hCount: target.recent24hCount, 134 + recent7dCount: target.recent7dCount, 135 + reasons: target.reasons, 136 + subscriptionPosts: target.subscriptionPosts, 137 + subscriptionReplies: target.subscriptionReplies, 138 + muted: target.muted, 139 + sourceUris: target.sourceUris, 140 + examples: target.examples, 141 + })) 142 + } 143 + 144 + function recommendationId(description: string, actorDids: string[], sourceUris: string[]) { 145 + return [description, actorDids.join(','), sourceUris.slice(0, 3).join(',')].join('|') 146 + } 147 + 148 + function blockedSubscriptionMessage(actorDids: string[], targetsByDid: Map<string, ManagementTarget>) { 149 + for (const did of actorDids) { 150 + const target = targetsByDid.get(did) 151 + if (target?.subscriptionEligible === false && target.subscriptionBlockedReason) { 152 + return target.subscriptionBlockedReason 153 + } 154 + } 155 + return '' 156 + } 157 + 158 + export async function generateRecommendations( 159 + unread: NormalizedNotification[], 160 + targets: ManagementTarget[], 161 + ) { 162 + if (unread.length === 0 || targets.length === 0) return [] 163 + const notificationsByUri = new Map(unread.map(row => [row.uri, row])) 164 + const targetsByDid = targetByDid(targets) 165 + const payload = JSON.stringify({ 166 + unreadCount: unread.length, 167 + targets: targetPayload(targets), 168 + }) 169 + const result = await toolResponse<RecommendationToolResponse>( 170 + payload, 171 + recommendationTool, 172 + recommendationToolChoice, 173 + ) 174 + if (!Array.isArray(result.recommendations)) { 175 + return [] 176 + } 177 + const recommendations: Recommendation[] = [] 178 + for (const row of result.recommendations.slice(0, 3)) { 179 + if (!row || typeof row !== 'object') continue 180 + const description = typeof row.description === 'string' ? row.description.trim() : '' 181 + const why = typeof row.why === 'string' ? row.why : '' 182 + const code = typeof row.code === 'string' ? row.code.trim() : '' 183 + const sourceUris = Array.isArray(row.sourceUris) 184 + ? row.sourceUris.filter((v: unknown): v is string => typeof v === 'string' && notificationsByUri.has(v)) 185 + : [] 186 + if (!description || !why.trim() || !code || sourceUris.length === 0) continue 187 + try { 188 + validateCode(code) 189 + } catch { 190 + continue 191 + } 192 + const actorDids = actorDidsForUris(sourceUris, notificationsByUri) 193 + const actorSet = new Set<string>( 194 + sourceUris.map((uri: string) => notificationsByUri.get(uri)?.authorName || '').filter(Boolean), 195 + ) 196 + const context = actorSet.size === 1 ? [...actorSet][0] : '' 197 + const recommendation: Recommendation = { 198 + id: recommendationId(description, actorDids, sourceUris), 199 + description, 200 + context, 201 + why: why.trim(), 202 + code, 203 + sourceUris, 204 + actorDids, 205 + actors: actorRefsForDids(actorDids, targetsByDid, notificationsByUri, sourceUris), 206 + evidence: evidenceForUris(sourceUris, notificationsByUri), 207 + } 208 + recommendations.push(recommendation) 209 + } 210 + return recommendations 211 + } 212 + 213 + export async function proposeAction( 214 + request: string, 215 + unread: NormalizedNotification[], 216 + targets: ManagementTarget[], 217 + ): Promise<ActionProposal> { 218 + const targetsByDid = targetByDid(targets) 219 + 220 + const payload = JSON.stringify({ 221 + request, 222 + unreadCount: unread.length, 223 + targets: targetPayload(targets), 224 + }) 225 + const result = await toolResponse<ProposalToolResponse>(payload, proposalTool, proposalToolChoice) 226 + 227 + let recommendation: Recommendation | undefined 228 + const notificationsByUri = new Map(unread.map(row => [row.uri, row])) 229 + const description = typeof result?.description === 'string' ? result.description.trim() : '' 230 + const why = typeof result?.why === 'string' ? result.why.trim() : '' 231 + const code = typeof result?.code === 'string' ? result.code.trim() : '' 232 + const unavailableReason = 233 + typeof result?.unavailableReason === 'string' ? result.unavailableReason.trim() : '' 234 + const requestedActorDids = Array.isArray(result?.actorDids) 235 + ? result.actorDids.filter((v: unknown): v is string => typeof v === 'string' && targetsByDid.has(v)) 236 + : [] 237 + const requestedSourceUris = Array.isArray(result?.sourceUris) 238 + ? result.sourceUris.filter((v: unknown): v is string => typeof v === 'string') 239 + : [] 240 + const sourceUris = requestedSourceUris.filter((uri: string) => notificationsByUri.has(uri)) 241 + if (description && why && code && (sourceUris.length > 0 || requestedActorDids.length > 0)) { 242 + try { 243 + validateCode(code) 244 + const actorDids = requestedActorDids.length 245 + ? requestedActorDids 246 + : actorDidsForUris(sourceUris, notificationsByUri) 247 + const blockedReason = 248 + code.includes('agent.app.bsky.notification.putActivitySubscription') 249 + ? blockedSubscriptionMessage(actorDids, targetsByDid) 250 + : '' 251 + if (blockedReason) { 252 + return { 253 + message: blockedReason, 254 + } 255 + } 256 + const actorSet = new Set<string>( 257 + [ 258 + ...sourceUris.map((uri: string) => notificationsByUri.get(uri)?.authorName || ''), 259 + ...actorDids.map(did => targetsByDid.get(did)?.actorName || targetsByDid.get(did)?.actorHandle || ''), 260 + ].filter(Boolean), 261 + ) 262 + const context = actorSet.size === 1 ? [...actorSet][0] : '' 263 + recommendation = { 264 + id: recommendationId(description, actorDids, sourceUris), 265 + description, 266 + context, 267 + why, 268 + code, 269 + sourceUris, 270 + actorDids, 271 + actors: actorRefsForDids(actorDids, targetsByDid, notificationsByUri, sourceUris), 272 + evidence: evidenceForUris(sourceUris, notificationsByUri), 273 + } 274 + } catch { 275 + recommendation = undefined 276 + } 277 + } 278 + 279 + return { 280 + message: 281 + typeof result?.message === 'string' && result.message.trim() 282 + ? result.message 283 + : unavailableReason 284 + ? unavailableReason 285 + : recommendation 286 + ? `Proposed: ${recommendation.description}.` 287 + : '', 288 + recommendation, 289 + } 290 + }
+16
src/render/html.ts
··· 1 + export function escapeHtml(value: string) { 2 + return value 3 + .replaceAll('&', '&amp;') 4 + .replaceAll('<', '&lt;') 5 + .replaceAll('>', '&gt;') 6 + .replaceAll('"', '&quot;') 7 + } 8 + 9 + export function actorLink(label: string, href: string, className = '') { 10 + const classes = className ? ` class="${escapeHtml(className)}"` : '' 11 + return `<a${classes} href="${escapeHtml(href)}" target="_blank" rel="noopener noreferrer">${escapeHtml(label)}</a>` 12 + } 13 + 14 + export function sameLabel(a: string, b: string) { 15 + return a.trim().toLowerCase() === b.trim().toLowerCase() 16 + }
+2
src/render/index.ts
··· 1 + export {renderPage} from './page' 2 + export {renderQueue} from './queue'
+63
src/render/page.ts
··· 1 + import type {AppState} from '../types' 2 + import {escapeHtml} from './html' 3 + import {proposalBlock, renderQueue, subscriptionsSection} from './queue' 4 + import {styles} from './styles' 5 + 6 + export function renderPage(state: AppState) { 7 + const subscriptions = subscriptionsSection(state.currentSubscriptions) 8 + const queue = renderQueue(state) 9 + const queueCount = state.queueState === 'loading' ? 'updating…' : `${state.recommendations.length} suggestions` 10 + 11 + return `<!doctype html> 12 + <html lang="en"> 13 + <head> 14 + <meta charset="utf-8"> 15 + <meta name="viewport" content="width=device-width, initial-scale=1"> 16 + <title>noti</title> 17 + <link rel="preconnect" href="https://fonts.googleapis.com"> 18 + <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin> 19 + <link href="https://fonts.googleapis.com/css2?family=Geist:wght@400;500;700;800&display=swap" rel="stylesheet"> 20 + <style>${styles}</style> 21 + </head> 22 + <body> 23 + <div class="page"> 24 + <div class="top"> 25 + <h1>noti</h1> 26 + <div class="controls" id="controls-root"> 27 + <div class="pill">${state.unreadCount} unread</div> 28 + <form method="post" action="/mark-all-read"> 29 + <button class="button" type="submit">mark all read</button> 30 + </form> 31 + </div> 32 + </div> 33 + 34 + <section class="ask" id="ask-root"> 35 + <div class="section-kicker">manage</div> 36 + <div class="ask-title">ask noti</div> 37 + <div class="ask-copy">for a notification change, like muting someone or turning posts or replies on or off.</div> 38 + <form class="ask-form" method="post" action="/propose" id="propose-form"> 39 + <div class="ask-input-shell"> 40 + <textarea name="message" placeholder="${escapeHtml(state.placeholder)}"></textarea> 41 + <div class="mention-menu" id="actor-suggestions" hidden></div> 42 + </div> 43 + <div class="ask-actions"> 44 + <div class="ask-hint">type <code>@</code> to look up an account</div> 45 + <button class="button propose" type="submit">suggest</button> 46 + </div> 47 + </form> 48 + <div id="proposal-feedback">${proposalBlock(state.proposal)}</div> 49 + </section> 50 + 51 + <div class="queue-header"> 52 + <div class="section-kicker">recommended actions</div> 53 + <div class="queue-titlebar" id="queue-count">${queueCount}</div> 54 + </div> 55 + <section class="queue" id="queue-root">${queue}</section> 56 + <div id="subscriptions-root">${subscriptions}</div> 57 + <a class="footer-link" href="https://bsky.app/notifications" target="_blank" rel="noopener noreferrer">view in Bluesky ↗</a> 58 + <div class="toast-stack" id="toast-stack" aria-live="polite"></div> 59 + </div> 60 + <script type="module" src="/app.js"></script> 61 + </body> 62 + </html>` 63 + }
+168
src/render/queue.ts
··· 1 + import {actorProfileUrl} from '../bluesky' 2 + import type {ActionProposal, AppState, ManagementTarget, Recommendation} from '../types' 3 + import {actorLink, escapeHtml, sameLabel} from './html' 4 + 5 + function recommendationActors(recommendation: Recommendation) { 6 + if (!recommendation.actors.length) return '' 7 + const labels = recommendation.actors 8 + .map(actor => actorLink(actor.name, actor.profileUrl, 'actor-link')) 9 + .join('<span class="actor-sep">·</span>') 10 + return `<div class="queue-actors">${labels}</div>` 11 + } 12 + 13 + function recommendationItem(recommendation: Recommendation, proposed = false) { 14 + const sourceInputs = recommendation.sourceUris 15 + .map(uri => `<input type="hidden" name="sourceUri" value="${escapeHtml(uri)}">`) 16 + .join('') 17 + const actorInputs = recommendation.actorDids 18 + .map(did => `<input type="hidden" name="actorDid" value="${escapeHtml(did)}">`) 19 + .join('') 20 + const context = 21 + recommendation.context && 22 + !(recommendation.actors.length === 1 && 23 + (sameLabel(recommendation.context, recommendation.actors[0].name) || 24 + sameLabel(recommendation.context, recommendation.actors[0].handle))) 25 + ? `<div class="queue-context">${escapeHtml(recommendation.context)}</div>` 26 + : '' 27 + 28 + return ` 29 + <section class="queue-item${proposed ? ' queue-item-proposed' : ''}"> 30 + ${proposed ? '<div class="queue-kicker">proposed action</div>' : ''} 31 + <div class="queue-title">${escapeHtml(recommendation.description)}</div> 32 + ${recommendationActors(recommendation)} 33 + ${context} 34 + <div class="queue-why">${escapeHtml(recommendation.why)}</div> 35 + <details class="queue-details"> 36 + <summary>inspect action</summary> 37 + <pre>${escapeHtml(recommendation.code)}</pre> 38 + </details> 39 + <div class="queue-button-row"> 40 + <form method="post" action="/apply"> 41 + <input type="hidden" name="description" value="${escapeHtml(recommendation.description)}"> 42 + <input type="hidden" name="code" value="${escapeHtml(recommendation.code)}"> 43 + ${sourceInputs} 44 + ${actorInputs} 45 + <button class="button" type="submit">apply</button> 46 + </form> 47 + </div> 48 + </section> 49 + ` 50 + } 51 + 52 + function subscriptionCode(actorDid: string, post: boolean, reply: boolean) { 53 + return [ 54 + 'async function run(agent, ctx) {', 55 + ' await agent.app.bsky.notification.putActivitySubscription({', 56 + ` subject: '${actorDid}',`, 57 + ` activitySubscription: { post: ${post ? 'true' : 'false'}, reply: ${reply ? 'true' : 'false'} },`, 58 + ' })', 59 + '}', 60 + ].join('\n') 61 + } 62 + 63 + function subscriptionStateLabel(target: ManagementTarget) { 64 + if (target.subscriptionPosts && target.subscriptionReplies) return 'current setting: posts and replies' 65 + if (target.subscriptionPosts) return 'current setting: posts only' 66 + if (target.subscriptionReplies) return 'current setting: replies only' 67 + return 'current setting: off' 68 + } 69 + 70 + function applyButton( 71 + label: string, 72 + description: string, 73 + code: string, 74 + actorDid: string, 75 + sourceUris: string[], 76 + buttonClass = '', 77 + ) { 78 + const classes = ['button', buttonClass].filter(Boolean).join(' ') 79 + const sourceInputs = sourceUris 80 + .map(uri => `<input type="hidden" name="sourceUri" value="${escapeHtml(uri)}">`) 81 + .join('') 82 + return ` 83 + <form method="post" action="/apply"> 84 + <input type="hidden" name="description" value="${escapeHtml(description)}"> 85 + <input type="hidden" name="code" value="${escapeHtml(code)}"> 86 + <input type="hidden" name="actorDid" value="${escapeHtml(actorDid)}"> 87 + ${sourceInputs} 88 + <button class="${escapeHtml(classes)}" type="submit">${escapeHtml(label)}</button> 89 + </form> 90 + ` 91 + } 92 + 93 + function toggleButton(target: ManagementTarget, kind: 'posts' | 'replies') { 94 + const actor = target.actorName || target.actorHandle 95 + const posts = target.subscriptionPosts 96 + const replies = target.subscriptionReplies 97 + const enabled = kind === 'posts' ? posts : replies 98 + const nextPosts = kind === 'posts' ? !posts : posts 99 + const nextReplies = kind === 'replies' ? !replies : replies 100 + const label = `${kind}: ${enabled ? 'on' : 'off'}` 101 + const description = `${enabled ? 'Turn off' : 'Turn on'} ${kind} from ${actor}` 102 + const buttonClass = enabled ? 'toggle-on' : 'toggle-off' 103 + return applyButton( 104 + label, 105 + description, 106 + subscriptionCode(target.actorDid, nextPosts, nextReplies), 107 + target.actorDid, 108 + target.sourceUris, 109 + buttonClass, 110 + ) 111 + } 112 + 113 + function subscriptionItem(target: ManagementTarget) { 114 + const actor = target.actorName || target.actorHandle 115 + const pressure = target.currentUnreadCount > 0 ? `${target.currentUnreadCount} unread now` : 'active subscription' 116 + const profile = actorLink(actor, actorProfileUrl(target.actorHandle || target.actorDid), 'actor-link') 117 + 118 + return ` 119 + <section class="queue-item"> 120 + <div class="queue-title">${profile}</div> 121 + <div class="queue-context">${escapeHtml(subscriptionStateLabel(target))}</div> 122 + <div class="queue-why">${escapeHtml(pressure)}</div> 123 + <div class="subscription-actions"> 124 + ${toggleButton(target, 'posts')} 125 + ${toggleButton(target, 'replies')} 126 + </div> 127 + </section> 128 + ` 129 + } 130 + 131 + export function subscriptionsSection(targets: ManagementTarget[]) { 132 + if (!targets.length) return '' 133 + const preview = targets 134 + .slice(0, 3) 135 + .map(target => target.actorName || target.actorHandle) 136 + .join(' · ') 137 + return ` 138 + <details class="subscriptions-panel"> 139 + <summary> 140 + <span class="section-kicker">subscriptions</span> 141 + <span class="subscriptions-summary">${targets.length} active${preview ? ` · ${escapeHtml(preview)}` : ''}</span> 142 + </summary> 143 + <section class="queue subscriptions-queue">${targets.map(subscriptionItem).join('')}</section> 144 + </details> 145 + ` 146 + } 147 + 148 + export function proposalBlock(proposal?: ActionProposal) { 149 + if (!proposal) return '' 150 + const message = proposal.message ? `<div class="assistant-message">${escapeHtml(proposal.message)}</div>` : '' 151 + const card = proposal.recommendation ? recommendationItem(proposal.recommendation, true) : '' 152 + return `${message}${card}` 153 + } 154 + 155 + export function renderQueue(state: Pick<AppState, 'recommendations' | 'queueState'>) { 156 + if (state.queueState === 'loading') { 157 + return ` 158 + <div class="loading-shell" aria-live="polite"> 159 + <div class="loading-title">sorting your notifications…</div> 160 + <div class="loading-copy">Looking for a small number of useful changes to suggest.</div> 161 + </div> 162 + ` 163 + } 164 + if (!state.recommendations.length) { 165 + return '<div class="empty">nothing to change right now</div>' 166 + } 167 + return state.recommendations.slice(0, 3).map(row => recommendationItem(row)).join('') 168 + }
+462
src/render/styles.ts
··· 1 + export const styles = ` 2 + :root { 3 + color-scheme: dark; 4 + --bg: #0b0f14; 5 + --panel: #111821; 6 + --panel-2: #0d131b; 7 + --border: #253445; 8 + --border-strong: #38506a; 9 + --text: #eef4ff; 10 + --muted: #b4c1d2; 11 + --muted-2: #9fb0c3; 12 + --accent: #7bb4ff; 13 + --accent-strong: #5ea3ff; 14 + --surface: rgba(18,25,34,0.98); 15 + --surface-soft: rgba(12,17,23,0.98); 16 + } 17 + * { box-sizing: border-box; } 18 + body { 19 + margin: 0; 20 + font-family: "Geist", ui-sans-serif, system-ui, sans-serif; 21 + background: var(--bg); 22 + color: var(--text); 23 + } 24 + .page { max-width: 980px; margin: 0 auto; padding: 22px 16px 72px; } 25 + .top { 26 + display:flex; 27 + flex-direction: column; 28 + gap: 12px; 29 + margin-bottom: 20px; 30 + } 31 + h1 { margin:0; font-size: 31px; font-weight: 800; letter-spacing: -0.04em; } 32 + .controls { 33 + display:flex; 34 + flex-wrap: wrap; 35 + gap: 8px; 36 + align-items:center; 37 + } 38 + .pill, .button { 39 + border:1px solid var(--border-strong); 40 + color:var(--text); 41 + font-weight:700; 42 + } 43 + .pill { 44 + border-radius: 999px; 45 + padding: 7px 12px; 46 + min-height: 42px; 47 + display: inline-flex; 48 + align-items: center; 49 + justify-content: center; 50 + font-size: 14px; 51 + background: rgba(21,32,43,0.78); 52 + color: var(--muted); 53 + } 54 + .button { 55 + border-radius: 14px; 56 + min-height: 42px; 57 + padding: 9px 14px; 58 + background:#15202b; 59 + cursor:pointer; 60 + font-size:14px; 61 + font-family: inherit; 62 + line-height: 1; 63 + transition: background .16s ease, border-color .16s ease; 64 + } 65 + .button:hover { border-color: #5c7da1; background: #182533; } 66 + .button:disabled { opacity: .65; cursor: wait; } 67 + .ask, .queue-item { 68 + border: 1px solid var(--border); 69 + background: linear-gradient(180deg, var(--surface), var(--surface-soft)); 70 + border-radius: 22px; 71 + } 72 + .ask { 73 + padding: 16px; 74 + margin-bottom: 20px; 75 + } 76 + .section-kicker { 77 + color: var(--accent); 78 + font-size: 11px; 79 + font-weight: 800; 80 + text-transform: uppercase; 81 + letter-spacing: .08em; 82 + margin-bottom: 8px; 83 + } 84 + .ask-title { 85 + font-size: 30px; 86 + font-weight: 800; 87 + letter-spacing: -0.03em; 88 + line-height: 1.02; 89 + margin-bottom: 2px; 90 + } 91 + .ask-copy { 92 + color:var(--muted-2); 93 + margin: 0 0 12px; 94 + line-height:1.42; 95 + max-width: 44rem; 96 + font-size: 15px; 97 + } 98 + .ask-form { 99 + display:flex; 100 + flex-direction: column; 101 + gap:8px; 102 + } 103 + .ask-input-shell { 104 + position: relative; 105 + } 106 + .ask-actions { 107 + display:flex; 108 + align-items:center; 109 + justify-content: space-between; 110 + gap: 12px; 111 + } 112 + .ask-hint { 113 + color: var(--muted-2); 114 + font-size: 13px; 115 + line-height: 1.4; 116 + } 117 + .ask-hint code { 118 + font-family: inherit; 119 + font-size: 12px; 120 + color: var(--accent); 121 + border: 1px solid #314153; 122 + border-radius: 8px; 123 + padding: 1px 6px; 124 + background: #0c1219; 125 + } 126 + textarea { 127 + width: 100%; 128 + min-height: 108px; 129 + resize: vertical; 130 + padding:14px 15px; 131 + background:#0c1219; 132 + border:1px solid #314153; 133 + border-radius:16px; 134 + color:var(--text); 135 + font: inherit; 136 + font-size: 15px; 137 + line-height: 1.45; 138 + } 139 + textarea:focus { 140 + outline: none; 141 + border-color: var(--accent-strong); 142 + box-shadow: 0 0 0 3px rgba(94,163,255,0.15); 143 + } 144 + .mention-menu { 145 + position: absolute; 146 + left: 0; 147 + right: 0; 148 + top: calc(100% + 8px); 149 + z-index: 20; 150 + border: 1px solid #314153; 151 + border-radius: 16px; 152 + background: linear-gradient(180deg, rgba(18,25,34,0.98), rgba(10,16,23,0.98)); 153 + box-shadow: 0 18px 40px rgba(0,0,0,0.35); 154 + overflow: hidden; 155 + } 156 + .mention-row { 157 + display: grid; 158 + gap: 2px; 159 + width: 100%; 160 + text-align: left; 161 + padding: 11px 13px; 162 + background: transparent; 163 + border: 0; 164 + border-bottom: 1px solid rgba(49,65,83,0.6); 165 + color: var(--text); 166 + font: inherit; 167 + cursor: pointer; 168 + } 169 + .mention-row:last-child { 170 + border-bottom: 0; 171 + } 172 + .mention-row:hover, 173 + .mention-row-active { 174 + background: rgba(27,45,64,0.92); 175 + } 176 + .mention-name { 177 + font-size: 14px; 178 + font-weight: 700; 179 + line-height: 1.25; 180 + } 181 + .mention-handle { 182 + font-size: 13px; 183 + color: var(--muted); 184 + line-height: 1.25; 185 + } 186 + .button.propose { 187 + min-width: 112px; 188 + align-self: flex-end; 189 + background: linear-gradient(180deg, #1b2d40, #162535); 190 + } 191 + .assistant-message { 192 + margin: 12px 0 2px; 193 + color:#d7e2f0; 194 + line-height: 1.5; 195 + font-size: 15px; 196 + } 197 + #proposal-feedback { 198 + min-height: 0; 199 + margin-top: 10px; 200 + } 201 + .queue-header { 202 + display:flex; 203 + align-items:center; 204 + justify-content:space-between; 205 + gap: 12px; 206 + margin: 18px 2px 12px; 207 + } 208 + .queue-titlebar { 209 + font-size: 14px; 210 + font-weight: 700; 211 + color: var(--muted); 212 + } 213 + .queue { display:grid; gap:14px; } 214 + .queue-item { padding: 16px; } 215 + .subscriptions-panel { 216 + margin-top: 18px; 217 + border: 1px solid var(--border); 218 + background: linear-gradient(180deg, rgba(18,25,34,0.92), rgba(12,17,23,0.92)); 219 + border-radius: 22px; 220 + padding: 18px 20px; 221 + } 222 + .subscriptions-panel summary { 223 + display:flex; 224 + flex-direction:column; 225 + gap:8px; 226 + cursor:pointer; 227 + list-style:none; 228 + } 229 + .subscriptions-panel summary::-webkit-details-marker { display:none; } 230 + .subscriptions-summary { 231 + color: var(--muted); 232 + font-size: 15px; 233 + font-weight: 600; 234 + line-height: 1.4; 235 + } 236 + .subscriptions-queue { 237 + margin-top: 16px; 238 + } 239 + .subscription-actions { 240 + display:flex; 241 + flex-wrap:wrap; 242 + gap:8px; 243 + margin-top: 12px; 244 + } 245 + .subscription-actions form { margin: 0; } 246 + .subscription-actions .button { 247 + min-width: 96px; 248 + } 249 + .toggle-on { 250 + background: #17314a; 251 + border-color: #4d7fb0; 252 + } 253 + .toggle-off { 254 + background: #101720; 255 + border-color: #314153; 256 + color: var(--muted); 257 + } 258 + .queue-kicker { color:var(--accent); font-size:12px; font-weight:800; text-transform:uppercase; letter-spacing:.08em; margin-bottom:8px; } 259 + .queue-title { font-size: 22px; font-weight: 800; letter-spacing: -0.03em; line-height:1.12; margin-bottom:8px; } 260 + .queue-context { 261 + color: var(--accent); 262 + font-size: 13px; 263 + font-weight: 700; 264 + margin-bottom: 8px; 265 + } 266 + .queue-actors { 267 + display: flex; 268 + flex-wrap: wrap; 269 + gap: 6px; 270 + margin-bottom: 8px; 271 + color: var(--accent); 272 + font-size: 13px; 273 + font-weight: 700; 274 + } 275 + .actor-sep { 276 + color: #4e6784; 277 + } 278 + .actor-link { 279 + color: var(--accent); 280 + text-decoration: none; 281 + } 282 + .actor-link:hover { 283 + text-decoration: underline; 284 + } 285 + .queue-why { 286 + color:#d7e2f0; 287 + line-height:1.5; 288 + margin-bottom: 12px; 289 + font-size: 15px; 290 + } 291 + .queue-details { margin-bottom: 10px; } 292 + .queue-details summary { 293 + cursor:pointer; 294 + color:var(--accent); 295 + font-weight:700; 296 + font-size: 14px; 297 + list-style: none; 298 + } 299 + .queue-details summary::-webkit-details-marker { display:none; } 300 + .queue-details pre { 301 + white-space: pre-wrap; 302 + word-break: break-word; 303 + background:#0b1016; 304 + border:1px solid #223140; 305 + padding:14px; 306 + border-radius:16px; 307 + color:#d7e2f0; 308 + overflow-x:auto; 309 + } 310 + .queue-details ul { 311 + margin: 12px 0 0; 312 + padding-left: 18px; 313 + color:#d5e0ee; 314 + line-height: 1.5; 315 + } 316 + .queue-button-row { 317 + display: flex; 318 + flex-wrap: wrap; 319 + align-items: center; 320 + gap: 10px; 321 + margin-top: 6px; 322 + } 323 + .queue-button-row form { 324 + margin: 0; 325 + } 326 + .queue-item form { margin-top: 6px; } 327 + .footer-link { 328 + display:inline-flex; 329 + align-items:center; 330 + gap:8px; 331 + margin-top: 18px; 332 + color:#c8d4e5; 333 + text-decoration:none; 334 + font-weight: 600; 335 + font-size: 14px; 336 + } 337 + .empty { 338 + color:var(--muted-2); 339 + padding: 16px 8px; 340 + border: 1px dashed #2b3b4d; 341 + border-radius: 14px; 342 + text-align: center; 343 + font-size: 14px; 344 + } 345 + .loading-shell { 346 + position: relative; 347 + border: 1px solid rgba(69, 100, 132, 0.5); 348 + border-radius: 14px; 349 + padding: 12px 12px 12px 14px; 350 + color: var(--muted); 351 + background: linear-gradient(180deg, rgba(15, 23, 31, 0.95), rgba(10, 16, 23, 0.95)); 352 + overflow: hidden; 353 + } 354 + .loading-shell::before { 355 + content: ""; 356 + position: absolute; 357 + inset: 0 auto 0 0; 358 + width: 3px; 359 + background: linear-gradient(180deg, #9dc6ff, #4f8fdf); 360 + } 361 + .loading-shell::after { 362 + content: ""; 363 + position: absolute; 364 + inset: auto 0 0 0; 365 + height: 2px; 366 + background: linear-gradient(90deg, rgba(94,163,255,0) 0%, rgba(94,163,255,0.85) 50%, rgba(94,163,255,0) 100%); 367 + transform: translateX(-40%); 368 + animation: progress-slide 1.2s ease-in-out infinite; 369 + } 370 + .loading-title { 371 + font-weight: 700; 372 + color: var(--text); 373 + margin-bottom: 4px; 374 + font-size: 14px; 375 + } 376 + .loading-copy { 377 + line-height: 1.45; 378 + color: var(--muted-2); 379 + font-size: 13px; 380 + } 381 + .toast-stack { 382 + position: fixed; 383 + right: 16px; 384 + bottom: 16px; 385 + display: grid; 386 + gap: 10px; 387 + z-index: 1000; 388 + pointer-events: none; 389 + } 390 + .toast { 391 + min-width: 260px; 392 + max-width: min(360px, calc(100vw - 32px)); 393 + border: 1px solid var(--border); 394 + border-radius: 16px; 395 + background: linear-gradient(180deg, rgba(18,25,34,0.97), rgba(12,17,23,0.97)); 396 + box-shadow: 0 10px 30px rgba(0,0,0,0.28); 397 + padding: 12px 14px; 398 + pointer-events: auto; 399 + } 400 + .toast-ok { 401 + border-color: rgba(74, 222, 128, 0.35); 402 + } 403 + .toast-bad { 404 + border-color: rgba(248, 113, 113, 0.35); 405 + } 406 + .toast-kicker { 407 + font-size: 11px; 408 + font-weight: 800; 409 + letter-spacing: .08em; 410 + text-transform: uppercase; 411 + margin-bottom: 4px; 412 + color: var(--accent); 413 + } 414 + .toast-ok .toast-kicker { color: #6ee7a2; } 415 + .toast-bad .toast-kicker { color: #fca5a5; } 416 + .toast-title { 417 + font-size: 14px; 418 + font-weight: 700; 419 + line-height: 1.35; 420 + margin-bottom: 4px; 421 + } 422 + .toast-copy { 423 + color: var(--muted); 424 + font-size: 13px; 425 + line-height: 1.45; 426 + } 427 + @keyframes progress-slide { 428 + 0% { transform: translateX(-55%); } 429 + 100% { transform: translateX(55%); } 430 + } 431 + @media (min-width: 700px) { 432 + .page { padding: 32px 20px 80px; } 433 + .top { 434 + flex-direction: row; 435 + justify-content: space-between; 436 + align-items: center; 437 + margin-bottom: 24px; 438 + } 439 + .ask { padding: 18px; } 440 + textarea { min-height: 96px; } 441 + } 442 + @media (max-width: 699px) { 443 + .ask-actions { 444 + flex-direction: column; 445 + align-items: stretch; 446 + } 447 + .ask-hint { 448 + order: 2; 449 + } 450 + .button.propose { 451 + width: 100%; 452 + align-self: stretch; 453 + } 454 + .ask-title { 455 + font-size: 28px; 456 + } 457 + .queue-header { 458 + align-items: flex-start; 459 + flex-direction: column; 460 + } 461 + } 462 + `
+430
src/server.ts
··· 1 + import { 2 + buildManagementTargets, 3 + createAgent, 4 + diffActorStates, 5 + getActorProfiles, 6 + getActorStates, 7 + listActivitySubscriptions, 8 + listMutedActors, 9 + listNotifications, 10 + resolveActor, 11 + searchActorSuggestions, 12 + } from './bluesky' 13 + import {extractActorQueries} from './actor-queries' 14 + import {executeCode} from './code-mode' 15 + import {proposeAction, generateRecommendations} from './recommend' 16 + import {renderPage, renderQueue} from './render/index' 17 + import type {ActionProposal, AppState, ApplyResultLine, ManagementTarget, Recommendation} from './types' 18 + 19 + const port = Number(process.env.PORT || 8000) 20 + const clientScript = new Bun.Transpiler({loader: 'ts'}).transformSync( 21 + await Bun.file(new URL('./client.ts', import.meta.url)).text(), 22 + ) 23 + 24 + function placeholderFromTargets(targets: ManagementTarget[]) { 25 + const top = targets[0] 26 + if (!top) return 'e.g. mute whoever is flooding my inbox' 27 + if (top.subscriptionPosts && top.subscriptionReplies) { 28 + return `e.g. turn off replies from ${top.actorName}, or mute ${top.actorName}` 29 + } 30 + if (top.subscriptionPosts || top.subscriptionReplies) { 31 + return `e.g. stop notifications from ${top.actorName}` 32 + } 33 + return `e.g. mute ${top.actorName}` 34 + } 35 + 36 + type BaseState = { 37 + unreadCount: number 38 + placeholder: string 39 + unread: Awaited<ReturnType<typeof listNotifications>> 40 + targets: ManagementTarget[] 41 + currentSubscriptions: ManagementTarget[] 42 + cacheKey: string 43 + } 44 + 45 + type ApplyVerification = { 46 + verified: boolean 47 + details: ApplyResultLine[] 48 + } 49 + 50 + const recommendationCache = new Map<string, {recommendations: Recommendation[]; at: number}>() 51 + const recommendationInflight = new Map<string, Promise<Recommendation[]>>() 52 + const MAX_CACHE_AGE_MS = 60_000 53 + 54 + function stateKey(unread: BaseState['unread'], targets: ManagementTarget[]) { 55 + return JSON.stringify({ 56 + unread: unread.map(row => [row.uri, row.authorDid, row.reason, row.indexedAt]), 57 + targets: targets.map(row => [row.actorDid, row.currentUnreadCount, row.subscriptionPosts, row.subscriptionReplies, row.muted]), 58 + }) 59 + } 60 + 61 + function currentSubscriptions(targets: ManagementTarget[]) { 62 + return targets.filter(target => target.subscriptionPosts || target.subscriptionReplies) 63 + } 64 + 65 + function subscriptionDetail(subscription: {post: boolean; reply: boolean} | null) { 66 + if (!subscription) return 'posts off, replies off' 67 + return `posts ${subscription.post ? 'on' : 'off'}, replies ${subscription.reply ? 'on' : 'off'}` 68 + } 69 + 70 + async function applyResultLines(agent: Awaited<ReturnType<typeof createAgent>>, affectedDids: string[], changes: ReturnType<typeof diffActorStates>): Promise<ApplyResultLine[]> { 71 + const profiles = await getActorProfiles(agent, affectedDids) 72 + return changes.map(change => { 73 + const profile = profiles.get(change.actorDid) 74 + const label = profile?.name || profile?.handle || change.actorDid 75 + const href = profile?.profileUrl 76 + if (change.kind === 'mute') { 77 + return { 78 + label, 79 + href, 80 + detail: change.muted ? 'muted in Bluesky' : 'unmuted in Bluesky', 81 + } 82 + } 83 + return { 84 + label, 85 + href, 86 + detail: subscriptionDetail(change.subscription), 87 + } 88 + }) 89 + } 90 + 91 + function toastPayload(description: string, verified: boolean, details: ApplyResultLine[]) { 92 + const primary = details[0] 93 + return { 94 + description, 95 + verified, 96 + label: primary?.label || '', 97 + href: primary?.href || '', 98 + text: primary?.detail || (verified ? 'Done.' : 'No state change detected.'), 99 + } 100 + } 101 + 102 + function verifiesNotificationSeen(code: string) { 103 + return code.includes('agent.app.bsky.notification.updateSeen') 104 + } 105 + 106 + async function verifyApply( 107 + agent: Awaited<ReturnType<typeof createAgent>>, 108 + code: string, 109 + affectedDids: string[], 110 + beforeActorStates: Awaited<ReturnType<typeof getActorStates>>, 111 + beforeUnreadCount: number, 112 + ): Promise<ApplyVerification> { 113 + const details: ApplyResultLine[] = [] 114 + 115 + if (affectedDids.length) { 116 + const after = await getActorStates(agent, affectedDids) 117 + const changes = diffActorStates(beforeActorStates, after) 118 + if (changes.length) { 119 + details.push(...(await applyResultLines(agent, affectedDids, changes))) 120 + } 121 + } 122 + 123 + if (verifiesNotificationSeen(code)) { 124 + const notifications = await listNotifications(agent) 125 + const afterUnreadCount = notifications.filter(row => !row.isRead).length 126 + if (afterUnreadCount !== beforeUnreadCount) { 127 + details.unshift({ 128 + label: 'Unread notifications', 129 + detail: `${beforeUnreadCount} → ${afterUnreadCount}`, 130 + }) 131 + } 132 + } 133 + 134 + if (!details.length) { 135 + return { 136 + verified: false, 137 + details: [ 138 + { 139 + label: 'Verification', 140 + detail: 'No state change detected. The mutation may not have taken effect.', 141 + }, 142 + ], 143 + } 144 + } 145 + 146 + return {verified: true, details} 147 + } 148 + 149 + async function buildBaseState(): Promise<BaseState> { 150 + const agent = await createAgent() 151 + const notifications = await listNotifications(agent) 152 + const unread = notifications.filter(row => !row.isRead) 153 + const [subscriptions, mutedActors] = await Promise.all([ 154 + listActivitySubscriptions(agent), 155 + listMutedActors(agent), 156 + ]) 157 + const targets = buildManagementTargets(notifications, subscriptions, mutedActors) 158 + return { 159 + unreadCount: unread.length, 160 + placeholder: placeholderFromTargets(targets), 161 + unread, 162 + targets, 163 + currentSubscriptions: currentSubscriptions(targets), 164 + cacheKey: stateKey(unread, targets), 165 + } 166 + } 167 + 168 + async function recommendationsFor(base: BaseState) { 169 + const cached = recommendationCache.get(base.cacheKey) 170 + if (cached && Date.now() - cached.at < MAX_CACHE_AGE_MS) { 171 + return cached.recommendations 172 + } 173 + const inflight = recommendationInflight.get(base.cacheKey) 174 + if (inflight) return await inflight 175 + const pending = generateRecommendations(base.unread, base.targets) 176 + .then(recommendations => { 177 + recommendationCache.set(base.cacheKey, {recommendations, at: Date.now()}) 178 + recommendationInflight.delete(base.cacheKey) 179 + return recommendations 180 + }) 181 + .catch(error => { 182 + recommendationCache.set(base.cacheKey, {recommendations: [], at: Date.now()}) 183 + recommendationInflight.delete(base.cacheKey) 184 + console.error('recommendation generation failed', error) 185 + return [] 186 + }) 187 + recommendationInflight.set(base.cacheKey, pending) 188 + return await pending 189 + } 190 + 191 + async function buildState(base?: BaseState): Promise<AppState> { 192 + const current = base ?? (await buildBaseState()) 193 + const recommendations = recommendationCache.get(current.cacheKey)?.recommendations ?? [] 194 + return { 195 + unreadCount: current.unreadCount, 196 + recommendations, 197 + currentSubscriptions: current.currentSubscriptions, 198 + placeholder: current.placeholder, 199 + queueState: recommendations.length ? 'ready' : 'loading', 200 + } 201 + } 202 + 203 + type ProposalTargetResolution = { 204 + targets: ManagementTarget[] 205 + resolvedActorDid?: string 206 + unresolvedQuery?: string 207 + } 208 + 209 + async function extendTargetsForProposal(base: BaseState, message: string): Promise<ProposalTargetResolution> { 210 + const queries = extractActorQueries(message) 211 + if (!queries.length) return {targets: base.targets} 212 + 213 + const existing = new Map(base.targets.map(target => [target.actorDid, target])) 214 + const agent = await createAgent() 215 + for (const query of queries) { 216 + try { 217 + const actor = await resolveActor(agent, query) 218 + if (!actor) continue 219 + const profile = await agent.getProfile({actor: actor.did}) 220 + const allowSubscriptions = profile.data.associated?.activitySubscription?.allowSubscriptions 221 + const viewer = profile.data.viewer 222 + const subscriptionEligible = 223 + !allowSubscriptions || 224 + allowSubscriptions === 'all' || 225 + (allowSubscriptions === 'followers' && Boolean(viewer?.following)) || 226 + (allowSubscriptions === 'mutuals' && Boolean(viewer?.following) && Boolean(viewer?.followedBy)) 227 + const subscriptionBlockedReason = 228 + allowSubscriptions === 'mutuals' && !subscriptionEligible 229 + ? `${actor.name} only allows notification subscriptions from mutuals, and they do not follow you back.` 230 + : allowSubscriptions === 'followers' && !subscriptionEligible 231 + ? `${actor.name} only allows notification subscriptions from followers, and you are not following them.` 232 + : undefined 233 + if (existing.has(actor.did)) { 234 + return { 235 + targets: base.targets.map(target => 236 + target.actorDid === actor.did 237 + ? { 238 + ...target, 239 + subscriptionPolicy: allowSubscriptions, 240 + subscriptionEligible, 241 + subscriptionBlockedReason, 242 + } 243 + : target, 244 + ), 245 + resolvedActorDid: actor.did, 246 + } 247 + } 248 + const state = (await getActorStates(agent, [actor.did])).get(actor.did) 249 + return { 250 + targets: [ 251 + ...base.targets, 252 + { 253 + actorDid: actor.did, 254 + actorHandle: actor.handle, 255 + actorName: actor.name, 256 + sourceUris: [], 257 + currentUnreadCount: 0, 258 + recent24hCount: 0, 259 + recent7dCount: 0, 260 + allTimeCount: 0, 261 + reasons: {}, 262 + subscriptionPosts: Boolean(state?.subscription?.post), 263 + subscriptionReplies: Boolean(state?.subscription?.reply), 264 + subscriptionPolicy: allowSubscriptions, 265 + subscriptionEligible, 266 + subscriptionBlockedReason, 267 + muted: Boolean(state?.muted), 268 + examples: [], 269 + }, 270 + ], 271 + resolvedActorDid: actor.did, 272 + } 273 + } catch (error) { 274 + console.error('actor resolution failed', query, error) 275 + } 276 + } 277 + 278 + return {targets: base.targets, unresolvedQuery: queries[0]} 279 + } 280 + 281 + async function buildProposalState(message: string): Promise<AppState> { 282 + const base = await buildBaseState() 283 + const recommendations = await recommendationsFor(base) 284 + const resolution = await extendTargetsForProposal(base, message) 285 + const proposal: ActionProposal = 286 + resolution.unresolvedQuery 287 + ? { 288 + message: `I couldn't identify "${resolution.unresolvedQuery}" from the name alone. Try again with their handle, like @name.bsky.social.`, 289 + } 290 + : await proposeAction(message, base.unread, resolution.targets) 291 + return { 292 + unreadCount: base.unreadCount, 293 + recommendations, 294 + currentSubscriptions: base.currentSubscriptions, 295 + placeholder: base.placeholder, 296 + queueState: 'ready', 297 + proposal, 298 + } 299 + } 300 + 301 + Bun.serve({ 302 + port, 303 + idleTimeout: 60, 304 + async fetch(req) { 305 + try { 306 + const url = new URL(req.url) 307 + 308 + if (req.method === 'GET' && url.pathname === '/app.js') { 309 + return new Response(clientScript, { 310 + headers: {'content-type': 'text/javascript; charset=utf-8'}, 311 + }) 312 + } 313 + 314 + if (req.method === 'GET' && url.pathname === '/health') { 315 + return Response.json({status: 'ok'}) 316 + } 317 + 318 + if (req.method === 'GET' && url.pathname === '/api/actors/typeahead') { 319 + const q = url.searchParams.get('q')?.trim() || '' 320 + if (!q) return Response.json({actors: []}) 321 + const agent = await createAgent() 322 + const actors = await searchActorSuggestions(agent, q, 6) 323 + return Response.json({actors}) 324 + } 325 + 326 + if (req.method === 'GET' && url.pathname === '/api/debug/state') { 327 + const base = await buildBaseState() 328 + const recommendations = await recommendationsFor(base) 329 + return Response.json({ 330 + unreadCount: base.unreadCount, 331 + unread: base.unread, 332 + targets: base.targets, 333 + currentSubscriptions: base.currentSubscriptions, 334 + cacheKey: base.cacheKey, 335 + recommendations, 336 + }) 337 + } 338 + 339 + if (req.method === 'GET' && url.pathname === '/queue') { 340 + const base = await buildBaseState() 341 + const recommendations = await recommendationsFor(base) 342 + return new Response(renderQueue({recommendations, queueState: 'ready'}), { 343 + headers: { 344 + 'content-type': 'text/html; charset=utf-8', 345 + 'x-noti-ready-count': String(recommendations.length), 346 + }, 347 + }) 348 + } 349 + 350 + if (req.method === 'POST' && url.pathname === '/mark-all-read') { 351 + const agent = await createAgent() 352 + await agent.app.bsky.notification.updateSeen({seenAt: new Date().toISOString()}) 353 + return Response.redirect(`${url.origin}/`, 303) 354 + } 355 + 356 + if (req.method === 'POST' && url.pathname === '/apply') { 357 + const form = await req.formData() 358 + const code = String(form.get('code') || '') 359 + const description = String(form.get('description') || 'action') 360 + const sourceUris = form.getAll('sourceUri').map(String) 361 + const actorDids = form.getAll('actorDid').map(String).filter(Boolean) 362 + const agent = await createAgent() 363 + const notifications = await listNotifications(agent) 364 + const beforeUnreadCount = notifications.filter(row => !row.isRead).length 365 + const relevant = notifications.filter(row => sourceUris.includes(row.uri)) 366 + const affectedDids = [ 367 + ...new Set([...actorDids, ...relevant.map(n => n.authorDid).filter(Boolean)]), 368 + ] as string[] 369 + const beforeActorStates = await getActorStates(agent, affectedDids) 370 + console.info('[apply] start', { 371 + description, 372 + actorDids, 373 + sourceUris, 374 + affectedDids, 375 + beforeActorStates: Object.fromEntries(beforeActorStates), 376 + beforeUnreadCount, 377 + }) 378 + await executeCode(code, agent, {sourceUris, notifications: relevant}) 379 + const verification = await verifyApply( 380 + agent, 381 + code, 382 + affectedDids, 383 + beforeActorStates, 384 + beforeUnreadCount, 385 + ) 386 + console.info('[apply] done', { 387 + description, 388 + affectedDids, 389 + verified: verification.verified, 390 + details: verification.details, 391 + }) 392 + 393 + if (req.headers.get('x-noti-ajax') === '1') { 394 + return Response.json({ 395 + ok: true, 396 + verified: verification.verified, 397 + toast: toastPayload(description, verification.verified, verification.details), 398 + }) 399 + } 400 + 401 + return Response.redirect(`${url.origin}/`, 303) 402 + } 403 + 404 + if (req.method === 'POST' && url.pathname === '/propose') { 405 + const form = await req.formData() 406 + const message = String(form.get('message') || '').trim() 407 + const state = message ? await buildProposalState(message) : await buildState() 408 + return new Response(renderPage(state), { 409 + headers: {'content-type': 'text/html; charset=utf-8'}, 410 + }) 411 + } 412 + 413 + if (req.method === 'GET' && url.pathname === '/') { 414 + const base = await buildBaseState() 415 + const state = await buildState(base) 416 + void recommendationsFor(base).catch(() => {}) 417 + return new Response(renderPage(state), { 418 + headers: {'content-type': 'text/html; charset=utf-8'}, 419 + }) 420 + } 421 + 422 + return new Response('not found', {status: 404}) 423 + } catch (error) { 424 + const message = error instanceof Error ? error.message : String(error) 425 + return new Response(`noti-ts error: ${message}`, {status: 500}) 426 + } 427 + }, 428 + }) 429 + 430 + console.log(`noti-ts listening on http://127.0.0.1:${port}`)
+91
src/state.ts
··· 1 + import {mkdirSync} from 'node:fs' 2 + import {dirname} from 'node:path' 3 + import {Database} from 'bun:sqlite' 4 + 5 + import type {NormalizedNotification} from './types' 6 + 7 + const dbPath = process.env.SQLITE_PATH || 'data/noti-ts.db' 8 + mkdirSync(dirname(dbPath), {recursive: true}) 9 + 10 + const db = new Database(dbPath, {create: true}) 11 + db.run('PRAGMA journal_mode = WAL;') 12 + db.run('PRAGMA busy_timeout = 5000;') 13 + 14 + db.run(` 15 + CREATE TABLE IF NOT EXISTS notifications ( 16 + uri TEXT PRIMARY KEY, 17 + actor_did TEXT, 18 + actor_handle TEXT, 19 + actor_name TEXT, 20 + reason TEXT, 21 + indexed_at TEXT NOT NULL, 22 + is_read INTEGER NOT NULL 23 + ); 24 + `) 25 + 26 + const upsertNotification = db.query(` 27 + INSERT INTO notifications (uri, actor_did, actor_handle, actor_name, reason, indexed_at, is_read) 28 + VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7) 29 + ON CONFLICT(uri) DO UPDATE SET 30 + actor_did = excluded.actor_did, 31 + actor_handle = excluded.actor_handle, 32 + actor_name = excluded.actor_name, 33 + reason = excluded.reason, 34 + indexed_at = excluded.indexed_at, 35 + is_read = excluded.is_read 36 + `) 37 + 38 + export function recordNotifications(notifications: NormalizedNotification[]) { 39 + const tx = db.transaction((rows: NormalizedNotification[]) => { 40 + for (const row of rows) { 41 + upsertNotification.run( 42 + row.uri, 43 + row.authorDid ?? null, 44 + row.authorHandle, 45 + row.authorName, 46 + row.reason, 47 + row.indexedAt, 48 + row.isRead ? 1 : 0, 49 + ) 50 + } 51 + }) 52 + tx(notifications) 53 + } 54 + 55 + export function actorHistory(actorDids: string[]) { 56 + const unique = [...new Set(actorDids.filter(Boolean))] 57 + const out = new Map<string, {allTimeCount: number; recent24hCount: number; recent7dCount: number}>() 58 + if (unique.length === 0) return out 59 + 60 + const now = Date.now() 61 + const since24h = new Date(now - 24 * 60 * 60 * 1000).toISOString() 62 + const since7d = new Date(now - 7 * 24 * 60 * 60 * 1000).toISOString() 63 + const query = db.query(` 64 + SELECT 65 + actor_did as actorDid, 66 + COUNT(*) as allTimeCount, 67 + SUM(CASE WHEN indexed_at >= ?1 THEN 1 ELSE 0 END) as recent24hCount, 68 + SUM(CASE WHEN indexed_at >= ?2 THEN 1 ELSE 0 END) as recent7dCount 69 + FROM notifications 70 + WHERE actor_did = ?3 71 + GROUP BY actor_did 72 + `) 73 + 74 + for (const did of unique) { 75 + const row = query.get(since24h, since7d, did) as 76 + | { 77 + actorDid: string 78 + allTimeCount: number 79 + recent24hCount: number 80 + recent7dCount: number 81 + } 82 + | null 83 + if (!row) continue 84 + out.set(did, { 85 + allTimeCount: Number(row.allTimeCount || 0), 86 + recent24hCount: Number(row.recent24hCount || 0), 87 + recent7dCount: Number(row.recent7dCount || 0), 88 + }) 89 + } 90 + return out 91 + }
+79
src/types.ts
··· 1 + export type NormalizedNotification = { 2 + uri: string 3 + reason: string 4 + reasonSubject?: string 5 + authorDid?: string 6 + authorHandle: string 7 + authorName: string 8 + indexedAt: string 9 + isRead: boolean 10 + text?: string 11 + subjectText?: string 12 + isReply: boolean 13 + } 14 + 15 + export type EvidenceRow = { 16 + uri: string 17 + actor: string 18 + actorProfileUrl?: string 19 + reason: string 20 + snippet: string 21 + } 22 + 23 + export type ActorRef = { 24 + did: string 25 + handle: string 26 + name: string 27 + profileUrl: string 28 + } 29 + 30 + export type ManagementTarget = { 31 + actorDid: string 32 + actorHandle: string 33 + actorName: string 34 + sourceUris: string[] 35 + currentUnreadCount: number 36 + recent24hCount: number 37 + recent7dCount: number 38 + allTimeCount: number 39 + reasons: Record<string, number> 40 + subscriptionPosts: boolean 41 + subscriptionReplies: boolean 42 + subscriptionPolicy?: string 43 + subscriptionEligible?: boolean 44 + subscriptionBlockedReason?: string 45 + muted: boolean 46 + examples: EvidenceRow[] 47 + } 48 + 49 + export type Recommendation = { 50 + id?: string 51 + sourceUris: string[] 52 + actorDids: string[] 53 + actors: ActorRef[] 54 + description: string 55 + context?: string 56 + why: string 57 + code: string 58 + evidence: EvidenceRow[] 59 + } 60 + 61 + export type ApplyResultLine = { 62 + label: string 63 + href?: string 64 + detail: string 65 + } 66 + 67 + export type ActionProposal = { 68 + message: string 69 + recommendation?: Recommendation 70 + } 71 + 72 + export type AppState = { 73 + unreadCount: number 74 + recommendations: Recommendation[] 75 + currentSubscriptions: ManagementTarget[] 76 + placeholder: string 77 + queueState?: 'loading' | 'ready' 78 + proposal?: ActionProposal 79 + }
+14
tsconfig.json
··· 1 + { 2 + "compilerOptions": { 3 + "target": "ES2022", 4 + "module": "ESNext", 5 + "moduleResolution": "Bundler", 6 + "strict": true, 7 + "noUnusedLocals": true, 8 + "noUnusedParameters": true, 9 + "esModuleInterop": true, 10 + "skipLibCheck": true, 11 + "types": ["bun"] 12 + }, 13 + "include": ["src/**/*.ts"] 14 + }
-2502
uv.lock
··· 1 - version = 1 2 - revision = 3 3 - requires-python = ">=3.14" 4 - 5 - [[package]] 6 - name = "ag-ui-protocol" 7 - version = "0.1.15" 8 - source = { registry = "https://pypi.org/simple" } 9 - dependencies = [ 10 - { name = "pydantic" }, 11 - ] 12 - sdist = { url = "https://files.pythonhosted.org/packages/57/71/96c21ae7e2fb9b610c1a90d38bd2de8b6e5b2900a63001f3882f43e519af/ag_ui_protocol-0.1.15.tar.gz", hash = "sha256:5e23c1042c7d4e364d685e68d2fb74d37c16bc83c66d270102d8eaedce56ad82", size = 6269, upload-time = "2026-04-01T15:44:33.136Z" } 13 - wheels = [ 14 - { url = "https://files.pythonhosted.org/packages/e4/a0/a73398d30bb0f9ad70cd70426151a4a19527a7296e48a3a16a50e1d5db05/ag_ui_protocol-0.1.15-py3-none-any.whl", hash = "sha256:85cde077023ccbc37b5ce2ad953537883c262d210320f201fc2ec4e85408b06a", size = 8661, upload-time = "2026-04-01T15:44:32.079Z" }, 15 - ] 16 - 17 - [[package]] 18 - name = "aiofile" 19 - version = "3.9.0" 20 - source = { registry = "https://pypi.org/simple" } 21 - dependencies = [ 22 - { name = "caio" }, 23 - ] 24 - sdist = { url = "https://files.pythonhosted.org/packages/67/e2/d7cb819de8df6b5c1968a2756c3cb4122d4fa2b8fc768b53b7c9e5edb646/aiofile-3.9.0.tar.gz", hash = "sha256:e5ad718bb148b265b6df1b3752c4d1d83024b93da9bd599df74b9d9ffcf7919b", size = 17943, upload-time = "2024-10-08T10:39:35.846Z" } 25 - wheels = [ 26 - { url = "https://files.pythonhosted.org/packages/50/25/da1f0b4dd970e52bf5a36c204c107e11a0c6d3ed195eba0bfbc664c312b2/aiofile-3.9.0-py3-none-any.whl", hash = "sha256:ce2f6c1571538cbdfa0143b04e16b208ecb0e9cb4148e528af8a640ed51cc8aa", size = 19539, upload-time = "2024-10-08T10:39:32.955Z" }, 27 - ] 28 - 29 - [[package]] 30 - name = "aiohappyeyeballs" 31 - version = "2.6.1" 32 - source = { registry = "https://pypi.org/simple" } 33 - sdist = { url = "https://files.pythonhosted.org/packages/26/30/f84a107a9c4331c14b2b586036f40965c128aa4fee4dda5d3d51cb14ad54/aiohappyeyeballs-2.6.1.tar.gz", hash = "sha256:c3f9d0113123803ccadfdf3f0faa505bc78e6a72d1cc4806cbd719826e943558", size = 22760, upload-time = "2025-03-12T01:42:48.764Z" } 34 - wheels = [ 35 - { url = "https://files.pythonhosted.org/packages/0f/15/5bf3b99495fb160b63f95972b81750f18f7f4e02ad051373b669d17d44f2/aiohappyeyeballs-2.6.1-py3-none-any.whl", hash = "sha256:f349ba8f4b75cb25c99c5c2d84e997e485204d2902a9597802b0371f09331fb8", size = 15265, upload-time = "2025-03-12T01:42:47.083Z" }, 36 - ] 37 - 38 - [[package]] 39 - name = "aiohttp" 40 - version = "3.13.5" 41 - source = { registry = "https://pypi.org/simple" } 42 - dependencies = [ 43 - { name = "aiohappyeyeballs" }, 44 - { name = "aiosignal" }, 45 - { name = "attrs" }, 46 - { name = "frozenlist" }, 47 - { name = "multidict" }, 48 - { name = "propcache" }, 49 - { name = "yarl" }, 50 - ] 51 - sdist = { url = "https://files.pythonhosted.org/packages/77/9a/152096d4808df8e4268befa55fba462f440f14beab85e8ad9bf990516918/aiohttp-3.13.5.tar.gz", hash = "sha256:9d98cc980ecc96be6eb4c1994ce35d28d8b1f5e5208a23b421187d1209dbb7d1", size = 7858271, upload-time = "2026-03-31T22:01:03.343Z" } 52 - wheels = [ 53 - { url = "https://files.pythonhosted.org/packages/5d/ce/46572759afc859e867a5bc8ec3487315869013f59281ce61764f76d879de/aiohttp-3.13.5-cp314-cp314-macosx_10_13_universal2.whl", hash = "sha256:eb4639f32fd4a9904ab8fb45bf3383ba71137f3d9d4ba25b3b3f3109977c5b8c", size = 745721, upload-time = "2026-03-31T21:58:50.229Z" }, 54 - { url = "https://files.pythonhosted.org/packages/13/fe/8a2efd7626dbe6049b2ef8ace18ffda8a4dfcbe1bcff3ac30c0c7575c20b/aiohttp-3.13.5-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:7e5dc4311bd5ac493886c63cbf76ab579dbe4641268e7c74e48e774c74b6f2be", size = 497663, upload-time = "2026-03-31T21:58:52.232Z" }, 55 - { url = "https://files.pythonhosted.org/packages/9b/91/cc8cc78a111826c54743d88651e1687008133c37e5ee615fee9b57990fac/aiohttp-3.13.5-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:756c3c304d394977519824449600adaf2be0ccee76d206ee339c5e76b70ded25", size = 499094, upload-time = "2026-03-31T21:58:54.566Z" }, 56 - { url = "https://files.pythonhosted.org/packages/0a/33/a8362cb15cf16a3af7e86ed11962d5cd7d59b449202dc576cdc731310bde/aiohttp-3.13.5-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ecc26751323224cf8186efcf7fbcbc30f4e1d8c7970659daf25ad995e4032a56", size = 1726701, upload-time = "2026-03-31T21:58:56.864Z" }, 57 - { url = "https://files.pythonhosted.org/packages/45/0c/c091ac5c3a17114bd76cbf85d674650969ddf93387876cf67f754204bd77/aiohttp-3.13.5-cp314-cp314-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:10a75acfcf794edf9d8db50e5a7ec5fc818b2a8d3f591ce93bc7b1210df016d2", size = 1683360, upload-time = "2026-03-31T21:58:59.072Z" }, 58 - { url = "https://files.pythonhosted.org/packages/23/73/bcee1c2b79bc275e964d1446c55c54441a461938e70267c86afaae6fba27/aiohttp-3.13.5-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:0f7a18f258d124cd678c5fe072fe4432a4d5232b0657fca7c1847f599233c83a", size = 1773023, upload-time = "2026-03-31T21:59:01.776Z" }, 59 - { url = "https://files.pythonhosted.org/packages/c7/ef/720e639df03004fee2d869f771799d8c23046dec47d5b81e396c7cda583a/aiohttp-3.13.5-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:df6104c009713d3a89621096f3e3e88cc323fd269dbd7c20afe18535094320be", size = 1853795, upload-time = "2026-03-31T21:59:04.568Z" }, 60 - { url = "https://files.pythonhosted.org/packages/bd/c9/989f4034fb46841208de7aeeac2c6d8300745ab4f28c42f629ba77c2d916/aiohttp-3.13.5-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:241a94f7de7c0c3b616627aaad530fe2cb620084a8b144d3be7b6ecfe95bae3b", size = 1730405, upload-time = "2026-03-31T21:59:07.221Z" }, 61 - { url = "https://files.pythonhosted.org/packages/ce/75/ee1fd286ca7dc599d824b5651dad7b3be7ff8d9a7e7b3fe9820d9180f7db/aiohttp-3.13.5-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:c974fb66180e58709b6fc402846f13791240d180b74de81d23913abe48e96d94", size = 1558082, upload-time = "2026-03-31T21:59:09.484Z" }, 62 - { url = "https://files.pythonhosted.org/packages/c3/20/1e9e6650dfc436340116b7aa89ff8cb2bbdf0abc11dfaceaad8f74273a10/aiohttp-3.13.5-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:6e27ea05d184afac78aabbac667450c75e54e35f62238d44463131bd3f96753d", size = 1692346, upload-time = "2026-03-31T21:59:12.068Z" }, 63 - { url = "https://files.pythonhosted.org/packages/d8/40/8ebc6658d48ea630ac7903912fe0dd4e262f0e16825aa4c833c56c9f1f56/aiohttp-3.13.5-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:a79a6d399cef33a11b6f004c67bb07741d91f2be01b8d712d52c75711b1e07c7", size = 1698891, upload-time = "2026-03-31T21:59:14.552Z" }, 64 - { url = "https://files.pythonhosted.org/packages/d8/78/ea0ae5ec8ba7a5c10bdd6e318f1ba5e76fcde17db8275188772afc7917a4/aiohttp-3.13.5-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:c632ce9c0b534fbe25b52c974515ed674937c5b99f549a92127c85f771a78772", size = 1742113, upload-time = "2026-03-31T21:59:17.068Z" }, 65 - { url = "https://files.pythonhosted.org/packages/8a/66/9d308ed71e3f2491be1acb8769d96c6f0c47d92099f3bc9119cada27b357/aiohttp-3.13.5-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:fceedde51fbd67ee2bcc8c0b33d0126cc8b51ef3bbde2f86662bd6d5a6f10ec5", size = 1553088, upload-time = "2026-03-31T21:59:19.541Z" }, 66 - { url = "https://files.pythonhosted.org/packages/da/a6/6cc25ed8dfc6e00c90f5c6d126a98e2cf28957ad06fa1036bd34b6f24a2c/aiohttp-3.13.5-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:f92995dfec9420bb69ae629abf422e516923ba79ba4403bc750d94fb4a6c68c1", size = 1757976, upload-time = "2026-03-31T21:59:22.311Z" }, 67 - { url = "https://files.pythonhosted.org/packages/c1/2b/cce5b0ffe0de99c83e5e36d8f828e4161e415660a9f3e58339d07cce3006/aiohttp-3.13.5-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:20ae0ff08b1f2c8788d6fb85afcb798654ae6ba0b747575f8562de738078457b", size = 1712444, upload-time = "2026-03-31T21:59:24.635Z" }, 68 - { url = "https://files.pythonhosted.org/packages/6c/cf/9e1795b4160c58d29421eafd1a69c6ce351e2f7c8d3c6b7e4ca44aea1a5b/aiohttp-3.13.5-cp314-cp314-win32.whl", hash = "sha256:b20df693de16f42b2472a9c485e1c948ee55524786a0a34345511afdd22246f3", size = 438128, upload-time = "2026-03-31T21:59:27.291Z" }, 69 - { url = "https://files.pythonhosted.org/packages/22/4d/eaedff67fc805aeba4ba746aec891b4b24cebb1a7d078084b6300f79d063/aiohttp-3.13.5-cp314-cp314-win_amd64.whl", hash = "sha256:f85c6f327bf0b8c29da7d93b1cabb6363fb5e4e160a32fa241ed2dce21b73162", size = 464029, upload-time = "2026-03-31T21:59:29.429Z" }, 70 - { url = "https://files.pythonhosted.org/packages/79/11/c27d9332ee20d68dd164dc12a6ecdef2e2e35ecc97ed6cf0d2442844624b/aiohttp-3.13.5-cp314-cp314t-macosx_10_13_universal2.whl", hash = "sha256:1efb06900858bb618ff5cee184ae2de5828896c448403d51fb633f09e109be0a", size = 778758, upload-time = "2026-03-31T21:59:31.547Z" }, 71 - { url = "https://files.pythonhosted.org/packages/04/fb/377aead2e0a3ba5f09b7624f702a964bdf4f08b5b6728a9799830c80041e/aiohttp-3.13.5-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:fee86b7c4bd29bdaf0d53d14739b08a106fdda809ca5fe032a15f52fae5fe254", size = 512883, upload-time = "2026-03-31T21:59:34.098Z" }, 72 - { url = "https://files.pythonhosted.org/packages/bb/a6/aa109a33671f7a5d3bd78b46da9d852797c5e665bfda7d6b373f56bff2ec/aiohttp-3.13.5-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:20058e23909b9e65f9da62b396b77dfa95965cbe840f8def6e572538b1d32e36", size = 516668, upload-time = "2026-03-31T21:59:36.497Z" }, 73 - { url = "https://files.pythonhosted.org/packages/79/b3/ca078f9f2fa9563c36fb8ef89053ea2bb146d6f792c5104574d49d8acb63/aiohttp-3.13.5-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:8cf20a8d6868cb15a73cab329ffc07291ba8c22b1b88176026106ae39aa6df0f", size = 1883461, upload-time = "2026-03-31T21:59:38.723Z" }, 74 - { url = "https://files.pythonhosted.org/packages/b7/e3/a7ad633ca1ca497b852233a3cce6906a56c3225fb6d9217b5e5e60b7419d/aiohttp-3.13.5-cp314-cp314t-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:330f5da04c987f1d5bdb8ae189137c77139f36bd1cb23779ca1a354a4b027800", size = 1747661, upload-time = "2026-03-31T21:59:41.187Z" }, 75 - { url = "https://files.pythonhosted.org/packages/33/b9/cd6fe579bed34a906d3d783fe60f2fa297ef55b27bb4538438ee49d4dc41/aiohttp-3.13.5-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:6f1cbf0c7926d315c3c26c2da41fd2b5d2fe01ac0e157b78caefc51a782196cf", size = 1863800, upload-time = "2026-03-31T21:59:43.84Z" }, 76 - { url = "https://files.pythonhosted.org/packages/c0/3f/2c1e2f5144cefa889c8afd5cf431994c32f3b29da9961698ff4e3811b79a/aiohttp-3.13.5-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:53fc049ed6390d05423ba33103ded7281fe897cf97878f369a527070bd95795b", size = 1958382, upload-time = "2026-03-31T21:59:46.187Z" }, 77 - { url = "https://files.pythonhosted.org/packages/66/1d/f31ec3f1013723b3babe3609e7f119c2c2fb6ef33da90061a705ef3e1bc8/aiohttp-3.13.5-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:898703aa2667e3c5ca4c54ca36cd73f58b7a38ef87a5606414799ebce4d3fd3a", size = 1803724, upload-time = "2026-03-31T21:59:48.656Z" }, 78 - { url = "https://files.pythonhosted.org/packages/0e/b4/57712dfc6f1542f067daa81eb61da282fab3e6f1966fca25db06c4fc62d5/aiohttp-3.13.5-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:0494a01ca9584eea1e5fbd6d748e61ecff218c51b576ee1999c23db7066417d8", size = 1640027, upload-time = "2026-03-31T21:59:51.284Z" }, 79 - { url = "https://files.pythonhosted.org/packages/25/3c/734c878fb43ec083d8e31bf029daae1beafeae582d1b35da234739e82ee7/aiohttp-3.13.5-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:6cf81fe010b8c17b09495cbd15c1d35afbc8fb405c0c9cf4738e5ae3af1d65be", size = 1806644, upload-time = "2026-03-31T21:59:53.753Z" }, 80 - { url = "https://files.pythonhosted.org/packages/20/a5/f671e5cbec1c21d044ff3078223f949748f3a7f86b14e34a365d74a5d21f/aiohttp-3.13.5-cp314-cp314t-musllinux_1_2_armv7l.whl", hash = "sha256:c564dd5f09ddc9d8f2c2d0a301cd30a79a2cc1b46dd1a73bef8f0038863d016b", size = 1791630, upload-time = "2026-03-31T21:59:56.239Z" }, 81 - { url = "https://files.pythonhosted.org/packages/0b/63/fb8d0ad63a0b8a99be97deac8c04dacf0785721c158bdf23d679a87aa99e/aiohttp-3.13.5-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:2994be9f6e51046c4f864598fd9abeb4fba6e88f0b2152422c9666dcd4aea9c6", size = 1809403, upload-time = "2026-03-31T21:59:59.103Z" }, 82 - { url = "https://files.pythonhosted.org/packages/59/0c/bfed7f30662fcf12206481c2aac57dedee43fe1c49275e85b3a1e1742294/aiohttp-3.13.5-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:157826e2fa245d2ef46c83ea8a5faf77ca19355d278d425c29fda0beb3318037", size = 1634924, upload-time = "2026-03-31T22:00:02.116Z" }, 83 - { url = "https://files.pythonhosted.org/packages/17/d6/fd518d668a09fd5a3319ae5e984d4d80b9a4b3df4e21c52f02251ef5a32e/aiohttp-3.13.5-cp314-cp314t-musllinux_1_2_s390x.whl", hash = "sha256:a8aca50daa9493e9e13c0f566201a9006f080e7c50e5e90d0b06f53146a54500", size = 1836119, upload-time = "2026-03-31T22:00:04.756Z" }, 84 - { url = "https://files.pythonhosted.org/packages/78/b7/15fb7a9d52e112a25b621c67b69c167805cb1f2ab8f1708a5c490d1b52fe/aiohttp-3.13.5-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:3b13560160d07e047a93f23aaa30718606493036253d5430887514715b67c9d9", size = 1772072, upload-time = "2026-03-31T22:00:07.494Z" }, 85 - { url = "https://files.pythonhosted.org/packages/7e/df/57ba7f0c4a553fc2bd8b6321df236870ec6fd64a2a473a8a13d4f733214e/aiohttp-3.13.5-cp314-cp314t-win32.whl", hash = "sha256:9a0f4474b6ea6818b41f82172d799e4b3d29e22c2c520ce4357856fced9af2f8", size = 471819, upload-time = "2026-03-31T22:00:10.277Z" }, 86 - { url = "https://files.pythonhosted.org/packages/62/29/2f8418269e46454a26171bfdd6a055d74febf32234e474930f2f60a17145/aiohttp-3.13.5-cp314-cp314t-win_amd64.whl", hash = "sha256:18a2f6c1182c51baa1d28d68fea51513cb2a76612f038853c0ad3c145423d3d9", size = 505441, upload-time = "2026-03-31T22:00:12.791Z" }, 87 - ] 88 - 89 - [[package]] 90 - name = "aiosignal" 91 - version = "1.4.0" 92 - source = { registry = "https://pypi.org/simple" } 93 - dependencies = [ 94 - { name = "frozenlist" }, 95 - ] 96 - sdist = { url = "https://files.pythonhosted.org/packages/61/62/06741b579156360248d1ec624842ad0edf697050bbaf7c3e46394e106ad1/aiosignal-1.4.0.tar.gz", hash = "sha256:f47eecd9468083c2029cc99945502cb7708b082c232f9aca65da147157b251c7", size = 25007, upload-time = "2025-07-03T22:54:43.528Z" } 97 - wheels = [ 98 - { url = "https://files.pythonhosted.org/packages/fb/76/641ae371508676492379f16e2fa48f4e2c11741bd63c48be4b12a6b09cba/aiosignal-1.4.0-py3-none-any.whl", hash = "sha256:053243f8b92b990551949e63930a839ff0cf0b0ebbe0597b0f3fb19e1a0fe82e", size = 7490, upload-time = "2025-07-03T22:54:42.156Z" }, 99 - ] 100 - 101 - [[package]] 102 - name = "annotated-doc" 103 - version = "0.0.4" 104 - source = { registry = "https://pypi.org/simple" } 105 - sdist = { url = "https://files.pythonhosted.org/packages/57/ba/046ceea27344560984e26a590f90bc7f4a75b06701f653222458922b558c/annotated_doc-0.0.4.tar.gz", hash = "sha256:fbcda96e87e9c92ad167c2e53839e57503ecfda18804ea28102353485033faa4", size = 7288, upload-time = "2025-11-10T22:07:42.062Z" } 106 - wheels = [ 107 - { url = "https://files.pythonhosted.org/packages/1e/d3/26bf1008eb3d2daa8ef4cacc7f3bfdc11818d111f7e2d0201bc6e3b49d45/annotated_doc-0.0.4-py3-none-any.whl", hash = "sha256:571ac1dc6991c450b25a9c2d84a3705e2ae7a53467b5d111c24fa8baabbed320", size = 5303, upload-time = "2025-11-10T22:07:40.673Z" }, 108 - ] 109 - 110 - [[package]] 111 - name = "annotated-types" 112 - version = "0.7.0" 113 - source = { registry = "https://pypi.org/simple" } 114 - sdist = { url = "https://files.pythonhosted.org/packages/ee/67/531ea369ba64dcff5ec9c3402f9f51bf748cec26dde048a2f973a4eea7f5/annotated_types-0.7.0.tar.gz", hash = "sha256:aff07c09a53a08bc8cfccb9c85b05f1aa9a2a6f23728d790723543408344ce89", size = 16081, upload-time = "2024-05-20T21:33:25.928Z" } 115 - wheels = [ 116 - { url = "https://files.pythonhosted.org/packages/78/b6/6307fbef88d9b5ee7421e68d78a9f162e0da4900bc5f5793f6d3d0e34fb8/annotated_types-0.7.0-py3-none-any.whl", hash = "sha256:1f02e8b43a8fbbc3f3e0d4f0f4bfc8131bcb4eebe8849b8e5c773f3a1c582a53", size = 13643, upload-time = "2024-05-20T21:33:24.1Z" }, 117 - ] 118 - 119 - [[package]] 120 - name = "anthropic" 121 - version = "0.94.1" 122 - source = { registry = "https://pypi.org/simple" } 123 - dependencies = [ 124 - { name = "anyio" }, 125 - { name = "distro" }, 126 - { name = "docstring-parser" }, 127 - { name = "httpx" }, 128 - { name = "jiter" }, 129 - { name = "pydantic" }, 130 - { name = "sniffio" }, 131 - { name = "typing-extensions" }, 132 - ] 133 - sdist = { url = "https://files.pythonhosted.org/packages/3e/f2/fb4a04ff676742588a41f94dd318883d6d77e88e2b5e20b664ae3bf20e1b/anthropic-0.94.1.tar.gz", hash = "sha256:96c7033069c16074f90638dff8bf1f1616f9eefeb8ef7d1b0df4a0393ab34685", size = 654451, upload-time = "2026-04-13T18:08:18.453Z" } 134 - wheels = [ 135 - { url = "https://files.pythonhosted.org/packages/05/18/6d5b5948cbe450d3c98336c8a8c4804aedb3ab24fa37922d05063f8ba266/anthropic-0.94.1-py3-none-any.whl", hash = "sha256:58fb20dc60f35e75a5a82a1c73a3e196ac3b18ff2ed4826cba345f4adb78919d", size = 627710, upload-time = "2026-04-13T18:08:19.629Z" }, 136 - ] 137 - 138 - [[package]] 139 - name = "anyio" 140 - version = "4.13.0" 141 - source = { registry = "https://pypi.org/simple" } 142 - dependencies = [ 143 - { name = "idna" }, 144 - ] 145 - sdist = { url = "https://files.pythonhosted.org/packages/19/14/2c5dd9f512b66549ae92767a9c7b330ae88e1932ca57876909410251fe13/anyio-4.13.0.tar.gz", hash = "sha256:334b70e641fd2221c1505b3890c69882fe4a2df910cba14d97019b90b24439dc", size = 231622, upload-time = "2026-03-24T12:59:09.671Z" } 146 - wheels = [ 147 - { url = "https://files.pythonhosted.org/packages/da/42/e921fccf5015463e32a3cf6ee7f980a6ed0f395ceeaa45060b61d86486c2/anyio-4.13.0-py3-none-any.whl", hash = "sha256:08b310f9e24a9594186fd75b4f73f4a4152069e3853f1ed8bfbf58369f4ad708", size = 114353, upload-time = "2026-03-24T12:59:08.246Z" }, 148 - ] 149 - 150 - [[package]] 151 - name = "argcomplete" 152 - version = "3.6.3" 153 - source = { registry = "https://pypi.org/simple" } 154 - sdist = { url = "https://files.pythonhosted.org/packages/38/61/0b9ae6399dd4a58d8c1b1dc5a27d6f2808023d0b5dd3104bb99f45a33ff6/argcomplete-3.6.3.tar.gz", hash = "sha256:62e8ed4fd6a45864acc8235409461b72c9a28ee785a2011cc5eb78318786c89c", size = 73754, upload-time = "2025-10-20T03:33:34.741Z" } 155 - wheels = [ 156 - { url = "https://files.pythonhosted.org/packages/74/f5/9373290775639cb67a2fce7f629a1c240dce9f12fe927bc32b2736e16dfc/argcomplete-3.6.3-py3-none-any.whl", hash = "sha256:f5007b3a600ccac5d25bbce33089211dfd49eab4a7718da3f10e3082525a92ce", size = 43846, upload-time = "2025-10-20T03:33:33.021Z" }, 157 - ] 158 - 159 - [[package]] 160 - name = "atproto" 161 - version = "0.0.65" 162 - source = { registry = "https://pypi.org/simple" } 163 - dependencies = [ 164 - { name = "click" }, 165 - { name = "cryptography" }, 166 - { name = "dnspython" }, 167 - { name = "httpx" }, 168 - { name = "libipld" }, 169 - { name = "pydantic" }, 170 - { name = "typing-extensions" }, 171 - { name = "websockets" }, 172 - ] 173 - sdist = { url = "https://files.pythonhosted.org/packages/b2/0f/b6e26f99ef730f1e5779f5833ba794343df78ee1e02041d3b05bd5005066/atproto-0.0.65.tar.gz", hash = "sha256:027c6ed98746a9e6f1bb24bc18db84b80b386037709ff3af9ef927dce3dd4938", size = 210996, upload-time = "2025-12-08T15:53:44.585Z" } 174 - wheels = [ 175 - { url = "https://files.pythonhosted.org/packages/e3/d9/360149e7bd9bac580496ce9fddc0ef320b3813aadd72be6abc011600862d/atproto-0.0.65-py3-none-any.whl", hash = "sha256:ea53dea57454c9e56318b5d25ceb35854d60ba238b38b0e5ca79aa1a2df85846", size = 446650, upload-time = "2025-12-08T15:53:43.029Z" }, 176 - ] 177 - 178 - [[package]] 179 - name = "attrs" 180 - version = "26.1.0" 181 - source = { registry = "https://pypi.org/simple" } 182 - sdist = { url = "https://files.pythonhosted.org/packages/9a/8e/82a0fe20a541c03148528be8cac2408564a6c9a0cc7e9171802bc1d26985/attrs-26.1.0.tar.gz", hash = "sha256:d03ceb89cb322a8fd706d4fb91940737b6642aa36998fe130a9bc96c985eff32", size = 952055, upload-time = "2026-03-19T14:22:25.026Z" } 183 - wheels = [ 184 - { url = "https://files.pythonhosted.org/packages/64/b4/17d4b0b2a2dc85a6df63d1157e028ed19f90d4cd97c36717afef2bc2f395/attrs-26.1.0-py3-none-any.whl", hash = "sha256:c647aa4a12dfbad9333ca4e71fe62ddc36f4e63b2d260a37a8b83d2f043ac309", size = 67548, upload-time = "2026-03-19T14:22:23.645Z" }, 185 - ] 186 - 187 - [[package]] 188 - name = "authlib" 189 - version = "1.6.10" 190 - source = { registry = "https://pypi.org/simple" } 191 - dependencies = [ 192 - { name = "cryptography" }, 193 - ] 194 - sdist = { url = "https://files.pythonhosted.org/packages/aa/e2/2cd626412bfc3c78b17ca5e5ea8d489f8cae31d40b061f4da0a89068d8a3/authlib-1.6.10.tar.gz", hash = "sha256:856a4f54d6ef3361ca6bb6d14a27e8b88f8097cca795fb428ffe13720e2ecde6", size = 165333, upload-time = "2026-04-13T13:30:34.718Z" } 195 - wheels = [ 196 - { url = "https://files.pythonhosted.org/packages/7d/f6/9093f1ed17b6e2f4ac50d214543d4ec5268902a70e2158a752a06423b5ef/authlib-1.6.10-py2.py3-none-any.whl", hash = "sha256:aa639b43292554539924a3b4aaa9e81cd67ab64d3e28b22428c61f1200240287", size = 244351, upload-time = "2026-04-13T13:30:33.34Z" }, 197 - ] 198 - 199 - [[package]] 200 - name = "beartype" 201 - version = "0.22.9" 202 - source = { registry = "https://pypi.org/simple" } 203 - sdist = { url = "https://files.pythonhosted.org/packages/c7/94/1009e248bbfbab11397abca7193bea6626806be9a327d399810d523a07cb/beartype-0.22.9.tar.gz", hash = "sha256:8f82b54aa723a2848a56008d18875f91c1db02c32ef6a62319a002e3e25a975f", size = 1608866, upload-time = "2025-12-13T06:50:30.72Z" } 204 - wheels = [ 205 - { url = "https://files.pythonhosted.org/packages/71/cc/18245721fa7747065ab478316c7fea7c74777d07f37ae60db2e84f8172e8/beartype-0.22.9-py3-none-any.whl", hash = "sha256:d16c9bbc61ea14637596c5f6fbff2ee99cbe3573e46a716401734ef50c3060c2", size = 1333658, upload-time = "2025-12-13T06:50:28.266Z" }, 206 - ] 207 - 208 - [[package]] 209 - name = "boto3" 210 - version = "1.42.89" 211 - source = { registry = "https://pypi.org/simple" } 212 - dependencies = [ 213 - { name = "botocore" }, 214 - { name = "jmespath" }, 215 - { name = "s3transfer" }, 216 - ] 217 - sdist = { url = "https://files.pythonhosted.org/packages/bb/0c/f7bccb22b245cabf392816baba20f9e95f78ace7dbc580fd40136e80e732/boto3-1.42.89.tar.gz", hash = "sha256:3e43aacc0801bba9bcd23a8c271c089af297a69565f783fcdd357ae0e330bf1e", size = 113165, upload-time = "2026-04-13T19:36:17.516Z" } 218 - wheels = [ 219 - { url = "https://files.pythonhosted.org/packages/b9/33/55103ba5ef9975ea54b8d39e69b76eb6e9fded3beae5f01065e26951a3a1/boto3-1.42.89-py3-none-any.whl", hash = "sha256:6204b189f4d0c655535f43d7eaa57ff4e8d965b8463c97e45952291211162932", size = 140556, upload-time = "2026-04-13T19:36:13.894Z" }, 220 - ] 221 - 222 - [[package]] 223 - name = "botocore" 224 - version = "1.42.89" 225 - source = { registry = "https://pypi.org/simple" } 226 - dependencies = [ 227 - { name = "jmespath" }, 228 - { name = "python-dateutil" }, 229 - { name = "urllib3" }, 230 - ] 231 - sdist = { url = "https://files.pythonhosted.org/packages/0f/cc/e6be943efa9051bd15c2ee14077c2b10d6e27c9e9385fc43a03a5c4ed8b5/botocore-1.42.89.tar.gz", hash = "sha256:95ac52f472dad29942f3088b278ab493044516c16dbf9133c975af16527baa99", size = 15206290, upload-time = "2026-04-13T19:36:02.321Z" } 232 - wheels = [ 233 - { url = "https://files.pythonhosted.org/packages/91/f1/90a7b8eda38b7c3a65ca7ee0075bdf310b6b471cb1b95fab6e8994323a50/botocore-1.42.89-py3-none-any.whl", hash = "sha256:d9b786c8d9db6473063b4cc5be0ba7e6a381082307bd6afb69d4216f9fa95f35", size = 14887287, upload-time = "2026-04-13T19:35:56.677Z" }, 234 - ] 235 - 236 - [[package]] 237 - name = "cachetools" 238 - version = "7.0.5" 239 - source = { registry = "https://pypi.org/simple" } 240 - sdist = { url = "https://files.pythonhosted.org/packages/af/dd/57fe3fdb6e65b25a5987fd2cdc7e22db0aef508b91634d2e57d22928d41b/cachetools-7.0.5.tar.gz", hash = "sha256:0cd042c24377200c1dcd225f8b7b12b0ca53cc2c961b43757e774ebe190fd990", size = 37367, upload-time = "2026-03-09T20:51:29.451Z" } 241 - wheels = [ 242 - { url = "https://files.pythonhosted.org/packages/06/f3/39cf3367b8107baa44f861dc802cbf16263c945b62d8265d36034fc07bea/cachetools-7.0.5-py3-none-any.whl", hash = "sha256:46bc8ebefbe485407621d0a4264b23c080cedd913921bad7ac3ed2f26c183114", size = 13918, upload-time = "2026-03-09T20:51:27.33Z" }, 243 - ] 244 - 245 - [[package]] 246 - name = "caio" 247 - version = "0.9.25" 248 - source = { registry = "https://pypi.org/simple" } 249 - sdist = { url = "https://files.pythonhosted.org/packages/92/88/b8527e1b00c1811db339a1df8bd1ae49d146fcea9d6a5c40e3a80aaeb38d/caio-0.9.25.tar.gz", hash = "sha256:16498e7f81d1d0f5a4c0ad3f2540e65fe25691376e0a5bd367f558067113ed10", size = 26781, upload-time = "2025-12-26T15:21:36.501Z" } 250 - wheels = [ 251 - { url = "https://files.pythonhosted.org/packages/69/ca/a08fdc7efdcc24e6a6131a93c85be1f204d41c58f474c42b0670af8c016b/caio-0.9.25-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:fab6078b9348e883c80a5e14b382e6ad6aabbc4429ca034e76e730cf464269db", size = 36978, upload-time = "2025-12-26T15:21:41.055Z" }, 252 - { url = "https://files.pythonhosted.org/packages/5e/6c/d4d24f65e690213c097174d26eda6831f45f4734d9d036d81790a27e7b78/caio-0.9.25-cp314-cp314-manylinux2010_x86_64.manylinux2014_x86_64.manylinux_2_12_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:44a6b58e52d488c75cfaa5ecaa404b2b41cc965e6c417e03251e868ecd5b6d77", size = 81832, upload-time = "2025-12-26T15:22:22.757Z" }, 253 - { url = "https://files.pythonhosted.org/packages/87/a4/e534cf7d2d0e8d880e25dd61e8d921ffcfe15bd696734589826f5a2df727/caio-0.9.25-cp314-cp314-manylinux_2_34_aarch64.whl", hash = "sha256:628a630eb7fb22381dd8e3c8ab7f59e854b9c806639811fc3f4310c6bd711d79", size = 81565, upload-time = "2026-03-04T22:08:27.483Z" }, 254 - { url = "https://files.pythonhosted.org/packages/3f/ed/bf81aeac1d290017e5e5ac3e880fd56ee15e50a6d0353986799d1bc5cfd5/caio-0.9.25-cp314-cp314-manylinux_2_34_x86_64.whl", hash = "sha256:0ba16aa605ccb174665357fc729cf500679c2d94d5f1458a6f0d5ca48f2060a7", size = 80071, upload-time = "2026-03-04T22:08:28.751Z" }, 255 - { url = "https://files.pythonhosted.org/packages/86/93/1f76c8d1bafe3b0614e06b2195784a3765bbf7b0a067661af9e2dd47fc33/caio-0.9.25-py3-none-any.whl", hash = "sha256:06c0bb02d6b929119b1cfbe1ca403c768b2013a369e2db46bfa2a5761cf82e40", size = 19087, upload-time = "2025-12-26T15:22:00.221Z" }, 256 - ] 257 - 258 - [[package]] 259 - name = "certifi" 260 - version = "2026.2.25" 261 - source = { registry = "https://pypi.org/simple" } 262 - 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" } 263 - wheels = [ 264 - { 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" }, 265 - ] 266 - 267 - [[package]] 268 - name = "cffi" 269 - version = "2.0.0" 270 - source = { registry = "https://pypi.org/simple" } 271 - dependencies = [ 272 - { name = "pycparser", marker = "implementation_name != 'PyPy'" }, 273 - ] 274 - sdist = { url = "https://files.pythonhosted.org/packages/eb/56/b1ba7935a17738ae8453301356628e8147c79dbb825bcbc73dc7401f9846/cffi-2.0.0.tar.gz", hash = "sha256:44d1b5909021139fe36001ae048dbdde8214afa20200eda0f64c068cac5d5529", size = 523588, upload-time = "2025-09-08T23:24:04.541Z" } 275 - wheels = [ 276 - { url = "https://files.pythonhosted.org/packages/92/c4/3ce07396253a83250ee98564f8d7e9789fab8e58858f35d07a9a2c78de9f/cffi-2.0.0-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:fc33c5141b55ed366cfaad382df24fe7dcbc686de5be719b207bb248e3053dc5", size = 185320, upload-time = "2025-09-08T23:23:18.087Z" }, 277 - { url = "https://files.pythonhosted.org/packages/59/dd/27e9fa567a23931c838c6b02d0764611c62290062a6d4e8ff7863daf9730/cffi-2.0.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:c654de545946e0db659b3400168c9ad31b5d29593291482c43e3564effbcee13", size = 181487, upload-time = "2025-09-08T23:23:19.622Z" }, 278 - { url = "https://files.pythonhosted.org/packages/d6/43/0e822876f87ea8a4ef95442c3d766a06a51fc5298823f884ef87aaad168c/cffi-2.0.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:24b6f81f1983e6df8db3adc38562c83f7d4a0c36162885ec7f7b77c7dcbec97b", size = 220049, upload-time = "2025-09-08T23:23:20.853Z" }, 279 - { url = "https://files.pythonhosted.org/packages/b4/89/76799151d9c2d2d1ead63c2429da9ea9d7aac304603de0c6e8764e6e8e70/cffi-2.0.0-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:12873ca6cb9b0f0d3a0da705d6086fe911591737a59f28b7936bdfed27c0d47c", size = 207793, upload-time = "2025-09-08T23:23:22.08Z" }, 280 - { url = "https://files.pythonhosted.org/packages/bb/dd/3465b14bb9e24ee24cb88c9e3730f6de63111fffe513492bf8c808a3547e/cffi-2.0.0-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:d9b97165e8aed9272a6bb17c01e3cc5871a594a446ebedc996e2397a1c1ea8ef", size = 206300, upload-time = "2025-09-08T23:23:23.314Z" }, 281 - { url = "https://files.pythonhosted.org/packages/47/d9/d83e293854571c877a92da46fdec39158f8d7e68da75bf73581225d28e90/cffi-2.0.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:afb8db5439b81cf9c9d0c80404b60c3cc9c3add93e114dcae767f1477cb53775", size = 219244, upload-time = "2025-09-08T23:23:24.541Z" }, 282 - { url = "https://files.pythonhosted.org/packages/2b/0f/1f177e3683aead2bb00f7679a16451d302c436b5cbf2505f0ea8146ef59e/cffi-2.0.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:737fe7d37e1a1bffe70bd5754ea763a62a066dc5913ca57e957824b72a85e205", size = 222828, upload-time = "2025-09-08T23:23:26.143Z" }, 283 - { url = "https://files.pythonhosted.org/packages/c6/0f/cafacebd4b040e3119dcb32fed8bdef8dfe94da653155f9d0b9dc660166e/cffi-2.0.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:38100abb9d1b1435bc4cc340bb4489635dc2f0da7456590877030c9b3d40b0c1", size = 220926, upload-time = "2025-09-08T23:23:27.873Z" }, 284 - { url = "https://files.pythonhosted.org/packages/3e/aa/df335faa45b395396fcbc03de2dfcab242cd61a9900e914fe682a59170b1/cffi-2.0.0-cp314-cp314-win32.whl", hash = "sha256:087067fa8953339c723661eda6b54bc98c5625757ea62e95eb4898ad5e776e9f", size = 175328, upload-time = "2025-09-08T23:23:44.61Z" }, 285 - { url = "https://files.pythonhosted.org/packages/bb/92/882c2d30831744296ce713f0feb4c1cd30f346ef747b530b5318715cc367/cffi-2.0.0-cp314-cp314-win_amd64.whl", hash = "sha256:203a48d1fb583fc7d78a4c6655692963b860a417c0528492a6bc21f1aaefab25", size = 185650, upload-time = "2025-09-08T23:23:45.848Z" }, 286 - { url = "https://files.pythonhosted.org/packages/9f/2c/98ece204b9d35a7366b5b2c6539c350313ca13932143e79dc133ba757104/cffi-2.0.0-cp314-cp314-win_arm64.whl", hash = "sha256:dbd5c7a25a7cb98f5ca55d258b103a2054f859a46ae11aaf23134f9cc0d356ad", size = 180687, upload-time = "2025-09-08T23:23:47.105Z" }, 287 - { url = "https://files.pythonhosted.org/packages/3e/61/c768e4d548bfa607abcda77423448df8c471f25dbe64fb2ef6d555eae006/cffi-2.0.0-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:9a67fc9e8eb39039280526379fb3a70023d77caec1852002b4da7e8b270c4dd9", size = 188773, upload-time = "2025-09-08T23:23:29.347Z" }, 288 - { url = "https://files.pythonhosted.org/packages/2c/ea/5f76bce7cf6fcd0ab1a1058b5af899bfbef198bea4d5686da88471ea0336/cffi-2.0.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:7a66c7204d8869299919db4d5069a82f1561581af12b11b3c9f48c584eb8743d", size = 185013, upload-time = "2025-09-08T23:23:30.63Z" }, 289 - { url = "https://files.pythonhosted.org/packages/be/b4/c56878d0d1755cf9caa54ba71e5d049479c52f9e4afc230f06822162ab2f/cffi-2.0.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:7cc09976e8b56f8cebd752f7113ad07752461f48a58cbba644139015ac24954c", size = 221593, upload-time = "2025-09-08T23:23:31.91Z" }, 290 - { url = "https://files.pythonhosted.org/packages/e0/0d/eb704606dfe8033e7128df5e90fee946bbcb64a04fcdaa97321309004000/cffi-2.0.0-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:92b68146a71df78564e4ef48af17551a5ddd142e5190cdf2c5624d0c3ff5b2e8", size = 209354, upload-time = "2025-09-08T23:23:33.214Z" }, 291 - { url = "https://files.pythonhosted.org/packages/d8/19/3c435d727b368ca475fb8742ab97c9cb13a0de600ce86f62eab7fa3eea60/cffi-2.0.0-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:b1e74d11748e7e98e2f426ab176d4ed720a64412b6a15054378afdb71e0f37dc", size = 208480, upload-time = "2025-09-08T23:23:34.495Z" }, 292 - { url = "https://files.pythonhosted.org/packages/d0/44/681604464ed9541673e486521497406fadcc15b5217c3e326b061696899a/cffi-2.0.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:28a3a209b96630bca57cce802da70c266eb08c6e97e5afd61a75611ee6c64592", size = 221584, upload-time = "2025-09-08T23:23:36.096Z" }, 293 - { url = "https://files.pythonhosted.org/packages/25/8e/342a504ff018a2825d395d44d63a767dd8ebc927ebda557fecdaca3ac33a/cffi-2.0.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:7553fb2090d71822f02c629afe6042c299edf91ba1bf94951165613553984512", size = 224443, upload-time = "2025-09-08T23:23:37.328Z" }, 294 - { url = "https://files.pythonhosted.org/packages/e1/5e/b666bacbbc60fbf415ba9988324a132c9a7a0448a9a8f125074671c0f2c3/cffi-2.0.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:6c6c373cfc5c83a975506110d17457138c8c63016b563cc9ed6e056a82f13ce4", size = 223437, upload-time = "2025-09-08T23:23:38.945Z" }, 295 - { url = "https://files.pythonhosted.org/packages/a0/1d/ec1a60bd1a10daa292d3cd6bb0b359a81607154fb8165f3ec95fe003b85c/cffi-2.0.0-cp314-cp314t-win32.whl", hash = "sha256:1fc9ea04857caf665289b7a75923f2c6ed559b8298a1b8c49e59f7dd95c8481e", size = 180487, upload-time = "2025-09-08T23:23:40.423Z" }, 296 - { url = "https://files.pythonhosted.org/packages/bf/41/4c1168c74fac325c0c8156f04b6749c8b6a8f405bbf91413ba088359f60d/cffi-2.0.0-cp314-cp314t-win_amd64.whl", hash = "sha256:d68b6cef7827e8641e8ef16f4494edda8b36104d79773a334beaa1e3521430f6", size = 191726, upload-time = "2025-09-08T23:23:41.742Z" }, 297 - { url = "https://files.pythonhosted.org/packages/ae/3a/dbeec9d1ee0844c679f6bb5d6ad4e9f198b1224f4e7a32825f47f6192b0c/cffi-2.0.0-cp314-cp314t-win_arm64.whl", hash = "sha256:0a1527a803f0a659de1af2e1fd700213caba79377e27e4693648c2923da066f9", size = 184195, upload-time = "2025-09-08T23:23:43.004Z" }, 298 - ] 299 - 300 - [[package]] 301 - name = "charset-normalizer" 302 - version = "3.4.7" 303 - source = { registry = "https://pypi.org/simple" } 304 - sdist = { url = "https://files.pythonhosted.org/packages/e7/a1/67fe25fac3c7642725500a3f6cfe5821ad557c3abb11c9d20d12c7008d3e/charset_normalizer-3.4.7.tar.gz", hash = "sha256:ae89db9e5f98a11a4bf50407d4363e7b09b31e55bc117b4f7d80aab97ba009e5", size = 144271, upload-time = "2026-04-02T09:28:39.342Z" } 305 - wheels = [ 306 - { url = "https://files.pythonhosted.org/packages/97/c8/c67cb8c70e19ef1960b97b22ed2a1567711de46c4ddf19799923adc836c2/charset_normalizer-3.4.7-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:c36c333c39be2dbca264d7803333c896ab8fa7d4d6f0ab7edb7dfd7aea6e98c0", size = 309234, upload-time = "2026-04-02T09:27:07.194Z" }, 307 - { url = "https://files.pythonhosted.org/packages/99/85/c091fdee33f20de70d6c8b522743b6f831a2f1cd3ff86de4c6a827c48a76/charset_normalizer-3.4.7-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1c2aed2e5e41f24ea8ef1590b8e848a79b56f3a5564a65ceec43c9d692dc7d8a", size = 208042, upload-time = "2026-04-02T09:27:08.749Z" }, 308 - { url = "https://files.pythonhosted.org/packages/87/1c/ab2ce611b984d2fd5d86a5a8a19c1ae26acac6bad967da4967562c75114d/charset_normalizer-3.4.7-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:54523e136b8948060c0fa0bc7b1b50c32c186f2fceee897a495406bb6e311d2b", size = 228706, upload-time = "2026-04-02T09:27:09.951Z" }, 309 - { url = "https://files.pythonhosted.org/packages/a8/29/2b1d2cb00bf085f59d29eb773ce58ec2d325430f8c216804a0a5cd83cbca/charset_normalizer-3.4.7-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:715479b9a2802ecac752a3b0efa2b0b60285cf962ee38414211abdfccc233b41", size = 224727, upload-time = "2026-04-02T09:27:11.175Z" }, 310 - { url = "https://files.pythonhosted.org/packages/47/5c/032c2d5a07fe4d4855fea851209cca2b6f03ebeb6d4e3afdb3358386a684/charset_normalizer-3.4.7-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:bd6c2a1c7573c64738d716488d2cdd3c00e340e4835707d8fdb8dc1a66ef164e", size = 215882, upload-time = "2026-04-02T09:27:12.446Z" }, 311 - { url = "https://files.pythonhosted.org/packages/2c/c2/356065d5a8b78ed04499cae5f339f091946a6a74f91e03476c33f0ab7100/charset_normalizer-3.4.7-cp314-cp314-manylinux_2_31_armv7l.whl", hash = "sha256:c45e9440fb78f8ddabcf714b68f936737a121355bf59f3907f4e17721b9d1aae", size = 200860, upload-time = "2026-04-02T09:27:13.721Z" }, 312 - { url = "https://files.pythonhosted.org/packages/0c/cd/a32a84217ced5039f53b29f460962abb2d4420def55afabe45b1c3c7483d/charset_normalizer-3.4.7-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:3534e7dcbdcf757da6b85a0bbf5b6868786d5982dd959b065e65481644817a18", size = 211564, upload-time = "2026-04-02T09:27:15.272Z" }, 313 - { url = "https://files.pythonhosted.org/packages/44/86/58e6f13ce26cc3b8f4a36b94a0f22ae2f00a72534520f4ae6857c4b81f89/charset_normalizer-3.4.7-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:e8ac484bf18ce6975760921bb6148041faa8fef0547200386ea0b52b5d27bf7b", size = 211276, upload-time = "2026-04-02T09:27:16.834Z" }, 314 - { url = "https://files.pythonhosted.org/packages/8f/fe/d17c32dc72e17e155e06883efa84514ca375f8a528ba2546bee73fc4df81/charset_normalizer-3.4.7-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:a5fe03b42827c13cdccd08e6c0247b6a6d4b5e3cdc53fd1749f5896adcdc2356", size = 201238, upload-time = "2026-04-02T09:27:18.229Z" }, 315 - { url = "https://files.pythonhosted.org/packages/6a/29/f33daa50b06525a237451cdb6c69da366c381a3dadcd833fa5676bc468b3/charset_normalizer-3.4.7-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:2d6eb928e13016cea4f1f21d1e10c1cebd5a421bc57ddf5b1142ae3f86824fab", size = 230189, upload-time = "2026-04-02T09:27:19.445Z" }, 316 - { url = "https://files.pythonhosted.org/packages/b6/6e/52c84015394a6a0bdcd435210a7e944c5f94ea1055f5cc5d56c5fe368e7b/charset_normalizer-3.4.7-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:e74327fb75de8986940def6e8dee4f127cc9752bee7355bb323cc5b2659b6d46", size = 211352, upload-time = "2026-04-02T09:27:20.79Z" }, 317 - { url = "https://files.pythonhosted.org/packages/8c/d7/4353be581b373033fb9198bf1da3cf8f09c1082561e8e922aa7b39bf9fe8/charset_normalizer-3.4.7-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:d6038d37043bced98a66e68d3aa2b6a35505dc01328cd65217cefe82f25def44", size = 227024, upload-time = "2026-04-02T09:27:22.063Z" }, 318 - { url = "https://files.pythonhosted.org/packages/30/45/99d18aa925bd1740098ccd3060e238e21115fffbfdcb8f3ece837d0ace6c/charset_normalizer-3.4.7-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:7579e913a5339fb8fa133f6bbcfd8e6749696206cf05acdbdca71a1b436d8e72", size = 217869, upload-time = "2026-04-02T09:27:23.486Z" }, 319 - { url = "https://files.pythonhosted.org/packages/5c/05/5ee478aa53f4bb7996482153d4bfe1b89e0f087f0ab6b294fcf92d595873/charset_normalizer-3.4.7-cp314-cp314-win32.whl", hash = "sha256:5b77459df20e08151cd6f8b9ef8ef1f961ef73d85c21a555c7eed5b79410ec10", size = 148541, upload-time = "2026-04-02T09:27:25.146Z" }, 320 - { url = "https://files.pythonhosted.org/packages/48/77/72dcb0921b2ce86420b2d79d454c7022bf5be40202a2a07906b9f2a35c97/charset_normalizer-3.4.7-cp314-cp314-win_amd64.whl", hash = "sha256:92a0a01ead5e668468e952e4238cccd7c537364eb7d851ab144ab6627dbbe12f", size = 159634, upload-time = "2026-04-02T09:27:26.642Z" }, 321 - { url = "https://files.pythonhosted.org/packages/c6/a3/c2369911cd72f02386e4e340770f6e158c7980267da16af8f668217abaa0/charset_normalizer-3.4.7-cp314-cp314-win_arm64.whl", hash = "sha256:67f6279d125ca0046a7fd386d01b311c6363844deac3e5b069b514ba3e63c246", size = 148384, upload-time = "2026-04-02T09:27:28.271Z" }, 322 - { url = "https://files.pythonhosted.org/packages/94/09/7e8a7f73d24dba1f0035fbbf014d2c36828fc1bf9c88f84093e57d315935/charset_normalizer-3.4.7-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:effc3f449787117233702311a1b7d8f59cba9ced946ba727bdc329ec69028e24", size = 330133, upload-time = "2026-04-02T09:27:29.474Z" }, 323 - { url = "https://files.pythonhosted.org/packages/8d/da/96975ddb11f8e977f706f45cddd8540fd8242f71ecdb5d18a80723dcf62c/charset_normalizer-3.4.7-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:fbccdc05410c9ee21bbf16a35f4c1d16123dcdeb8a1d38f33654fa21d0234f79", size = 216257, upload-time = "2026-04-02T09:27:30.793Z" }, 324 - { url = "https://files.pythonhosted.org/packages/e5/e8/1d63bf8ef2d388e95c64b2098f45f84758f6d102a087552da1485912637b/charset_normalizer-3.4.7-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:733784b6d6def852c814bce5f318d25da2ee65dd4839a0718641c696e09a2960", size = 234851, upload-time = "2026-04-02T09:27:32.44Z" }, 325 - { url = "https://files.pythonhosted.org/packages/9b/40/e5ff04233e70da2681fa43969ad6f66ca5611d7e669be0246c4c7aaf6dc8/charset_normalizer-3.4.7-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:a89c23ef8d2c6b27fd200a42aa4ac72786e7c60d40efdc76e6011260b6e949c4", size = 233393, upload-time = "2026-04-02T09:27:34.03Z" }, 326 - { url = "https://files.pythonhosted.org/packages/be/c1/06c6c49d5a5450f76899992f1ee40b41d076aee9279b49cf9974d2f313d5/charset_normalizer-3.4.7-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6c114670c45346afedc0d947faf3c7f701051d2518b943679c8ff88befe14f8e", size = 223251, upload-time = "2026-04-02T09:27:35.369Z" }, 327 - { url = "https://files.pythonhosted.org/packages/2b/9f/f2ff16fb050946169e3e1f82134d107e5d4ae72647ec8a1b1446c148480f/charset_normalizer-3.4.7-cp314-cp314t-manylinux_2_31_armv7l.whl", hash = "sha256:a180c5e59792af262bf263b21a3c49353f25945d8d9f70628e73de370d55e1e1", size = 206609, upload-time = "2026-04-02T09:27:36.661Z" }, 328 - { url = "https://files.pythonhosted.org/packages/69/d5/a527c0cd8d64d2eab7459784fb4169a0ac76e5a6fc5237337982fd61347e/charset_normalizer-3.4.7-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:3c9a494bc5ec77d43cea229c4f6db1e4d8fe7e1bbffa8b6f0f0032430ff8ab44", size = 220014, upload-time = "2026-04-02T09:27:38.019Z" }, 329 - { url = "https://files.pythonhosted.org/packages/7e/80/8a7b8104a3e203074dc9aa2c613d4b726c0e136bad1cc734594b02867972/charset_normalizer-3.4.7-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:8d828b6667a32a728a1ad1d93957cdf37489c57b97ae6c4de2860fa749b8fc1e", size = 218979, upload-time = "2026-04-02T09:27:39.37Z" }, 330 - { url = "https://files.pythonhosted.org/packages/02/9a/b759b503d507f375b2b5c153e4d2ee0a75aa215b7f2489cf314f4541f2c0/charset_normalizer-3.4.7-cp314-cp314t-musllinux_1_2_armv7l.whl", hash = "sha256:cf1493cd8607bec4d8a7b9b004e699fcf8f9103a9284cc94962cb73d20f9d4a3", size = 209238, upload-time = "2026-04-02T09:27:40.722Z" }, 331 - { url = "https://files.pythonhosted.org/packages/c2/4e/0f3f5d47b86bdb79256e7290b26ac847a2832d9a4033f7eb2cd4bcf4bb5b/charset_normalizer-3.4.7-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:0c96c3b819b5c3e9e165495db84d41914d6894d55181d2d108cc1a69bfc9cce0", size = 236110, upload-time = "2026-04-02T09:27:42.33Z" }, 332 - { url = "https://files.pythonhosted.org/packages/96/23/bce28734eb3ed2c91dcf93abeb8a5cf393a7b2749725030bb630e554fdd8/charset_normalizer-3.4.7-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:752a45dc4a6934060b3b0dab47e04edc3326575f82be64bc4fc293914566503e", size = 219824, upload-time = "2026-04-02T09:27:43.924Z" }, 333 - { url = "https://files.pythonhosted.org/packages/2c/6f/6e897c6984cc4d41af319b077f2f600fc8214eb2fe2d6bcb79141b882400/charset_normalizer-3.4.7-cp314-cp314t-musllinux_1_2_s390x.whl", hash = "sha256:8778f0c7a52e56f75d12dae53ae320fae900a8b9b4164b981b9c5ce059cd1fcb", size = 233103, upload-time = "2026-04-02T09:27:45.348Z" }, 334 - { url = "https://files.pythonhosted.org/packages/76/22/ef7bd0fe480a0ae9b656189ec00744b60933f68b4f42a7bb06589f6f576a/charset_normalizer-3.4.7-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:ce3412fbe1e31eb81ea42f4169ed94861c56e643189e1e75f0041f3fe7020abe", size = 225194, upload-time = "2026-04-02T09:27:46.706Z" }, 335 - { url = "https://files.pythonhosted.org/packages/c5/a7/0e0ab3e0b5bc1219bd80a6a0d4d72ca74d9250cb2382b7c699c147e06017/charset_normalizer-3.4.7-cp314-cp314t-win32.whl", hash = "sha256:c03a41a8784091e67a39648f70c5f97b5b6a37f216896d44d2cdcb82615339a0", size = 159827, upload-time = "2026-04-02T09:27:48.053Z" }, 336 - { url = "https://files.pythonhosted.org/packages/7a/1d/29d32e0fb40864b1f878c7f5a0b343ae676c6e2b271a2d55cc3a152391da/charset_normalizer-3.4.7-cp314-cp314t-win_amd64.whl", hash = "sha256:03853ed82eeebbce3c2abfdbc98c96dc205f32a79627688ac9a27370ea61a49c", size = 174168, upload-time = "2026-04-02T09:27:49.795Z" }, 337 - { url = "https://files.pythonhosted.org/packages/de/32/d92444ad05c7a6e41fb2036749777c163baf7a0301a040cb672d6b2b1ae9/charset_normalizer-3.4.7-cp314-cp314t-win_arm64.whl", hash = "sha256:c35abb8bfff0185efac5878da64c45dafd2b37fb0383add1be155a763c1f083d", size = 153018, upload-time = "2026-04-02T09:27:51.116Z" }, 338 - { url = "https://files.pythonhosted.org/packages/db/8f/61959034484a4a7c527811f4721e75d02d653a35afb0b6054474d8185d4c/charset_normalizer-3.4.7-py3-none-any.whl", hash = "sha256:3dce51d0f5e7951f8bb4900c257dad282f49190fdbebecd4ba99bcc41fef404d", size = 61958, upload-time = "2026-04-02T09:28:37.794Z" }, 339 - ] 340 - 341 - [[package]] 342 - name = "click" 343 - version = "8.3.2" 344 - source = { registry = "https://pypi.org/simple" } 345 - dependencies = [ 346 - { name = "colorama", marker = "sys_platform == 'win32'" }, 347 - ] 348 - sdist = { url = "https://files.pythonhosted.org/packages/57/75/31212c6bf2503fdf920d87fee5d7a86a2e3bcf444984126f13d8e4016804/click-8.3.2.tar.gz", hash = "sha256:14162b8b3b3550a7d479eafa77dfd3c38d9dc8951f6f69c78913a8f9a7540fd5", size = 302856, upload-time = "2026-04-03T19:14:45.118Z" } 349 - wheels = [ 350 - { url = "https://files.pythonhosted.org/packages/e4/20/71885d8b97d4f3dde17b1fdb92dbd4908b00541c5a3379787137285f602e/click-8.3.2-py3-none-any.whl", hash = "sha256:1924d2c27c5653561cd2cae4548d1406039cb79b858b747cfea24924bbc1616d", size = 108379, upload-time = "2026-04-03T19:14:43.505Z" }, 351 - ] 352 - 353 - [[package]] 354 - name = "cohere" 355 - version = "5.21.1" 356 - source = { registry = "https://pypi.org/simple" } 357 - dependencies = [ 358 - { name = "fastavro" }, 359 - { name = "httpx" }, 360 - { name = "pydantic" }, 361 - { name = "pydantic-core" }, 362 - { name = "requests" }, 363 - { name = "tokenizers" }, 364 - { name = "types-requests" }, 365 - { name = "typing-extensions" }, 366 - ] 367 - sdist = { url = "https://files.pythonhosted.org/packages/d2/75/4c346f6e2322e545f8452692304bd4eca15a2a0209ab9af6a0d1a7810b67/cohere-5.21.1.tar.gz", hash = "sha256:e5ade4423b928b01ff2038980e1b62b2a5bb412c8ab83e30882753b810a5509f", size = 191272, upload-time = "2026-03-26T15:09:27.857Z" } 368 - wheels = [ 369 - { url = "https://files.pythonhosted.org/packages/0a/50/5538f02ec6d10fbb84f29c1b18c68ff2a03d7877926a80275efdf8755a9f/cohere-5.21.1-py3-none-any.whl", hash = "sha256:f15592ec60d8cf12f01563db94ec28c388c61269d9617f23c2d6d910e505344e", size = 334262, upload-time = "2026-03-26T15:09:26.284Z" }, 370 - ] 371 - 372 - [[package]] 373 - name = "colorama" 374 - version = "0.4.6" 375 - source = { registry = "https://pypi.org/simple" } 376 - sdist = { url = "https://files.pythonhosted.org/packages/d8/53/6f443c9a4a8358a93a6792e2acffb9d9d5cb0a5cfd8802644b7b1c9a02e4/colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44", size = 27697, upload-time = "2022-10-25T02:36:22.414Z" } 377 - wheels = [ 378 - { url = "https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6", size = 25335, upload-time = "2022-10-25T02:36:20.889Z" }, 379 - ] 380 - 381 - [[package]] 382 - name = "cryptography" 383 - version = "46.0.7" 384 - source = { registry = "https://pypi.org/simple" } 385 - dependencies = [ 386 - { name = "cffi", marker = "platform_python_implementation != 'PyPy'" }, 387 - ] 388 - sdist = { url = "https://files.pythonhosted.org/packages/47/93/ac8f3d5ff04d54bc814e961a43ae5b0b146154c89c61b47bb07557679b18/cryptography-46.0.7.tar.gz", hash = "sha256:e4cfd68c5f3e0bfdad0d38e023239b96a2fe84146481852dffbcca442c245aa5", size = 750652, upload-time = "2026-04-08T01:57:54.692Z" } 389 - wheels = [ 390 - { url = "https://files.pythonhosted.org/packages/0b/5d/4a8f770695d73be252331e60e526291e3df0c9b27556a90a6b47bccca4c2/cryptography-46.0.7-cp311-abi3-macosx_10_9_universal2.whl", hash = "sha256:ea42cbe97209df307fdc3b155f1b6fa2577c0defa8f1f7d3be7d31d189108ad4", size = 7179869, upload-time = "2026-04-08T01:56:17.157Z" }, 391 - { url = "https://files.pythonhosted.org/packages/5f/45/6d80dc379b0bbc1f9d1e429f42e4cb9e1d319c7a8201beffd967c516ea01/cryptography-46.0.7-cp311-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:b36a4695e29fe69215d75960b22577197aca3f7a25b9cf9d165dcfe9d80bc325", size = 4275492, upload-time = "2026-04-08T01:56:19.36Z" }, 392 - { url = "https://files.pythonhosted.org/packages/4a/9a/1765afe9f572e239c3469f2cb429f3ba7b31878c893b246b4b2994ffe2fe/cryptography-46.0.7-cp311-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:5ad9ef796328c5e3c4ceed237a183f5d41d21150f972455a9d926593a1dcb308", size = 4426670, upload-time = "2026-04-08T01:56:21.415Z" }, 393 - { url = "https://files.pythonhosted.org/packages/8f/3e/af9246aaf23cd4ee060699adab1e47ced3f5f7e7a8ffdd339f817b446462/cryptography-46.0.7-cp311-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:73510b83623e080a2c35c62c15298096e2a5dc8d51c3b4e1740211839d0dea77", size = 4280275, upload-time = "2026-04-08T01:56:23.539Z" }, 394 - { url = "https://files.pythonhosted.org/packages/0f/54/6bbbfc5efe86f9d71041827b793c24811a017c6ac0fd12883e4caa86b8ed/cryptography-46.0.7-cp311-abi3-manylinux_2_28_ppc64le.whl", hash = "sha256:cbd5fb06b62bd0721e1170273d3f4d5a277044c47ca27ee257025146c34cbdd1", size = 4928402, upload-time = "2026-04-08T01:56:25.624Z" }, 395 - { url = "https://files.pythonhosted.org/packages/2d/cf/054b9d8220f81509939599c8bdbc0c408dbd2bdd41688616a20731371fe0/cryptography-46.0.7-cp311-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:420b1e4109cc95f0e5700eed79908cef9268265c773d3a66f7af1eef53d409ef", size = 4459985, upload-time = "2026-04-08T01:56:27.309Z" }, 396 - { url = "https://files.pythonhosted.org/packages/f9/46/4e4e9c6040fb01c7467d47217d2f882daddeb8828f7df800cb806d8a2288/cryptography-46.0.7-cp311-abi3-manylinux_2_31_armv7l.whl", hash = "sha256:24402210aa54baae71d99441d15bb5a1919c195398a87b563df84468160a65de", size = 3990652, upload-time = "2026-04-08T01:56:29.095Z" }, 397 - { url = "https://files.pythonhosted.org/packages/36/5f/313586c3be5a2fbe87e4c9a254207b860155a8e1f3cca99f9910008e7d08/cryptography-46.0.7-cp311-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:8a469028a86f12eb7d2fe97162d0634026d92a21f3ae0ac87ed1c4a447886c83", size = 4279805, upload-time = "2026-04-08T01:56:30.928Z" }, 398 - { url = "https://files.pythonhosted.org/packages/69/33/60dfc4595f334a2082749673386a4d05e4f0cf4df8248e63b2c3437585f2/cryptography-46.0.7-cp311-abi3-manylinux_2_34_ppc64le.whl", hash = "sha256:9694078c5d44c157ef3162e3bf3946510b857df5a3955458381d1c7cfc143ddb", size = 4892883, upload-time = "2026-04-08T01:56:32.614Z" }, 399 - { url = "https://files.pythonhosted.org/packages/c7/0b/333ddab4270c4f5b972f980adef4faa66951a4aaf646ca067af597f15563/cryptography-46.0.7-cp311-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:42a1e5f98abb6391717978baf9f90dc28a743b7d9be7f0751a6f56a75d14065b", size = 4459756, upload-time = "2026-04-08T01:56:34.306Z" }, 400 - { url = "https://files.pythonhosted.org/packages/d2/14/633913398b43b75f1234834170947957c6b623d1701ffc7a9600da907e89/cryptography-46.0.7-cp311-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:91bbcb08347344f810cbe49065914fe048949648f6bd5c2519f34619142bbe85", size = 4410244, upload-time = "2026-04-08T01:56:35.977Z" }, 401 - { url = "https://files.pythonhosted.org/packages/10/f2/19ceb3b3dc14009373432af0c13f46aa08e3ce334ec6eff13492e1812ccd/cryptography-46.0.7-cp311-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:5d1c02a14ceb9148cc7816249f64f623fbfee39e8c03b3650d842ad3f34d637e", size = 4674868, upload-time = "2026-04-08T01:56:38.034Z" }, 402 - { url = "https://files.pythonhosted.org/packages/1a/bb/a5c213c19ee94b15dfccc48f363738633a493812687f5567addbcbba9f6f/cryptography-46.0.7-cp311-abi3-win32.whl", hash = "sha256:d23c8ca48e44ee015cd0a54aeccdf9f09004eba9fc96f38c911011d9ff1bd457", size = 3026504, upload-time = "2026-04-08T01:56:39.666Z" }, 403 - { url = "https://files.pythonhosted.org/packages/2b/02/7788f9fefa1d060ca68717c3901ae7fffa21ee087a90b7f23c7a603c32ae/cryptography-46.0.7-cp311-abi3-win_amd64.whl", hash = "sha256:397655da831414d165029da9bc483bed2fe0e75dde6a1523ec2fe63f3c46046b", size = 3488363, upload-time = "2026-04-08T01:56:41.893Z" }, 404 - { url = "https://files.pythonhosted.org/packages/7b/56/15619b210e689c5403bb0540e4cb7dbf11a6bf42e483b7644e471a2812b3/cryptography-46.0.7-cp314-cp314t-macosx_10_9_universal2.whl", hash = "sha256:d151173275e1728cf7839aaa80c34fe550c04ddb27b34f48c232193df8db5842", size = 7119671, upload-time = "2026-04-08T01:56:44Z" }, 405 - { url = "https://files.pythonhosted.org/packages/74/66/e3ce040721b0b5599e175ba91ab08884c75928fbeb74597dd10ef13505d2/cryptography-46.0.7-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:db0f493b9181c7820c8134437eb8b0b4792085d37dbb24da050476ccb664e59c", size = 4268551, upload-time = "2026-04-08T01:56:46.071Z" }, 406 - { url = "https://files.pythonhosted.org/packages/03/11/5e395f961d6868269835dee1bafec6a1ac176505a167f68b7d8818431068/cryptography-46.0.7-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:ebd6daf519b9f189f85c479427bbd6e9c9037862cf8fe89ee35503bd209ed902", size = 4408887, upload-time = "2026-04-08T01:56:47.718Z" }, 407 - { url = "https://files.pythonhosted.org/packages/40/53/8ed1cf4c3b9c8e611e7122fb56f1c32d09e1fff0f1d77e78d9ff7c82653e/cryptography-46.0.7-cp314-cp314t-manylinux_2_28_aarch64.whl", hash = "sha256:b7b412817be92117ec5ed95f880defe9cf18a832e8cafacf0a22337dc1981b4d", size = 4271354, upload-time = "2026-04-08T01:56:49.312Z" }, 408 - { url = "https://files.pythonhosted.org/packages/50/46/cf71e26025c2e767c5609162c866a78e8a2915bbcfa408b7ca495c6140c4/cryptography-46.0.7-cp314-cp314t-manylinux_2_28_ppc64le.whl", hash = "sha256:fbfd0e5f273877695cb93baf14b185f4878128b250cc9f8e617ea0c025dfb022", size = 4905845, upload-time = "2026-04-08T01:56:50.916Z" }, 409 - { url = "https://files.pythonhosted.org/packages/c0/ea/01276740375bac6249d0a971ebdf6b4dc9ead0ee0a34ef3b5a88c1a9b0d4/cryptography-46.0.7-cp314-cp314t-manylinux_2_28_x86_64.whl", hash = "sha256:ffca7aa1d00cf7d6469b988c581598f2259e46215e0140af408966a24cf086ce", size = 4444641, upload-time = "2026-04-08T01:56:52.882Z" }, 410 - { url = "https://files.pythonhosted.org/packages/3d/4c/7d258f169ae71230f25d9f3d06caabcff8c3baf0978e2b7d65e0acac3827/cryptography-46.0.7-cp314-cp314t-manylinux_2_31_armv7l.whl", hash = "sha256:60627cf07e0d9274338521205899337c5d18249db56865f943cbe753aa96f40f", size = 3967749, upload-time = "2026-04-08T01:56:54.597Z" }, 411 - { url = "https://files.pythonhosted.org/packages/b5/2a/2ea0767cad19e71b3530e4cad9605d0b5e338b6a1e72c37c9c1ceb86c333/cryptography-46.0.7-cp314-cp314t-manylinux_2_34_aarch64.whl", hash = "sha256:80406c3065e2c55d7f49a9550fe0c49b3f12e5bfff5dedb727e319e1afb9bf99", size = 4270942, upload-time = "2026-04-08T01:56:56.416Z" }, 412 - { url = "https://files.pythonhosted.org/packages/41/3d/fe14df95a83319af25717677e956567a105bb6ab25641acaa093db79975d/cryptography-46.0.7-cp314-cp314t-manylinux_2_34_ppc64le.whl", hash = "sha256:c5b1ccd1239f48b7151a65bc6dd54bcfcc15e028c8ac126d3fada09db0e07ef1", size = 4871079, upload-time = "2026-04-08T01:56:58.31Z" }, 413 - { url = "https://files.pythonhosted.org/packages/9c/59/4a479e0f36f8f378d397f4eab4c850b4ffb79a2f0d58704b8fa0703ddc11/cryptography-46.0.7-cp314-cp314t-manylinux_2_34_x86_64.whl", hash = "sha256:d5f7520159cd9c2154eb61eb67548ca05c5774d39e9c2c4339fd793fe7d097b2", size = 4443999, upload-time = "2026-04-08T01:57:00.508Z" }, 414 - { url = "https://files.pythonhosted.org/packages/28/17/b59a741645822ec6d04732b43c5d35e4ef58be7bfa84a81e5ae6f05a1d33/cryptography-46.0.7-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:fcd8eac50d9138c1d7fc53a653ba60a2bee81a505f9f8850b6b2888555a45d0e", size = 4399191, upload-time = "2026-04-08T01:57:02.654Z" }, 415 - { url = "https://files.pythonhosted.org/packages/59/6a/bb2e166d6d0e0955f1e9ff70f10ec4b2824c9cfcdb4da772c7dd69cc7d80/cryptography-46.0.7-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:65814c60f8cc400c63131584e3e1fad01235edba2614b61fbfbfa954082db0ee", size = 4655782, upload-time = "2026-04-08T01:57:04.592Z" }, 416 - { url = "https://files.pythonhosted.org/packages/95/b6/3da51d48415bcb63b00dc17c2eff3a651b7c4fed484308d0f19b30e8cb2c/cryptography-46.0.7-cp314-cp314t-win32.whl", hash = "sha256:fdd1736fed309b4300346f88f74cd120c27c56852c3838cab416e7a166f67298", size = 3002227, upload-time = "2026-04-08T01:57:06.91Z" }, 417 - { url = "https://files.pythonhosted.org/packages/32/a8/9f0e4ed57ec9cebe506e58db11ae472972ecb0c659e4d52bbaee80ca340a/cryptography-46.0.7-cp314-cp314t-win_amd64.whl", hash = "sha256:e06acf3c99be55aa3b516397fe42f5855597f430add9c17fa46bf2e0fb34c9bb", size = 3475332, upload-time = "2026-04-08T01:57:08.807Z" }, 418 - { url = "https://files.pythonhosted.org/packages/a7/7f/cd42fc3614386bc0c12f0cb3c4ae1fc2bbca5c9662dfed031514911d513d/cryptography-46.0.7-cp38-abi3-macosx_10_9_universal2.whl", hash = "sha256:462ad5cb1c148a22b2e3bcc5ad52504dff325d17daf5df8d88c17dda1f75f2a4", size = 7165618, upload-time = "2026-04-08T01:57:10.645Z" }, 419 - { url = "https://files.pythonhosted.org/packages/a5/d0/36a49f0262d2319139d2829f773f1b97ef8aef7f97e6e5bd21455e5a8fb5/cryptography-46.0.7-cp38-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:84d4cced91f0f159a7ddacad249cc077e63195c36aac40b4150e7a57e84fffe7", size = 4270628, upload-time = "2026-04-08T01:57:12.885Z" }, 420 - { url = "https://files.pythonhosted.org/packages/8a/6c/1a42450f464dda6ffbe578a911f773e54dd48c10f9895a23a7e88b3e7db5/cryptography-46.0.7-cp38-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:128c5edfe5e5938b86b03941e94fac9ee793a94452ad1365c9fc3f4f62216832", size = 4415405, upload-time = "2026-04-08T01:57:14.923Z" }, 421 - { url = "https://files.pythonhosted.org/packages/9a/92/4ed714dbe93a066dc1f4b4581a464d2d7dbec9046f7c8b7016f5286329e2/cryptography-46.0.7-cp38-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:5e51be372b26ef4ba3de3c167cd3d1022934bc838ae9eaad7e644986d2a3d163", size = 4272715, upload-time = "2026-04-08T01:57:16.638Z" }, 422 - { url = "https://files.pythonhosted.org/packages/b7/e6/a26b84096eddd51494bba19111f8fffe976f6a09f132706f8f1bf03f51f7/cryptography-46.0.7-cp38-abi3-manylinux_2_28_ppc64le.whl", hash = "sha256:cdf1a610ef82abb396451862739e3fc93b071c844399e15b90726ef7470eeaf2", size = 4918400, upload-time = "2026-04-08T01:57:19.021Z" }, 423 - { url = "https://files.pythonhosted.org/packages/c7/08/ffd537b605568a148543ac3c2b239708ae0bd635064bab41359252ef88ed/cryptography-46.0.7-cp38-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:1d25aee46d0c6f1a501adcddb2d2fee4b979381346a78558ed13e50aa8a59067", size = 4450634, upload-time = "2026-04-08T01:57:21.185Z" }, 424 - { url = "https://files.pythonhosted.org/packages/16/01/0cd51dd86ab5b9befe0d031e276510491976c3a80e9f6e31810cce46c4ad/cryptography-46.0.7-cp38-abi3-manylinux_2_31_armv7l.whl", hash = "sha256:cdfbe22376065ffcf8be74dc9a909f032df19bc58a699456a21712d6e5eabfd0", size = 3985233, upload-time = "2026-04-08T01:57:22.862Z" }, 425 - { url = "https://files.pythonhosted.org/packages/92/49/819d6ed3a7d9349c2939f81b500a738cb733ab62fbecdbc1e38e83d45e12/cryptography-46.0.7-cp38-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:abad9dac36cbf55de6eb49badd4016806b3165d396f64925bf2999bcb67837ba", size = 4271955, upload-time = "2026-04-08T01:57:24.814Z" }, 426 - { url = "https://files.pythonhosted.org/packages/80/07/ad9b3c56ebb95ed2473d46df0847357e01583f4c52a85754d1a55e29e4d0/cryptography-46.0.7-cp38-abi3-manylinux_2_34_ppc64le.whl", hash = "sha256:935ce7e3cfdb53e3536119a542b839bb94ec1ad081013e9ab9b7cfd478b05006", size = 4879888, upload-time = "2026-04-08T01:57:26.88Z" }, 427 - { url = "https://files.pythonhosted.org/packages/b8/c7/201d3d58f30c4c2bdbe9b03844c291feb77c20511cc3586daf7edc12a47b/cryptography-46.0.7-cp38-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:35719dc79d4730d30f1c2b6474bd6acda36ae2dfae1e3c16f2051f215df33ce0", size = 4449961, upload-time = "2026-04-08T01:57:29.068Z" }, 428 - { url = "https://files.pythonhosted.org/packages/a5/ef/649750cbf96f3033c3c976e112265c33906f8e462291a33d77f90356548c/cryptography-46.0.7-cp38-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:7bbc6ccf49d05ac8f7d7b5e2e2c33830d4fe2061def88210a126d130d7f71a85", size = 4401696, upload-time = "2026-04-08T01:57:31.029Z" }, 429 - { url = "https://files.pythonhosted.org/packages/41/52/a8908dcb1a389a459a29008c29966c1d552588d4ae6d43f3a1a4512e0ebe/cryptography-46.0.7-cp38-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:a1529d614f44b863a7b480c6d000fe93b59acee9c82ffa027cfadc77521a9f5e", size = 4664256, upload-time = "2026-04-08T01:57:33.144Z" }, 430 - { url = "https://files.pythonhosted.org/packages/4b/fa/f0ab06238e899cc3fb332623f337a7364f36f4bb3f2534c2bb95a35b132c/cryptography-46.0.7-cp38-abi3-win32.whl", hash = "sha256:f247c8c1a1fb45e12586afbb436ef21ff1e80670b2861a90353d9b025583d246", size = 3013001, upload-time = "2026-04-08T01:57:34.933Z" }, 431 - { url = "https://files.pythonhosted.org/packages/d2/f1/00ce3bde3ca542d1acd8f8cfa38e446840945aa6363f9b74746394b14127/cryptography-46.0.7-cp38-abi3-win_amd64.whl", hash = "sha256:506c4ff91eff4f82bdac7633318a526b1d1309fc07ca76a3ad182cb5b686d6d3", size = 3472985, upload-time = "2026-04-08T01:57:36.714Z" }, 432 - ] 433 - 434 - [[package]] 435 - name = "cyclopts" 436 - version = "4.10.2" 437 - source = { registry = "https://pypi.org/simple" } 438 - dependencies = [ 439 - { name = "attrs" }, 440 - { name = "docstring-parser" }, 441 - { name = "rich" }, 442 - { name = "rich-rst" }, 443 - ] 444 - sdist = { url = "https://files.pythonhosted.org/packages/66/2c/fced34890f6e5a93a4b7afb2c71e8eee2a0719fb26193a0abf159ecb714d/cyclopts-4.10.2.tar.gz", hash = "sha256:d7b950457ef2563596d56331f80cbbbf86a2772535fb8b315c4f03bc7e6127f1", size = 166664, upload-time = "2026-04-08T23:57:45.805Z" } 445 - wheels = [ 446 - { url = "https://files.pythonhosted.org/packages/b4/bd/05055d8360cef0757d79367157f3b15c0a0715e81e08f86a04018ec045f0/cyclopts-4.10.2-py3-none-any.whl", hash = "sha256:a1f2d6f8f7afac9456b48f75a40b36658778ddc9c6d406b520d017ae32c990fe", size = 204314, upload-time = "2026-04-08T23:57:46.969Z" }, 447 - ] 448 - 449 - [[package]] 450 - name = "distro" 451 - version = "1.9.0" 452 - source = { registry = "https://pypi.org/simple" } 453 - sdist = { url = "https://files.pythonhosted.org/packages/fc/f8/98eea607f65de6527f8a2e8885fc8015d3e6f5775df186e443e0964a11c3/distro-1.9.0.tar.gz", hash = "sha256:2fa77c6fd8940f116ee1d6b94a2f90b13b5ea8d019b98bc8bafdcabcdd9bdbed", size = 60722, upload-time = "2023-12-24T09:54:32.31Z" } 454 - wheels = [ 455 - { url = "https://files.pythonhosted.org/packages/12/b3/231ffd4ab1fc9d679809f356cebee130ac7daa00d6d6f3206dd4fd137e9e/distro-1.9.0-py3-none-any.whl", hash = "sha256:7bffd925d65168f85027d8da9af6bddab658135b840670a223589bc0c8ef02b2", size = 20277, upload-time = "2023-12-24T09:54:30.421Z" }, 456 - ] 457 - 458 - [[package]] 459 - name = "dnspython" 460 - version = "2.8.0" 461 - source = { registry = "https://pypi.org/simple" } 462 - sdist = { url = "https://files.pythonhosted.org/packages/8c/8b/57666417c0f90f08bcafa776861060426765fdb422eb10212086fb811d26/dnspython-2.8.0.tar.gz", hash = "sha256:181d3c6996452cb1189c4046c61599b84a5a86e099562ffde77d26984ff26d0f", size = 368251, upload-time = "2025-09-07T18:58:00.022Z" } 463 - wheels = [ 464 - { url = "https://files.pythonhosted.org/packages/ba/5a/18ad964b0086c6e62e2e7500f7edc89e3faa45033c71c1893d34eed2b2de/dnspython-2.8.0-py3-none-any.whl", hash = "sha256:01d9bbc4a2d76bf0db7c1f729812ded6d912bd318d3b1cf81d30c0f845dbf3af", size = 331094, upload-time = "2025-09-07T18:57:58.071Z" }, 465 - ] 466 - 467 - [[package]] 468 - name = "docstring-parser" 469 - version = "0.17.0" 470 - source = { registry = "https://pypi.org/simple" } 471 - sdist = { url = "https://files.pythonhosted.org/packages/b2/9d/c3b43da9515bd270df0f80548d9944e389870713cc1fe2b8fb35fe2bcefd/docstring_parser-0.17.0.tar.gz", hash = "sha256:583de4a309722b3315439bb31d64ba3eebada841f2e2cee23b99df001434c912", size = 27442, upload-time = "2025-07-21T07:35:01.868Z" } 472 - wheels = [ 473 - { url = "https://files.pythonhosted.org/packages/55/e2/2537ebcff11c1ee1ff17d8d0b6f4db75873e3b0fb32c2d4a2ee31ecb310a/docstring_parser-0.17.0-py3-none-any.whl", hash = "sha256:cf2569abd23dce8099b300f9b4fa8191e9582dda731fd533daf54c4551658708", size = 36896, upload-time = "2025-07-21T07:35:00.684Z" }, 474 - ] 475 - 476 - [[package]] 477 - name = "docutils" 478 - version = "0.22.4" 479 - source = { registry = "https://pypi.org/simple" } 480 - sdist = { url = "https://files.pythonhosted.org/packages/ae/b6/03bb70946330e88ffec97aefd3ea75ba575cb2e762061e0e62a213befee8/docutils-0.22.4.tar.gz", hash = "sha256:4db53b1fde9abecbb74d91230d32ab626d94f6badfc575d6db9194a49df29968", size = 2291750, upload-time = "2025-12-18T19:00:26.443Z" } 481 - wheels = [ 482 - { url = "https://files.pythonhosted.org/packages/02/10/5da547df7a391dcde17f59520a231527b8571e6f46fc8efb02ccb370ab12/docutils-0.22.4-py3-none-any.whl", hash = "sha256:d0013f540772d1420576855455d050a2180186c91c15779301ac2ccb3eeb68de", size = 633196, upload-time = "2025-12-18T19:00:18.077Z" }, 483 - ] 484 - 485 - [[package]] 486 - name = "email-validator" 487 - version = "2.3.0" 488 - source = { registry = "https://pypi.org/simple" } 489 - dependencies = [ 490 - { name = "dnspython" }, 491 - { name = "idna" }, 492 - ] 493 - sdist = { url = "https://files.pythonhosted.org/packages/f5/22/900cb125c76b7aaa450ce02fd727f452243f2e91a61af068b40adba60ea9/email_validator-2.3.0.tar.gz", hash = "sha256:9fc05c37f2f6cf439ff414f8fc46d917929974a82244c20eb10231ba60c54426", size = 51238, upload-time = "2025-08-26T13:09:06.831Z" } 494 - wheels = [ 495 - { url = "https://files.pythonhosted.org/packages/de/15/545e2b6cf2e3be84bc1ed85613edd75b8aea69807a71c26f4ca6a9258e82/email_validator-2.3.0-py3-none-any.whl", hash = "sha256:80f13f623413e6b197ae73bb10bf4eb0908faf509ad8362c5edeb0be7fd450b4", size = 35604, upload-time = "2025-08-26T13:09:05.858Z" }, 496 - ] 497 - 498 - [[package]] 499 - name = "eval-type-backport" 500 - version = "0.3.1" 501 - source = { registry = "https://pypi.org/simple" } 502 - sdist = { url = "https://files.pythonhosted.org/packages/fb/a3/cafafb4558fd638aadfe4121dc6cefb8d743368c085acb2f521df0f3d9d7/eval_type_backport-0.3.1.tar.gz", hash = "sha256:57e993f7b5b69d271e37482e62f74e76a0276c82490cf8e4f0dffeb6b332d5ed", size = 9445, upload-time = "2025-12-02T11:51:42.987Z" } 503 - wheels = [ 504 - { url = "https://files.pythonhosted.org/packages/cf/22/fdc2e30d43ff853720042fa15baa3e6122722be1a7950a98233ebb55cd71/eval_type_backport-0.3.1-py3-none-any.whl", hash = "sha256:279ab641905e9f11129f56a8a78f493518515b83402b860f6f06dd7c011fdfa8", size = 6063, upload-time = "2025-12-02T11:51:41.665Z" }, 505 - ] 506 - 507 - [[package]] 508 - name = "exceptiongroup" 509 - version = "1.3.1" 510 - source = { registry = "https://pypi.org/simple" } 511 - sdist = { url = "https://files.pythonhosted.org/packages/50/79/66800aadf48771f6b62f7eb014e352e5d06856655206165d775e675a02c9/exceptiongroup-1.3.1.tar.gz", hash = "sha256:8b412432c6055b0b7d14c310000ae93352ed6754f70fa8f7c34141f91c4e3219", size = 30371, upload-time = "2025-11-21T23:01:54.787Z" } 512 - wheels = [ 513 - { url = "https://files.pythonhosted.org/packages/8a/0e/97c33bf5009bdbac74fd2beace167cab3f978feb69cc36f1ef79360d6c4e/exceptiongroup-1.3.1-py3-none-any.whl", hash = "sha256:a7a39a3bd276781e98394987d3a5701d0c4edffb633bb7a5144577f82c773598", size = 16740, upload-time = "2025-11-21T23:01:53.443Z" }, 514 - ] 515 - 516 - [[package]] 517 - name = "executing" 518 - version = "2.2.1" 519 - source = { registry = "https://pypi.org/simple" } 520 - sdist = { url = "https://files.pythonhosted.org/packages/cc/28/c14e053b6762b1044f34a13aab6859bbf40456d37d23aa286ac24cfd9a5d/executing-2.2.1.tar.gz", hash = "sha256:3632cc370565f6648cc328b32435bd120a1e4ebb20c77e3fdde9a13cd1e533c4", size = 1129488, upload-time = "2025-09-01T09:48:10.866Z" } 521 - wheels = [ 522 - { url = "https://files.pythonhosted.org/packages/c1/ea/53f2148663b321f21b5a606bd5f191517cf40b7072c0497d3c92c4a13b1e/executing-2.2.1-py2.py3-none-any.whl", hash = "sha256:760643d3452b4d777d295bb167ccc74c64a81df23fb5e08eff250c425a4b2017", size = 28317, upload-time = "2025-09-01T09:48:08.5Z" }, 523 - ] 524 - 525 - [[package]] 526 - name = "fastapi" 527 - version = "0.135.3" 528 - source = { registry = "https://pypi.org/simple" } 529 - dependencies = [ 530 - { name = "annotated-doc" }, 531 - { name = "pydantic" }, 532 - { name = "starlette" }, 533 - { name = "typing-extensions" }, 534 - { name = "typing-inspection" }, 535 - ] 536 - sdist = { url = "https://files.pythonhosted.org/packages/f7/e6/7adb4c5fa231e82c35b8f5741a9f2d055f520c29af5546fd70d3e8e1cd2e/fastapi-0.135.3.tar.gz", hash = "sha256:bd6d7caf1a2bdd8d676843cdcd2287729572a1ef524fc4d65c17ae002a1be654", size = 396524, upload-time = "2026-04-01T16:23:58.188Z" } 537 - wheels = [ 538 - { url = "https://files.pythonhosted.org/packages/84/a4/5caa2de7f917a04ada20018eccf60d6cc6145b0199d55ca3711b0fc08312/fastapi-0.135.3-py3-none-any.whl", hash = "sha256:9b0f590c813acd13d0ab43dd8494138eb58e484bfac405db1f3187cfc5810d98", size = 117734, upload-time = "2026-04-01T16:23:59.328Z" }, 539 - ] 540 - 541 - [[package]] 542 - name = "fastavro" 543 - version = "1.12.1" 544 - source = { registry = "https://pypi.org/simple" } 545 - sdist = { url = "https://files.pythonhosted.org/packages/65/8b/fa2d3287fd2267be6261d0177c6809a7fa12c5600ddb33490c8dc29e77b2/fastavro-1.12.1.tar.gz", hash = "sha256:2f285be49e45bc047ab2f6bed040bb349da85db3f3c87880e4b92595ea093b2b", size = 1025661, upload-time = "2025-10-10T15:40:55.41Z" } 546 - wheels = [ 547 - { url = "https://files.pythonhosted.org/packages/58/54/b7b4a0c3fb5fcba38128542da1b26c4e6d69933c923f493548bdfd63ab6a/fastavro-1.12.1-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:9445da127751ba65975d8e4bdabf36bfcfdad70fc35b2d988e3950cce0ec0e7c", size = 1001377, upload-time = "2025-10-10T15:41:59.241Z" }, 548 - { url = "https://files.pythonhosted.org/packages/1e/4f/0e589089c7df0d8f57d7e5293fdc34efec9a3b758a0d4d0c99a7937e2492/fastavro-1.12.1-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ed924233272719b5d5a6a0b4d80ef3345fc7e84fc7a382b6232192a9112d38a6", size = 3320401, upload-time = "2025-10-10T15:42:01.682Z" }, 549 - { url = "https://files.pythonhosted.org/packages/f9/19/260110d56194ae29d7e423a336fccea8bcd103196d00f0b364b732bdb84e/fastavro-1.12.1-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:3616e2f0e1c9265e92954fa099db79c6e7817356d3ff34f4bcc92699ae99697c", size = 3350894, upload-time = "2025-10-10T15:42:04.073Z" }, 550 - { url = "https://files.pythonhosted.org/packages/d0/96/58b0411e8be9694d5972bee3167d6c1fd1fdfdf7ce253c1a19a327208f4f/fastavro-1.12.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:cb0337b42fd3c047fcf0e9b7597bd6ad25868de719f29da81eabb6343f08d399", size = 3229644, upload-time = "2025-10-10T15:42:06.221Z" }, 551 - { url = "https://files.pythonhosted.org/packages/5b/db/38660660eac82c30471d9101f45b3acfdcbadfe42d8f7cdb129459a45050/fastavro-1.12.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:64961ab15b74b7c168717bbece5660e0f3d457837c3cc9d9145181d011199fa7", size = 3329704, upload-time = "2025-10-10T15:42:08.384Z" }, 552 - { url = "https://files.pythonhosted.org/packages/9d/a9/1672910f458ecb30b596c9e59e41b7c00309b602a0494341451e92e62747/fastavro-1.12.1-cp314-cp314-win_amd64.whl", hash = "sha256:792356d320f6e757e89f7ac9c22f481e546c886454a6709247f43c0dd7058004", size = 452911, upload-time = "2025-10-10T15:42:09.795Z" }, 553 - { url = "https://files.pythonhosted.org/packages/dc/8d/2e15d0938ded1891b33eff252e8500605508b799c2e57188a933f0bd744c/fastavro-1.12.1-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:120aaf82ac19d60a1016afe410935fe94728752d9c2d684e267e5b7f0e70f6d9", size = 3541999, upload-time = "2025-10-10T15:42:11.794Z" }, 554 - { url = "https://files.pythonhosted.org/packages/a7/1c/6dfd082a205be4510543221b734b1191299e6a1810c452b6bc76dfa6968e/fastavro-1.12.1-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b6a3462934b20a74f9ece1daa49c2e4e749bd9a35fa2657b53bf62898fba80f5", size = 3433972, upload-time = "2025-10-10T15:42:14.485Z" }, 555 - { url = "https://files.pythonhosted.org/packages/24/90/9de694625a1a4b727b1ad0958d220cab25a9b6cf7f16a5c7faa9ea7b2261/fastavro-1.12.1-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:1f81011d54dd47b12437b51dd93a70a9aa17b61307abf26542fc3c13efbc6c51", size = 3368752, upload-time = "2025-10-10T15:42:16.618Z" }, 556 - { url = "https://files.pythonhosted.org/packages/fa/93/b44f67589e4d439913dab6720f7e3507b0fa8b8e56d06f6fc875ced26afb/fastavro-1.12.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:43ded16b3f4a9f1a42f5970c2aa618acb23ea59c4fcaa06680bdf470b255e5a8", size = 3386636, upload-time = "2025-10-10T15:42:18.974Z" }, 557 - ] 558 - 559 - [[package]] 560 - name = "fastmcp" 561 - version = "3.2.4" 562 - source = { registry = "https://pypi.org/simple" } 563 - dependencies = [ 564 - { name = "authlib" }, 565 - { name = "cyclopts" }, 566 - { name = "exceptiongroup" }, 567 - { name = "griffelib" }, 568 - { name = "httpx" }, 569 - { name = "jsonref" }, 570 - { name = "jsonschema-path" }, 571 - { name = "mcp" }, 572 - { name = "openapi-pydantic" }, 573 - { name = "opentelemetry-api" }, 574 - { name = "packaging" }, 575 - { name = "platformdirs" }, 576 - { name = "py-key-value-aio", extra = ["filetree", "keyring", "memory"] }, 577 - { name = "pydantic", extra = ["email"] }, 578 - { name = "pyperclip" }, 579 - { name = "python-dotenv" }, 580 - { name = "pyyaml" }, 581 - { name = "rich" }, 582 - { name = "uncalled-for" }, 583 - { name = "uvicorn" }, 584 - { name = "watchfiles" }, 585 - { name = "websockets" }, 586 - ] 587 - sdist = { url = "https://files.pythonhosted.org/packages/9c/13/29544fbc6dfe45ea38046af0067311e0bad7acc7d1f2ad38bb08f2409fe2/fastmcp-3.2.4.tar.gz", hash = "sha256:083ecb75b44a4169e7fc0f632f94b781bdb0ff877c6b35b9877cbb566fd4d4d1", size = 28746127, upload-time = "2026-04-14T01:42:24.174Z" } 588 - wheels = [ 589 - { url = "https://files.pythonhosted.org/packages/cf/76/b310d52fa0e30d39bd937eb58ec2c1f1ea1b5f519f0575e9dd9612f01deb/fastmcp-3.2.4-py3-none-any.whl", hash = "sha256:e6c9c429171041455e47ab94bb3f83c4657622a0ec28922f6940053959bd58a9", size = 728599, upload-time = "2026-04-14T01:42:26.85Z" }, 590 - ] 591 - 592 - [[package]] 593 - name = "filelock" 594 - version = "3.25.2" 595 - source = { registry = "https://pypi.org/simple" } 596 - sdist = { url = "https://files.pythonhosted.org/packages/94/b8/00651a0f559862f3bb7d6f7477b192afe3f583cc5e26403b44e59a55ab34/filelock-3.25.2.tar.gz", hash = "sha256:b64ece2b38f4ca29dd3e810287aa8c48182bbecd1ae6e9ae126c9b35f1382694", size = 40480, upload-time = "2026-03-11T20:45:38.487Z" } 597 - wheels = [ 598 - { url = "https://files.pythonhosted.org/packages/a4/a5/842ae8f0c08b61d6484b52f99a03510a3a72d23141942d216ebe81fefbce/filelock-3.25.2-py3-none-any.whl", hash = "sha256:ca8afb0da15f229774c9ad1b455ed96e85a81373065fb10446672f64444ddf70", size = 26759, upload-time = "2026-03-11T20:45:37.437Z" }, 599 - ] 600 - 601 - [[package]] 602 - name = "frozenlist" 603 - version = "1.8.0" 604 - source = { registry = "https://pypi.org/simple" } 605 - sdist = { url = "https://files.pythonhosted.org/packages/2d/f5/c831fac6cc817d26fd54c7eaccd04ef7e0288806943f7cc5bbf69f3ac1f0/frozenlist-1.8.0.tar.gz", hash = "sha256:3ede829ed8d842f6cd48fc7081d7a41001a56f1f38603f9d49bf3020d59a31ad", size = 45875, upload-time = "2025-10-06T05:38:17.865Z" } 606 - wheels = [ 607 - { url = "https://files.pythonhosted.org/packages/f1/c8/85da824b7e7b9b6e7f7705b2ecaf9591ba6f79c1177f324c2735e41d36a2/frozenlist-1.8.0-cp314-cp314-macosx_10_13_universal2.whl", hash = "sha256:cee686f1f4cadeb2136007ddedd0aaf928ab95216e7691c63e50a8ec066336d0", size = 86127, upload-time = "2025-10-06T05:37:08.438Z" }, 608 - { url = "https://files.pythonhosted.org/packages/8e/e8/a1185e236ec66c20afd72399522f142c3724c785789255202d27ae992818/frozenlist-1.8.0-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:119fb2a1bd47307e899c2fac7f28e85b9a543864df47aa7ec9d3c1b4545f096f", size = 49698, upload-time = "2025-10-06T05:37:09.48Z" }, 609 - { url = "https://files.pythonhosted.org/packages/a1/93/72b1736d68f03fda5fdf0f2180fb6caaae3894f1b854d006ac61ecc727ee/frozenlist-1.8.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:4970ece02dbc8c3a92fcc5228e36a3e933a01a999f7094ff7c23fbd2beeaa67c", size = 49749, upload-time = "2025-10-06T05:37:10.569Z" }, 610 - { url = "https://files.pythonhosted.org/packages/a7/b2/fabede9fafd976b991e9f1b9c8c873ed86f202889b864756f240ce6dd855/frozenlist-1.8.0-cp314-cp314-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:cba69cb73723c3f329622e34bdbf5ce1f80c21c290ff04256cff1cd3c2036ed2", size = 231298, upload-time = "2025-10-06T05:37:11.993Z" }, 611 - { url = "https://files.pythonhosted.org/packages/3a/3b/d9b1e0b0eed36e70477ffb8360c49c85c8ca8ef9700a4e6711f39a6e8b45/frozenlist-1.8.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:778a11b15673f6f1df23d9586f83c4846c471a8af693a22e066508b77d201ec8", size = 232015, upload-time = "2025-10-06T05:37:13.194Z" }, 612 - { url = "https://files.pythonhosted.org/packages/dc/94/be719d2766c1138148564a3960fc2c06eb688da592bdc25adcf856101be7/frozenlist-1.8.0-cp314-cp314-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:0325024fe97f94c41c08872db482cf8ac4800d80e79222c6b0b7b162d5b13686", size = 225038, upload-time = "2025-10-06T05:37:14.577Z" }, 613 - { url = "https://files.pythonhosted.org/packages/e4/09/6712b6c5465f083f52f50cf74167b92d4ea2f50e46a9eea0523d658454ae/frozenlist-1.8.0-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:97260ff46b207a82a7567b581ab4190bd4dfa09f4db8a8b49d1a958f6aa4940e", size = 240130, upload-time = "2025-10-06T05:37:15.781Z" }, 614 - { url = "https://files.pythonhosted.org/packages/f8/d4/cd065cdcf21550b54f3ce6a22e143ac9e4836ca42a0de1022da8498eac89/frozenlist-1.8.0-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:54b2077180eb7f83dd52c40b2750d0a9f175e06a42e3213ce047219de902717a", size = 242845, upload-time = "2025-10-06T05:37:17.037Z" }, 615 - { url = "https://files.pythonhosted.org/packages/62/c3/f57a5c8c70cd1ead3d5d5f776f89d33110b1addae0ab010ad774d9a44fb9/frozenlist-1.8.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:2f05983daecab868a31e1da44462873306d3cbfd76d1f0b5b69c473d21dbb128", size = 229131, upload-time = "2025-10-06T05:37:18.221Z" }, 616 - { url = "https://files.pythonhosted.org/packages/6c/52/232476fe9cb64f0742f3fde2b7d26c1dac18b6d62071c74d4ded55e0ef94/frozenlist-1.8.0-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:33f48f51a446114bc5d251fb2954ab0164d5be02ad3382abcbfe07e2531d650f", size = 240542, upload-time = "2025-10-06T05:37:19.771Z" }, 617 - { url = "https://files.pythonhosted.org/packages/5f/85/07bf3f5d0fb5414aee5f47d33c6f5c77bfe49aac680bfece33d4fdf6a246/frozenlist-1.8.0-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:154e55ec0655291b5dd1b8731c637ecdb50975a2ae70c606d100750a540082f7", size = 237308, upload-time = "2025-10-06T05:37:20.969Z" }, 618 - { url = "https://files.pythonhosted.org/packages/11/99/ae3a33d5befd41ac0ca2cc7fd3aa707c9c324de2e89db0e0f45db9a64c26/frozenlist-1.8.0-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:4314debad13beb564b708b4a496020e5306c7333fa9a3ab90374169a20ffab30", size = 238210, upload-time = "2025-10-06T05:37:22.252Z" }, 619 - { url = "https://files.pythonhosted.org/packages/b2/60/b1d2da22f4970e7a155f0adde9b1435712ece01b3cd45ba63702aea33938/frozenlist-1.8.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:073f8bf8becba60aa931eb3bc420b217bb7d5b8f4750e6f8b3be7f3da85d38b7", size = 231972, upload-time = "2025-10-06T05:37:23.5Z" }, 620 - { url = "https://files.pythonhosted.org/packages/3f/ab/945b2f32de889993b9c9133216c068b7fcf257d8595a0ac420ac8677cab0/frozenlist-1.8.0-cp314-cp314-win32.whl", hash = "sha256:bac9c42ba2ac65ddc115d930c78d24ab8d4f465fd3fc473cdedfccadb9429806", size = 40536, upload-time = "2025-10-06T05:37:25.581Z" }, 621 - { url = "https://files.pythonhosted.org/packages/59/ad/9caa9b9c836d9ad6f067157a531ac48b7d36499f5036d4141ce78c230b1b/frozenlist-1.8.0-cp314-cp314-win_amd64.whl", hash = "sha256:3e0761f4d1a44f1d1a47996511752cf3dcec5bbdd9cc2b4fe595caf97754b7a0", size = 44330, upload-time = "2025-10-06T05:37:26.928Z" }, 622 - { url = "https://files.pythonhosted.org/packages/82/13/e6950121764f2676f43534c555249f57030150260aee9dcf7d64efda11dd/frozenlist-1.8.0-cp314-cp314-win_arm64.whl", hash = "sha256:d1eaff1d00c7751b7c6662e9c5ba6eb2c17a2306ba5e2a37f24ddf3cc953402b", size = 40627, upload-time = "2025-10-06T05:37:28.075Z" }, 623 - { url = "https://files.pythonhosted.org/packages/c0/c7/43200656ecc4e02d3f8bc248df68256cd9572b3f0017f0a0c4e93440ae23/frozenlist-1.8.0-cp314-cp314t-macosx_10_13_universal2.whl", hash = "sha256:d3bb933317c52d7ea5004a1c442eef86f426886fba134ef8cf4226ea6ee1821d", size = 89238, upload-time = "2025-10-06T05:37:29.373Z" }, 624 - { url = "https://files.pythonhosted.org/packages/d1/29/55c5f0689b9c0fb765055629f472c0de484dcaf0acee2f7707266ae3583c/frozenlist-1.8.0-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:8009897cdef112072f93a0efdce29cd819e717fd2f649ee3016efd3cd885a7ed", size = 50738, upload-time = "2025-10-06T05:37:30.792Z" }, 625 - { url = "https://files.pythonhosted.org/packages/ba/7d/b7282a445956506fa11da8c2db7d276adcbf2b17d8bb8407a47685263f90/frozenlist-1.8.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:2c5dcbbc55383e5883246d11fd179782a9d07a986c40f49abe89ddf865913930", size = 51739, upload-time = "2025-10-06T05:37:32.127Z" }, 626 - { url = "https://files.pythonhosted.org/packages/62/1c/3d8622e60d0b767a5510d1d3cf21065b9db874696a51ea6d7a43180a259c/frozenlist-1.8.0-cp314-cp314t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:39ecbc32f1390387d2aa4f5a995e465e9e2f79ba3adcac92d68e3e0afae6657c", size = 284186, upload-time = "2025-10-06T05:37:33.21Z" }, 627 - { url = "https://files.pythonhosted.org/packages/2d/14/aa36d5f85a89679a85a1d44cd7a6657e0b1c75f61e7cad987b203d2daca8/frozenlist-1.8.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:92db2bf818d5cc8d9c1f1fc56b897662e24ea5adb36ad1f1d82875bd64e03c24", size = 292196, upload-time = "2025-10-06T05:37:36.107Z" }, 628 - { url = "https://files.pythonhosted.org/packages/05/23/6bde59eb55abd407d34f77d39a5126fb7b4f109a3f611d3929f14b700c66/frozenlist-1.8.0-cp314-cp314t-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:2dc43a022e555de94c3b68a4ef0b11c4f747d12c024a520c7101709a2144fb37", size = 273830, upload-time = "2025-10-06T05:37:37.663Z" }, 629 - { url = "https://files.pythonhosted.org/packages/d2/3f/22cff331bfad7a8afa616289000ba793347fcd7bc275f3b28ecea2a27909/frozenlist-1.8.0-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:cb89a7f2de3602cfed448095bab3f178399646ab7c61454315089787df07733a", size = 294289, upload-time = "2025-10-06T05:37:39.261Z" }, 630 - { url = "https://files.pythonhosted.org/packages/a4/89/5b057c799de4838b6c69aa82b79705f2027615e01be996d2486a69ca99c4/frozenlist-1.8.0-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:33139dc858c580ea50e7e60a1b0ea003efa1fd42e6ec7fdbad78fff65fad2fd2", size = 300318, upload-time = "2025-10-06T05:37:43.213Z" }, 631 - { url = "https://files.pythonhosted.org/packages/30/de/2c22ab3eb2a8af6d69dc799e48455813bab3690c760de58e1bf43b36da3e/frozenlist-1.8.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:168c0969a329b416119507ba30b9ea13688fafffac1b7822802537569a1cb0ef", size = 282814, upload-time = "2025-10-06T05:37:45.337Z" }, 632 - { url = "https://files.pythonhosted.org/packages/59/f7/970141a6a8dbd7f556d94977858cfb36fa9b66e0892c6dd780d2219d8cd8/frozenlist-1.8.0-cp314-cp314t-musllinux_1_2_armv7l.whl", hash = "sha256:28bd570e8e189d7f7b001966435f9dac6718324b5be2990ac496cf1ea9ddb7fe", size = 291762, upload-time = "2025-10-06T05:37:46.657Z" }, 633 - { url = "https://files.pythonhosted.org/packages/c1/15/ca1adae83a719f82df9116d66f5bb28bb95557b3951903d39135620ef157/frozenlist-1.8.0-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:b2a095d45c5d46e5e79ba1e5b9cb787f541a8dee0433836cea4b96a2c439dcd8", size = 289470, upload-time = "2025-10-06T05:37:47.946Z" }, 634 - { url = "https://files.pythonhosted.org/packages/ac/83/dca6dc53bf657d371fbc88ddeb21b79891e747189c5de990b9dfff2ccba1/frozenlist-1.8.0-cp314-cp314t-musllinux_1_2_s390x.whl", hash = "sha256:eab8145831a0d56ec9c4139b6c3e594c7a83c2c8be25d5bcf2d86136a532287a", size = 289042, upload-time = "2025-10-06T05:37:49.499Z" }, 635 - { url = "https://files.pythonhosted.org/packages/96/52/abddd34ca99be142f354398700536c5bd315880ed0a213812bc491cff5e4/frozenlist-1.8.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:974b28cf63cc99dfb2188d8d222bc6843656188164848c4f679e63dae4b0708e", size = 283148, upload-time = "2025-10-06T05:37:50.745Z" }, 636 - { url = "https://files.pythonhosted.org/packages/af/d3/76bd4ed4317e7119c2b7f57c3f6934aba26d277acc6309f873341640e21f/frozenlist-1.8.0-cp314-cp314t-win32.whl", hash = "sha256:342c97bf697ac5480c0a7ec73cd700ecfa5a8a40ac923bd035484616efecc2df", size = 44676, upload-time = "2025-10-06T05:37:52.222Z" }, 637 - { url = "https://files.pythonhosted.org/packages/89/76/c615883b7b521ead2944bb3480398cbb07e12b7b4e4d073d3752eb721558/frozenlist-1.8.0-cp314-cp314t-win_amd64.whl", hash = "sha256:06be8f67f39c8b1dc671f5d83aaefd3358ae5cdcf8314552c57e7ed3e6475bdd", size = 49451, upload-time = "2025-10-06T05:37:53.425Z" }, 638 - { url = "https://files.pythonhosted.org/packages/e0/a3/5982da14e113d07b325230f95060e2169f5311b1017ea8af2a29b374c289/frozenlist-1.8.0-cp314-cp314t-win_arm64.whl", hash = "sha256:102e6314ca4da683dca92e3b1355490fed5f313b768500084fbe6371fddfdb79", size = 42507, upload-time = "2025-10-06T05:37:54.513Z" }, 639 - { url = "https://files.pythonhosted.org/packages/9a/9a/e35b4a917281c0b8419d4207f4334c8e8c5dbf4f3f5f9ada73958d937dcc/frozenlist-1.8.0-py3-none-any.whl", hash = "sha256:0c18a16eab41e82c295618a77502e17b195883241c563b00f0aa5106fc4eaa0d", size = 13409, upload-time = "2025-10-06T05:38:16.721Z" }, 640 - ] 641 - 642 - [[package]] 643 - name = "fsspec" 644 - version = "2026.3.0" 645 - source = { registry = "https://pypi.org/simple" } 646 - sdist = { url = "https://files.pythonhosted.org/packages/e1/cf/b50ddf667c15276a9ab15a70ef5f257564de271957933ffea49d2cdbcdfb/fsspec-2026.3.0.tar.gz", hash = "sha256:1ee6a0e28677557f8c2f994e3eea77db6392b4de9cd1f5d7a9e87a0ae9d01b41", size = 313547, upload-time = "2026-03-27T19:11:14.892Z" } 647 - wheels = [ 648 - { url = "https://files.pythonhosted.org/packages/d5/1f/5f4a3cd9e4440e9d9bc78ad0a91a1c8d46b4d429d5239ebe6793c9fe5c41/fsspec-2026.3.0-py3-none-any.whl", hash = "sha256:d2ceafaad1b3457968ed14efa28798162f1638dbb5d2a6868a2db002a5ee39a4", size = 202595, upload-time = "2026-03-27T19:11:13.595Z" }, 649 - ] 650 - 651 - [[package]] 652 - name = "genai-prices" 653 - version = "0.0.56" 654 - source = { registry = "https://pypi.org/simple" } 655 - dependencies = [ 656 - { name = "httpx" }, 657 - { name = "pydantic" }, 658 - ] 659 - sdist = { url = "https://files.pythonhosted.org/packages/44/6b/94b3018a672c7775edfb485f0fed8f6068fba75e49b067e8a1ac5eb96764/genai_prices-0.0.56.tar.gz", hash = "sha256:ac24b16a84d0ab97539bfa48dfa4649689de8e3ce71c12ebacef29efb1998045", size = 65872, upload-time = "2026-03-20T20:33:00.732Z" } 660 - wheels = [ 661 - { url = "https://files.pythonhosted.org/packages/a3/f6/8ef7e4c286deb2709d11ca96a5237caae3ef4876ab3c48095856cfd2df30/genai_prices-0.0.56-py3-none-any.whl", hash = "sha256:dbe86be8f3f556bed1b72209ed36851fec8b01793b3b220f42921a4e7da945f6", size = 68966, upload-time = "2026-03-20T20:33:02.555Z" }, 662 - ] 663 - 664 - [[package]] 665 - name = "google-auth" 666 - version = "2.49.2" 667 - source = { registry = "https://pypi.org/simple" } 668 - dependencies = [ 669 - { name = "cryptography" }, 670 - { name = "pyasn1-modules" }, 671 - ] 672 - sdist = { url = "https://files.pythonhosted.org/packages/c6/fc/e925290a1ad95c975c459e2df070fac2b90954e13a0370ac505dff78cb99/google_auth-2.49.2.tar.gz", hash = "sha256:c1ae38500e73065dcae57355adb6278cf8b5c8e391994ae9cbadbcb9631ab409", size = 333958, upload-time = "2026-04-10T00:41:21.888Z" } 673 - wheels = [ 674 - { url = "https://files.pythonhosted.org/packages/73/76/d241a5c927433420507215df6cac1b1fa4ac0ba7a794df42a84326c68da8/google_auth-2.49.2-py3-none-any.whl", hash = "sha256:c2720924dfc82dedb962c9f52cabb2ab16714fd0a6a707e40561d217574ed6d5", size = 240638, upload-time = "2026-04-10T00:41:14.501Z" }, 675 - ] 676 - 677 - [package.optional-dependencies] 678 - requests = [ 679 - { name = "requests" }, 680 - ] 681 - 682 - [[package]] 683 - name = "google-genai" 684 - version = "1.73.0" 685 - source = { registry = "https://pypi.org/simple" } 686 - dependencies = [ 687 - { name = "anyio" }, 688 - { name = "distro" }, 689 - { name = "google-auth", extra = ["requests"] }, 690 - { name = "httpx" }, 691 - { name = "pydantic" }, 692 - { name = "requests" }, 693 - { name = "sniffio" }, 694 - { name = "tenacity" }, 695 - { name = "typing-extensions" }, 696 - { name = "websockets" }, 697 - ] 698 - sdist = { url = "https://files.pythonhosted.org/packages/99/5f/b293b1a78a547b0dd061642a3f6087f0a52c1b723eafa58f94ccdc3e0d2a/google_genai-1.73.0.tar.gz", hash = "sha256:569395b2c225e12bcd8758b8affe1af480e0a1b1c71d652d38c705677057e05f", size = 530812, upload-time = "2026-04-13T20:40:02.642Z" } 699 - wheels = [ 700 - { url = "https://files.pythonhosted.org/packages/5a/73/fb36ced456688c9b95a8ab49a1f408f5b3e69a589788f3eb25016002dd7a/google_genai-1.73.0-py3-none-any.whl", hash = "sha256:dfb0214b834bf977e3841de512cfb651d2fe76309f85064b80c2bc11da99d76b", size = 786072, upload-time = "2026-04-13T20:40:00.365Z" }, 701 - ] 702 - 703 - [[package]] 704 - name = "googleapis-common-protos" 705 - version = "1.74.0" 706 - source = { registry = "https://pypi.org/simple" } 707 - dependencies = [ 708 - { name = "protobuf" }, 709 - ] 710 - sdist = { url = "https://files.pythonhosted.org/packages/20/18/a746c8344152d368a5aac738d4c857012f2c5d1fd2eac7e17b647a7861bd/googleapis_common_protos-1.74.0.tar.gz", hash = "sha256:57971e4eeeba6aad1163c1f0fc88543f965bb49129b8bb55b2b7b26ecab084f1", size = 151254, upload-time = "2026-04-02T21:23:26.679Z" } 711 - wheels = [ 712 - { url = "https://files.pythonhosted.org/packages/b6/b0/be5d3329badb9230b765de6eea66b73abd5944bdeb5afb3562ddcd80ae84/googleapis_common_protos-1.74.0-py3-none-any.whl", hash = "sha256:702216f78610bb510e3f12ac3cafd281b7ac45cc5d86e90ad87e4d301a3426b5", size = 300743, upload-time = "2026-04-02T21:22:49.108Z" }, 713 - ] 714 - 715 - [[package]] 716 - name = "griffelib" 717 - version = "2.0.2" 718 - source = { registry = "https://pypi.org/simple" } 719 - sdist = { url = "https://files.pythonhosted.org/packages/9d/82/74f4a3310cdabfbb10da554c3a672847f1ed33c6f61dd472681ce7f1fe67/griffelib-2.0.2.tar.gz", hash = "sha256:3cf20b3bc470e83763ffbf236e0076b1211bac1bc67de13daf494640f2de707e", size = 166461, upload-time = "2026-03-27T11:34:51.091Z" } 720 - wheels = [ 721 - { url = "https://files.pythonhosted.org/packages/11/8c/c9138d881c79aa0ea9ed83cbd58d5ca75624378b38cee225dcf5c42cc91f/griffelib-2.0.2-py3-none-any.whl", hash = "sha256:925c857658fb1ba40c0772c37acbc2ab650bd794d9c1b9726922e36ea4117ea1", size = 142357, upload-time = "2026-03-27T11:34:46.275Z" }, 722 - ] 723 - 724 - [[package]] 725 - name = "groq" 726 - version = "1.1.2" 727 - source = { registry = "https://pypi.org/simple" } 728 - dependencies = [ 729 - { name = "anyio" }, 730 - { name = "distro" }, 731 - { name = "httpx" }, 732 - { name = "pydantic" }, 733 - { name = "sniffio" }, 734 - { name = "typing-extensions" }, 735 - ] 736 - sdist = { url = "https://files.pythonhosted.org/packages/d3/c7/a2153b639062f59f9bc93a1b5507c0c4a6b654b8a9edbf432ec2f4a62d2d/groq-1.1.2.tar.gz", hash = "sha256:9ec2b5b6a1c4856a8c6c38741353c5ab37472a4e3fded02af783750d849cc988", size = 154033, upload-time = "2026-03-25T23:16:10.313Z" } 737 - wheels = [ 738 - { url = "https://files.pythonhosted.org/packages/34/b0/83e3892a4597a4b8ebf8a662aeaf314765c4c2340516eb1d049b459b24fc/groq-1.1.2-py3-none-any.whl", hash = "sha256:348cb7a674b6aa7105719b533f6fc48fd32b503bc9256924aaed6dc186f778b5", size = 141700, upload-time = "2026-03-25T23:16:08.998Z" }, 739 - ] 740 - 741 - [[package]] 742 - name = "grpcio" 743 - version = "1.80.0" 744 - source = { registry = "https://pypi.org/simple" } 745 - dependencies = [ 746 - { name = "typing-extensions" }, 747 - ] 748 - sdist = { url = "https://files.pythonhosted.org/packages/b7/48/af6173dbca4454f4637a4678b67f52ca7e0c1ed7d5894d89d434fecede05/grpcio-1.80.0.tar.gz", hash = "sha256:29aca15edd0688c22ba01d7cc01cb000d72b2033f4a3c72a81a19b56fd143257", size = 12978905, upload-time = "2026-03-30T08:49:10.502Z" } 749 - wheels = [ 750 - { url = "https://files.pythonhosted.org/packages/c5/6d/e65307ce20f5a09244ba9e9d8476e99fb039de7154f37fb85f26978b59c3/grpcio-1.80.0-cp314-cp314-linux_armv7l.whl", hash = "sha256:3d4147a97c8344d065d01bbf8b6acec2cf86fb0400d40696c8bdad34a64ffc0e", size = 6017376, upload-time = "2026-03-30T08:48:10.005Z" }, 751 - { url = "https://files.pythonhosted.org/packages/69/10/9cef5d9650c72625a699c549940f0abb3c4bfdb5ed45a5ce431f92f31806/grpcio-1.80.0-cp314-cp314-macosx_11_0_universal2.whl", hash = "sha256:d8e11f167935b3eb089ac9038e1a063e6d7dbe995c0bb4a661e614583352e76f", size = 12018133, upload-time = "2026-03-30T08:48:12.927Z" }, 752 - { url = "https://files.pythonhosted.org/packages/04/82/983aabaad82ba26113caceeb9091706a0696b25da004fe3defb5b346e15b/grpcio-1.80.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:f14b618fc30de822681ee986cfdcc2d9327229dc4c98aed16896761cacd468b9", size = 6574748, upload-time = "2026-03-30T08:48:16.386Z" }, 753 - { url = "https://files.pythonhosted.org/packages/07/d7/031666ef155aa0bf399ed7e19439656c38bbd143779ae0861b038ce82abd/grpcio-1.80.0-cp314-cp314-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:4ed39fbdcf9b87370f6e8df4e39ca7b38b3e5e9d1b0013c7b6be9639d6578d14", size = 7277711, upload-time = "2026-03-30T08:48:19.627Z" }, 754 - { url = "https://files.pythonhosted.org/packages/e8/43/f437a78f7f4f1d311804189e8f11fb311a01049b2e08557c1068d470cb2e/grpcio-1.80.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:2dcc70e9f0ba987526e8e8603a610fb4f460e42899e74e7a518bf3c68fe1bf05", size = 6785372, upload-time = "2026-03-30T08:48:22.373Z" }, 755 - { url = "https://files.pythonhosted.org/packages/93/3d/f6558e9c6296cb4227faa5c43c54a34c68d32654b829f53288313d16a86e/grpcio-1.80.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:448c884b668b868562b1bda833c5fce6272d26e1926ec46747cda05741d302c1", size = 7395268, upload-time = "2026-03-30T08:48:25.638Z" }, 756 - { url = "https://files.pythonhosted.org/packages/06/21/0fdd77e84720b08843c371a2efa6f2e19dbebf56adc72df73d891f5506f0/grpcio-1.80.0-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:a1dc80fe55685b4a543555e6eef975303b36c8db1023b1599b094b92aa77965f", size = 8392000, upload-time = "2026-03-30T08:48:28.974Z" }, 757 - { url = "https://files.pythonhosted.org/packages/f5/68/67f4947ed55d2e69f2cc199ab9fd85e0a0034d813bbeef84df6d2ba4d4b7/grpcio-1.80.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:31b9ac4ad1aa28ffee5503821fafd09e4da0a261ce1c1281c6c8da0423c83b6e", size = 7828477, upload-time = "2026-03-30T08:48:32.054Z" }, 758 - { url = "https://files.pythonhosted.org/packages/44/b6/8d4096691b2e385e8271911a0de4f35f0a6c7d05aff7098e296c3de86939/grpcio-1.80.0-cp314-cp314-win32.whl", hash = "sha256:367ce30ba67d05e0592470428f0ec1c31714cab9ef19b8f2e37be1f4c7d32fae", size = 4218563, upload-time = "2026-03-30T08:48:34.538Z" }, 759 - { url = "https://files.pythonhosted.org/packages/e5/8c/bbe6baf2557262834f2070cf668515fa308b2d38a4bbf771f8f7872a7036/grpcio-1.80.0-cp314-cp314-win_amd64.whl", hash = "sha256:3b01e1f5464c583d2f567b2e46ff0d516ef979978f72091fd81f5ab7fa6e2e7f", size = 5019457, upload-time = "2026-03-30T08:48:37.308Z" }, 760 - ] 761 - 762 - [[package]] 763 - name = "h11" 764 - version = "0.16.0" 765 - source = { registry = "https://pypi.org/simple" } 766 - sdist = { url = "https://files.pythonhosted.org/packages/01/ee/02a2c011bdab74c6fb3c75474d40b3052059d95df7e73351460c8588d963/h11-0.16.0.tar.gz", hash = "sha256:4e35b956cf45792e4caa5885e69fba00bdbc6ffafbfa020300e549b208ee5ff1", size = 101250, upload-time = "2025-04-24T03:35:25.427Z" } 767 - wheels = [ 768 - { url = "https://files.pythonhosted.org/packages/04/4b/29cac41a4d98d144bf5f6d33995617b185d14b22401f75ca86f384e87ff1/h11-0.16.0-py3-none-any.whl", hash = "sha256:63cf8bbe7522de3bf65932fda1d9c2772064ffb3dae62d55932da54b31cb6c86", size = 37515, upload-time = "2025-04-24T03:35:24.344Z" }, 769 - ] 770 - 771 - [[package]] 772 - name = "hf-xet" 773 - version = "1.4.3" 774 - source = { registry = "https://pypi.org/simple" } 775 - sdist = { url = "https://files.pythonhosted.org/packages/53/92/ec9ad04d0b5728dca387a45af7bc98fbb0d73b2118759f5f6038b61a57e8/hf_xet-1.4.3.tar.gz", hash = "sha256:8ddedb73c8c08928c793df2f3401ec26f95be7f7e516a7bee2fbb546f6676113", size = 670477, upload-time = "2026-03-31T22:40:07.874Z" } 776 - wheels = [ 777 - { url = "https://files.pythonhosted.org/packages/ec/36/3e8f85ca9fe09b8de2b2e10c63b3b3353d7dda88a0b3d426dffbe7b8313b/hf_xet-1.4.3-cp314-cp314t-macosx_10_12_x86_64.whl", hash = "sha256:5251d5ece3a81815bae9abab41cf7ddb7bcb8f56411bce0827f4a3071c92fdc6", size = 3801019, upload-time = "2026-03-31T22:39:56.651Z" }, 778 - { url = "https://files.pythonhosted.org/packages/b5/9c/defb6cb1de28bccb7bd8d95f6e60f72a3d3fa4cb3d0329c26fb9a488bfe7/hf_xet-1.4.3-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:1feb0f3abeacee143367c326a128a2e2b60868ec12a36c225afb1d6c5a05e6d2", size = 3558746, upload-time = "2026-03-31T22:39:54.766Z" }, 779 - { url = "https://files.pythonhosted.org/packages/c1/bd/8d001191893178ff8e826e46ad5299446e62b93cd164e17b0ffea08832ec/hf_xet-1.4.3-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:8b301fc150290ca90b4fccd079829b84bb4786747584ae08b94b4577d82fb791", size = 4207692, upload-time = "2026-03-31T22:39:46.246Z" }, 780 - { url = "https://files.pythonhosted.org/packages/ce/48/6790b402803250e9936435613d3a78b9aaeee7973439f0918848dde58309/hf_xet-1.4.3-cp314-cp314t-manylinux_2_28_aarch64.whl", hash = "sha256:d972fbe95ddc0d3c0fc49b31a8a69f47db35c1e3699bf316421705741aab6653", size = 3986281, upload-time = "2026-03-31T22:39:44.648Z" }, 781 - { url = "https://files.pythonhosted.org/packages/51/56/ea62552fe53db652a9099eda600b032d75554d0e86c12a73824bfedef88b/hf_xet-1.4.3-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:c5b48db1ee344a805a1b9bd2cda9b6b65fe77ed3787bd6e87ad5521141d317cd", size = 4187414, upload-time = "2026-03-31T22:40:04.951Z" }, 782 - { url = "https://files.pythonhosted.org/packages/7d/f5/bc1456d4638061bea997e6d2db60a1a613d7b200e0755965ec312dc1ef79/hf_xet-1.4.3-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:22bdc1f5fb8b15bf2831440b91d1c9bbceeb7e10c81a12e8d75889996a5c9da8", size = 4424368, upload-time = "2026-03-31T22:40:06.347Z" }, 783 - { url = "https://files.pythonhosted.org/packages/e4/76/ab597bae87e1f06d18d3ecb8ed7f0d3c9a37037fc32ce76233d369273c64/hf_xet-1.4.3-cp314-cp314t-win_amd64.whl", hash = "sha256:0392c79b7cf48418cd61478c1a925246cf10639f4cd9d94368d8ca1e8df9ea07", size = 3672280, upload-time = "2026-03-31T22:40:16.401Z" }, 784 - { url = "https://files.pythonhosted.org/packages/62/05/2e462d34e23a09a74d73785dbed71cc5dbad82a72eee2ad60a72a554155d/hf_xet-1.4.3-cp314-cp314t-win_arm64.whl", hash = "sha256:681c92a07796325778a79d76c67011764ecc9042a8c3579332b61b63ae512075", size = 3528945, upload-time = "2026-03-31T22:40:14.995Z" }, 785 - { url = "https://files.pythonhosted.org/packages/ac/9f/9c23e4a447b8f83120798f9279d0297a4d1360bdbf59ef49ebec78fe2545/hf_xet-1.4.3-cp37-abi3-macosx_10_12_x86_64.whl", hash = "sha256:d0da85329eaf196e03e90b84c2d0aca53bd4573d097a75f99609e80775f98025", size = 3805048, upload-time = "2026-03-31T22:39:53.105Z" }, 786 - { url = "https://files.pythonhosted.org/packages/0b/f8/7aacb8e5f4a7899d39c787b5984e912e6c18b11be136ef13947d7a66d265/hf_xet-1.4.3-cp37-abi3-macosx_11_0_arm64.whl", hash = "sha256:e23717ce4186b265f69afa66e6f0069fe7efbf331546f5c313d00e123dc84583", size = 3562178, upload-time = "2026-03-31T22:39:51.295Z" }, 787 - { url = "https://files.pythonhosted.org/packages/df/9a/a24b26dc8a65f0ecc0fe5be981a19e61e7ca963b85e062c083f3a9100529/hf_xet-1.4.3-cp37-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:fc360b70c815bf340ed56c7b8c63aacf11762a4b099b2fe2c9bd6d6068668c08", size = 4212320, upload-time = "2026-03-31T22:39:42.922Z" }, 788 - { url = "https://files.pythonhosted.org/packages/53/60/46d493db155d2ee2801b71fb1b0fd67696359047fdd8caee2c914cc50c79/hf_xet-1.4.3-cp37-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:39f2d2e9654cd9b4319885733993807aab6de9dfbd34c42f0b78338d6617421f", size = 3991546, upload-time = "2026-03-31T22:39:41.335Z" }, 789 - { url = "https://files.pythonhosted.org/packages/bc/f5/067363e1c96c6b17256910830d1b54099d06287e10f4ec6ec4e7e08371fc/hf_xet-1.4.3-cp37-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:49ad8a8cead2b56051aa84d7fce3e1335efe68df3cf6c058f22a65513885baac", size = 4193200, upload-time = "2026-03-31T22:40:01.936Z" }, 790 - { url = "https://files.pythonhosted.org/packages/42/4b/53951592882d9c23080c7644542fda34a3813104e9e11fa1a7d82d419cb8/hf_xet-1.4.3-cp37-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:7716d62015477a70ea272d2d68cd7cad140f61c52ee452e133e139abfe2c17ba", size = 4429392, upload-time = "2026-03-31T22:40:03.492Z" }, 791 - { url = "https://files.pythonhosted.org/packages/8a/21/75a6c175b4e79662ad8e62f46a40ce341d8d6b206b06b4320d07d55b188c/hf_xet-1.4.3-cp37-abi3-win_amd64.whl", hash = "sha256:6b591fcad34e272a5b02607485e4f2a1334aebf1bc6d16ce8eb1eb8978ac2021", size = 3677359, upload-time = "2026-03-31T22:40:13.619Z" }, 792 - { url = "https://files.pythonhosted.org/packages/8a/7c/44314ecd0e89f8b2b51c9d9e5e7a60a9c1c82024ac471d415860557d3cd8/hf_xet-1.4.3-cp37-abi3-win_arm64.whl", hash = "sha256:7c2c7e20bcfcc946dc67187c203463f5e932e395845d098cc2a93f5b67ca0b47", size = 3533664, upload-time = "2026-03-31T22:40:12.152Z" }, 793 - ] 794 - 795 - [[package]] 796 - name = "httpcore" 797 - version = "1.0.9" 798 - source = { registry = "https://pypi.org/simple" } 799 - dependencies = [ 800 - { name = "certifi" }, 801 - { name = "h11" }, 802 - ] 803 - sdist = { url = "https://files.pythonhosted.org/packages/06/94/82699a10bca87a5556c9c59b5963f2d039dbd239f25bc2a63907a05a14cb/httpcore-1.0.9.tar.gz", hash = "sha256:6e34463af53fd2ab5d807f399a9b45ea31c3dfa2276f15a2c3f00afff6e176e8", size = 85484, upload-time = "2025-04-24T22:06:22.219Z" } 804 - wheels = [ 805 - { url = "https://files.pythonhosted.org/packages/7e/f5/f66802a942d491edb555dd61e3a9961140fd64c90bce1eafd741609d334d/httpcore-1.0.9-py3-none-any.whl", hash = "sha256:2d400746a40668fc9dec9810239072b40b4484b640a8c38fd654a024c7a1bf55", size = 78784, upload-time = "2025-04-24T22:06:20.566Z" }, 806 - ] 807 - 808 - [[package]] 809 - name = "httptools" 810 - version = "0.7.1" 811 - source = { registry = "https://pypi.org/simple" } 812 - sdist = { url = "https://files.pythonhosted.org/packages/b5/46/120a669232c7bdedb9d52d4aeae7e6c7dfe151e99dc70802e2fc7a5e1993/httptools-0.7.1.tar.gz", hash = "sha256:abd72556974f8e7c74a259655924a717a2365b236c882c3f6f8a45fe94703ac9", size = 258961, upload-time = "2025-10-10T03:55:08.559Z" } 813 - wheels = [ 814 - { url = "https://files.pythonhosted.org/packages/34/50/9d095fcbb6de2d523e027a2f304d4551855c2f46e0b82befd718b8b20056/httptools-0.7.1-cp314-cp314-macosx_10_13_universal2.whl", hash = "sha256:c08fe65728b8d70b6923ce31e3956f859d5e1e8548e6f22ec520a962c6757270", size = 203619, upload-time = "2025-10-10T03:54:54.321Z" }, 815 - { url = "https://files.pythonhosted.org/packages/07/f0/89720dc5139ae54b03f861b5e2c55a37dba9a5da7d51e1e824a1f343627f/httptools-0.7.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:7aea2e3c3953521c3c51106ee11487a910d45586e351202474d45472db7d72d3", size = 108714, upload-time = "2025-10-10T03:54:55.163Z" }, 816 - { url = "https://files.pythonhosted.org/packages/b3/cb/eea88506f191fb552c11787c23f9a405f4c7b0c5799bf73f2249cd4f5228/httptools-0.7.1-cp314-cp314-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:0e68b8582f4ea9166be62926077a3334064d422cf08ab87d8b74664f8e9058e1", size = 472909, upload-time = "2025-10-10T03:54:56.056Z" }, 817 - { url = "https://files.pythonhosted.org/packages/e0/4a/a548bdfae6369c0d078bab5769f7b66f17f1bfaa6fa28f81d6be6959066b/httptools-0.7.1-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:df091cf961a3be783d6aebae963cc9b71e00d57fa6f149025075217bc6a55a7b", size = 470831, upload-time = "2025-10-10T03:54:57.219Z" }, 818 - { url = "https://files.pythonhosted.org/packages/4d/31/14df99e1c43bd132eec921c2e7e11cda7852f65619bc0fc5bdc2d0cb126c/httptools-0.7.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:f084813239e1eb403ddacd06a30de3d3e09a9b76e7894dcda2b22f8a726e9c60", size = 452631, upload-time = "2025-10-10T03:54:58.219Z" }, 819 - { url = "https://files.pythonhosted.org/packages/22/d2/b7e131f7be8d854d48cb6d048113c30f9a46dca0c9a8b08fcb3fcd588cdc/httptools-0.7.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:7347714368fb2b335e9063bc2b96f2f87a9ceffcd9758ac295f8bbcd3ffbc0ca", size = 452910, upload-time = "2025-10-10T03:54:59.366Z" }, 820 - { url = "https://files.pythonhosted.org/packages/53/cf/878f3b91e4e6e011eff6d1fa9ca39f7eb17d19c9d7971b04873734112f30/httptools-0.7.1-cp314-cp314-win_amd64.whl", hash = "sha256:cfabda2a5bb85aa2a904ce06d974a3f30fb36cc63d7feaddec05d2050acede96", size = 88205, upload-time = "2025-10-10T03:55:00.389Z" }, 821 - ] 822 - 823 - [[package]] 824 - name = "httpx" 825 - version = "0.28.1" 826 - source = { registry = "https://pypi.org/simple" } 827 - dependencies = [ 828 - { name = "anyio" }, 829 - { name = "certifi" }, 830 - { name = "httpcore" }, 831 - { name = "idna" }, 832 - ] 833 - sdist = { url = "https://files.pythonhosted.org/packages/b1/df/48c586a5fe32a0f01324ee087459e112ebb7224f646c0b5023f5e79e9956/httpx-0.28.1.tar.gz", hash = "sha256:75e98c5f16b0f35b567856f597f06ff2270a374470a5c2392242528e3e3e42fc", size = 141406, upload-time = "2024-12-06T15:37:23.222Z" } 834 - wheels = [ 835 - { url = "https://files.pythonhosted.org/packages/2a/39/e50c7c3a983047577ee07d2a9e53faf5a69493943ec3f6a384bdc792deb2/httpx-0.28.1-py3-none-any.whl", hash = "sha256:d909fcccc110f8c7faf814ca82a9a4d816bc5a6dbfea25d6591d6985b8ba59ad", size = 73517, upload-time = "2024-12-06T15:37:21.509Z" }, 836 - ] 837 - 838 - [[package]] 839 - name = "httpx-sse" 840 - version = "0.4.3" 841 - source = { registry = "https://pypi.org/simple" } 842 - sdist = { url = "https://files.pythonhosted.org/packages/0f/4c/751061ffa58615a32c31b2d82e8482be8dd4a89154f003147acee90f2be9/httpx_sse-0.4.3.tar.gz", hash = "sha256:9b1ed0127459a66014aec3c56bebd93da3c1bc8bb6618c8082039a44889a755d", size = 15943, upload-time = "2025-10-10T21:48:22.271Z" } 843 - wheels = [ 844 - { url = "https://files.pythonhosted.org/packages/d2/fd/6668e5aec43ab844de6fc74927e155a3b37bf40d7c3790e49fc0406b6578/httpx_sse-0.4.3-py3-none-any.whl", hash = "sha256:0ac1c9fe3c0afad2e0ebb25a934a59f4c7823b60792691f779fad2c5568830fc", size = 8960, upload-time = "2025-10-10T21:48:21.158Z" }, 845 - ] 846 - 847 - [[package]] 848 - name = "huggingface-hub" 849 - version = "1.10.1" 850 - source = { registry = "https://pypi.org/simple" } 851 - dependencies = [ 852 - { name = "filelock" }, 853 - { name = "fsspec" }, 854 - { name = "hf-xet", marker = "platform_machine == 'AMD64' or platform_machine == 'aarch64' or platform_machine == 'amd64' or platform_machine == 'arm64' or platform_machine == 'x86_64'" }, 855 - { name = "httpx" }, 856 - { name = "packaging" }, 857 - { name = "pyyaml" }, 858 - { name = "tqdm" }, 859 - { name = "typer" }, 860 - { name = "typing-extensions" }, 861 - ] 862 - sdist = { url = "https://files.pythonhosted.org/packages/e4/28/baf5d745559503ce8d28cf5bc9551f5ac59158eafd7b6a6afff0bcdb0f50/huggingface_hub-1.10.1.tar.gz", hash = "sha256:696c53cf9c2ac9befbfb5dd41d05392a031c69fc6930d1ed9671debd405b6fff", size = 758094, upload-time = "2026-04-09T15:01:18.928Z" } 863 - wheels = [ 864 - { url = "https://files.pythonhosted.org/packages/83/8c/c7a33f3efaa8d6a5bc40e012e5ecc2d72c2e6124550ca9085fe0ceed9993/huggingface_hub-1.10.1-py3-none-any.whl", hash = "sha256:6b981107a62fbe68c74374418983399c632e35786dcd14642a9f2972633c8b5a", size = 642630, upload-time = "2026-04-09T15:01:17.35Z" }, 865 - ] 866 - 867 - [[package]] 868 - name = "idna" 869 - version = "3.11" 870 - source = { registry = "https://pypi.org/simple" } 871 - sdist = { url = "https://files.pythonhosted.org/packages/6f/6d/0703ccc57f3a7233505399edb88de3cbd678da106337b9fcde432b65ed60/idna-3.11.tar.gz", hash = "sha256:795dafcc9c04ed0c1fb032c2aa73654d8e8c5023a7df64a53f39190ada629902", size = 194582, upload-time = "2025-10-12T14:55:20.501Z" } 872 - wheels = [ 873 - { url = "https://files.pythonhosted.org/packages/0e/61/66938bbb5fc52dbdf84594873d5b51fb1f7c7794e9c0f5bd885f30bc507b/idna-3.11-py3-none-any.whl", hash = "sha256:771a87f49d9defaf64091e6e6fe9c18d4833f140bd19464795bc32d966ca37ea", size = 71008, upload-time = "2025-10-12T14:55:18.883Z" }, 874 - ] 875 - 876 - [[package]] 877 - name = "importlib-metadata" 878 - version = "8.7.1" 879 - source = { registry = "https://pypi.org/simple" } 880 - dependencies = [ 881 - { name = "zipp" }, 882 - ] 883 - sdist = { url = "https://files.pythonhosted.org/packages/f3/49/3b30cad09e7771a4982d9975a8cbf64f00d4a1ececb53297f1d9a7be1b10/importlib_metadata-8.7.1.tar.gz", hash = "sha256:49fef1ae6440c182052f407c8d34a68f72efc36db9ca90dc0113398f2fdde8bb", size = 57107, upload-time = "2025-12-21T10:00:19.278Z" } 884 - wheels = [ 885 - { url = "https://files.pythonhosted.org/packages/fa/5e/f8e9a1d23b9c20a551a8a02ea3637b4642e22c2626e3a13a9a29cdea99eb/importlib_metadata-8.7.1-py3-none-any.whl", hash = "sha256:5a1f80bf1daa489495071efbb095d75a634cf28a8bc299581244063b53176151", size = 27865, upload-time = "2025-12-21T10:00:18.329Z" }, 886 - ] 887 - 888 - [[package]] 889 - name = "jaraco-classes" 890 - version = "3.4.0" 891 - source = { registry = "https://pypi.org/simple" } 892 - dependencies = [ 893 - { name = "more-itertools" }, 894 - ] 895 - sdist = { url = "https://files.pythonhosted.org/packages/06/c0/ed4a27bc5571b99e3cff68f8a9fa5b56ff7df1c2251cc715a652ddd26402/jaraco.classes-3.4.0.tar.gz", hash = "sha256:47a024b51d0239c0dd8c8540c6c7f484be3b8fcf0b2d85c13825780d3b3f3acd", size = 11780, upload-time = "2024-03-31T07:27:36.643Z" } 896 - wheels = [ 897 - { url = "https://files.pythonhosted.org/packages/7f/66/b15ce62552d84bbfcec9a4873ab79d993a1dd4edb922cbfccae192bd5b5f/jaraco.classes-3.4.0-py3-none-any.whl", hash = "sha256:f662826b6bed8cace05e7ff873ce0f9283b5c924470fe664fff1c2f00f581790", size = 6777, upload-time = "2024-03-31T07:27:34.792Z" }, 898 - ] 899 - 900 - [[package]] 901 - name = "jaraco-context" 902 - version = "6.1.2" 903 - source = { registry = "https://pypi.org/simple" } 904 - sdist = { url = "https://files.pythonhosted.org/packages/af/50/4763cd07e722bb6285316d390a164bc7e479db9d90daa769f22578f698b4/jaraco_context-6.1.2.tar.gz", hash = "sha256:f1a6c9d391e661cc5b8d39861ff077a7dc24dc23833ccee564b234b81c82dfe3", size = 16801, upload-time = "2026-03-20T22:13:33.922Z" } 905 - wheels = [ 906 - { url = "https://files.pythonhosted.org/packages/f2/58/bc8954bda5fcda97bd7c19be11b85f91973d67a706ed4a3aec33e7de22db/jaraco_context-6.1.2-py3-none-any.whl", hash = "sha256:bf8150b79a2d5d91ae48629d8b427a8f7ba0e1097dd6202a9059f29a36379535", size = 7871, upload-time = "2026-03-20T22:13:32.808Z" }, 907 - ] 908 - 909 - [[package]] 910 - name = "jaraco-functools" 911 - version = "4.4.0" 912 - source = { registry = "https://pypi.org/simple" } 913 - dependencies = [ 914 - { name = "more-itertools" }, 915 - ] 916 - sdist = { url = "https://files.pythonhosted.org/packages/0f/27/056e0638a86749374d6f57d0b0db39f29509cce9313cf91bdc0ac4d91084/jaraco_functools-4.4.0.tar.gz", hash = "sha256:da21933b0417b89515562656547a77b4931f98176eb173644c0d35032a33d6bb", size = 19943, upload-time = "2025-12-21T09:29:43.6Z" } 917 - wheels = [ 918 - { url = "https://files.pythonhosted.org/packages/fd/c4/813bb09f0985cb21e959f21f2464169eca882656849adf727ac7bb7e1767/jaraco_functools-4.4.0-py3-none-any.whl", hash = "sha256:9eec1e36f45c818d9bf307c8948eb03b2b56cd44087b3cdc989abca1f20b9176", size = 10481, upload-time = "2025-12-21T09:29:42.27Z" }, 919 - ] 920 - 921 - [[package]] 922 - name = "jeepney" 923 - version = "0.9.0" 924 - source = { registry = "https://pypi.org/simple" } 925 - sdist = { url = "https://files.pythonhosted.org/packages/7b/6f/357efd7602486741aa73ffc0617fb310a29b588ed0fd69c2399acbb85b0c/jeepney-0.9.0.tar.gz", hash = "sha256:cf0e9e845622b81e4a28df94c40345400256ec608d0e55bb8a3feaa9163f5732", size = 106758, upload-time = "2025-02-27T18:51:01.684Z" } 926 - wheels = [ 927 - { url = "https://files.pythonhosted.org/packages/b2/a3/e137168c9c44d18eff0376253da9f1e9234d0239e0ee230d2fee6cea8e55/jeepney-0.9.0-py3-none-any.whl", hash = "sha256:97e5714520c16fc0a45695e5365a2e11b81ea79bba796e26f9f1d178cb182683", size = 49010, upload-time = "2025-02-27T18:51:00.104Z" }, 928 - ] 929 - 930 - [[package]] 931 - name = "jinja2" 932 - version = "3.1.6" 933 - source = { registry = "https://pypi.org/simple" } 934 - dependencies = [ 935 - { name = "markupsafe" }, 936 - ] 937 - sdist = { url = "https://files.pythonhosted.org/packages/df/bf/f7da0350254c0ed7c72f3e33cef02e048281fec7ecec5f032d4aac52226b/jinja2-3.1.6.tar.gz", hash = "sha256:0137fb05990d35f1275a587e9aee6d56da821fc83491a0fb838183be43f66d6d", size = 245115, upload-time = "2025-03-05T20:05:02.478Z" } 938 - wheels = [ 939 - { url = "https://files.pythonhosted.org/packages/62/a1/3d680cbfd5f4b8f15abc1d571870c5fc3e594bb582bc3b64ea099db13e56/jinja2-3.1.6-py3-none-any.whl", hash = "sha256:85ece4451f492d0c13c5dd7c13a64681a86afae63a5f347908daf103ce6d2f67", size = 134899, upload-time = "2025-03-05T20:05:00.369Z" }, 940 - ] 941 - 942 - [[package]] 943 - name = "jiter" 944 - version = "0.14.0" 945 - source = { registry = "https://pypi.org/simple" } 946 - sdist = { url = "https://files.pythonhosted.org/packages/6e/c1/0cddc6eb17d4c53a99840953f95dd3accdc5cfc7a337b0e9b26476276be9/jiter-0.14.0.tar.gz", hash = "sha256:e8a39e66dac7153cf3f964a12aad515afa8d74938ec5cc0018adcdae5367c79e", size = 165725, upload-time = "2026-04-10T14:28:42.01Z" } 947 - wheels = [ 948 - { url = "https://files.pythonhosted.org/packages/4f/1e/354ed92461b165bd581f9ef5150971a572c873ec3b68a916d5aa91da3cc2/jiter-0.14.0-cp314-cp314-macosx_10_12_x86_64.whl", hash = "sha256:6f396837fc7577871ca8c12edaf239ed9ccef3bbe39904ae9b8b63ce0a48b140", size = 315277, upload-time = "2026-04-10T14:27:18.109Z" }, 949 - { url = "https://files.pythonhosted.org/packages/a6/95/8c7c7028aa8636ac21b7a55faef3e34215e6ed0cbf5ae58258427f621aa3/jiter-0.14.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:a4d50ea3d8ba4176f79754333bd35f1bbcd28e91adc13eb9b7ca91bc52a6cef9", size = 315923, upload-time = "2026-04-10T14:27:19.603Z" }, 950 - { url = "https://files.pythonhosted.org/packages/47/40/e2a852a44c4a089f2681a16611b7ce113224a80fd8504c46d78491b47220/jiter-0.14.0-cp314-cp314-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ce17f8a050447d1b4153bda4fb7d26e6a9e74eb4f4a41913f30934c5075bf615", size = 344943, upload-time = "2026-04-10T14:27:21.262Z" }, 951 - { url = "https://files.pythonhosted.org/packages/fc/1f/670f92adee1e9895eac41e8a4d623b6da68c4d46249d8b556b60b63f949e/jiter-0.14.0-cp314-cp314-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:f4f1c4b125e1652aefbc2e2c1617b60a160ab789d180e3d423c41439e5f32850", size = 369725, upload-time = "2026-04-10T14:27:22.766Z" }, 952 - { url = "https://files.pythonhosted.org/packages/01/2f/541c9ba567d05de1c4874a0f8f8c5e3fd78e2b874266623da9a775cf46e0/jiter-0.14.0-cp314-cp314-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:be808176a6a3a14321d18c603f2d40741858a7c4fc982f83232842689fe86dd9", size = 461210, upload-time = "2026-04-10T14:27:24.315Z" }, 953 - { url = "https://files.pythonhosted.org/packages/ce/a9/c31cbec09627e0d5de7aeaec7690dba03e090caa808fefd8133137cf45bc/jiter-0.14.0-cp314-cp314-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:26679d58ba816f88c3849306dd58cb863a90a1cf352cdd4ef67e30ccf8a77994", size = 380002, upload-time = "2026-04-10T14:27:26.155Z" }, 954 - { url = "https://files.pythonhosted.org/packages/50/02/3c05c1666c41904a2f607475a73e7a4763d1cbde2d18229c4f85b22dc253/jiter-0.14.0-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:80381f5a19af8fa9aef743f080e34f6b25ebd89656475f8cf0470ec6157052aa", size = 354678, upload-time = "2026-04-10T14:27:27.701Z" }, 955 - { url = "https://files.pythonhosted.org/packages/7d/97/e15b33545c2b13518f560d695f974b9891b311641bdcf178d63177e8801e/jiter-0.14.0-cp314-cp314-manylinux_2_31_riscv64.whl", hash = "sha256:004df5fdb8ecbd6d99f3227df18ba1a259254c4359736a2e6f036c944e02d7c5", size = 358920, upload-time = "2026-04-10T14:27:29.256Z" }, 956 - { url = "https://files.pythonhosted.org/packages/ad/d2/8b1461def6b96ba44530df20d07ef7a1c7da22f3f9bf1727e2d611077bf1/jiter-0.14.0-cp314-cp314-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:cff5708f7ed0fa098f2b53446c6fa74c48469118e5cd7497b4f1cd569ab06928", size = 394512, upload-time = "2026-04-10T14:27:31.344Z" }, 957 - { url = "https://files.pythonhosted.org/packages/e3/88/837566dd6ed6e452e8d3205355afd484ce44b2533edfa4ed73a298ea893e/jiter-0.14.0-cp314-cp314-musllinux_1_1_aarch64.whl", hash = "sha256:2492e5f06c36a976d25c7cc347a60e26d5470178d44cde1b9b75e60b4e519f28", size = 521120, upload-time = "2026-04-10T14:27:33.299Z" }, 958 - { url = "https://files.pythonhosted.org/packages/89/6b/b00b45c4d1b4c031777fe161d620b755b5b02cdade1e316dcb46e4471d63/jiter-0.14.0-cp314-cp314-musllinux_1_1_x86_64.whl", hash = "sha256:7609cfbe3a03d37bfdbf5052012d5a879e72b83168a363deae7b3a26564d57de", size = 553668, upload-time = "2026-04-10T14:27:34.868Z" }, 959 - { url = "https://files.pythonhosted.org/packages/ad/d8/6fe5b42011d19397433d345716eac16728ac241862a2aac9c91923c7509a/jiter-0.14.0-cp314-cp314-win32.whl", hash = "sha256:7282342d32e357543565286b6450378c3cd402eea333fc1ebe146f1fabb306fc", size = 207001, upload-time = "2026-04-10T14:27:36.455Z" }, 960 - { url = "https://files.pythonhosted.org/packages/e5/43/5c2e08da1efad5e410f0eaaabeadd954812612c33fbbd8fd5328b489139d/jiter-0.14.0-cp314-cp314-win_amd64.whl", hash = "sha256:bd77945f38866a448e73b0b7637366afa814d4617790ecd88a18ca74377e6c02", size = 202187, upload-time = "2026-04-10T14:27:38Z" }, 961 - { url = "https://files.pythonhosted.org/packages/aa/1f/6e39ac0b4cdfa23e606af5b245df5f9adaa76f35e0c5096790da430ca506/jiter-0.14.0-cp314-cp314-win_arm64.whl", hash = "sha256:f2d4c61da0821ee42e0cdf5489da60a6d074306313a377c2b35af464955a3611", size = 192257, upload-time = "2026-04-10T14:27:39.504Z" }, 962 - { url = "https://files.pythonhosted.org/packages/05/57/7dbc0ffbbb5176a27e3518716608aa464aee2e2887dc938f0b900a120449/jiter-0.14.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:1bf7ff85517dd2f20a5750081d2b75083c1b269cf75afc7511bdf1f9548beb3b", size = 323441, upload-time = "2026-04-10T14:27:41.039Z" }, 963 - { url = "https://files.pythonhosted.org/packages/83/6e/7b3314398d8983f06b557aa21b670511ec72d3b79a68ee5e4d9bff972286/jiter-0.14.0-cp314-cp314t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c8ef8791c3e78d6c6b157c6d360fbb5c715bebb8113bc6a9303c5caff012754a", size = 348109, upload-time = "2026-04-10T14:27:42.552Z" }, 964 - { url = "https://files.pythonhosted.org/packages/ae/4f/8dc674bcd7db6dba566de73c08c763c337058baff1dbeb34567045b27cdc/jiter-0.14.0-cp314-cp314t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:e74663b8b10da1fe0f4e4703fd7980d24ad17174b6bb35d8498d6e3ebce2ae6a", size = 368328, upload-time = "2026-04-10T14:27:44.574Z" }, 965 - { url = "https://files.pythonhosted.org/packages/3b/5f/188e09a1f20906f98bbdec44ed820e19f4e8eb8aff88b9d1a5a497587ff3/jiter-0.14.0-cp314-cp314t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1aca29ba52913f78362ec9c2da62f22cdc4c3083313403f90c15460979b84d9b", size = 463301, upload-time = "2026-04-10T14:27:46.717Z" }, 966 - { url = "https://files.pythonhosted.org/packages/ac/f0/19046ef965ed8f349e8554775bb12ff4352f443fbe12b95d31f575891256/jiter-0.14.0-cp314-cp314t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8b39b7d87a952b79949af5fef44d2544e58c21a28da7f1bae3ef166455c61746", size = 378891, upload-time = "2026-04-10T14:27:48.32Z" }, 967 - { url = "https://files.pythonhosted.org/packages/c4/c3/da43bd8431ee175695777ee78cf0e93eacbb47393ff493f18c45231b427d/jiter-0.14.0-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:78d918a68b26e9fab068c2b5453577ef04943ab2807b9a6275df2a812599a310", size = 360749, upload-time = "2026-04-10T14:27:49.88Z" }, 968 - { url = "https://files.pythonhosted.org/packages/72/26/e054771be889707c6161dbdec9c23d33a9ec70945395d70f07cfea1e9a6f/jiter-0.14.0-cp314-cp314t-manylinux_2_31_riscv64.whl", hash = "sha256:b08997c35aee1201c1a5361466a8fb9162d03ae7bf6568df70b6c859f1e654a4", size = 358526, upload-time = "2026-04-10T14:27:51.504Z" }, 969 - { url = "https://files.pythonhosted.org/packages/c3/0f/7bea65ea2a6d91f2bf989ff11a18136644392bf2b0497a1fa50934c30a9c/jiter-0.14.0-cp314-cp314t-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:260bf7ca20704d58d41f669e5e9fe7fe2fa72901a6b324e79056f5d52e9c9be2", size = 393926, upload-time = "2026-04-10T14:27:53.368Z" }, 970 - { url = "https://files.pythonhosted.org/packages/3c/a1/b1ff7d70deef61ac0b7c6c2f12d2ace950cdeecb4fdc94500a0926802857/jiter-0.14.0-cp314-cp314t-musllinux_1_1_aarch64.whl", hash = "sha256:37826e3df29e60f30a382f9294348d0238ef127f4b5d7f5f8da78b5b9e050560", size = 521052, upload-time = "2026-04-10T14:27:55.058Z" }, 971 - { url = "https://files.pythonhosted.org/packages/0b/7b/3b0649983cbaf15eda26a414b5b1982e910c67bd6f7b1b490f3cfc76896a/jiter-0.14.0-cp314-cp314t-musllinux_1_1_x86_64.whl", hash = "sha256:645be49c46f2900937ba0eaf871ad5183c96858c0af74b6becc7f4e367e36e06", size = 553716, upload-time = "2026-04-10T14:27:57.269Z" }, 972 - { url = "https://files.pythonhosted.org/packages/97/f8/33d78c83bd93ae0c0af05293a6660f88a1977caef39a6d72a84afab94ce0/jiter-0.14.0-cp314-cp314t-win32.whl", hash = "sha256:2f7877ed45118de283786178eceaf877110abacd04fde31efff3940ae9672674", size = 207957, upload-time = "2026-04-10T14:27:59.285Z" }, 973 - { url = "https://files.pythonhosted.org/packages/d6/ac/2b760516c03e2227826d1f7025d89bf6bf6357a28fe75c2a2800873c50bf/jiter-0.14.0-cp314-cp314t-win_amd64.whl", hash = "sha256:14c0cb10337c49f5eafe8e7364daca5e29a020ea03580b8f8e6c597fed4e1588", size = 204690, upload-time = "2026-04-10T14:28:00.962Z" }, 974 - { url = "https://files.pythonhosted.org/packages/dc/2e/a44c20c58aeed0355f2d326969a181696aeb551a25195f47563908a815be/jiter-0.14.0-cp314-cp314t-win_arm64.whl", hash = "sha256:5419d4aa2024961da9fe12a9cfe7484996735dca99e8e090b5c88595ef1951ff", size = 191338, upload-time = "2026-04-10T14:28:02.853Z" }, 975 - ] 976 - 977 - [[package]] 978 - name = "jmespath" 979 - version = "1.1.0" 980 - source = { registry = "https://pypi.org/simple" } 981 - sdist = { url = "https://files.pythonhosted.org/packages/d3/59/322338183ecda247fb5d1763a6cbe46eff7222eaeebafd9fa65d4bf5cb11/jmespath-1.1.0.tar.gz", hash = "sha256:472c87d80f36026ae83c6ddd0f1d05d4e510134ed462851fd5f754c8c3cbb88d", size = 27377, upload-time = "2026-01-22T16:35:26.279Z" } 982 - wheels = [ 983 - { url = "https://files.pythonhosted.org/packages/14/2f/967ba146e6d58cf6a652da73885f52fc68001525b4197effc174321d70b4/jmespath-1.1.0-py3-none-any.whl", hash = "sha256:a5663118de4908c91729bea0acadca56526eb2698e83de10cd116ae0f4e97c64", size = 20419, upload-time = "2026-01-22T16:35:24.919Z" }, 984 - ] 985 - 986 - [[package]] 987 - name = "jsonpath-python" 988 - version = "1.1.5" 989 - source = { registry = "https://pypi.org/simple" } 990 - sdist = { url = "https://files.pythonhosted.org/packages/2d/db/2f4ecc24da35c6142b39c353d5b7c16eef955cc94b35a48d3fa47996d7c3/jsonpath_python-1.1.5.tar.gz", hash = "sha256:ceea2efd9e56add09330a2c9631ea3d55297b9619348c1055e5bfb9cb0b8c538", size = 87352, upload-time = "2026-03-17T06:16:40.597Z" } 991 - wheels = [ 992 - { url = "https://files.pythonhosted.org/packages/28/50/1a313fb700526b134c71eb8a225d8b83be0385dbb0204337b4379c698cef/jsonpath_python-1.1.5-py3-none-any.whl", hash = "sha256:a60315404d70a65e76c9a782c84e50600480221d94a58af47b7b4d437351cb4b", size = 14090, upload-time = "2026-03-17T06:16:39.152Z" }, 993 - ] 994 - 995 - [[package]] 996 - name = "jsonref" 997 - version = "1.1.0" 998 - source = { registry = "https://pypi.org/simple" } 999 - sdist = { url = "https://files.pythonhosted.org/packages/aa/0d/c1f3277e90ccdb50d33ed5ba1ec5b3f0a242ed8c1b1a85d3afeb68464dca/jsonref-1.1.0.tar.gz", hash = "sha256:32fe8e1d85af0fdefbebce950af85590b22b60f9e95443176adbde4e1ecea552", size = 8814, upload-time = "2023-01-16T16:10:04.455Z" } 1000 - wheels = [ 1001 - { url = "https://files.pythonhosted.org/packages/0c/ec/e1db9922bceb168197a558a2b8c03a7963f1afe93517ddd3cf99f202f996/jsonref-1.1.0-py3-none-any.whl", hash = "sha256:590dc7773df6c21cbf948b5dac07a72a251db28b0238ceecce0a2abfa8ec30a9", size = 9425, upload-time = "2023-01-16T16:10:02.255Z" }, 1002 - ] 1003 - 1004 - [[package]] 1005 - name = "jsonschema" 1006 - version = "4.26.0" 1007 - source = { registry = "https://pypi.org/simple" } 1008 - dependencies = [ 1009 - { name = "attrs" }, 1010 - { name = "jsonschema-specifications" }, 1011 - { name = "referencing" }, 1012 - { name = "rpds-py" }, 1013 - ] 1014 - sdist = { url = "https://files.pythonhosted.org/packages/b3/fc/e067678238fa451312d4c62bf6e6cf5ec56375422aee02f9cb5f909b3047/jsonschema-4.26.0.tar.gz", hash = "sha256:0c26707e2efad8aa1bfc5b7ce170f3fccc2e4918ff85989ba9ffa9facb2be326", size = 366583, upload-time = "2026-01-07T13:41:07.246Z" } 1015 - wheels = [ 1016 - { url = "https://files.pythonhosted.org/packages/69/90/f63fb5873511e014207a475e2bb4e8b2e570d655b00ac19a9a0ca0a385ee/jsonschema-4.26.0-py3-none-any.whl", hash = "sha256:d489f15263b8d200f8387e64b4c3a75f06629559fb73deb8fdfb525f2dab50ce", size = 90630, upload-time = "2026-01-07T13:41:05.306Z" }, 1017 - ] 1018 - 1019 - [[package]] 1020 - name = "jsonschema-path" 1021 - version = "0.4.5" 1022 - source = { registry = "https://pypi.org/simple" } 1023 - dependencies = [ 1024 - { name = "pathable" }, 1025 - { name = "pyyaml" }, 1026 - { name = "referencing" }, 1027 - ] 1028 - sdist = { url = "https://files.pythonhosted.org/packages/5b/8a/7e6102f2b8bdc6705a9eb5294f8f6f9ccd3a8420e8e8e19671d1dd773251/jsonschema_path-0.4.5.tar.gz", hash = "sha256:c6cd7d577ae290c7defd4f4029e86fdb248ca1bd41a07557795b3c95e5144918", size = 15113, upload-time = "2026-03-03T09:56:46.87Z" } 1029 - wheels = [ 1030 - { url = "https://files.pythonhosted.org/packages/04/d5/4e96c44f6c1ea3d812cf5391d81a4f5abaa540abf8d04ecd7f66e0ed11df/jsonschema_path-0.4.5-py3-none-any.whl", hash = "sha256:7d77a2c3f3ec569a40efe5c5f942c44c1af2a6f96fe0866794c9ef5b8f87fd65", size = 19368, upload-time = "2026-03-03T09:56:45.39Z" }, 1031 - ] 1032 - 1033 - [[package]] 1034 - name = "jsonschema-specifications" 1035 - version = "2025.9.1" 1036 - source = { registry = "https://pypi.org/simple" } 1037 - dependencies = [ 1038 - { name = "referencing" }, 1039 - ] 1040 - sdist = { url = "https://files.pythonhosted.org/packages/19/74/a633ee74eb36c44aa6d1095e7cc5569bebf04342ee146178e2d36600708b/jsonschema_specifications-2025.9.1.tar.gz", hash = "sha256:b540987f239e745613c7a9176f3edb72b832a4ac465cf02712288397832b5e8d", size = 32855, upload-time = "2025-09-08T01:34:59.186Z" } 1041 - wheels = [ 1042 - { url = "https://files.pythonhosted.org/packages/41/45/1a4ed80516f02155c51f51e8cedb3c1902296743db0bbc66608a0db2814f/jsonschema_specifications-2025.9.1-py3-none-any.whl", hash = "sha256:98802fee3a11ee76ecaca44429fda8a41bff98b00a0f2838151b113f210cc6fe", size = 18437, upload-time = "2025-09-08T01:34:57.871Z" }, 1043 - ] 1044 - 1045 - [[package]] 1046 - name = "keyring" 1047 - version = "25.7.0" 1048 - source = { registry = "https://pypi.org/simple" } 1049 - dependencies = [ 1050 - { name = "jaraco-classes" }, 1051 - { name = "jaraco-context" }, 1052 - { name = "jaraco-functools" }, 1053 - { name = "jeepney", marker = "sys_platform == 'linux'" }, 1054 - { name = "pywin32-ctypes", marker = "sys_platform == 'win32'" }, 1055 - { name = "secretstorage", marker = "sys_platform == 'linux'" }, 1056 - ] 1057 - sdist = { url = "https://files.pythonhosted.org/packages/43/4b/674af6ef2f97d56f0ab5153bf0bfa28ccb6c3ed4d1babf4305449668807b/keyring-25.7.0.tar.gz", hash = "sha256:fe01bd85eb3f8fb3dd0405defdeac9a5b4f6f0439edbb3149577f244a2e8245b", size = 63516, upload-time = "2025-11-16T16:26:09.482Z" } 1058 - wheels = [ 1059 - { url = "https://files.pythonhosted.org/packages/81/db/e655086b7f3a705df045bf0933bdd9c2f79bb3c97bfef1384598bb79a217/keyring-25.7.0-py3-none-any.whl", hash = "sha256:be4a0b195f149690c166e850609a477c532ddbfbaed96a404d4e43f8d5e2689f", size = 39160, upload-time = "2025-11-16T16:26:08.402Z" }, 1060 - ] 1061 - 1062 - [[package]] 1063 - name = "libipld" 1064 - version = "3.3.2" 1065 - source = { registry = "https://pypi.org/simple" } 1066 - sdist = { url = "https://files.pythonhosted.org/packages/83/2b/4e84e033268d2717c692e5034e016b1d82501736cd297586fd1c7378ccd5/libipld-3.3.2.tar.gz", hash = "sha256:7e85ccd9136110e63943d95232b193c893e369c406273d235160e5cc4b39c9ce", size = 4401259, upload-time = "2025-12-05T13:00:20.34Z" } 1067 - wheels = [ 1068 - { url = "https://files.pythonhosted.org/packages/de/d6/9ab52adf13ee501b50624ef1265657aa30b3267998dfadcb44d77bbeef42/libipld-3.3.2-cp314-cp314-macosx_10_12_x86_64.whl", hash = "sha256:5947e99b40e923170094a3313c9f3629c6ed475465ba95eadce6cdcf08f1f65a", size = 268909, upload-time = "2025-12-05T12:59:02.485Z" }, 1069 - { url = "https://files.pythonhosted.org/packages/c2/12/d6f04fb3d6911a276940c89b5ad3e6168d79fda9ae79a812d4da91c433d6/libipld-3.3.2-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:f46179c722baf74c627c01c0bf85be7fcbde66bbf7c5f8c1bbb57bd3a17b861b", size = 261052, upload-time = "2025-12-05T12:59:03.829Z" }, 1070 - { url = "https://files.pythonhosted.org/packages/d8/23/6cade33d39f00eb71fde1c8fe6f73c5db5274ef8abeac3d2e6d989e65718/libipld-3.3.2-cp314-cp314-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5e3e9be4bdeb90dbc537a53f8d06e8b2c703f4b7868f9316958e1bbde526a143", size = 280280, upload-time = "2025-12-05T12:59:05.13Z" }, 1071 - { url = "https://files.pythonhosted.org/packages/2c/42/50445b6c1c418a3514feb7d267d308e9fb9fe473fbbfaa205bc288ffe5ed/libipld-3.3.2-cp314-cp314-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:b155c02626b194439f4b519a53985aedc8637ae56cf640ea6acf6172a37465de", size = 290306, upload-time = "2025-12-05T12:59:06.372Z" }, 1072 - { url = "https://files.pythonhosted.org/packages/bf/b1/7c197e21f1635ba31b2f4e893d3368598a48d990cebc4308ba496bad1409/libipld-3.3.2-cp314-cp314-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9a1d84c630961cff188deaa2129c86d69f5779c8d02046fbe0c629ef162bc3df", size = 315801, upload-time = "2025-12-05T12:59:07.918Z" }, 1073 - { url = "https://files.pythonhosted.org/packages/83/df/51a549e3017cc496a80852063124793007cb9b4cf2cae2e8a99f5c3dd814/libipld-3.3.2-cp314-cp314-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5393886a7e387751904681ecfa7e5912471b46043f044baa041a2b4772e4f839", size = 330420, upload-time = "2025-12-05T12:59:09.185Z" }, 1074 - { url = "https://files.pythonhosted.org/packages/2e/f8/84107ad6431311283dadf697fd238ea271e0af1068a0d13e574be5027f32/libipld-3.3.2-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:44ca1ba44cb801686557e9544d248e013a2d5d1ab9fed796f090bb0d51d8f4ef", size = 283791, upload-time = "2025-12-05T12:59:10.481Z" }, 1075 - { url = "https://files.pythonhosted.org/packages/35/c5/e3c5116b66383f7e54b9d1feb6d6e254a383311a4cce2940942f07d45893/libipld-3.3.2-cp314-cp314-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:fd0877ef4a1bd6e42ba52659769b5b766583c67b3cfb4e7143f9d10b81fb7a74", size = 309401, upload-time = "2025-12-05T12:59:11.711Z" }, 1076 - { url = "https://files.pythonhosted.org/packages/bd/b5/b9345d47569806e6f0041d339c9a1ec0be765fd8a3588308a7a40c383dd9/libipld-3.3.2-cp314-cp314-musllinux_1_1_aarch64.whl", hash = "sha256:91b02da059a6ae7f783efa826f640ab1ca5eb5dd370bfd3f41071693a363c4fb", size = 463929, upload-time = "2025-12-05T12:59:13.344Z" }, 1077 - { url = "https://files.pythonhosted.org/packages/92/4b/ae985a308191771e5a9e8e3108a3a4ed7090147e21a7cda0c0e345adc22a/libipld-3.3.2-cp314-cp314-musllinux_1_1_x86_64.whl", hash = "sha256:95a2c4f507c88c01a797ec97ce10603bea684c03208227703e007485dc631971", size = 460308, upload-time = "2025-12-05T12:59:14.702Z" }, 1078 - { url = "https://files.pythonhosted.org/packages/5c/d6/98aafc9721dd239e578e2826cbb1e9ef438d76c0ec125bce64346e439041/libipld-3.3.2-cp314-cp314-win32.whl", hash = "sha256:5a50cbf5b3b73164fbb88169573ed3e824024c12fbc5f9efd87fb5c8f236ccc1", size = 159315, upload-time = "2025-12-05T12:59:16.004Z" }, 1079 - { url = "https://files.pythonhosted.org/packages/e2/9c/6b7b91a417162743d9ea109e142fe485b2f6dafadb276c6e5a393f772715/libipld-3.3.2-cp314-cp314-win_amd64.whl", hash = "sha256:c1f3ed8f70b215a294b5c6830e91af48acde96b3c8a6cae13304291f8240b939", size = 159168, upload-time = "2025-12-05T12:59:17.308Z" }, 1080 - { url = "https://files.pythonhosted.org/packages/22/19/bb42dc53bb8855c1f40b4a431ed3cb2df257bd5a6af61842626712c83073/libipld-3.3.2-cp314-cp314-win_arm64.whl", hash = "sha256:08261503b7307c6d9acbd3b2a221da9294b457204dcefce446f627893abb077e", size = 149324, upload-time = "2025-12-05T12:59:18.815Z" }, 1081 - ] 1082 - 1083 - [[package]] 1084 - name = "logfire" 1085 - version = "4.32.0" 1086 - source = { registry = "https://pypi.org/simple" } 1087 - dependencies = [ 1088 - { name = "executing" }, 1089 - { name = "opentelemetry-exporter-otlp-proto-http" }, 1090 - { name = "opentelemetry-instrumentation" }, 1091 - { name = "opentelemetry-sdk" }, 1092 - { name = "protobuf" }, 1093 - { name = "rich" }, 1094 - { name = "typing-extensions" }, 1095 - ] 1096 - sdist = { url = "https://files.pythonhosted.org/packages/98/64/f927d4f9de1f1371047b9016adba1ec2e08258301708d548d41f86f27772/logfire-4.32.0.tar.gz", hash = "sha256:f1dc9d756a4b28f0483645244aaf3ea8535b8e2ae5a1068442a968ca0c746304", size = 1088575, upload-time = "2026-04-10T19:36:54.172Z" } 1097 - wheels = [ 1098 - { url = "https://files.pythonhosted.org/packages/02/33/81b13e1f2044b5fe0112068a2494526db9cfdf784030a2ea57688279360a/logfire-4.32.0-py3-none-any.whl", hash = "sha256:d9cff51c3c093c4161ece87a65e6ac6e2d862258b62494c30d93d713e9858758", size = 312412, upload-time = "2026-04-10T19:36:50.97Z" }, 1099 - ] 1100 - 1101 - [package.optional-dependencies] 1102 - httpx = [ 1103 - { name = "opentelemetry-instrumentation-httpx" }, 1104 - ] 1105 - 1106 - [[package]] 1107 - name = "logfire-api" 1108 - version = "4.32.0" 1109 - source = { registry = "https://pypi.org/simple" } 1110 - sdist = { url = "https://files.pythonhosted.org/packages/cb/17/7a50c55077e50b088e056a90e0754836ee6f1cb31ba6a2cd7f4282afd70b/logfire_api-4.32.0.tar.gz", hash = "sha256:aae7d7f1e38d04c6fa9449b15b18cc77336474feae56afc507e6f053aa1afb83", size = 78813, upload-time = "2026-04-10T19:36:55.69Z" } 1111 - wheels = [ 1112 - { url = "https://files.pythonhosted.org/packages/58/56/fe8d8aa60e796e059992fb7359ec5dda4ef72db4fccfbd362a2ee0595ec1/logfire_api-4.32.0-py3-none-any.whl", hash = "sha256:062526b31ca5e4bde5455bd5230bfb713df23189aedb370c8c47c6ed8ec02a37", size = 124427, upload-time = "2026-04-10T19:36:52.695Z" }, 1113 - ] 1114 - 1115 - [[package]] 1116 - name = "markdown-it-py" 1117 - version = "4.0.0" 1118 - source = { registry = "https://pypi.org/simple" } 1119 - dependencies = [ 1120 - { name = "mdurl" }, 1121 - ] 1122 - sdist = { url = "https://files.pythonhosted.org/packages/5b/f5/4ec618ed16cc4f8fb3b701563655a69816155e79e24a17b651541804721d/markdown_it_py-4.0.0.tar.gz", hash = "sha256:cb0a2b4aa34f932c007117b194e945bd74e0ec24133ceb5bac59009cda1cb9f3", size = 73070, upload-time = "2025-08-11T12:57:52.854Z" } 1123 - wheels = [ 1124 - { url = "https://files.pythonhosted.org/packages/94/54/e7d793b573f298e1c9013b8c4dade17d481164aa517d1d7148619c2cedbf/markdown_it_py-4.0.0-py3-none-any.whl", hash = "sha256:87327c59b172c5011896038353a81343b6754500a08cd7a4973bb48c6d578147", size = 87321, upload-time = "2025-08-11T12:57:51.923Z" }, 1125 - ] 1126 - 1127 - [[package]] 1128 - name = "markupsafe" 1129 - version = "3.0.3" 1130 - source = { registry = "https://pypi.org/simple" } 1131 - sdist = { url = "https://files.pythonhosted.org/packages/7e/99/7690b6d4034fffd95959cbe0c02de8deb3098cc577c67bb6a24fe5d7caa7/markupsafe-3.0.3.tar.gz", hash = "sha256:722695808f4b6457b320fdc131280796bdceb04ab50fe1795cd540799ebe1698", size = 80313, upload-time = "2025-09-27T18:37:40.426Z" } 1132 - wheels = [ 1133 - { url = "https://files.pythonhosted.org/packages/33/8a/8e42d4838cd89b7dde187011e97fe6c3af66d8c044997d2183fbd6d31352/markupsafe-3.0.3-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:eaa9599de571d72e2daf60164784109f19978b327a3910d3e9de8c97b5b70cfe", size = 11619, upload-time = "2025-09-27T18:37:06.342Z" }, 1134 - { url = "https://files.pythonhosted.org/packages/b5/64/7660f8a4a8e53c924d0fa05dc3a55c9cee10bbd82b11c5afb27d44b096ce/markupsafe-3.0.3-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:c47a551199eb8eb2121d4f0f15ae0f923d31350ab9280078d1e5f12b249e0026", size = 12029, upload-time = "2025-09-27T18:37:07.213Z" }, 1135 - { url = "https://files.pythonhosted.org/packages/da/ef/e648bfd021127bef5fa12e1720ffed0c6cbb8310c8d9bea7266337ff06de/markupsafe-3.0.3-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f34c41761022dd093b4b6896d4810782ffbabe30f2d443ff5f083e0cbbb8c737", size = 24408, upload-time = "2025-09-27T18:37:09.572Z" }, 1136 - { url = "https://files.pythonhosted.org/packages/41/3c/a36c2450754618e62008bf7435ccb0f88053e07592e6028a34776213d877/markupsafe-3.0.3-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:457a69a9577064c05a97c41f4e65148652db078a3a509039e64d3467b9e7ef97", size = 23005, upload-time = "2025-09-27T18:37:10.58Z" }, 1137 - { url = "https://files.pythonhosted.org/packages/bc/20/b7fdf89a8456b099837cd1dc21974632a02a999ec9bf7ca3e490aacd98e7/markupsafe-3.0.3-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:e8afc3f2ccfa24215f8cb28dcf43f0113ac3c37c2f0f0806d8c70e4228c5cf4d", size = 22048, upload-time = "2025-09-27T18:37:11.547Z" }, 1138 - { url = "https://files.pythonhosted.org/packages/9a/a7/591f592afdc734f47db08a75793a55d7fbcc6902a723ae4cfbab61010cc5/markupsafe-3.0.3-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:ec15a59cf5af7be74194f7ab02d0f59a62bdcf1a537677ce67a2537c9b87fcda", size = 23821, upload-time = "2025-09-27T18:37:12.48Z" }, 1139 - { url = "https://files.pythonhosted.org/packages/7d/33/45b24e4f44195b26521bc6f1a82197118f74df348556594bd2262bda1038/markupsafe-3.0.3-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:0eb9ff8191e8498cca014656ae6b8d61f39da5f95b488805da4bb029cccbfbaf", size = 21606, upload-time = "2025-09-27T18:37:13.485Z" }, 1140 - { url = "https://files.pythonhosted.org/packages/ff/0e/53dfaca23a69fbfbbf17a4b64072090e70717344c52eaaaa9c5ddff1e5f0/markupsafe-3.0.3-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:2713baf880df847f2bece4230d4d094280f4e67b1e813eec43b4c0e144a34ffe", size = 23043, upload-time = "2025-09-27T18:37:14.408Z" }, 1141 - { url = "https://files.pythonhosted.org/packages/46/11/f333a06fc16236d5238bfe74daccbca41459dcd8d1fa952e8fbd5dccfb70/markupsafe-3.0.3-cp314-cp314-win32.whl", hash = "sha256:729586769a26dbceff69f7a7dbbf59ab6572b99d94576a5592625d5b411576b9", size = 14747, upload-time = "2025-09-27T18:37:15.36Z" }, 1142 - { url = "https://files.pythonhosted.org/packages/28/52/182836104b33b444e400b14f797212f720cbc9ed6ba34c800639d154e821/markupsafe-3.0.3-cp314-cp314-win_amd64.whl", hash = "sha256:bdc919ead48f234740ad807933cdf545180bfbe9342c2bb451556db2ed958581", size = 15341, upload-time = "2025-09-27T18:37:16.496Z" }, 1143 - { url = "https://files.pythonhosted.org/packages/6f/18/acf23e91bd94fd7b3031558b1f013adfa21a8e407a3fdb32745538730382/markupsafe-3.0.3-cp314-cp314-win_arm64.whl", hash = "sha256:5a7d5dc5140555cf21a6fefbdbf8723f06fcd2f63ef108f2854de715e4422cb4", size = 14073, upload-time = "2025-09-27T18:37:17.476Z" }, 1144 - { url = "https://files.pythonhosted.org/packages/3c/f0/57689aa4076e1b43b15fdfa646b04653969d50cf30c32a102762be2485da/markupsafe-3.0.3-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:1353ef0c1b138e1907ae78e2f6c63ff67501122006b0f9abad68fda5f4ffc6ab", size = 11661, upload-time = "2025-09-27T18:37:18.453Z" }, 1145 - { url = "https://files.pythonhosted.org/packages/89/c3/2e67a7ca217c6912985ec766c6393b636fb0c2344443ff9d91404dc4c79f/markupsafe-3.0.3-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:1085e7fbddd3be5f89cc898938f42c0b3c711fdcb37d75221de2666af647c175", size = 12069, upload-time = "2025-09-27T18:37:19.332Z" }, 1146 - { url = "https://files.pythonhosted.org/packages/f0/00/be561dce4e6ca66b15276e184ce4b8aec61fe83662cce2f7d72bd3249d28/markupsafe-3.0.3-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1b52b4fb9df4eb9ae465f8d0c228a00624de2334f216f178a995ccdcf82c4634", size = 25670, upload-time = "2025-09-27T18:37:20.245Z" }, 1147 - { url = "https://files.pythonhosted.org/packages/50/09/c419f6f5a92e5fadde27efd190eca90f05e1261b10dbd8cbcb39cd8ea1dc/markupsafe-3.0.3-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:fed51ac40f757d41b7c48425901843666a6677e3e8eb0abcff09e4ba6e664f50", size = 23598, upload-time = "2025-09-27T18:37:21.177Z" }, 1148 - { url = "https://files.pythonhosted.org/packages/22/44/a0681611106e0b2921b3033fc19bc53323e0b50bc70cffdd19f7d679bb66/markupsafe-3.0.3-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:f190daf01f13c72eac4efd5c430a8de82489d9cff23c364c3ea822545032993e", size = 23261, upload-time = "2025-09-27T18:37:22.167Z" }, 1149 - { url = "https://files.pythonhosted.org/packages/5f/57/1b0b3f100259dc9fffe780cfb60d4be71375510e435efec3d116b6436d43/markupsafe-3.0.3-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:e56b7d45a839a697b5eb268c82a71bd8c7f6c94d6fd50c3d577fa39a9f1409f5", size = 24835, upload-time = "2025-09-27T18:37:23.296Z" }, 1150 - { url = "https://files.pythonhosted.org/packages/26/6a/4bf6d0c97c4920f1597cc14dd720705eca0bf7c787aebc6bb4d1bead5388/markupsafe-3.0.3-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:f3e98bb3798ead92273dc0e5fd0f31ade220f59a266ffd8a4f6065e0a3ce0523", size = 22733, upload-time = "2025-09-27T18:37:24.237Z" }, 1151 - { url = "https://files.pythonhosted.org/packages/14/c7/ca723101509b518797fedc2fdf79ba57f886b4aca8a7d31857ba3ee8281f/markupsafe-3.0.3-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:5678211cb9333a6468fb8d8be0305520aa073f50d17f089b5b4b477ea6e67fdc", size = 23672, upload-time = "2025-09-27T18:37:25.271Z" }, 1152 - { url = "https://files.pythonhosted.org/packages/fb/df/5bd7a48c256faecd1d36edc13133e51397e41b73bb77e1a69deab746ebac/markupsafe-3.0.3-cp314-cp314t-win32.whl", hash = "sha256:915c04ba3851909ce68ccc2b8e2cd691618c4dc4c4232fb7982bca3f41fd8c3d", size = 14819, upload-time = "2025-09-27T18:37:26.285Z" }, 1153 - { url = "https://files.pythonhosted.org/packages/1a/8a/0402ba61a2f16038b48b39bccca271134be00c5c9f0f623208399333c448/markupsafe-3.0.3-cp314-cp314t-win_amd64.whl", hash = "sha256:4faffd047e07c38848ce017e8725090413cd80cbc23d86e55c587bf979e579c9", size = 15426, upload-time = "2025-09-27T18:37:27.316Z" }, 1154 - { url = "https://files.pythonhosted.org/packages/70/bc/6f1c2f612465f5fa89b95bead1f44dcb607670fd42891d8fdcd5d039f4f4/markupsafe-3.0.3-cp314-cp314t-win_arm64.whl", hash = "sha256:32001d6a8fc98c8cb5c947787c5d08b0a50663d139f1305bac5885d98d9b40fa", size = 14146, upload-time = "2025-09-27T18:37:28.327Z" }, 1155 - ] 1156 - 1157 - [[package]] 1158 - name = "mcp" 1159 - version = "1.27.0" 1160 - source = { registry = "https://pypi.org/simple" } 1161 - dependencies = [ 1162 - { name = "anyio" }, 1163 - { name = "httpx" }, 1164 - { name = "httpx-sse" }, 1165 - { name = "jsonschema" }, 1166 - { name = "pydantic" }, 1167 - { name = "pydantic-settings" }, 1168 - { name = "pyjwt", extra = ["crypto"] }, 1169 - { name = "python-multipart" }, 1170 - { name = "pywin32", marker = "sys_platform == 'win32'" }, 1171 - { name = "sse-starlette" }, 1172 - { name = "starlette" }, 1173 - { name = "typing-extensions" }, 1174 - { name = "typing-inspection" }, 1175 - { name = "uvicorn", marker = "sys_platform != 'emscripten'" }, 1176 - ] 1177 - sdist = { url = "https://files.pythonhosted.org/packages/8b/eb/c0cfc62075dc6e1ec1c64d352ae09ac051d9334311ed226f1f425312848a/mcp-1.27.0.tar.gz", hash = "sha256:d3dc35a7eec0d458c1da4976a48f982097ddaab87e278c5511d5a4a56e852b83", size = 607509, upload-time = "2026-04-02T14:48:08.88Z" } 1178 - wheels = [ 1179 - { url = "https://files.pythonhosted.org/packages/9c/46/f6b4ad632c67ef35209a66127e4bddc95759649dd595f71f13fba11bdf9a/mcp-1.27.0-py3-none-any.whl", hash = "sha256:5ce1fa81614958e267b21fb2aa34e0aea8e2c6ede60d52aba45fd47246b4d741", size = 215967, upload-time = "2026-04-02T14:48:07.24Z" }, 1180 - ] 1181 - 1182 - [[package]] 1183 - name = "mdurl" 1184 - version = "0.1.2" 1185 - source = { registry = "https://pypi.org/simple" } 1186 - sdist = { url = "https://files.pythonhosted.org/packages/d6/54/cfe61301667036ec958cb99bd3efefba235e65cdeb9c84d24a8293ba1d90/mdurl-0.1.2.tar.gz", hash = "sha256:bb413d29f5eea38f31dd4754dd7377d4465116fb207585f97bf925588687c1ba", size = 8729, upload-time = "2022-08-14T12:40:10.846Z" } 1187 - wheels = [ 1188 - { url = "https://files.pythonhosted.org/packages/b3/38/89ba8ad64ae25be8de66a6d463314cf1eb366222074cfda9ee839c56a4b4/mdurl-0.1.2-py3-none-any.whl", hash = "sha256:84008a41e51615a49fc9966191ff91509e3c40b939176e643fd50a5c2196b8f8", size = 9979, upload-time = "2022-08-14T12:40:09.779Z" }, 1189 - ] 1190 - 1191 - [[package]] 1192 - name = "mistralai" 1193 - version = "2.3.2" 1194 - source = { registry = "https://pypi.org/simple" } 1195 - dependencies = [ 1196 - { name = "eval-type-backport" }, 1197 - { name = "httpx" }, 1198 - { name = "jsonpath-python" }, 1199 - { name = "opentelemetry-api" }, 1200 - { name = "opentelemetry-semantic-conventions" }, 1201 - { name = "pydantic" }, 1202 - { name = "python-dateutil" }, 1203 - { name = "typing-inspection" }, 1204 - ] 1205 - sdist = { url = "https://files.pythonhosted.org/packages/5c/08/f8fed64eab35ad1ded82f92bf834160302db19f5bbda5a904df7d4dbf3c4/mistralai-2.3.2.tar.gz", hash = "sha256:a02c7e90ac165e8680c849551ff5fe9788e9fc10b7dbe71817443dc63cc5e9c9", size = 394665, upload-time = "2026-04-10T14:05:36.31Z" } 1206 - wheels = [ 1207 - { url = "https://files.pythonhosted.org/packages/83/e0/9f246d18f8fe6e815f7cae88c437c986b342d3ff568607c8e0d53f4710d0/mistralai-2.3.2-py3-none-any.whl", hash = "sha256:dec05f446502289b76add9796d75465c070fc539b3c01d6f7d3297833bb64981", size = 935914, upload-time = "2026-04-10T14:05:37.845Z" }, 1208 - ] 1209 - 1210 - [[package]] 1211 - name = "more-itertools" 1212 - version = "11.0.2" 1213 - source = { registry = "https://pypi.org/simple" } 1214 - sdist = { url = "https://files.pythonhosted.org/packages/a2/f7/139d22fef48ac78127d18e01d80cf1be40236ae489769d17f35c3d425293/more_itertools-11.0.2.tar.gz", hash = "sha256:392a9e1e362cbc106a2457d37cabf9b36e5e12efd4ebff1654630e76597df804", size = 144659, upload-time = "2026-04-09T15:01:33.297Z" } 1215 - wheels = [ 1216 - { url = "https://files.pythonhosted.org/packages/cb/98/6af411189d9413534c3eb691182bff1f5c6d44ed2f93f2edfe52a1bbceb8/more_itertools-11.0.2-py3-none-any.whl", hash = "sha256:6e35b35f818b01f691643c6c611bc0902f2e92b46c18fffa77ae1e7c46e912e4", size = 71939, upload-time = "2026-04-09T15:01:32.21Z" }, 1217 - ] 1218 - 1219 - [[package]] 1220 - name = "multidict" 1221 - version = "6.7.1" 1222 - source = { registry = "https://pypi.org/simple" } 1223 - sdist = { url = "https://files.pythonhosted.org/packages/1a/c2/c2d94cbe6ac1753f3fc980da97b3d930efe1da3af3c9f5125354436c073d/multidict-6.7.1.tar.gz", hash = "sha256:ec6652a1bee61c53a3e5776b6049172c53b6aaba34f18c9ad04f82712bac623d", size = 102010, upload-time = "2026-01-26T02:46:45.979Z" } 1224 - wheels = [ 1225 - { url = "https://files.pythonhosted.org/packages/91/cc/db74228a8be41884a567e88a62fd589a913708fcf180d029898c17a9a371/multidict-6.7.1-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:8f333ec9c5eb1b7105e3b84b53141e66ca05a19a605368c55450b6ba208cb9ee", size = 75190, upload-time = "2026-01-26T02:45:10.651Z" }, 1226 - { url = "https://files.pythonhosted.org/packages/d5/22/492f2246bb5b534abd44804292e81eeaf835388901f0c574bac4eeec73c5/multidict-6.7.1-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:a407f13c188f804c759fc6a9f88286a565c242a76b27626594c133b82883b5c2", size = 44486, upload-time = "2026-01-26T02:45:11.938Z" }, 1227 - { url = "https://files.pythonhosted.org/packages/f1/4f/733c48f270565d78b4544f2baddc2fb2a245e5a8640254b12c36ac7ac68e/multidict-6.7.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:0e161ddf326db5577c3a4cc2d8648f81456e8a20d40415541587a71620d7a7d1", size = 43219, upload-time = "2026-01-26T02:45:14.346Z" }, 1228 - { url = "https://files.pythonhosted.org/packages/24/bb/2c0c2287963f4259c85e8bcbba9182ced8d7fca65c780c38e99e61629d11/multidict-6.7.1-cp314-cp314-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:1e3a8bb24342a8201d178c3b4984c26ba81a577c80d4d525727427460a50c22d", size = 245132, upload-time = "2026-01-26T02:45:15.712Z" }, 1229 - { url = "https://files.pythonhosted.org/packages/a7/f9/44d4b3064c65079d2467888794dea218d1601898ac50222ab8a9a8094460/multidict-6.7.1-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:97231140a50f5d447d3164f994b86a0bed7cd016e2682f8650d6a9158e14fd31", size = 252420, upload-time = "2026-01-26T02:45:17.293Z" }, 1230 - { url = "https://files.pythonhosted.org/packages/8b/13/78f7275e73fa17b24c9a51b0bd9d73ba64bb32d0ed51b02a746eb876abe7/multidict-6.7.1-cp314-cp314-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:6b10359683bd8806a200fd2909e7c8ca3a7b24ec1d8132e483d58e791d881048", size = 233510, upload-time = "2026-01-26T02:45:19.356Z" }, 1231 - { url = "https://files.pythonhosted.org/packages/4b/25/8167187f62ae3cbd52da7893f58cb036b47ea3fb67138787c76800158982/multidict-6.7.1-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:283ddac99f7ac25a4acadbf004cb5ae34480bbeb063520f70ce397b281859362", size = 264094, upload-time = "2026-01-26T02:45:20.834Z" }, 1232 - { url = "https://files.pythonhosted.org/packages/a1/e7/69a3a83b7b030cf283fb06ce074a05a02322359783424d7edf0f15fe5022/multidict-6.7.1-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:538cec1e18c067d0e6103aa9a74f9e832904c957adc260e61cd9d8cf0c3b3d37", size = 260786, upload-time = "2026-01-26T02:45:22.818Z" }, 1233 - { url = "https://files.pythonhosted.org/packages/fe/3b/8ec5074bcfc450fe84273713b4b0a0dd47c0249358f5d82eb8104ffe2520/multidict-6.7.1-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:7eee46ccb30ff48a1e35bb818cc90846c6be2b68240e42a78599166722cea709", size = 248483, upload-time = "2026-01-26T02:45:24.368Z" }, 1234 - { url = "https://files.pythonhosted.org/packages/48/5a/d5a99e3acbca0e29c5d9cba8f92ceb15dce78bab963b308ae692981e3a5d/multidict-6.7.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:fa263a02f4f2dd2d11a7b1bb4362aa7cb1049f84a9235d31adf63f30143469a0", size = 248403, upload-time = "2026-01-26T02:45:25.982Z" }, 1235 - { url = "https://files.pythonhosted.org/packages/35/48/e58cd31f6c7d5102f2a4bf89f96b9cf7e00b6c6f3d04ecc44417c00a5a3c/multidict-6.7.1-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:2e1425e2f99ec5bd36c15a01b690a1a2456209c5deed58f95469ffb46039ccbb", size = 240315, upload-time = "2026-01-26T02:45:27.487Z" }, 1236 - { url = "https://files.pythonhosted.org/packages/94/33/1cd210229559cb90b6786c30676bb0c58249ff42f942765f88793b41fdce/multidict-6.7.1-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:497394b3239fc6f0e13a78a3e1b61296e72bf1c5f94b4c4eb80b265c37a131cd", size = 245528, upload-time = "2026-01-26T02:45:28.991Z" }, 1237 - { url = "https://files.pythonhosted.org/packages/64/f2/6e1107d226278c876c783056b7db43d800bb64c6131cec9c8dfb6903698e/multidict-6.7.1-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:233b398c29d3f1b9676b4b6f75c518a06fcb2ea0b925119fb2c1bc35c05e1601", size = 258784, upload-time = "2026-01-26T02:45:30.503Z" }, 1238 - { url = "https://files.pythonhosted.org/packages/4d/c1/11f664f14d525e4a1b5327a82d4de61a1db604ab34c6603bb3c2cc63ad34/multidict-6.7.1-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:93b1818e4a6e0930454f0f2af7dfce69307ca03cdcfb3739bf4d91241967b6c1", size = 251980, upload-time = "2026-01-26T02:45:32.603Z" }, 1239 - { url = "https://files.pythonhosted.org/packages/e1/9f/75a9ac888121d0c5bbd4ecf4eead45668b1766f6baabfb3b7f66a410e231/multidict-6.7.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:f33dc2a3abe9249ea5d8360f969ec7f4142e7ac45ee7014d8f8d5acddf178b7b", size = 243602, upload-time = "2026-01-26T02:45:34.043Z" }, 1240 - { url = "https://files.pythonhosted.org/packages/9a/e7/50bf7b004cc8525d80dbbbedfdc7aed3e4c323810890be4413e589074032/multidict-6.7.1-cp314-cp314-win32.whl", hash = "sha256:3ab8b9d8b75aef9df299595d5388b14530839f6422333357af1339443cff777d", size = 40930, upload-time = "2026-01-26T02:45:36.278Z" }, 1241 - { url = "https://files.pythonhosted.org/packages/e0/bf/52f25716bbe93745595800f36fb17b73711f14da59ed0bb2eba141bc9f0f/multidict-6.7.1-cp314-cp314-win_amd64.whl", hash = "sha256:5e01429a929600e7dab7b166062d9bb54a5eed752384c7384c968c2afab8f50f", size = 45074, upload-time = "2026-01-26T02:45:37.546Z" }, 1242 - { url = "https://files.pythonhosted.org/packages/97/ab/22803b03285fa3a525f48217963da3a65ae40f6a1b6f6cf2768879e208f9/multidict-6.7.1-cp314-cp314-win_arm64.whl", hash = "sha256:4885cb0e817aef5d00a2e8451d4665c1808378dc27c2705f1bf4ef8505c0d2e5", size = 42471, upload-time = "2026-01-26T02:45:38.889Z" }, 1243 - { url = "https://files.pythonhosted.org/packages/e0/6d/f9293baa6146ba9507e360ea0292b6422b016907c393e2f63fc40ab7b7b5/multidict-6.7.1-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:0458c978acd8e6ea53c81eefaddbbee9c6c5e591f41b3f5e8e194780fe026581", size = 82401, upload-time = "2026-01-26T02:45:40.254Z" }, 1244 - { url = "https://files.pythonhosted.org/packages/7a/68/53b5494738d83558d87c3c71a486504d8373421c3e0dbb6d0db48ad42ee0/multidict-6.7.1-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:c0abd12629b0af3cf590982c0b413b1e7395cd4ec026f30986818ab95bfaa94a", size = 48143, upload-time = "2026-01-26T02:45:41.635Z" }, 1245 - { url = "https://files.pythonhosted.org/packages/37/e8/5284c53310dcdc99ce5d66563f6e5773531a9b9fe9ec7a615e9bc306b05f/multidict-6.7.1-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:14525a5f61d7d0c94b368a42cff4c9a4e7ba2d52e2672a7b23d84dc86fb02b0c", size = 46507, upload-time = "2026-01-26T02:45:42.99Z" }, 1246 - { url = "https://files.pythonhosted.org/packages/e4/fc/6800d0e5b3875568b4083ecf5f310dcf91d86d52573160834fb4bfcf5e4f/multidict-6.7.1-cp314-cp314t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:17307b22c217b4cf05033dabefe68255a534d637c6c9b0cc8382718f87be4262", size = 239358, upload-time = "2026-01-26T02:45:44.376Z" }, 1247 - { url = "https://files.pythonhosted.org/packages/41/75/4ad0973179361cdf3a113905e6e088173198349131be2b390f9fa4da5fc6/multidict-6.7.1-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:7a7e590ff876a3eaf1c02a4dfe0724b6e69a9e9de6d8f556816f29c496046e59", size = 246884, upload-time = "2026-01-26T02:45:47.167Z" }, 1248 - { url = "https://files.pythonhosted.org/packages/c3/9c/095bb28b5da139bd41fb9a5d5caff412584f377914bd8787c2aa98717130/multidict-6.7.1-cp314-cp314t-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:5fa6a95dfee63893d80a34758cd0e0c118a30b8dcb46372bf75106c591b77889", size = 225878, upload-time = "2026-01-26T02:45:48.698Z" }, 1249 - { url = "https://files.pythonhosted.org/packages/07/d0/c0a72000243756e8f5a277b6b514fa005f2c73d481b7d9e47cd4568aa2e4/multidict-6.7.1-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:a0543217a6a017692aa6ae5cc39adb75e587af0f3a82288b1492eb73dd6cc2a4", size = 253542, upload-time = "2026-01-26T02:45:50.164Z" }, 1250 - { url = "https://files.pythonhosted.org/packages/c0/6b/f69da15289e384ecf2a68837ec8b5ad8c33e973aa18b266f50fe55f24b8c/multidict-6.7.1-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:f99fe611c312b3c1c0ace793f92464d8cd263cc3b26b5721950d977b006b6c4d", size = 252403, upload-time = "2026-01-26T02:45:51.779Z" }, 1251 - { url = "https://files.pythonhosted.org/packages/a2/76/b9669547afa5a1a25cd93eaca91c0da1c095b06b6d2d8ec25b713588d3a1/multidict-6.7.1-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9004d8386d133b7e6135679424c91b0b854d2d164af6ea3f289f8f2761064609", size = 244889, upload-time = "2026-01-26T02:45:53.27Z" }, 1252 - { url = "https://files.pythonhosted.org/packages/7e/a9/a50d2669e506dad33cfc45b5d574a205587b7b8a5f426f2fbb2e90882588/multidict-6.7.1-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:e628ef0e6859ffd8273c69412a2465c4be4a9517d07261b33334b5ec6f3c7489", size = 241982, upload-time = "2026-01-26T02:45:54.919Z" }, 1253 - { url = "https://files.pythonhosted.org/packages/c5/bb/1609558ad8b456b4827d3c5a5b775c93b87878fd3117ed3db3423dfbce1b/multidict-6.7.1-cp314-cp314t-musllinux_1_2_armv7l.whl", hash = "sha256:841189848ba629c3552035a6a7f5bf3b02eb304e9fea7492ca220a8eda6b0e5c", size = 232415, upload-time = "2026-01-26T02:45:56.981Z" }, 1254 - { url = "https://files.pythonhosted.org/packages/d8/59/6f61039d2aa9261871e03ab9dc058a550d240f25859b05b67fd70f80d4b3/multidict-6.7.1-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:ce1bbd7d780bb5a0da032e095c951f7014d6b0a205f8318308140f1a6aba159e", size = 240337, upload-time = "2026-01-26T02:45:58.698Z" }, 1255 - { url = "https://files.pythonhosted.org/packages/a1/29/fdc6a43c203890dc2ae9249971ecd0c41deaedfe00d25cb6564b2edd99eb/multidict-6.7.1-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:b26684587228afed0d50cf804cc71062cc9c1cdf55051c4c6345d372947b268c", size = 248788, upload-time = "2026-01-26T02:46:00.862Z" }, 1256 - { url = "https://files.pythonhosted.org/packages/a9/14/a153a06101323e4cf086ecee3faadba52ff71633d471f9685c42e3736163/multidict-6.7.1-cp314-cp314t-musllinux_1_2_s390x.whl", hash = "sha256:9f9af11306994335398293f9958071019e3ab95e9a707dc1383a35613f6abcb9", size = 242842, upload-time = "2026-01-26T02:46:02.824Z" }, 1257 - { url = "https://files.pythonhosted.org/packages/41/5f/604ae839e64a4a6efc80db94465348d3b328ee955e37acb24badbcd24d83/multidict-6.7.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:b4938326284c4f1224178a560987b6cf8b4d38458b113d9b8c1db1a836e640a2", size = 240237, upload-time = "2026-01-26T02:46:05.898Z" }, 1258 - { url = "https://files.pythonhosted.org/packages/5f/60/c3a5187bf66f6fb546ff4ab8fb5a077cbdd832d7b1908d4365c7f74a1917/multidict-6.7.1-cp314-cp314t-win32.whl", hash = "sha256:98655c737850c064a65e006a3df7c997cd3b220be4ec8fe26215760b9697d4d7", size = 48008, upload-time = "2026-01-26T02:46:07.468Z" }, 1259 - { url = "https://files.pythonhosted.org/packages/0c/f7/addf1087b860ac60e6f382240f64fb99f8bfb532bb06f7c542b83c29ca61/multidict-6.7.1-cp314-cp314t-win_amd64.whl", hash = "sha256:497bde6223c212ba11d462853cfa4f0ae6ef97465033e7dc9940cdb3ab5b48e5", size = 53542, upload-time = "2026-01-26T02:46:08.809Z" }, 1260 - { url = "https://files.pythonhosted.org/packages/4c/81/4629d0aa32302ef7b2ec65c75a728cc5ff4fa410c50096174c1632e70b3e/multidict-6.7.1-cp314-cp314t-win_arm64.whl", hash = "sha256:2bbd113e0d4af5db41d5ebfe9ccaff89de2120578164f86a5d17d5a576d1e5b2", size = 44719, upload-time = "2026-01-26T02:46:11.146Z" }, 1261 - { url = "https://files.pythonhosted.org/packages/81/08/7036c080d7117f28a4af526d794aab6a84463126db031b007717c1a6676e/multidict-6.7.1-py3-none-any.whl", hash = "sha256:55d97cc6dae627efa6a6e548885712d4864b81110ac76fa4e534c03819fa4a56", size = 12319, upload-time = "2026-01-26T02:46:44.004Z" }, 1262 - ] 1263 - 1264 - [[package]] 1265 - name = "nexus-rpc" 1266 - version = "1.4.0" 1267 - source = { registry = "https://pypi.org/simple" } 1268 - dependencies = [ 1269 - { name = "typing-extensions" }, 1270 - ] 1271 - sdist = { url = "https://files.pythonhosted.org/packages/35/d5/cd1ffb202b76ebc1b33c1332a3416e55a39929006982adc2b1eb069aaa9b/nexus_rpc-1.4.0.tar.gz", hash = "sha256:3b8b373d4865671789cc43623e3dc0bcbf192562e40e13727e17f1c149050fba", size = 82367, upload-time = "2026-02-25T22:01:34.053Z" } 1272 - wheels = [ 1273 - { url = "https://files.pythonhosted.org/packages/11/52/6327a5f4fda01207205038a106a99848a41c83e933cd23ea2cab3d2ebc6c/nexus_rpc-1.4.0-py3-none-any.whl", hash = "sha256:14c953d3519113f8ccec533a9efdb6b10c28afef75d11cdd6d422640c40b3a49", size = 29645, upload-time = "2026-02-25T22:01:33.122Z" }, 1274 - ] 1275 - 1276 - [[package]] 1277 - name = "noti" 1278 - version = "0.1.0" 1279 - source = { editable = "." } 1280 - dependencies = [ 1281 - { name = "atproto" }, 1282 - { name = "fastapi" }, 1283 - { name = "jinja2" }, 1284 - { name = "pydantic-ai" }, 1285 - { name = "pydantic-settings" }, 1286 - { name = "uvicorn", extra = ["standard"] }, 1287 - ] 1288 - 1289 - [package.metadata] 1290 - requires-dist = [ 1291 - { name = "atproto", specifier = ">=0.0.65" }, 1292 - { name = "fastapi", specifier = ">=0.135.3" }, 1293 - { name = "jinja2", specifier = ">=3.1.6" }, 1294 - { name = "pydantic-ai", specifier = ">=1.81.0" }, 1295 - { name = "pydantic-settings", specifier = ">=2.13.1" }, 1296 - { name = "uvicorn", extras = ["standard"], specifier = ">=0.44.0" }, 1297 - ] 1298 - 1299 - [[package]] 1300 - name = "openai" 1301 - version = "2.31.0" 1302 - source = { registry = "https://pypi.org/simple" } 1303 - dependencies = [ 1304 - { name = "anyio" }, 1305 - { name = "distro" }, 1306 - { name = "httpx" }, 1307 - { name = "jiter" }, 1308 - { name = "pydantic" }, 1309 - { name = "sniffio" }, 1310 - { name = "tqdm" }, 1311 - { name = "typing-extensions" }, 1312 - ] 1313 - sdist = { url = "https://files.pythonhosted.org/packages/94/fe/64b3d035780b3188f86c4f6f1bc202e7bb74757ef028802112273b9dcacf/openai-2.31.0.tar.gz", hash = "sha256:43ca59a88fc973ad1848d86b98d7fac207e265ebbd1828b5e4bdfc85f79427a5", size = 684772, upload-time = "2026-04-08T21:01:41.797Z" } 1314 - wheels = [ 1315 - { url = "https://files.pythonhosted.org/packages/66/bc/a8f7c3aa03452fedbb9af8be83e959adba96a6b4a35e416faffcc959c568/openai-2.31.0-py3-none-any.whl", hash = "sha256:44e1344d87e56a493d649b17e2fac519d1368cbb0745f59f1957c4c26de50a0a", size = 1153479, upload-time = "2026-04-08T21:01:39.217Z" }, 1316 - ] 1317 - 1318 - [[package]] 1319 - name = "openapi-pydantic" 1320 - version = "0.5.1" 1321 - source = { registry = "https://pypi.org/simple" } 1322 - dependencies = [ 1323 - { name = "pydantic" }, 1324 - ] 1325 - sdist = { url = "https://files.pythonhosted.org/packages/02/2e/58d83848dd1a79cb92ed8e63f6ba901ca282c5f09d04af9423ec26c56fd7/openapi_pydantic-0.5.1.tar.gz", hash = "sha256:ff6835af6bde7a459fb93eb93bb92b8749b754fc6e51b2f1590a19dc3005ee0d", size = 60892, upload-time = "2025-01-08T19:29:27.083Z" } 1326 - wheels = [ 1327 - { url = "https://files.pythonhosted.org/packages/12/cf/03675d8bd8ecbf4445504d8071adab19f5f993676795708e36402ab38263/openapi_pydantic-0.5.1-py3-none-any.whl", hash = "sha256:a3a09ef4586f5bd760a8df7f43028b60cafb6d9f61de2acba9574766255ab146", size = 96381, upload-time = "2025-01-08T19:29:25.275Z" }, 1328 - ] 1329 - 1330 - [[package]] 1331 - name = "opentelemetry-api" 1332 - version = "1.39.1" 1333 - source = { registry = "https://pypi.org/simple" } 1334 - dependencies = [ 1335 - { name = "importlib-metadata" }, 1336 - { name = "typing-extensions" }, 1337 - ] 1338 - sdist = { url = "https://files.pythonhosted.org/packages/97/b9/3161be15bb8e3ad01be8be5a968a9237c3027c5be504362ff800fca3e442/opentelemetry_api-1.39.1.tar.gz", hash = "sha256:fbde8c80e1b937a2c61f20347e91c0c18a1940cecf012d62e65a7caf08967c9c", size = 65767, upload-time = "2025-12-11T13:32:39.182Z" } 1339 - wheels = [ 1340 - { url = "https://files.pythonhosted.org/packages/cf/df/d3f1ddf4bb4cb50ed9b1139cc7b1c54c34a1e7ce8fd1b9a37c0d1551a6bd/opentelemetry_api-1.39.1-py3-none-any.whl", hash = "sha256:2edd8463432a7f8443edce90972169b195e7d6a05500cd29e6d13898187c9950", size = 66356, upload-time = "2025-12-11T13:32:17.304Z" }, 1341 - ] 1342 - 1343 - [[package]] 1344 - name = "opentelemetry-exporter-otlp-proto-common" 1345 - version = "1.39.1" 1346 - source = { registry = "https://pypi.org/simple" } 1347 - dependencies = [ 1348 - { name = "opentelemetry-proto" }, 1349 - ] 1350 - sdist = { url = "https://files.pythonhosted.org/packages/e9/9d/22d241b66f7bbde88a3bfa6847a351d2c46b84de23e71222c6aae25c7050/opentelemetry_exporter_otlp_proto_common-1.39.1.tar.gz", hash = "sha256:763370d4737a59741c89a67b50f9e39271639ee4afc999dadfe768541c027464", size = 20409, upload-time = "2025-12-11T13:32:40.885Z" } 1351 - wheels = [ 1352 - { url = "https://files.pythonhosted.org/packages/8c/02/ffc3e143d89a27ac21fd557365b98bd0653b98de8a101151d5805b5d4c33/opentelemetry_exporter_otlp_proto_common-1.39.1-py3-none-any.whl", hash = "sha256:08f8a5862d64cc3435105686d0216c1365dc5701f86844a8cd56597d0c764fde", size = 18366, upload-time = "2025-12-11T13:32:20.2Z" }, 1353 - ] 1354 - 1355 - [[package]] 1356 - name = "opentelemetry-exporter-otlp-proto-http" 1357 - version = "1.39.1" 1358 - source = { registry = "https://pypi.org/simple" } 1359 - dependencies = [ 1360 - { name = "googleapis-common-protos" }, 1361 - { name = "opentelemetry-api" }, 1362 - { name = "opentelemetry-exporter-otlp-proto-common" }, 1363 - { name = "opentelemetry-proto" }, 1364 - { name = "opentelemetry-sdk" }, 1365 - { name = "requests" }, 1366 - { name = "typing-extensions" }, 1367 - ] 1368 - sdist = { url = "https://files.pythonhosted.org/packages/80/04/2a08fa9c0214ae38880df01e8bfae12b067ec0793446578575e5080d6545/opentelemetry_exporter_otlp_proto_http-1.39.1.tar.gz", hash = "sha256:31bdab9745c709ce90a49a0624c2bd445d31a28ba34275951a6a362d16a0b9cb", size = 17288, upload-time = "2025-12-11T13:32:42.029Z" } 1369 - wheels = [ 1370 - { url = "https://files.pythonhosted.org/packages/95/f1/b27d3e2e003cd9a3592c43d099d2ed8d0a947c15281bf8463a256db0b46c/opentelemetry_exporter_otlp_proto_http-1.39.1-py3-none-any.whl", hash = "sha256:d9f5207183dd752a412c4cd564ca8875ececba13be6e9c6c370ffb752fd59985", size = 19641, upload-time = "2025-12-11T13:32:22.248Z" }, 1371 - ] 1372 - 1373 - [[package]] 1374 - name = "opentelemetry-instrumentation" 1375 - version = "0.60b1" 1376 - source = { registry = "https://pypi.org/simple" } 1377 - dependencies = [ 1378 - { name = "opentelemetry-api" }, 1379 - { name = "opentelemetry-semantic-conventions" }, 1380 - { name = "packaging" }, 1381 - { name = "wrapt" }, 1382 - ] 1383 - sdist = { url = "https://files.pythonhosted.org/packages/41/0f/7e6b713ac117c1f5e4e3300748af699b9902a2e5e34c9cf443dde25a01fa/opentelemetry_instrumentation-0.60b1.tar.gz", hash = "sha256:57ddc7974c6eb35865af0426d1a17132b88b2ed8586897fee187fd5b8944bd6a", size = 31706, upload-time = "2025-12-11T13:36:42.515Z" } 1384 - wheels = [ 1385 - { url = "https://files.pythonhosted.org/packages/77/d2/6788e83c5c86a2690101681aeef27eeb2a6bf22df52d3f263a22cee20915/opentelemetry_instrumentation-0.60b1-py3-none-any.whl", hash = "sha256:04480db952b48fb1ed0073f822f0ee26012b7be7c3eac1a3793122737c78632d", size = 33096, upload-time = "2025-12-11T13:35:33.067Z" }, 1386 - ] 1387 - 1388 - [[package]] 1389 - name = "opentelemetry-instrumentation-httpx" 1390 - version = "0.60b1" 1391 - source = { registry = "https://pypi.org/simple" } 1392 - dependencies = [ 1393 - { name = "opentelemetry-api" }, 1394 - { name = "opentelemetry-instrumentation" }, 1395 - { name = "opentelemetry-semantic-conventions" }, 1396 - { name = "opentelemetry-util-http" }, 1397 - { name = "wrapt" }, 1398 - ] 1399 - sdist = { url = "https://files.pythonhosted.org/packages/86/08/11208bcfcab4fc2023252c3f322aa397fd9ad948355fea60f5fc98648603/opentelemetry_instrumentation_httpx-0.60b1.tar.gz", hash = "sha256:a506ebaf28c60112cbe70ad4f0338f8603f148938cb7b6794ce1051cd2b270ae", size = 20611, upload-time = "2025-12-11T13:37:01.661Z" } 1400 - wheels = [ 1401 - { url = "https://files.pythonhosted.org/packages/43/59/b98e84eebf745ffc75397eaad4763795bff8a30cbf2373a50ed4e70646c5/opentelemetry_instrumentation_httpx-0.60b1-py3-none-any.whl", hash = "sha256:f37636dd742ad2af83d896ba69601ed28da51fa4e25d1ab62fde89ce413e275b", size = 15701, upload-time = "2025-12-11T13:36:04.56Z" }, 1402 - ] 1403 - 1404 - [[package]] 1405 - name = "opentelemetry-proto" 1406 - version = "1.39.1" 1407 - source = { registry = "https://pypi.org/simple" } 1408 - dependencies = [ 1409 - { name = "protobuf" }, 1410 - ] 1411 - sdist = { url = "https://files.pythonhosted.org/packages/49/1d/f25d76d8260c156c40c97c9ed4511ec0f9ce353f8108ca6e7561f82a06b2/opentelemetry_proto-1.39.1.tar.gz", hash = "sha256:6c8e05144fc0d3ed4d22c2289c6b126e03bcd0e6a7da0f16cedd2e1c2772e2c8", size = 46152, upload-time = "2025-12-11T13:32:48.681Z" } 1412 - wheels = [ 1413 - { url = "https://files.pythonhosted.org/packages/51/95/b40c96a7b5203005a0b03d8ce8cd212ff23f1793d5ba289c87a097571b18/opentelemetry_proto-1.39.1-py3-none-any.whl", hash = "sha256:22cdc78efd3b3765d09e68bfbd010d4fc254c9818afd0b6b423387d9dee46007", size = 72535, upload-time = "2025-12-11T13:32:33.866Z" }, 1414 - ] 1415 - 1416 - [[package]] 1417 - name = "opentelemetry-sdk" 1418 - version = "1.39.1" 1419 - source = { registry = "https://pypi.org/simple" } 1420 - dependencies = [ 1421 - { name = "opentelemetry-api" }, 1422 - { name = "opentelemetry-semantic-conventions" }, 1423 - { name = "typing-extensions" }, 1424 - ] 1425 - sdist = { url = "https://files.pythonhosted.org/packages/eb/fb/c76080c9ba07e1e8235d24cdcc4d125ef7aa3edf23eb4e497c2e50889adc/opentelemetry_sdk-1.39.1.tar.gz", hash = "sha256:cf4d4563caf7bff906c9f7967e2be22d0d6b349b908be0d90fb21c8e9c995cc6", size = 171460, upload-time = "2025-12-11T13:32:49.369Z" } 1426 - wheels = [ 1427 - { url = "https://files.pythonhosted.org/packages/7c/98/e91cf858f203d86f4eccdf763dcf01cf03f1dae80c3750f7e635bfa206b6/opentelemetry_sdk-1.39.1-py3-none-any.whl", hash = "sha256:4d5482c478513ecb0a5d938dcc61394e647066e0cc2676bee9f3af3f3f45f01c", size = 132565, upload-time = "2025-12-11T13:32:35.069Z" }, 1428 - ] 1429 - 1430 - [[package]] 1431 - name = "opentelemetry-semantic-conventions" 1432 - version = "0.60b1" 1433 - source = { registry = "https://pypi.org/simple" } 1434 - dependencies = [ 1435 - { name = "opentelemetry-api" }, 1436 - { name = "typing-extensions" }, 1437 - ] 1438 - sdist = { url = "https://files.pythonhosted.org/packages/91/df/553f93ed38bf22f4b999d9be9c185adb558982214f33eae539d3b5cd0858/opentelemetry_semantic_conventions-0.60b1.tar.gz", hash = "sha256:87c228b5a0669b748c76d76df6c364c369c28f1c465e50f661e39737e84bc953", size = 137935, upload-time = "2025-12-11T13:32:50.487Z" } 1439 - wheels = [ 1440 - { url = "https://files.pythonhosted.org/packages/7a/5e/5958555e09635d09b75de3c4f8b9cae7335ca545d77392ffe7331534c402/opentelemetry_semantic_conventions-0.60b1-py3-none-any.whl", hash = "sha256:9fa8c8b0c110da289809292b0591220d3a7b53c1526a23021e977d68597893fb", size = 219982, upload-time = "2025-12-11T13:32:36.955Z" }, 1441 - ] 1442 - 1443 - [[package]] 1444 - name = "opentelemetry-util-http" 1445 - version = "0.60b1" 1446 - source = { registry = "https://pypi.org/simple" } 1447 - sdist = { url = "https://files.pythonhosted.org/packages/50/fc/c47bb04a1d8a941a4061307e1eddfa331ed4d0ab13d8a9781e6db256940a/opentelemetry_util_http-0.60b1.tar.gz", hash = "sha256:0d97152ca8c8a41ced7172d29d3622a219317f74ae6bb3027cfbdcf22c3cc0d6", size = 11053, upload-time = "2025-12-11T13:37:25.115Z" } 1448 - wheels = [ 1449 - { url = "https://files.pythonhosted.org/packages/16/5c/d3f1733665f7cd582ef0842fb1d2ed0bc1fba10875160593342d22bba375/opentelemetry_util_http-0.60b1-py3-none-any.whl", hash = "sha256:66381ba28550c91bee14dcba8979ace443444af1ed609226634596b4b0faf199", size = 8947, upload-time = "2025-12-11T13:36:37.151Z" }, 1450 - ] 1451 - 1452 - [[package]] 1453 - name = "packaging" 1454 - version = "25.0" 1455 - source = { registry = "https://pypi.org/simple" } 1456 - sdist = { url = "https://files.pythonhosted.org/packages/a1/d4/1fc4078c65507b51b96ca8f8c3ba19e6a61c8253c72794544580a7b6c24d/packaging-25.0.tar.gz", hash = "sha256:d443872c98d677bf60f6a1f2f8c1cb748e8fe762d2bf9d3148b5599295b0fc4f", size = 165727, upload-time = "2025-04-19T11:48:59.673Z" } 1457 - wheels = [ 1458 - { url = "https://files.pythonhosted.org/packages/20/12/38679034af332785aac8774540895e234f4d07f7545804097de4b666afd8/packaging-25.0-py3-none-any.whl", hash = "sha256:29572ef2b1f17581046b3a2227d5c611fb25ec70ca1ba8554b24b0e69331a484", size = 66469, upload-time = "2025-04-19T11:48:57.875Z" }, 1459 - ] 1460 - 1461 - [[package]] 1462 - name = "pathable" 1463 - version = "0.5.0" 1464 - source = { registry = "https://pypi.org/simple" } 1465 - sdist = { url = "https://files.pythonhosted.org/packages/72/55/b748445cb4ea6b125626f15379be7c96d1035d4fa3e8fee362fa92298abf/pathable-0.5.0.tar.gz", hash = "sha256:d81938348a1cacb525e7c75166270644782c0fb9c8cecc16be033e71427e0ef1", size = 16655, upload-time = "2026-02-20T08:47:00.748Z" } 1466 - wheels = [ 1467 - { url = "https://files.pythonhosted.org/packages/52/96/5a770e5c461462575474468e5af931cff9de036e7c2b4fea23c1c58d2cbe/pathable-0.5.0-py3-none-any.whl", hash = "sha256:646e3d09491a6351a0c82632a09c02cdf70a252e73196b36d8a15ba0a114f0a6", size = 16867, upload-time = "2026-02-20T08:46:59.536Z" }, 1468 - ] 1469 - 1470 - [[package]] 1471 - name = "platformdirs" 1472 - version = "4.9.6" 1473 - source = { registry = "https://pypi.org/simple" } 1474 - sdist = { url = "https://files.pythonhosted.org/packages/9f/4a/0883b8e3802965322523f0b200ecf33d31f10991d0401162f4b23c698b42/platformdirs-4.9.6.tar.gz", hash = "sha256:3bfa75b0ad0db84096ae777218481852c0ebc6c727b3168c1b9e0118e458cf0a", size = 29400, upload-time = "2026-04-09T00:04:10.812Z" } 1475 - wheels = [ 1476 - { url = "https://files.pythonhosted.org/packages/75/a6/a0a304dc33b49145b21f4808d763822111e67d1c3a32b524a1baf947b6e1/platformdirs-4.9.6-py3-none-any.whl", hash = "sha256:e61adb1d5e5cb3441b4b7710bea7e4c12250ca49439228cc1021c00dcfac0917", size = 21348, upload-time = "2026-04-09T00:04:09.463Z" }, 1477 - ] 1478 - 1479 - [[package]] 1480 - name = "prompt-toolkit" 1481 - version = "3.0.52" 1482 - source = { registry = "https://pypi.org/simple" } 1483 - dependencies = [ 1484 - { name = "wcwidth" }, 1485 - ] 1486 - sdist = { url = "https://files.pythonhosted.org/packages/a1/96/06e01a7b38dce6fe1db213e061a4602dd6032a8a97ef6c1a862537732421/prompt_toolkit-3.0.52.tar.gz", hash = "sha256:28cde192929c8e7321de85de1ddbe736f1375148b02f2e17edd840042b1be855", size = 434198, upload-time = "2025-08-27T15:24:02.057Z" } 1487 - wheels = [ 1488 - { url = "https://files.pythonhosted.org/packages/84/03/0d3ce49e2505ae70cf43bc5bb3033955d2fc9f932163e84dc0779cc47f48/prompt_toolkit-3.0.52-py3-none-any.whl", hash = "sha256:9aac639a3bbd33284347de5ad8d68ecc044b91a762dc39b7c21095fcd6a19955", size = 391431, upload-time = "2025-08-27T15:23:59.498Z" }, 1489 - ] 1490 - 1491 - [[package]] 1492 - name = "propcache" 1493 - version = "0.4.1" 1494 - source = { registry = "https://pypi.org/simple" } 1495 - sdist = { url = "https://files.pythonhosted.org/packages/9e/da/e9fc233cf63743258bff22b3dfa7ea5baef7b5bc324af47a0ad89b8ffc6f/propcache-0.4.1.tar.gz", hash = "sha256:f48107a8c637e80362555f37ecf49abe20370e557cc4ab374f04ec4423c97c3d", size = 46442, upload-time = "2025-10-08T19:49:02.291Z" } 1496 - wheels = [ 1497 - { url = "https://files.pythonhosted.org/packages/8e/5c/bca52d654a896f831b8256683457ceddd490ec18d9ec50e97dfd8fc726a8/propcache-0.4.1-cp314-cp314-macosx_10_13_universal2.whl", hash = "sha256:3f7124c9d820ba5548d431afb4632301acf965db49e666aa21c305cbe8c6de12", size = 78152, upload-time = "2025-10-08T19:47:51.051Z" }, 1498 - { url = "https://files.pythonhosted.org/packages/65/9b/03b04e7d82a5f54fb16113d839f5ea1ede58a61e90edf515f6577c66fa8f/propcache-0.4.1-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:c0d4b719b7da33599dfe3b22d3db1ef789210a0597bc650b7cee9c77c2be8c5c", size = 44869, upload-time = "2025-10-08T19:47:52.594Z" }, 1499 - { url = "https://files.pythonhosted.org/packages/b2/fa/89a8ef0468d5833a23fff277b143d0573897cf75bd56670a6d28126c7d68/propcache-0.4.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:9f302f4783709a78240ebc311b793f123328716a60911d667e0c036bc5dcbded", size = 46596, upload-time = "2025-10-08T19:47:54.073Z" }, 1500 - { url = "https://files.pythonhosted.org/packages/86/bd/47816020d337f4a746edc42fe8d53669965138f39ee117414c7d7a340cfe/propcache-0.4.1-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c80ee5802e3fb9ea37938e7eecc307fb984837091d5fd262bb37238b1ae97641", size = 206981, upload-time = "2025-10-08T19:47:55.715Z" }, 1501 - { url = "https://files.pythonhosted.org/packages/df/f6/c5fa1357cc9748510ee55f37173eb31bfde6d94e98ccd9e6f033f2fc06e1/propcache-0.4.1-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:ed5a841e8bb29a55fb8159ed526b26adc5bdd7e8bd7bf793ce647cb08656cdf4", size = 211490, upload-time = "2025-10-08T19:47:57.499Z" }, 1502 - { url = "https://files.pythonhosted.org/packages/80/1e/e5889652a7c4a3846683401a48f0f2e5083ce0ec1a8a5221d8058fbd1adf/propcache-0.4.1-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:55c72fd6ea2da4c318e74ffdf93c4fe4e926051133657459131a95c846d16d44", size = 215371, upload-time = "2025-10-08T19:47:59.317Z" }, 1503 - { url = "https://files.pythonhosted.org/packages/b2/f2/889ad4b2408f72fe1a4f6a19491177b30ea7bf1a0fd5f17050ca08cfc882/propcache-0.4.1-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8326e144341460402713f91df60ade3c999d601e7eb5ff8f6f7862d54de0610d", size = 201424, upload-time = "2025-10-08T19:48:00.67Z" }, 1504 - { url = "https://files.pythonhosted.org/packages/27/73/033d63069b57b0812c8bd19f311faebeceb6ba31b8f32b73432d12a0b826/propcache-0.4.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:060b16ae65bc098da7f6d25bf359f1f31f688384858204fe5d652979e0015e5b", size = 197566, upload-time = "2025-10-08T19:48:02.604Z" }, 1505 - { url = "https://files.pythonhosted.org/packages/dc/89/ce24f3dc182630b4e07aa6d15f0ff4b14ed4b9955fae95a0b54c58d66c05/propcache-0.4.1-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:89eb3fa9524f7bec9de6e83cf3faed9d79bffa560672c118a96a171a6f55831e", size = 193130, upload-time = "2025-10-08T19:48:04.499Z" }, 1506 - { url = "https://files.pythonhosted.org/packages/a9/24/ef0d5fd1a811fb5c609278d0209c9f10c35f20581fcc16f818da959fc5b4/propcache-0.4.1-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:dee69d7015dc235f526fe80a9c90d65eb0039103fe565776250881731f06349f", size = 202625, upload-time = "2025-10-08T19:48:06.213Z" }, 1507 - { url = "https://files.pythonhosted.org/packages/f5/02/98ec20ff5546f68d673df2f7a69e8c0d076b5abd05ca882dc7ee3a83653d/propcache-0.4.1-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:5558992a00dfd54ccbc64a32726a3357ec93825a418a401f5cc67df0ac5d9e49", size = 204209, upload-time = "2025-10-08T19:48:08.432Z" }, 1508 - { url = "https://files.pythonhosted.org/packages/a0/87/492694f76759b15f0467a2a93ab68d32859672b646aa8a04ce4864e7932d/propcache-0.4.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:c9b822a577f560fbd9554812526831712c1436d2c046cedee4c3796d3543b144", size = 197797, upload-time = "2025-10-08T19:48:09.968Z" }, 1509 - { url = "https://files.pythonhosted.org/packages/ee/36/66367de3575db1d2d3f3d177432bd14ee577a39d3f5d1b3d5df8afe3b6e2/propcache-0.4.1-cp314-cp314-win32.whl", hash = "sha256:ab4c29b49d560fe48b696cdcb127dd36e0bc2472548f3bf56cc5cb3da2b2984f", size = 38140, upload-time = "2025-10-08T19:48:11.232Z" }, 1510 - { url = "https://files.pythonhosted.org/packages/0c/2a/a758b47de253636e1b8aef181c0b4f4f204bf0dd964914fb2af90a95b49b/propcache-0.4.1-cp314-cp314-win_amd64.whl", hash = "sha256:5a103c3eb905fcea0ab98be99c3a9a5ab2de60228aa5aceedc614c0281cf6153", size = 41257, upload-time = "2025-10-08T19:48:12.707Z" }, 1511 - { url = "https://files.pythonhosted.org/packages/34/5e/63bd5896c3fec12edcbd6f12508d4890d23c265df28c74b175e1ef9f4f3b/propcache-0.4.1-cp314-cp314-win_arm64.whl", hash = "sha256:74c1fb26515153e482e00177a1ad654721bf9207da8a494a0c05e797ad27b992", size = 38097, upload-time = "2025-10-08T19:48:13.923Z" }, 1512 - { url = "https://files.pythonhosted.org/packages/99/85/9ff785d787ccf9bbb3f3106f79884a130951436f58392000231b4c737c80/propcache-0.4.1-cp314-cp314t-macosx_10_13_universal2.whl", hash = "sha256:824e908bce90fb2743bd6b59db36eb4f45cd350a39637c9f73b1c1ea66f5b75f", size = 81455, upload-time = "2025-10-08T19:48:15.16Z" }, 1513 - { url = "https://files.pythonhosted.org/packages/90/85/2431c10c8e7ddb1445c1f7c4b54d886e8ad20e3c6307e7218f05922cad67/propcache-0.4.1-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:c2b5e7db5328427c57c8e8831abda175421b709672f6cfc3d630c3b7e2146393", size = 46372, upload-time = "2025-10-08T19:48:16.424Z" }, 1514 - { url = "https://files.pythonhosted.org/packages/01/20/b0972d902472da9bcb683fa595099911f4d2e86e5683bcc45de60dd05dc3/propcache-0.4.1-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:6f6ff873ed40292cd4969ef5310179afd5db59fdf055897e282485043fc80ad0", size = 48411, upload-time = "2025-10-08T19:48:17.577Z" }, 1515 - { url = "https://files.pythonhosted.org/packages/e2/e3/7dc89f4f21e8f99bad3d5ddb3a3389afcf9da4ac69e3deb2dcdc96e74169/propcache-0.4.1-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:49a2dc67c154db2c1463013594c458881a069fcf98940e61a0569016a583020a", size = 275712, upload-time = "2025-10-08T19:48:18.901Z" }, 1516 - { url = "https://files.pythonhosted.org/packages/20/67/89800c8352489b21a8047c773067644e3897f02ecbbd610f4d46b7f08612/propcache-0.4.1-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:005f08e6a0529984491e37d8dbc3dd86f84bd78a8ceb5fa9a021f4c48d4984be", size = 273557, upload-time = "2025-10-08T19:48:20.762Z" }, 1517 - { url = "https://files.pythonhosted.org/packages/e2/a1/b52b055c766a54ce6d9c16d9aca0cad8059acd9637cdf8aa0222f4a026ef/propcache-0.4.1-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:5c3310452e0d31390da9035c348633b43d7e7feb2e37be252be6da45abd1abcc", size = 280015, upload-time = "2025-10-08T19:48:22.592Z" }, 1518 - { url = "https://files.pythonhosted.org/packages/48/c8/33cee30bd890672c63743049f3c9e4be087e6780906bfc3ec58528be59c1/propcache-0.4.1-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4c3c70630930447f9ef1caac7728c8ad1c56bc5015338b20fed0d08ea2480b3a", size = 262880, upload-time = "2025-10-08T19:48:23.947Z" }, 1519 - { url = "https://files.pythonhosted.org/packages/0c/b1/8f08a143b204b418285c88b83d00edbd61afbc2c6415ffafc8905da7038b/propcache-0.4.1-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:8e57061305815dfc910a3634dcf584f08168a8836e6999983569f51a8544cd89", size = 260938, upload-time = "2025-10-08T19:48:25.656Z" }, 1520 - { url = "https://files.pythonhosted.org/packages/cf/12/96e4664c82ca2f31e1c8dff86afb867348979eb78d3cb8546a680287a1e9/propcache-0.4.1-cp314-cp314t-musllinux_1_2_armv7l.whl", hash = "sha256:521a463429ef54143092c11a77e04056dd00636f72e8c45b70aaa3140d639726", size = 247641, upload-time = "2025-10-08T19:48:27.207Z" }, 1521 - { url = "https://files.pythonhosted.org/packages/18/ed/e7a9cfca28133386ba52278136d42209d3125db08d0a6395f0cba0c0285c/propcache-0.4.1-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:120c964da3fdc75e3731aa392527136d4ad35868cc556fd09bb6d09172d9a367", size = 262510, upload-time = "2025-10-08T19:48:28.65Z" }, 1522 - { url = "https://files.pythonhosted.org/packages/f5/76/16d8bf65e8845dd62b4e2b57444ab81f07f40caa5652b8969b87ddcf2ef6/propcache-0.4.1-cp314-cp314t-musllinux_1_2_s390x.whl", hash = "sha256:d8f353eb14ee3441ee844ade4277d560cdd68288838673273b978e3d6d2c8f36", size = 263161, upload-time = "2025-10-08T19:48:30.133Z" }, 1523 - { url = "https://files.pythonhosted.org/packages/e7/70/c99e9edb5d91d5ad8a49fa3c1e8285ba64f1476782fed10ab251ff413ba1/propcache-0.4.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:ab2943be7c652f09638800905ee1bab2c544e537edb57d527997a24c13dc1455", size = 257393, upload-time = "2025-10-08T19:48:31.567Z" }, 1524 - { url = "https://files.pythonhosted.org/packages/08/02/87b25304249a35c0915d236575bc3574a323f60b47939a2262b77632a3ee/propcache-0.4.1-cp314-cp314t-win32.whl", hash = "sha256:05674a162469f31358c30bcaa8883cb7829fa3110bf9c0991fe27d7896c42d85", size = 42546, upload-time = "2025-10-08T19:48:32.872Z" }, 1525 - { url = "https://files.pythonhosted.org/packages/cb/ef/3c6ecf8b317aa982f309835e8f96987466123c6e596646d4e6a1dfcd080f/propcache-0.4.1-cp314-cp314t-win_amd64.whl", hash = "sha256:990f6b3e2a27d683cb7602ed6c86f15ee6b43b1194736f9baaeb93d0016633b1", size = 46259, upload-time = "2025-10-08T19:48:34.226Z" }, 1526 - { url = "https://files.pythonhosted.org/packages/c4/2d/346e946d4951f37eca1e4f55be0f0174c52cd70720f84029b02f296f4a38/propcache-0.4.1-cp314-cp314t-win_arm64.whl", hash = "sha256:ecef2343af4cc68e05131e45024ba34f6095821988a9d0a02aa7c73fcc448aa9", size = 40428, upload-time = "2025-10-08T19:48:35.441Z" }, 1527 - { url = "https://files.pythonhosted.org/packages/5b/5a/bc7b4a4ef808fa59a816c17b20c4bef6884daebbdf627ff2a161da67da19/propcache-0.4.1-py3-none-any.whl", hash = "sha256:af2a6052aeb6cf17d3e46ee169099044fd8224cbaf75c76a2ef596e8163e2237", size = 13305, upload-time = "2025-10-08T19:49:00.792Z" }, 1528 - ] 1529 - 1530 - [[package]] 1531 - name = "protobuf" 1532 - version = "6.33.6" 1533 - source = { registry = "https://pypi.org/simple" } 1534 - sdist = { url = "https://files.pythonhosted.org/packages/66/70/e908e9c5e52ef7c3a6c7902c9dfbb34c7e29c25d2f81ade3856445fd5c94/protobuf-6.33.6.tar.gz", hash = "sha256:a6768d25248312c297558af96a9f9c929e8c4cee0659cb07e780731095f38135", size = 444531, upload-time = "2026-03-18T19:05:00.988Z" } 1535 - wheels = [ 1536 - { url = "https://files.pythonhosted.org/packages/fc/9f/2f509339e89cfa6f6a4c4ff50438db9ca488dec341f7e454adad60150b00/protobuf-6.33.6-cp310-abi3-win32.whl", hash = "sha256:7d29d9b65f8afef196f8334e80d6bc1d5d4adedb449971fefd3723824e6e77d3", size = 425739, upload-time = "2026-03-18T19:04:48.373Z" }, 1537 - { url = "https://files.pythonhosted.org/packages/76/5d/683efcd4798e0030c1bab27374fd13a89f7c2515fb1f3123efdfaa5eab57/protobuf-6.33.6-cp310-abi3-win_amd64.whl", hash = "sha256:0cd27b587afca21b7cfa59a74dcbd48a50f0a6400cfb59391340ad729d91d326", size = 437089, upload-time = "2026-03-18T19:04:50.381Z" }, 1538 - { url = "https://files.pythonhosted.org/packages/5c/01/a3c3ed5cd186f39e7880f8303cc51385a198a81469d53d0fdecf1f64d929/protobuf-6.33.6-cp39-abi3-macosx_10_9_universal2.whl", hash = "sha256:9720e6961b251bde64edfdab7d500725a2af5280f3f4c87e57c0208376aa8c3a", size = 427737, upload-time = "2026-03-18T19:04:51.866Z" }, 1539 - { url = "https://files.pythonhosted.org/packages/ee/90/b3c01fdec7d2f627b3a6884243ba328c1217ed2d978def5c12dc50d328a3/protobuf-6.33.6-cp39-abi3-manylinux2014_aarch64.whl", hash = "sha256:e2afbae9b8e1825e3529f88d514754e094278bb95eadc0e199751cdd9a2e82a2", size = 324610, upload-time = "2026-03-18T19:04:53.096Z" }, 1540 - { url = "https://files.pythonhosted.org/packages/9b/ca/25afc144934014700c52e05103c2421997482d561f3101ff352e1292fb81/protobuf-6.33.6-cp39-abi3-manylinux2014_s390x.whl", hash = "sha256:c96c37eec15086b79762ed265d59ab204dabc53056e3443e702d2681f4b39ce3", size = 339381, upload-time = "2026-03-18T19:04:54.616Z" }, 1541 - { url = "https://files.pythonhosted.org/packages/16/92/d1e32e3e0d894fe00b15ce28ad4944ab692713f2e7f0a99787405e43533a/protobuf-6.33.6-cp39-abi3-manylinux2014_x86_64.whl", hash = "sha256:e9db7e292e0ab79dd108d7f1a94fe31601ce1ee3f7b79e0692043423020b0593", size = 323436, upload-time = "2026-03-18T19:04:55.768Z" }, 1542 - { url = "https://files.pythonhosted.org/packages/c4/72/02445137af02769918a93807b2b7890047c32bfb9f90371cbc12688819eb/protobuf-6.33.6-py3-none-any.whl", hash = "sha256:77179e006c476e69bf8e8ce866640091ec42e1beb80b213c3900006ecfba6901", size = 170656, upload-time = "2026-03-18T19:04:59.826Z" }, 1543 - ] 1544 - 1545 - [[package]] 1546 - name = "py-key-value-aio" 1547 - version = "0.4.4" 1548 - source = { registry = "https://pypi.org/simple" } 1549 - dependencies = [ 1550 - { name = "beartype" }, 1551 - { name = "typing-extensions" }, 1552 - ] 1553 - sdist = { url = "https://files.pythonhosted.org/packages/04/3c/0397c072a38d4bc580994b42e0c90c5f44f679303489e4376289534735e5/py_key_value_aio-0.4.4.tar.gz", hash = "sha256:e3012e6243ed7cc09bb05457bd4d03b1ba5c2b1ca8700096b3927db79ffbbe55", size = 92300, upload-time = "2026-02-16T21:21:43.245Z" } 1554 - wheels = [ 1555 - { url = "https://files.pythonhosted.org/packages/32/69/f1b537ee70b7def42d63124a539ed3026a11a3ffc3086947a1ca6e861868/py_key_value_aio-0.4.4-py3-none-any.whl", hash = "sha256:18e17564ecae61b987f909fc2cd41ee2012c84b4b1dcb8c055cf8b4bc1bf3f5d", size = 152291, upload-time = "2026-02-16T21:21:44.241Z" }, 1556 - ] 1557 - 1558 - [package.optional-dependencies] 1559 - filetree = [ 1560 - { name = "aiofile" }, 1561 - { name = "anyio" }, 1562 - ] 1563 - keyring = [ 1564 - { name = "keyring" }, 1565 - ] 1566 - memory = [ 1567 - { name = "cachetools" }, 1568 - ] 1569 - 1570 - [[package]] 1571 - name = "pyasn1" 1572 - version = "0.6.3" 1573 - source = { registry = "https://pypi.org/simple" } 1574 - sdist = { url = "https://files.pythonhosted.org/packages/5c/5f/6583902b6f79b399c9c40674ac384fd9cd77805f9e6205075f828ef11fb2/pyasn1-0.6.3.tar.gz", hash = "sha256:697a8ecd6d98891189184ca1fa05d1bb00e2f84b5977c481452050549c8a72cf", size = 148685, upload-time = "2026-03-17T01:06:53.382Z" } 1575 - wheels = [ 1576 - { url = "https://files.pythonhosted.org/packages/5d/a0/7d793dce3fa811fe047d6ae2431c672364b462850c6235ae306c0efd025f/pyasn1-0.6.3-py3-none-any.whl", hash = "sha256:a80184d120f0864a52a073acc6fc642847d0be408e7c7252f31390c0f4eadcde", size = 83997, upload-time = "2026-03-17T01:06:52.036Z" }, 1577 - ] 1578 - 1579 - [[package]] 1580 - name = "pyasn1-modules" 1581 - version = "0.4.2" 1582 - source = { registry = "https://pypi.org/simple" } 1583 - dependencies = [ 1584 - { name = "pyasn1" }, 1585 - ] 1586 - sdist = { url = "https://files.pythonhosted.org/packages/e9/e6/78ebbb10a8c8e4b61a59249394a4a594c1a7af95593dc933a349c8d00964/pyasn1_modules-0.4.2.tar.gz", hash = "sha256:677091de870a80aae844b1ca6134f54652fa2c8c5a52aa396440ac3106e941e6", size = 307892, upload-time = "2025-03-28T02:41:22.17Z" } 1587 - wheels = [ 1588 - { url = "https://files.pythonhosted.org/packages/47/8d/d529b5d697919ba8c11ad626e835d4039be708a35b0d22de83a269a6682c/pyasn1_modules-0.4.2-py3-none-any.whl", hash = "sha256:29253a9207ce32b64c3ac6600edc75368f98473906e8fd1043bd6b5b1de2c14a", size = 181259, upload-time = "2025-03-28T02:41:19.028Z" }, 1589 - ] 1590 - 1591 - [[package]] 1592 - name = "pycparser" 1593 - version = "3.0" 1594 - source = { registry = "https://pypi.org/simple" } 1595 - sdist = { url = "https://files.pythonhosted.org/packages/1b/7d/92392ff7815c21062bea51aa7b87d45576f649f16458d78b7cf94b9ab2e6/pycparser-3.0.tar.gz", hash = "sha256:600f49d217304a5902ac3c37e1281c9fe94e4d0489de643a9504c5cdfdfc6b29", size = 103492, upload-time = "2026-01-21T14:26:51.89Z" } 1596 - wheels = [ 1597 - { url = "https://files.pythonhosted.org/packages/0c/c3/44f3fbbfa403ea2a7c779186dc20772604442dde72947e7d01069cbe98e3/pycparser-3.0-py3-none-any.whl", hash = "sha256:b727414169a36b7d524c1c3e31839a521725078d7b2ff038656844266160a992", size = 48172, upload-time = "2026-01-21T14:26:50.693Z" }, 1598 - ] 1599 - 1600 - [[package]] 1601 - name = "pydantic" 1602 - version = "2.13.0" 1603 - source = { registry = "https://pypi.org/simple" } 1604 - dependencies = [ 1605 - { name = "annotated-types" }, 1606 - { name = "pydantic-core" }, 1607 - { name = "typing-extensions" }, 1608 - { name = "typing-inspection" }, 1609 - ] 1610 - sdist = { url = "https://files.pythonhosted.org/packages/84/6b/69fd5c7194b21ebde0f8637e2a4ddc766ada29d472bfa6a5ca533d79549a/pydantic-2.13.0.tar.gz", hash = "sha256:b89b575b6e670ebf6e7448c01b41b244f471edd276cd0b6fe02e7e7aca320070", size = 843468, upload-time = "2026-04-13T10:51:35.571Z" } 1611 - wheels = [ 1612 - { url = "https://files.pythonhosted.org/packages/01/d7/c3a52c61f5b7be648e919005820fbac33028c6149994cd64453f49951c17/pydantic-2.13.0-py3-none-any.whl", hash = "sha256:ab0078b90da5f3e2fd2e71e3d9b457ddcb35d0350854fbda93b451e28d56baaf", size = 471872, upload-time = "2026-04-13T10:51:33.343Z" }, 1613 - ] 1614 - 1615 - [package.optional-dependencies] 1616 - email = [ 1617 - { name = "email-validator" }, 1618 - ] 1619 - 1620 - [[package]] 1621 - name = "pydantic-ai" 1622 - version = "1.81.0" 1623 - source = { registry = "https://pypi.org/simple" } 1624 - dependencies = [ 1625 - { name = "pydantic-ai-slim", extra = ["ag-ui", "anthropic", "bedrock", "cli", "cohere", "evals", "fastmcp", "google", "groq", "huggingface", "logfire", "mcp", "mistral", "openai", "retries", "spec", "temporal", "ui", "vertexai", "xai"] }, 1626 - ] 1627 - sdist = { url = "https://files.pythonhosted.org/packages/52/f1/612b9a742452614a5f28030f9a119999d139ba354171697c59bdbd6d85e6/pydantic_ai-1.81.0.tar.gz", hash = "sha256:2c860aafa9ab09dd88cf408c02f6bbde1aabe18e7c6bb59ebcc68fb3be27d22b", size = 12816, upload-time = "2026-04-14T01:47:56.682Z" } 1628 - wheels = [ 1629 - { url = "https://files.pythonhosted.org/packages/7a/93/fef51a92fb2481e5e1633b31ff6fceb1bdb7e2bf0265f9c56832f6182900/pydantic_ai-1.81.0-py3-none-any.whl", hash = "sha256:90a8d4aaf8497d2105c6ef6917865016a3602515c12fe0f4204fd7e64c57eb1c", size = 7576, upload-time = "2026-04-14T01:47:48.739Z" }, 1630 - ] 1631 - 1632 - [[package]] 1633 - name = "pydantic-ai-slim" 1634 - version = "1.81.0" 1635 - source = { registry = "https://pypi.org/simple" } 1636 - dependencies = [ 1637 - { name = "genai-prices" }, 1638 - { name = "griffelib" }, 1639 - { name = "httpx" }, 1640 - { name = "opentelemetry-api" }, 1641 - { name = "pydantic" }, 1642 - { name = "pydantic-graph" }, 1643 - { name = "typing-inspection" }, 1644 - ] 1645 - sdist = { url = "https://files.pythonhosted.org/packages/85/e9/8fbc609f28cb708bfcc5db7864d40bd4ac84f3e3780321ca50d7739f3c3d/pydantic_ai_slim-1.81.0.tar.gz", hash = "sha256:9895e2d3ae46b8e0342af5b862c987cfb86df87ef887dc8352e372b183db6c06", size = 550997, upload-time = "2026-04-14T01:47:58.757Z" } 1646 - wheels = [ 1647 - { url = "https://files.pythonhosted.org/packages/e4/81/dc7062fc325ebb448ed9311edfde895c009ddf3cbd3d65c815b8a52c4bf8/pydantic_ai_slim-1.81.0-py3-none-any.whl", hash = "sha256:1d3dd19a53bcdcc9baf75d7b60daee87a80f0647df221776a76b177f4526ed2a", size = 705019, upload-time = "2026-04-14T01:47:51.399Z" }, 1648 - ] 1649 - 1650 - [package.optional-dependencies] 1651 - ag-ui = [ 1652 - { name = "ag-ui-protocol" }, 1653 - { name = "starlette" }, 1654 - ] 1655 - anthropic = [ 1656 - { name = "anthropic" }, 1657 - ] 1658 - bedrock = [ 1659 - { name = "boto3" }, 1660 - ] 1661 - cli = [ 1662 - { name = "argcomplete" }, 1663 - { name = "prompt-toolkit" }, 1664 - { name = "pyperclip" }, 1665 - { name = "pyyaml" }, 1666 - { name = "rich" }, 1667 - ] 1668 - cohere = [ 1669 - { name = "cohere", marker = "sys_platform != 'emscripten'" }, 1670 - ] 1671 - evals = [ 1672 - { name = "pydantic-evals" }, 1673 - ] 1674 - fastmcp = [ 1675 - { name = "fastmcp" }, 1676 - ] 1677 - google = [ 1678 - { name = "google-genai" }, 1679 - ] 1680 - groq = [ 1681 - { name = "groq" }, 1682 - ] 1683 - huggingface = [ 1684 - { name = "huggingface-hub" }, 1685 - ] 1686 - logfire = [ 1687 - { name = "logfire", extra = ["httpx"] }, 1688 - ] 1689 - mcp = [ 1690 - { name = "mcp" }, 1691 - ] 1692 - mistral = [ 1693 - { name = "mistralai" }, 1694 - ] 1695 - openai = [ 1696 - { name = "openai" }, 1697 - { name = "tiktoken" }, 1698 - ] 1699 - retries = [ 1700 - { name = "tenacity" }, 1701 - ] 1702 - spec = [ 1703 - { name = "pydantic-handlebars" }, 1704 - { name = "pyyaml" }, 1705 - ] 1706 - temporal = [ 1707 - { name = "temporalio" }, 1708 - ] 1709 - ui = [ 1710 - { name = "starlette" }, 1711 - ] 1712 - vertexai = [ 1713 - { name = "google-auth" }, 1714 - { name = "requests" }, 1715 - ] 1716 - xai = [ 1717 - { name = "xai-sdk" }, 1718 - ] 1719 - 1720 - [[package]] 1721 - name = "pydantic-core" 1722 - version = "2.46.0" 1723 - source = { registry = "https://pypi.org/simple" } 1724 - dependencies = [ 1725 - { name = "typing-extensions" }, 1726 - ] 1727 - sdist = { url = "https://files.pythonhosted.org/packages/6f/0a/9414cddf82eda3976b14048cc0fa8f5b5d1aecb0b22e1dcd2dbfe0e139b1/pydantic_core-2.46.0.tar.gz", hash = "sha256:82d2498c96be47b47e903e1378d1d0f770097ec56ea953322f39936a7cf34977", size = 471441, upload-time = "2026-04-13T09:06:33.813Z" } 1728 - wheels = [ 1729 - { url = "https://files.pythonhosted.org/packages/36/3b/914891d384cdbf9a6f464eb13713baa22ea1e453d4da80fb7da522079370/pydantic_core-2.46.0-cp314-cp314-macosx_10_12_x86_64.whl", hash = "sha256:4fc801c290342350ffc82d77872054a934b2e24163727263362170c1db5416ca", size = 2113349, upload-time = "2026-04-13T09:04:59.407Z" }, 1730 - { url = "https://files.pythonhosted.org/packages/35/95/3a0c6f65e231709fb3463e32943c69d10285cb50203a2130a4732053a06d/pydantic_core-2.46.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:0a36f2cc88170cc177930afcc633a8c15907ea68b59ac16bd180c2999d714940", size = 1949170, upload-time = "2026-04-13T09:06:09.935Z" }, 1731 - { url = "https://files.pythonhosted.org/packages/d1/63/d845c36a608469fe7bee226edeff0984c33dbfe7aecd755b0e7ab5a275c4/pydantic_core-2.46.0-cp314-cp314-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2a3912e0c568a1f99d4d6d3e41def40179d61424c0ca1c8c87c4877d7f6fd7fb", size = 1977914, upload-time = "2026-04-13T09:04:56.16Z" }, 1732 - { url = "https://files.pythonhosted.org/packages/08/6f/f2e7a7f85931fb31671f5378d1c7fc70606e4b36d59b1b48e1bd1ef5d916/pydantic_core-2.46.0-cp314-cp314-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:3534c3415ed1a19ab23096b628916a827f7858ec8db49ad5d7d1e44dc13c0d7b", size = 2050538, upload-time = "2026-04-13T09:05:06.789Z" }, 1733 - { url = "https://files.pythonhosted.org/packages/8c/97/f4aa7181dd9a16dd9059a99fc48fdab0c2aab68307283a5c04cf56de68c4/pydantic_core-2.46.0-cp314-cp314-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:21067396fc285609323a4db2f63a87570044abe0acddfcca8b135fc7948e3db7", size = 2236294, upload-time = "2026-04-13T09:07:03.2Z" }, 1734 - { url = "https://files.pythonhosted.org/packages/24/c1/6a5042fc32765c87101b500f394702890af04239c318b6002cfd627b710d/pydantic_core-2.46.0-cp314-cp314-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2afd85b7be186e2fe7cdbb09a3d964bcc2042f65bbcc64ad800b3c7915032655", size = 2312954, upload-time = "2026-04-13T09:06:11.919Z" }, 1735 - { url = "https://files.pythonhosted.org/packages/cb/e4/566101a561492ce8454f0844ca29c3b675a6b3a7b3ff577db85ed05c8c50/pydantic_core-2.46.0-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:67e2c2e171b78db8154da602de72ffdc473c6ee51de8a9d80c0f1cd4051abfc7", size = 2102533, upload-time = "2026-04-13T09:06:58.664Z" }, 1736 - { url = "https://files.pythonhosted.org/packages/3e/ac/adc11ee1646a5c4dd9abb09a00e7909e6dc25beddc0b1310ca734bb9b48e/pydantic_core-2.46.0-cp314-cp314-manylinux_2_31_riscv64.whl", hash = "sha256:c16ae1f3170267b1a37e16dba5c297bdf60c8b5657b147909ca8774ce7366644", size = 2169447, upload-time = "2026-04-13T09:04:11.143Z" }, 1737 - { url = "https://files.pythonhosted.org/packages/26/73/408e686b45b82d28ac19e8229e07282254dbee6a5d24c5c7cf3cf3716613/pydantic_core-2.46.0-cp314-cp314-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:133b69e1c1ba34d3702eed73f19f7f966928f9aa16663b55c2ebce0893cca42e", size = 2200672, upload-time = "2026-04-13T09:03:54.056Z" }, 1738 - { url = "https://files.pythonhosted.org/packages/0a/3b/807d5b035ec891b57b9079ce881f48263936c37bd0d154a056e7fd152afb/pydantic_core-2.46.0-cp314-cp314-musllinux_1_1_aarch64.whl", hash = "sha256:15ed8e5bde505133d96b41702f31f06829c46b05488211a5b1c7877e11de5eb5", size = 2188293, upload-time = "2026-04-13T09:07:07.614Z" }, 1739 - { url = "https://files.pythonhosted.org/packages/f1/ed/719b307516285099d1196c52769fdbe676fd677da007b9c349ae70b7226d/pydantic_core-2.46.0-cp314-cp314-musllinux_1_1_armv7l.whl", hash = "sha256:8cfc29a1c66a7f0fcb36262e92f353dd0b9c4061d558fceb022e698a801cb8ae", size = 2335023, upload-time = "2026-04-13T09:04:05.176Z" }, 1740 - { url = "https://files.pythonhosted.org/packages/8d/90/8718e4ae98c4e8a7325afdc079be82be1e131d7a47cb6c098844a9531ffe/pydantic_core-2.46.0-cp314-cp314-musllinux_1_1_x86_64.whl", hash = "sha256:e1155708540f13845bf68d5ac511a55c76cfe2e057ed12b4bf3adac1581fc5c2", size = 2377155, upload-time = "2026-04-13T09:06:18.081Z" }, 1741 - { url = "https://files.pythonhosted.org/packages/dd/dc/7172789283b963f81da2fc92b186e22de55687019079f71c4d570822502b/pydantic_core-2.46.0-cp314-cp314-win32.whl", hash = "sha256:de5635a48df6b2eef161d10ea1bc2626153197333662ba4cd700ee7ec1aba7f5", size = 1963078, upload-time = "2026-04-13T09:05:30.615Z" }, 1742 - { url = "https://files.pythonhosted.org/packages/e0/69/03a7ea4b6264def3a44eabf577528bcec2f49468c5698b2044dea54dc07e/pydantic_core-2.46.0-cp314-cp314-win_amd64.whl", hash = "sha256:f07a5af60c5e7cf53dd1ff734228bd72d0dc9938e64a75b5bb308ca350d9681e", size = 2068439, upload-time = "2026-04-13T09:04:57.729Z" }, 1743 - { url = "https://files.pythonhosted.org/packages/f5/eb/1c3afcfdee2ab6634b802ab0a0f1966df4c8b630028ec56a1cb0a710dc58/pydantic_core-2.46.0-cp314-cp314-win_arm64.whl", hash = "sha256:e7a77eca3c7d5108ff509db20aae6f80d47c7ed7516d8b96c387aacc42f3ce0f", size = 2026470, upload-time = "2026-04-13T09:05:08.654Z" }, 1744 - { url = "https://files.pythonhosted.org/packages/5c/30/1177dde61b200785c4739665e3aa03a9d4b2c25d2d0408b07d585e633965/pydantic_core-2.46.0-cp314-cp314t-macosx_10_12_x86_64.whl", hash = "sha256:5e7cdd4398bee1aaeafe049ac366b0f887451d9ae418fd8785219c13fea2f928", size = 2107447, upload-time = "2026-04-13T09:05:46.314Z" }, 1745 - { url = "https://files.pythonhosted.org/packages/b1/60/4e0f61f99bdabbbc309d364a2791e1ba31e778a4935bc43391a7bdec0744/pydantic_core-2.46.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:5c2c92d82808e27cef3f7ab3ed63d657d0c755e0dbe5b8a58342e37bdf09bd2e", size = 1926927, upload-time = "2026-04-13T09:06:20.371Z" }, 1746 - { url = "https://files.pythonhosted.org/packages/1d/d0/67f89a8269152c1d6eaa81f04e75a507372ebd8ca7382855a065222caa80/pydantic_core-2.46.0-cp314-cp314t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0bab80af91cd7014b45d1089303b5f844a9d91d7da60eabf3d5f9694b32a6655", size = 1966613, upload-time = "2026-04-13T09:07:05.389Z" }, 1747 - { url = "https://files.pythonhosted.org/packages/cd/07/8dfdc3edc78f29a80fb31f366c50203ec904cff6a4c923599bf50ac0d0ff/pydantic_core-2.46.0-cp314-cp314t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:1e49ffdb714bc990f00b39d1ad1d683033875b5af15582f60c1f34ad3eeccfaa", size = 2032902, upload-time = "2026-04-13T09:06:42.47Z" }, 1748 - { url = "https://files.pythonhosted.org/packages/b0/2a/111c5e8fe24f99c46bcad7d3a82a8f6dbc738066e2c72c04c71f827d8c78/pydantic_core-2.46.0-cp314-cp314t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ca877240e8dbdeef3a66f751dc41e5a74893767d510c22a22fc5c0199844f0ce", size = 2244456, upload-time = "2026-04-13T09:05:36.484Z" }, 1749 - { url = "https://files.pythonhosted.org/packages/6b/7c/cfc5d11c15a63ece26e148572c77cfbb2c7f08d315a7b63ef0fe0711d753/pydantic_core-2.46.0-cp314-cp314t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:87e6843f89ecd2f596d7294e33196c61343186255b9880c4f1b725fde8b0e20d", size = 2294535, upload-time = "2026-04-13T09:06:01.689Z" }, 1750 - { url = "https://files.pythonhosted.org/packages/c4/2c/f0d744e3dab7bd026a3f4670a97a295157cff923a2666d30a15a70a7e3d0/pydantic_core-2.46.0-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e20bc5add1dd9bc3b9a3600d40632e679376569098345500799a6ad7c5d46c72", size = 2104621, upload-time = "2026-04-13T09:04:34.388Z" }, 1751 - { url = "https://files.pythonhosted.org/packages/a7/64/e7cc4698dc024264d214b51d5a47a2404221b12060dd537d76f831b2120a/pydantic_core-2.46.0-cp314-cp314t-manylinux_2_31_riscv64.whl", hash = "sha256:ee6ff79a5f0289d64a9d6696a3ce1f98f925b803dd538335a118231e26d6d827", size = 2130718, upload-time = "2026-04-13T09:04:26.23Z" }, 1752 - { url = "https://files.pythonhosted.org/packages/0b/a8/224e655fec21f7d4441438ad2ecaccb33b5a3876ce7bb2098c74a49efc14/pydantic_core-2.46.0-cp314-cp314t-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:52d35cfb58c26323101c7065508d7bb69bb56338cda9ea47a7b32be581af055d", size = 2180738, upload-time = "2026-04-13T09:05:50.253Z" }, 1753 - { url = "https://files.pythonhosted.org/packages/32/7b/b3025618ed4c4e4cbaa9882731c19625db6669896b621760ea95bc1125ef/pydantic_core-2.46.0-cp314-cp314t-musllinux_1_1_aarch64.whl", hash = "sha256:d14cc5a6f260fa78e124061eebc5769af6534fc837e9a62a47f09a2c341fa4ea", size = 2171222, upload-time = "2026-04-13T09:07:29.929Z" }, 1754 - { url = "https://files.pythonhosted.org/packages/7b/e3/68170aa1d891920af09c1f2f34df61dc5ff3a746400027155523e3400e89/pydantic_core-2.46.0-cp314-cp314t-musllinux_1_1_armv7l.whl", hash = "sha256:4f7ff859d663b6635f6307a10803d07f0d09487e16c3d36b1744af51dbf948b2", size = 2320040, upload-time = "2026-04-13T09:06:35.732Z" }, 1755 - { url = "https://files.pythonhosted.org/packages/67/1b/5e65807001b84972476300c1f49aea2b4971b7e9fffb5c2654877dadd274/pydantic_core-2.46.0-cp314-cp314t-musllinux_1_1_x86_64.whl", hash = "sha256:8ef749be6ed0d69dba31902aaa8255a9bb269ae50c93888c4df242d8bb7acd9e", size = 2377062, upload-time = "2026-04-13T09:07:39.945Z" }, 1756 - { url = "https://files.pythonhosted.org/packages/75/03/48caa9dd5f28f7662bd52bff454d9a451f6b7e5e4af95e289e5e170749c9/pydantic_core-2.46.0-cp314-cp314t-win32.whl", hash = "sha256:d93ca72870133f86360e4bb0c78cd4e6ba2a0f9f3738a6486909ffc031463b32", size = 1951028, upload-time = "2026-04-13T09:04:20.224Z" }, 1757 - { url = "https://files.pythonhosted.org/packages/87/ed/e97ff55fe28c0e6e3cba641d622b15e071370b70e5f07c496b07b65db7c9/pydantic_core-2.46.0-cp314-cp314t-win_amd64.whl", hash = "sha256:6ebb2668afd657e2127cb40f2ceb627dd78e74e9dfde14d9bf6cdd532a29ff59", size = 2048519, upload-time = "2026-04-13T09:05:10.464Z" }, 1758 - { url = "https://files.pythonhosted.org/packages/b6/51/e0db8267a287994546925f252e329eeae4121b1e77e76353418da5a3adf0/pydantic_core-2.46.0-cp314-cp314t-win_arm64.whl", hash = "sha256:4864f5bbb7993845baf9209bae1669a8a76769296a018cb569ebda9dcb4241f5", size = 2026791, upload-time = "2026-04-13T09:04:37.724Z" }, 1759 - ] 1760 - 1761 - [[package]] 1762 - name = "pydantic-evals" 1763 - version = "1.81.0" 1764 - source = { registry = "https://pypi.org/simple" } 1765 - dependencies = [ 1766 - { name = "anyio" }, 1767 - { name = "logfire-api" }, 1768 - { name = "pydantic" }, 1769 - { name = "pydantic-ai-slim" }, 1770 - { name = "pyyaml" }, 1771 - { name = "rich" }, 1772 - ] 1773 - sdist = { url = "https://files.pythonhosted.org/packages/5e/47/8e1bc88e1fce1a636c4d4bc5fe883f37d2737f9c03efcd7aeb9238ebf07c/pydantic_evals-1.81.0.tar.gz", hash = "sha256:70fd1d9a1e8c17b8e45affd635427f05e2d1e92111d98287b3bd1393d32a65b3", size = 65783, upload-time = "2026-04-14T01:48:00.03Z" } 1774 - wheels = [ 1775 - { url = "https://files.pythonhosted.org/packages/8e/8e/9bee73a780b769c1cb0fa4ad88f22f0dc27d2231358e0ad727b0a9c2f036/pydantic_evals-1.81.0-py3-none-any.whl", hash = "sha256:49516ddadd8064365684cdb4bdfda2e5f1d9b786bebae239d84445727d1e3f26", size = 77710, upload-time = "2026-04-14T01:47:53.166Z" }, 1776 - ] 1777 - 1778 - [[package]] 1779 - name = "pydantic-graph" 1780 - version = "1.81.0" 1781 - source = { registry = "https://pypi.org/simple" } 1782 - dependencies = [ 1783 - { name = "httpx" }, 1784 - { name = "logfire-api" }, 1785 - { name = "pydantic" }, 1786 - { name = "typing-inspection" }, 1787 - ] 1788 - sdist = { url = "https://files.pythonhosted.org/packages/bd/69/c38ea1c4c8b9789ce1777b408b9fe0f889638415cd97f53a9f95ffbbb044/pydantic_graph-1.81.0.tar.gz", hash = "sha256:721b33324dc25b2ce5956fed8a362e8c558163b45d39e9f83e8ea7b5d44743c0", size = 59240, upload-time = "2026-04-14T01:48:00.99Z" } 1789 - wheels = [ 1790 - { url = "https://files.pythonhosted.org/packages/4c/bd/9b0561bea26a9918c819b391c3de08cdaa9ef99bab9b4fba8f557df70503/pydantic_graph-1.81.0-py3-none-any.whl", hash = "sha256:9f6256612323d9708b2a3a140db3a3e8ee2fe30c3f1befa897ea473e82cc0faa", size = 73063, upload-time = "2026-04-14T01:47:54.631Z" }, 1791 - ] 1792 - 1793 - [[package]] 1794 - name = "pydantic-handlebars" 1795 - version = "0.1.0" 1796 - source = { registry = "https://pypi.org/simple" } 1797 - dependencies = [ 1798 - { name = "pydantic" }, 1799 - ] 1800 - sdist = { url = "https://files.pythonhosted.org/packages/90/16/d41768bd3fd77e6250c20be11a3e68fee5fff07c3356455e6708f6a60f2a/pydantic_handlebars-0.1.0.tar.gz", hash = "sha256:1931c54946add1b5e3796c9bf6a005ed7662cef0109bb05c352f0b3d031a1260", size = 159826, upload-time = "2026-03-01T20:00:17.497Z" } 1801 - wheels = [ 1802 - { url = "https://files.pythonhosted.org/packages/99/5f/86b1630be61bdebf253c2f953a6c3f073ec21bb0725565ea3896802e1ca3/pydantic_handlebars-0.1.0-py3-none-any.whl", hash = "sha256:8a436fe8bc607295eb04bec58bd6e2c9498c9e069c557ff0b505e3d568c783bc", size = 40890, upload-time = "2026-03-01T20:00:16.106Z" }, 1803 - ] 1804 - 1805 - [[package]] 1806 - name = "pydantic-settings" 1807 - version = "2.13.1" 1808 - source = { registry = "https://pypi.org/simple" } 1809 - dependencies = [ 1810 - { name = "pydantic" }, 1811 - { name = "python-dotenv" }, 1812 - { name = "typing-inspection" }, 1813 - ] 1814 - 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" } 1815 - wheels = [ 1816 - { 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" }, 1817 - ] 1818 - 1819 - [[package]] 1820 - name = "pygments" 1821 - version = "2.20.0" 1822 - source = { registry = "https://pypi.org/simple" } 1823 - sdist = { url = "https://files.pythonhosted.org/packages/c3/b2/bc9c9196916376152d655522fdcebac55e66de6603a76a02bca1b6414f6c/pygments-2.20.0.tar.gz", hash = "sha256:6757cd03768053ff99f3039c1a36d6c0aa0b263438fcab17520b30a303a82b5f", size = 4955991, upload-time = "2026-03-29T13:29:33.898Z" } 1824 - wheels = [ 1825 - { url = "https://files.pythonhosted.org/packages/f4/7e/a72dd26f3b0f4f2bf1dd8923c85f7ceb43172af56d63c7383eb62b332364/pygments-2.20.0-py3-none-any.whl", hash = "sha256:81a9e26dd42fd28a23a2d169d86d7ac03b46e2f8b59ed4698fb4785f946d0176", size = 1231151, upload-time = "2026-03-29T13:29:30.038Z" }, 1826 - ] 1827 - 1828 - [[package]] 1829 - name = "pyjwt" 1830 - version = "2.12.1" 1831 - source = { registry = "https://pypi.org/simple" } 1832 - sdist = { url = "https://files.pythonhosted.org/packages/c2/27/a3b6e5bf6ff856d2509292e95c8f57f0df7017cf5394921fc4e4ef40308a/pyjwt-2.12.1.tar.gz", hash = "sha256:c74a7a2adf861c04d002db713dd85f84beb242228e671280bf709d765b03672b", size = 102564, upload-time = "2026-03-13T19:27:37.25Z" } 1833 - wheels = [ 1834 - { url = "https://files.pythonhosted.org/packages/e5/7a/8dd906bd22e79e47397a61742927f6747fe93242ef86645ee9092e610244/pyjwt-2.12.1-py3-none-any.whl", hash = "sha256:28ca37c070cad8ba8cd9790cd940535d40274d22f80ab87f3ac6a713e6e8454c", size = 29726, upload-time = "2026-03-13T19:27:35.677Z" }, 1835 - ] 1836 - 1837 - [package.optional-dependencies] 1838 - crypto = [ 1839 - { name = "cryptography" }, 1840 - ] 1841 - 1842 - [[package]] 1843 - name = "pyperclip" 1844 - version = "1.11.0" 1845 - source = { registry = "https://pypi.org/simple" } 1846 - sdist = { url = "https://files.pythonhosted.org/packages/e8/52/d87eba7cb129b81563019d1679026e7a112ef76855d6159d24754dbd2a51/pyperclip-1.11.0.tar.gz", hash = "sha256:244035963e4428530d9e3a6101a1ef97209c6825edab1567beac148ccc1db1b6", size = 12185, upload-time = "2025-09-26T14:40:37.245Z" } 1847 - wheels = [ 1848 - { url = "https://files.pythonhosted.org/packages/df/80/fc9d01d5ed37ba4c42ca2b55b4339ae6e200b456be3a1aaddf4a9fa99b8c/pyperclip-1.11.0-py3-none-any.whl", hash = "sha256:299403e9ff44581cb9ba2ffeed69c7aa96a008622ad0c46cb575ca75b5b84273", size = 11063, upload-time = "2025-09-26T14:40:36.069Z" }, 1849 - ] 1850 - 1851 - [[package]] 1852 - name = "python-dateutil" 1853 - version = "2.9.0.post0" 1854 - source = { registry = "https://pypi.org/simple" } 1855 - dependencies = [ 1856 - { name = "six" }, 1857 - ] 1858 - sdist = { url = "https://files.pythonhosted.org/packages/66/c0/0c8b6ad9f17a802ee498c46e004a0eb49bc148f2fd230864601a86dcf6db/python-dateutil-2.9.0.post0.tar.gz", hash = "sha256:37dd54208da7e1cd875388217d5e00ebd4179249f90fb72437e91a35459a0ad3", size = 342432, upload-time = "2024-03-01T18:36:20.211Z" } 1859 - wheels = [ 1860 - { 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" }, 1861 - ] 1862 - 1863 - [[package]] 1864 - name = "python-dotenv" 1865 - version = "1.2.2" 1866 - source = { registry = "https://pypi.org/simple" } 1867 - sdist = { url = "https://files.pythonhosted.org/packages/82/ed/0301aeeac3e5353ef3d94b6ec08bbcabd04a72018415dcb29e588514bba8/python_dotenv-1.2.2.tar.gz", hash = "sha256:2c371a91fbd7ba082c2c1dc1f8bf89ca22564a087c2c287cd9b662adde799cf3", size = 50135, upload-time = "2026-03-01T16:00:26.196Z" } 1868 - wheels = [ 1869 - { url = "https://files.pythonhosted.org/packages/0b/d7/1959b9648791274998a9c3526f6d0ec8fd2233e4d4acce81bbae76b44b2a/python_dotenv-1.2.2-py3-none-any.whl", hash = "sha256:1d8214789a24de455a8b8bd8ae6fe3c6b69a5e3d64aa8a8e5d68e694bbcb285a", size = 22101, upload-time = "2026-03-01T16:00:25.09Z" }, 1870 - ] 1871 - 1872 - [[package]] 1873 - name = "python-multipart" 1874 - version = "0.0.26" 1875 - source = { registry = "https://pypi.org/simple" } 1876 - sdist = { url = "https://files.pythonhosted.org/packages/88/71/b145a380824a960ebd60e1014256dbb7d2253f2316ff2d73dfd8928ec2c3/python_multipart-0.0.26.tar.gz", hash = "sha256:08fadc45918cd615e26846437f50c5d6d23304da32c341f289a617127b081f17", size = 43501, upload-time = "2026-04-10T14:09:59.473Z" } 1877 - wheels = [ 1878 - { url = "https://files.pythonhosted.org/packages/9a/22/f1925cdda983ab66fc8ec6ec8014b959262747e58bdca26a4e3d1da29d56/python_multipart-0.0.26-py3-none-any.whl", hash = "sha256:c0b169f8c4484c13b0dcf2ef0ec3a4adb255c4b7d18d8e420477d2b1dd03f185", size = 28847, upload-time = "2026-04-10T14:09:58.131Z" }, 1879 - ] 1880 - 1881 - [[package]] 1882 - name = "pywin32" 1883 - version = "311" 1884 - source = { registry = "https://pypi.org/simple" } 1885 - wheels = [ 1886 - { url = "https://files.pythonhosted.org/packages/c9/31/097f2e132c4f16d99a22bfb777e0fd88bd8e1c634304e102f313af69ace5/pywin32-311-cp314-cp314-win32.whl", hash = "sha256:b7a2c10b93f8986666d0c803ee19b5990885872a7de910fc460f9b0c2fbf92ee", size = 8840714, upload-time = "2025-07-14T20:13:32.449Z" }, 1887 - { url = "https://files.pythonhosted.org/packages/90/4b/07c77d8ba0e01349358082713400435347df8426208171ce297da32c313d/pywin32-311-cp314-cp314-win_amd64.whl", hash = "sha256:3aca44c046bd2ed8c90de9cb8427f581c479e594e99b5c0bb19b29c10fd6cb87", size = 9656800, upload-time = "2025-07-14T20:13:34.312Z" }, 1888 - { url = "https://files.pythonhosted.org/packages/c0/d2/21af5c535501a7233e734b8af901574572da66fcc254cb35d0609c9080dd/pywin32-311-cp314-cp314-win_arm64.whl", hash = "sha256:a508e2d9025764a8270f93111a970e1d0fbfc33f4153b388bb649b7eec4f9b42", size = 8932540, upload-time = "2025-07-14T20:13:36.379Z" }, 1889 - ] 1890 - 1891 - [[package]] 1892 - name = "pywin32-ctypes" 1893 - version = "0.2.3" 1894 - source = { registry = "https://pypi.org/simple" } 1895 - sdist = { url = "https://files.pythonhosted.org/packages/85/9f/01a1a99704853cb63f253eea009390c88e7131c67e66a0a02099a8c917cb/pywin32-ctypes-0.2.3.tar.gz", hash = "sha256:d162dc04946d704503b2edc4d55f3dba5c1d539ead017afa00142c38b9885755", size = 29471, upload-time = "2024-08-14T10:15:34.626Z" } 1896 - wheels = [ 1897 - { url = "https://files.pythonhosted.org/packages/de/3d/8161f7711c017e01ac9f008dfddd9410dff3674334c233bde66e7ba65bbf/pywin32_ctypes-0.2.3-py3-none-any.whl", hash = "sha256:8a1513379d709975552d202d942d9837758905c8d01eb82b8bcc30918929e7b8", size = 30756, upload-time = "2024-08-14T10:15:33.187Z" }, 1898 - ] 1899 - 1900 - [[package]] 1901 - name = "pyyaml" 1902 - version = "6.0.3" 1903 - source = { registry = "https://pypi.org/simple" } 1904 - sdist = { url = "https://files.pythonhosted.org/packages/05/8e/961c0007c59b8dd7729d542c61a4d537767a59645b82a0b521206e1e25c2/pyyaml-6.0.3.tar.gz", hash = "sha256:d76623373421df22fb4cf8817020cbb7ef15c725b9d5e45f17e189bfc384190f", size = 130960, upload-time = "2025-09-25T21:33:16.546Z" } 1905 - wheels = [ 1906 - { url = "https://files.pythonhosted.org/packages/9d/8c/f4bd7f6465179953d3ac9bc44ac1a8a3e6122cf8ada906b4f96c60172d43/pyyaml-6.0.3-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:8d1fab6bb153a416f9aeb4b8763bc0f22a5586065f86f7664fc23339fc1c1fac", size = 181814, upload-time = "2025-09-25T21:32:35.712Z" }, 1907 - { url = "https://files.pythonhosted.org/packages/bd/9c/4d95bb87eb2063d20db7b60faa3840c1b18025517ae857371c4dd55a6b3a/pyyaml-6.0.3-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:34d5fcd24b8445fadc33f9cf348c1047101756fd760b4dacb5c3e99755703310", size = 173809, upload-time = "2025-09-25T21:32:36.789Z" }, 1908 - { url = "https://files.pythonhosted.org/packages/92/b5/47e807c2623074914e29dabd16cbbdd4bf5e9b2db9f8090fa64411fc5382/pyyaml-6.0.3-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:501a031947e3a9025ed4405a168e6ef5ae3126c59f90ce0cd6f2bfc477be31b7", size = 766454, upload-time = "2025-09-25T21:32:37.966Z" }, 1909 - { url = "https://files.pythonhosted.org/packages/02/9e/e5e9b168be58564121efb3de6859c452fccde0ab093d8438905899a3a483/pyyaml-6.0.3-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:b3bc83488de33889877a0f2543ade9f70c67d66d9ebb4ac959502e12de895788", size = 836355, upload-time = "2025-09-25T21:32:39.178Z" }, 1910 - { url = "https://files.pythonhosted.org/packages/88/f9/16491d7ed2a919954993e48aa941b200f38040928474c9e85ea9e64222c3/pyyaml-6.0.3-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c458b6d084f9b935061bc36216e8a69a7e293a2f1e68bf956dcd9e6cbcd143f5", size = 794175, upload-time = "2025-09-25T21:32:40.865Z" }, 1911 - { url = "https://files.pythonhosted.org/packages/dd/3f/5989debef34dc6397317802b527dbbafb2b4760878a53d4166579111411e/pyyaml-6.0.3-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:7c6610def4f163542a622a73fb39f534f8c101d690126992300bf3207eab9764", size = 755228, upload-time = "2025-09-25T21:32:42.084Z" }, 1912 - { url = "https://files.pythonhosted.org/packages/d7/ce/af88a49043cd2e265be63d083fc75b27b6ed062f5f9fd6cdc223ad62f03e/pyyaml-6.0.3-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:5190d403f121660ce8d1d2c1bb2ef1bd05b5f68533fc5c2ea899bd15f4399b35", size = 789194, upload-time = "2025-09-25T21:32:43.362Z" }, 1913 - { url = "https://files.pythonhosted.org/packages/23/20/bb6982b26a40bb43951265ba29d4c246ef0ff59c9fdcdf0ed04e0687de4d/pyyaml-6.0.3-cp314-cp314-win_amd64.whl", hash = "sha256:4a2e8cebe2ff6ab7d1050ecd59c25d4c8bd7e6f400f5f82b96557ac0abafd0ac", size = 156429, upload-time = "2025-09-25T21:32:57.844Z" }, 1914 - { url = "https://files.pythonhosted.org/packages/f4/f4/a4541072bb9422c8a883ab55255f918fa378ecf083f5b85e87fc2b4eda1b/pyyaml-6.0.3-cp314-cp314-win_arm64.whl", hash = "sha256:93dda82c9c22deb0a405ea4dc5f2d0cda384168e466364dec6255b293923b2f3", size = 143912, upload-time = "2025-09-25T21:32:59.247Z" }, 1915 - { url = "https://files.pythonhosted.org/packages/7c/f9/07dd09ae774e4616edf6cda684ee78f97777bdd15847253637a6f052a62f/pyyaml-6.0.3-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:02893d100e99e03eda1c8fd5c441d8c60103fd175728e23e431db1b589cf5ab3", size = 189108, upload-time = "2025-09-25T21:32:44.377Z" }, 1916 - { url = "https://files.pythonhosted.org/packages/4e/78/8d08c9fb7ce09ad8c38ad533c1191cf27f7ae1effe5bb9400a46d9437fcf/pyyaml-6.0.3-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:c1ff362665ae507275af2853520967820d9124984e0f7466736aea23d8611fba", size = 183641, upload-time = "2025-09-25T21:32:45.407Z" }, 1917 - { url = "https://files.pythonhosted.org/packages/7b/5b/3babb19104a46945cf816d047db2788bcaf8c94527a805610b0289a01c6b/pyyaml-6.0.3-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6adc77889b628398debc7b65c073bcb99c4a0237b248cacaf3fe8a557563ef6c", size = 831901, upload-time = "2025-09-25T21:32:48.83Z" }, 1918 - { url = "https://files.pythonhosted.org/packages/8b/cc/dff0684d8dc44da4d22a13f35f073d558c268780ce3c6ba1b87055bb0b87/pyyaml-6.0.3-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:a80cb027f6b349846a3bf6d73b5e95e782175e52f22108cfa17876aaeff93702", size = 861132, upload-time = "2025-09-25T21:32:50.149Z" }, 1919 - { url = "https://files.pythonhosted.org/packages/b1/5e/f77dc6b9036943e285ba76b49e118d9ea929885becb0a29ba8a7c75e29fe/pyyaml-6.0.3-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:00c4bdeba853cc34e7dd471f16b4114f4162dc03e6b7afcc2128711f0eca823c", size = 839261, upload-time = "2025-09-25T21:32:51.808Z" }, 1920 - { url = "https://files.pythonhosted.org/packages/ce/88/a9db1376aa2a228197c58b37302f284b5617f56a5d959fd1763fb1675ce6/pyyaml-6.0.3-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:66e1674c3ef6f541c35191caae2d429b967b99e02040f5ba928632d9a7f0f065", size = 805272, upload-time = "2025-09-25T21:32:52.941Z" }, 1921 - { url = "https://files.pythonhosted.org/packages/da/92/1446574745d74df0c92e6aa4a7b0b3130706a4142b2d1a5869f2eaa423c6/pyyaml-6.0.3-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:16249ee61e95f858e83976573de0f5b2893b3677ba71c9dd36b9cf8be9ac6d65", size = 829923, upload-time = "2025-09-25T21:32:54.537Z" }, 1922 - { url = "https://files.pythonhosted.org/packages/f0/7a/1c7270340330e575b92f397352af856a8c06f230aa3e76f86b39d01b416a/pyyaml-6.0.3-cp314-cp314t-win_amd64.whl", hash = "sha256:4ad1906908f2f5ae4e5a8ddfce73c320c2a1429ec52eafd27138b7f1cbe341c9", size = 174062, upload-time = "2025-09-25T21:32:55.767Z" }, 1923 - { url = "https://files.pythonhosted.org/packages/f1/12/de94a39c2ef588c7e6455cfbe7343d3b2dc9d6b6b2f40c4c6565744c873d/pyyaml-6.0.3-cp314-cp314t-win_arm64.whl", hash = "sha256:ebc55a14a21cb14062aa4162f906cd962b28e2e9ea38f9b4391244cd8de4ae0b", size = 149341, upload-time = "2025-09-25T21:32:56.828Z" }, 1924 - ] 1925 - 1926 - [[package]] 1927 - name = "referencing" 1928 - version = "0.37.0" 1929 - source = { registry = "https://pypi.org/simple" } 1930 - dependencies = [ 1931 - { name = "attrs" }, 1932 - { name = "rpds-py" }, 1933 - ] 1934 - sdist = { url = "https://files.pythonhosted.org/packages/22/f5/df4e9027acead3ecc63e50fe1e36aca1523e1719559c499951bb4b53188f/referencing-0.37.0.tar.gz", hash = "sha256:44aefc3142c5b842538163acb373e24cce6632bd54bdb01b21ad5863489f50d8", size = 78036, upload-time = "2025-10-13T15:30:48.871Z" } 1935 - wheels = [ 1936 - { url = "https://files.pythonhosted.org/packages/2c/58/ca301544e1fa93ed4f80d724bf5b194f6e4b945841c5bfd555878eea9fcb/referencing-0.37.0-py3-none-any.whl", hash = "sha256:381329a9f99628c9069361716891d34ad94af76e461dcb0335825aecc7692231", size = 26766, upload-time = "2025-10-13T15:30:47.625Z" }, 1937 - ] 1938 - 1939 - [[package]] 1940 - name = "regex" 1941 - version = "2026.4.4" 1942 - source = { registry = "https://pypi.org/simple" } 1943 - sdist = { url = "https://files.pythonhosted.org/packages/cb/0e/3a246dbf05666918bd3664d9d787f84a9108f6f43cc953a077e4a7dfdb7e/regex-2026.4.4.tar.gz", hash = "sha256:e08270659717f6973523ce3afbafa53515c4dc5dcad637dc215b6fd50f689423", size = 416000, upload-time = "2026-04-03T20:56:28.155Z" } 1944 - wheels = [ 1945 - { url = "https://files.pythonhosted.org/packages/f0/f5/ed97c2dc47b5fbd4b73c0d7d75f9ebc8eca139f2bbef476bba35f28c0a77/regex-2026.4.4-cp314-cp314-macosx_10_13_universal2.whl", hash = "sha256:2da82d643fa698e5e5210e54af90181603d5853cf469f5eedf9bfc8f59b4b8c7", size = 490343, upload-time = "2026-04-03T20:55:15.241Z" }, 1946 - { url = "https://files.pythonhosted.org/packages/80/e9/de4828a7385ec166d673a5790ad06ac48cdaa98bc0960108dd4b9cc1aef7/regex-2026.4.4-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:54a1189ad9d9357760557c91103d5e421f0a2dabe68a5cdf9103d0dcf4e00752", size = 291909, upload-time = "2026-04-03T20:55:17.558Z" }, 1947 - { url = "https://files.pythonhosted.org/packages/b4/d6/5cfbfc97f3201a4d24b596a77957e092030dcc4205894bc035cedcfce62f/regex-2026.4.4-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:76d67d5afb1fe402d10a6403bae668d000441e2ab115191a804287d53b772951", size = 289692, upload-time = "2026-04-03T20:55:20.561Z" }, 1948 - { url = "https://files.pythonhosted.org/packages/8e/ac/f2212d9fd56fe897e36d0110ba30ba2d247bd6410c5bd98499c7e5a1e1f2/regex-2026.4.4-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e7cd3e4ee8d80447a83bbc9ab0c8459781fa77087f856c3e740d7763be0df27f", size = 796979, upload-time = "2026-04-03T20:55:22.56Z" }, 1949 - { url = "https://files.pythonhosted.org/packages/c9/e3/a016c12675fbac988a60c7e1c16e67823ff0bc016beb27bd7a001dbdabc6/regex-2026.4.4-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:2e19e18c568d2866d8b6a6dfad823db86193503f90823a8f66689315ba28fbe8", size = 866744, upload-time = "2026-04-03T20:55:24.646Z" }, 1950 - { url = "https://files.pythonhosted.org/packages/af/a4/0b90ca4cf17adc3cb43de80ec71018c37c88ad64987e8d0d481a95ca60b5/regex-2026.4.4-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:7698a6f38730fd1385d390d1ed07bb13dce39aa616aca6a6d89bea178464b9a4", size = 911613, upload-time = "2026-04-03T20:55:27.033Z" }, 1951 - { url = "https://files.pythonhosted.org/packages/8e/3b/2b3dac0b82d41ab43aa87c6ecde63d71189d03fe8854b8ca455a315edac3/regex-2026.4.4-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:173a66f3651cdb761018078e2d9487f4cf971232c990035ec0eb1cdc6bf929a9", size = 800551, upload-time = "2026-04-03T20:55:29.532Z" }, 1952 - { url = "https://files.pythonhosted.org/packages/25/fe/5365eb7aa0e753c4b5957815c321519ecab033c279c60e1b1ae2367fa810/regex-2026.4.4-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:fa7922bbb2cc84fa062d37723f199d4c0cd200245ce269c05db82d904db66b83", size = 776911, upload-time = "2026-04-03T20:55:31.526Z" }, 1953 - { url = "https://files.pythonhosted.org/packages/aa/b3/7fb0072156bba065e3b778a7bc7b0a6328212be5dd6a86fd207e0c4f2dab/regex-2026.4.4-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:59f67cd0a0acaf0e564c20bbd7f767286f23e91e2572c5703bf3e56ea7557edb", size = 785751, upload-time = "2026-04-03T20:55:33.797Z" }, 1954 - { url = "https://files.pythonhosted.org/packages/02/1a/9f83677eb699273e56e858f7bd95acdbee376d42f59e8bfca2fd80d79df3/regex-2026.4.4-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:475e50f3f73f73614f7cba5524d6de49dee269df00272a1b85e3d19f6d498465", size = 860484, upload-time = "2026-04-03T20:55:35.745Z" }, 1955 - { url = "https://files.pythonhosted.org/packages/3b/7a/93937507b61cfcff8b4c5857f1b452852b09f741daa9acae15c971d8554e/regex-2026.4.4-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:a1c0c7d67b64d85ac2e1879923bad2f08a08f3004055f2f406ef73c850114bd4", size = 765939, upload-time = "2026-04-03T20:55:37.972Z" }, 1956 - { url = "https://files.pythonhosted.org/packages/86/ea/81a7f968a351c6552b1670ead861e2a385be730ee28402233020c67f9e0f/regex-2026.4.4-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:1371c2ccbb744d66ee63631cc9ca12aa233d5749972626b68fe1a649dd98e566", size = 851417, upload-time = "2026-04-03T20:55:39.92Z" }, 1957 - { url = "https://files.pythonhosted.org/packages/4c/7e/323c18ce4b5b8f44517a36342961a0306e931e499febbd876bb149d900f0/regex-2026.4.4-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:59968142787042db793348a3f5b918cf24ced1f23247328530e063f89c128a95", size = 789056, upload-time = "2026-04-03T20:55:42.303Z" }, 1958 - { url = "https://files.pythonhosted.org/packages/c0/af/e7510f9b11b1913b0cd44eddb784b2d650b2af6515bfce4cffcc5bfd1d38/regex-2026.4.4-cp314-cp314-win32.whl", hash = "sha256:59efe72d37fd5a91e373e5146f187f921f365f4abc1249a5ab446a60f30dd5f8", size = 272130, upload-time = "2026-04-03T20:55:44.995Z" }, 1959 - { url = "https://files.pythonhosted.org/packages/9a/51/57dae534c915e2d3a21490e88836fa2ae79dde3b66255ecc0c0a155d2c10/regex-2026.4.4-cp314-cp314-win_amd64.whl", hash = "sha256:e0aab3ff447845049d676827d2ff714aab4f73f340e155b7de7458cf53baa5a4", size = 280992, upload-time = "2026-04-03T20:55:47.316Z" }, 1960 - { url = "https://files.pythonhosted.org/packages/0a/5e/abaf9f4c3792e34edb1434f06717fae2b07888d85cb5cec29f9204931bf8/regex-2026.4.4-cp314-cp314-win_arm64.whl", hash = "sha256:a7a5bb6aa0cf62208bb4fa079b0c756734f8ad0e333b425732e8609bd51ee22f", size = 273563, upload-time = "2026-04-03T20:55:49.273Z" }, 1961 - { url = "https://files.pythonhosted.org/packages/ff/06/35da85f9f217b9538b99cbb170738993bcc3b23784322decb77619f11502/regex-2026.4.4-cp314-cp314t-macosx_10_13_universal2.whl", hash = "sha256:97850d0638391bdc7d35dc1c1039974dcb921eaafa8cc935ae4d7f272b1d60b3", size = 494191, upload-time = "2026-04-03T20:55:51.258Z" }, 1962 - { url = "https://files.pythonhosted.org/packages/54/5b/1bc35f479eef8285c4baf88d8c002023efdeebb7b44a8735b36195486ae7/regex-2026.4.4-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:ee7337f88f2a580679f7bbfe69dc86c043954f9f9c541012f49abc554a962f2e", size = 293877, upload-time = "2026-04-03T20:55:53.214Z" }, 1963 - { url = "https://files.pythonhosted.org/packages/39/5b/f53b9ad17480b3ddd14c90da04bfb55ac6894b129e5dea87bcaf7d00e336/regex-2026.4.4-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:7429f4e6192c11d659900c0648ba8776243bf396ab95558b8c51a345afeddde6", size = 292410, upload-time = "2026-04-03T20:55:55.736Z" }, 1964 - { url = "https://files.pythonhosted.org/packages/bb/56/52377f59f60a7c51aa4161eecf0b6032c20b461805aca051250da435ffc9/regex-2026.4.4-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:dc4f10fbd5dd13dcf4265b4cc07d69ca70280742870c97ae10093e3d66000359", size = 811831, upload-time = "2026-04-03T20:55:57.802Z" }, 1965 - { url = "https://files.pythonhosted.org/packages/dd/63/8026310bf066f702a9c361f83a8c9658f3fe4edb349f9c1e5d5273b7c40c/regex-2026.4.4-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:a152560af4f9742b96f3827090f866eeec5becd4765c8e0d3473d9d280e76a5a", size = 871199, upload-time = "2026-04-03T20:56:00.333Z" }, 1966 - { url = "https://files.pythonhosted.org/packages/20/9f/a514bbb00a466dbb506d43f187a04047f7be1505f10a9a15615ead5080ee/regex-2026.4.4-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:54170b3e95339f415d54651f97df3bff7434a663912f9358237941bbf9143f55", size = 917649, upload-time = "2026-04-03T20:56:02.445Z" }, 1967 - { url = "https://files.pythonhosted.org/packages/cb/6b/8399f68dd41a2030218839b9b18360d79b86d22b9fab5ef477c7f23ca67c/regex-2026.4.4-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:07f190d65f5a72dcb9cf7106bfc3d21e7a49dd2879eda2207b683f32165e4d99", size = 816388, upload-time = "2026-04-03T20:56:04.595Z" }, 1968 - { url = "https://files.pythonhosted.org/packages/1e/9c/103963f47c24339a483b05edd568594c2be486188f688c0170fd504b2948/regex-2026.4.4-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:9a2741ce5a29d3c84b0b94261ba630ab459a1b847a0d6beca7d62d188175c790", size = 785746, upload-time = "2026-04-03T20:56:07.13Z" }, 1969 - { url = "https://files.pythonhosted.org/packages/fa/ee/7f6054c0dec0cee3463c304405e4ff42e27cff05bf36fcb34be549ab17bd/regex-2026.4.4-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:b26c30df3a28fd9793113dac7385a4deb7294a06c0f760dd2b008bd49a9139bc", size = 801483, upload-time = "2026-04-03T20:56:09.365Z" }, 1970 - { url = "https://files.pythonhosted.org/packages/30/c2/51d3d941cf6070dc00c3338ecf138615fc3cce0421c3df6abe97a08af61a/regex-2026.4.4-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:421439d1bee44b19f4583ccf42670ca464ffb90e9fdc38d37f39d1ddd1e44f1f", size = 866331, upload-time = "2026-04-03T20:56:12.039Z" }, 1971 - { url = "https://files.pythonhosted.org/packages/16/e8/76d50dcc122ac33927d939f350eebcfe3dbcbda96913e03433fc36de5e63/regex-2026.4.4-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:b40379b53ecbc747fd9bdf4a0ea14eb8188ca1bd0f54f78893a39024b28f4863", size = 772673, upload-time = "2026-04-03T20:56:14.558Z" }, 1972 - { url = "https://files.pythonhosted.org/packages/a5/6e/5f6bf75e20ea6873d05ba4ec78378c375cbe08cdec571c83fbb01606e563/regex-2026.4.4-cp314-cp314t-musllinux_1_2_s390x.whl", hash = "sha256:08c55c13d2eef54f73eeadc33146fb0baaa49e7335eb1aff6ae1324bf0ddbe4a", size = 857146, upload-time = "2026-04-03T20:56:16.663Z" }, 1973 - { url = "https://files.pythonhosted.org/packages/0b/33/3c76d9962949e487ebba353a18e89399f292287204ac8f2f4cfc3a51c233/regex-2026.4.4-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:9776b85f510062f5a75ef112afe5f494ef1635607bf1cc220c1391e9ac2f5e81", size = 803463, upload-time = "2026-04-03T20:56:18.923Z" }, 1974 - { url = "https://files.pythonhosted.org/packages/19/eb/ef32dcd2cb69b69bc0c3e55205bce94a7def48d495358946bc42186dcccc/regex-2026.4.4-cp314-cp314t-win32.whl", hash = "sha256:385edaebde5db5be103577afc8699fea73a0e36a734ba24870be7ffa61119d74", size = 275709, upload-time = "2026-04-03T20:56:20.996Z" }, 1975 - { url = "https://files.pythonhosted.org/packages/a0/86/c291bf740945acbf35ed7dbebf8e2eea2f3f78041f6bd7cdab80cb274dc0/regex-2026.4.4-cp314-cp314t-win_amd64.whl", hash = "sha256:5d354b18839328927832e2fa5f7c95b7a3ccc39e7a681529e1685898e6436d45", size = 285622, upload-time = "2026-04-03T20:56:23.641Z" }, 1976 - { url = "https://files.pythonhosted.org/packages/d5/e7/ec846d560ae6a597115153c02ca6138a7877a1748b2072d9521c10a93e58/regex-2026.4.4-cp314-cp314t-win_arm64.whl", hash = "sha256:af0384cb01a33600c49505c27c6c57ab0b27bf84a74e28524c92ca897ebdac9d", size = 275773, upload-time = "2026-04-03T20:56:26.07Z" }, 1977 - ] 1978 - 1979 - [[package]] 1980 - name = "requests" 1981 - version = "2.33.1" 1982 - source = { registry = "https://pypi.org/simple" } 1983 - dependencies = [ 1984 - { name = "certifi" }, 1985 - { name = "charset-normalizer" }, 1986 - { name = "idna" }, 1987 - { name = "urllib3" }, 1988 - ] 1989 - sdist = { url = "https://files.pythonhosted.org/packages/5f/a4/98b9c7c6428a668bf7e42ebb7c79d576a1c3c1e3ae2d47e674b468388871/requests-2.33.1.tar.gz", hash = "sha256:18817f8c57c6263968bc123d237e3b8b08ac046f5456bd1e307ee8f4250d3517", size = 134120, upload-time = "2026-03-30T16:09:15.531Z" } 1990 - wheels = [ 1991 - { url = "https://files.pythonhosted.org/packages/d7/8e/7540e8a2036f79a125c1d2ebadf69ed7901608859186c856fa0388ef4197/requests-2.33.1-py3-none-any.whl", hash = "sha256:4e6d1ef462f3626a1f0a0a9c42dd93c63bad33f9f1c1937509b8c5c8718ab56a", size = 64947, upload-time = "2026-03-30T16:09:13.83Z" }, 1992 - ] 1993 - 1994 - [[package]] 1995 - name = "rich" 1996 - version = "15.0.0" 1997 - source = { registry = "https://pypi.org/simple" } 1998 - dependencies = [ 1999 - { name = "markdown-it-py" }, 2000 - { name = "pygments" }, 2001 - ] 2002 - sdist = { url = "https://files.pythonhosted.org/packages/c0/8f/0722ca900cc807c13a6a0c696dacf35430f72e0ec571c4275d2371fca3e9/rich-15.0.0.tar.gz", hash = "sha256:edd07a4824c6b40189fb7ac9bc4c52536e9780fbbfbddf6f1e2502c31b068c36", size = 230680, upload-time = "2026-04-12T08:24:00.75Z" } 2003 - wheels = [ 2004 - { url = "https://files.pythonhosted.org/packages/82/3b/64d4899d73f91ba49a8c18a8ff3f0ea8f1c1d75481760df8c68ef5235bf5/rich-15.0.0-py3-none-any.whl", hash = "sha256:33bd4ef74232fb73fe9279a257718407f169c09b78a87ad3d296f548e27de0bb", size = 310654, upload-time = "2026-04-12T08:24:02.83Z" }, 2005 - ] 2006 - 2007 - [[package]] 2008 - name = "rich-rst" 2009 - version = "1.3.2" 2010 - source = { registry = "https://pypi.org/simple" } 2011 - dependencies = [ 2012 - { name = "docutils" }, 2013 - { name = "rich" }, 2014 - ] 2015 - sdist = { url = "https://files.pythonhosted.org/packages/bc/6d/a506aaa4a9eaa945ed8ab2b7347859f53593864289853c5d6d62b77246e0/rich_rst-1.3.2.tar.gz", hash = "sha256:a1196fdddf1e364b02ec68a05e8ff8f6914fee10fbca2e6b6735f166bb0da8d4", size = 14936, upload-time = "2025-10-14T16:49:45.332Z" } 2016 - wheels = [ 2017 - { url = "https://files.pythonhosted.org/packages/13/2f/b4530fbf948867702d0a3f27de4a6aab1d156f406d72852ab902c4d04de9/rich_rst-1.3.2-py3-none-any.whl", hash = "sha256:a99b4907cbe118cf9d18b0b44de272efa61f15117c61e39ebdc431baf5df722a", size = 12567, upload-time = "2025-10-14T16:49:42.953Z" }, 2018 - ] 2019 - 2020 - [[package]] 2021 - name = "rpds-py" 2022 - version = "0.30.0" 2023 - source = { registry = "https://pypi.org/simple" } 2024 - sdist = { url = "https://files.pythonhosted.org/packages/20/af/3f2f423103f1113b36230496629986e0ef7e199d2aa8392452b484b38ced/rpds_py-0.30.0.tar.gz", hash = "sha256:dd8ff7cf90014af0c0f787eea34794ebf6415242ee1d6fa91eaba725cc441e84", size = 69469, upload-time = "2025-11-30T20:24:38.837Z" } 2025 - wheels = [ 2026 - { url = "https://files.pythonhosted.org/packages/86/81/dad16382ebbd3d0e0328776d8fd7ca94220e4fa0798d1dc5e7da48cb3201/rpds_py-0.30.0-cp314-cp314-macosx_10_12_x86_64.whl", hash = "sha256:68f19c879420aa08f61203801423f6cd5ac5f0ac4ac82a2368a9fcd6a9a075e0", size = 362099, upload-time = "2025-11-30T20:23:27.316Z" }, 2027 - { url = "https://files.pythonhosted.org/packages/2b/60/19f7884db5d5603edf3c6bce35408f45ad3e97e10007df0e17dd57af18f8/rpds_py-0.30.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:ec7c4490c672c1a0389d319b3a9cfcd098dcdc4783991553c332a15acf7249be", size = 353192, upload-time = "2025-11-30T20:23:29.151Z" }, 2028 - { url = "https://files.pythonhosted.org/packages/bf/c4/76eb0e1e72d1a9c4703c69607cec123c29028bff28ce41588792417098ac/rpds_py-0.30.0-cp314-cp314-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f251c812357a3fed308d684a5079ddfb9d933860fc6de89f2b7ab00da481e65f", size = 384080, upload-time = "2025-11-30T20:23:30.785Z" }, 2029 - { url = "https://files.pythonhosted.org/packages/72/87/87ea665e92f3298d1b26d78814721dc39ed8d2c74b86e83348d6b48a6f31/rpds_py-0.30.0-cp314-cp314-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:ac98b175585ecf4c0348fd7b29c3864bda53b805c773cbf7bfdaffc8070c976f", size = 394841, upload-time = "2025-11-30T20:23:32.209Z" }, 2030 - { url = "https://files.pythonhosted.org/packages/77/ad/7783a89ca0587c15dcbf139b4a8364a872a25f861bdb88ed99f9b0dec985/rpds_py-0.30.0-cp314-cp314-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3e62880792319dbeb7eb866547f2e35973289e7d5696c6e295476448f5b63c87", size = 516670, upload-time = "2025-11-30T20:23:33.742Z" }, 2031 - { url = "https://files.pythonhosted.org/packages/5b/3c/2882bdac942bd2172f3da574eab16f309ae10a3925644e969536553cb4ee/rpds_py-0.30.0-cp314-cp314-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:4e7fc54e0900ab35d041b0601431b0a0eb495f0851a0639b6ef90f7741b39a18", size = 408005, upload-time = "2025-11-30T20:23:35.253Z" }, 2032 - { url = "https://files.pythonhosted.org/packages/ce/81/9a91c0111ce1758c92516a3e44776920b579d9a7c09b2b06b642d4de3f0f/rpds_py-0.30.0-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:47e77dc9822d3ad616c3d5759ea5631a75e5809d5a28707744ef79d7a1bcfcad", size = 382112, upload-time = "2025-11-30T20:23:36.842Z" }, 2033 - { url = "https://files.pythonhosted.org/packages/cf/8e/1da49d4a107027e5fbc64daeab96a0706361a2918da10cb41769244b805d/rpds_py-0.30.0-cp314-cp314-manylinux_2_31_riscv64.whl", hash = "sha256:b4dc1a6ff022ff85ecafef7979a2c6eb423430e05f1165d6688234e62ba99a07", size = 399049, upload-time = "2025-11-30T20:23:38.343Z" }, 2034 - { url = "https://files.pythonhosted.org/packages/df/5a/7ee239b1aa48a127570ec03becbb29c9d5a9eb092febbd1699d567cae859/rpds_py-0.30.0-cp314-cp314-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:4559c972db3a360808309e06a74628b95eaccbf961c335c8fe0d590cf587456f", size = 415661, upload-time = "2025-11-30T20:23:40.263Z" }, 2035 - { url = "https://files.pythonhosted.org/packages/70/ea/caa143cf6b772f823bc7929a45da1fa83569ee49b11d18d0ada7f5ee6fd6/rpds_py-0.30.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:0ed177ed9bded28f8deb6ab40c183cd1192aa0de40c12f38be4d59cd33cb5c65", size = 565606, upload-time = "2025-11-30T20:23:42.186Z" }, 2036 - { url = "https://files.pythonhosted.org/packages/64/91/ac20ba2d69303f961ad8cf55bf7dbdb4763f627291ba3d0d7d67333cced9/rpds_py-0.30.0-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:ad1fa8db769b76ea911cb4e10f049d80bf518c104f15b3edb2371cc65375c46f", size = 591126, upload-time = "2025-11-30T20:23:44.086Z" }, 2037 - { url = "https://files.pythonhosted.org/packages/21/20/7ff5f3c8b00c8a95f75985128c26ba44503fb35b8e0259d812766ea966c7/rpds_py-0.30.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:46e83c697b1f1c72b50e5ee5adb4353eef7406fb3f2043d64c33f20ad1c2fc53", size = 553371, upload-time = "2025-11-30T20:23:46.004Z" }, 2038 - { url = "https://files.pythonhosted.org/packages/72/c7/81dadd7b27c8ee391c132a6b192111ca58d866577ce2d9b0ca157552cce0/rpds_py-0.30.0-cp314-cp314-win32.whl", hash = "sha256:ee454b2a007d57363c2dfd5b6ca4a5d7e2c518938f8ed3b706e37e5d470801ed", size = 215298, upload-time = "2025-11-30T20:23:47.696Z" }, 2039 - { url = "https://files.pythonhosted.org/packages/3e/d2/1aaac33287e8cfb07aab2e6b8ac1deca62f6f65411344f1433c55e6f3eb8/rpds_py-0.30.0-cp314-cp314-win_amd64.whl", hash = "sha256:95f0802447ac2d10bcc69f6dc28fe95fdf17940367b21d34e34c737870758950", size = 228604, upload-time = "2025-11-30T20:23:49.501Z" }, 2040 - { url = "https://files.pythonhosted.org/packages/e8/95/ab005315818cc519ad074cb7784dae60d939163108bd2b394e60dc7b5461/rpds_py-0.30.0-cp314-cp314-win_arm64.whl", hash = "sha256:613aa4771c99f03346e54c3f038e4cc574ac09a3ddfb0e8878487335e96dead6", size = 222391, upload-time = "2025-11-30T20:23:50.96Z" }, 2041 - { url = "https://files.pythonhosted.org/packages/9e/68/154fe0194d83b973cdedcdcc88947a2752411165930182ae41d983dcefa6/rpds_py-0.30.0-cp314-cp314t-macosx_10_12_x86_64.whl", hash = "sha256:7e6ecfcb62edfd632e56983964e6884851786443739dbfe3582947e87274f7cb", size = 364868, upload-time = "2025-11-30T20:23:52.494Z" }, 2042 - { url = "https://files.pythonhosted.org/packages/83/69/8bbc8b07ec854d92a8b75668c24d2abcb1719ebf890f5604c61c9369a16f/rpds_py-0.30.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:a1d0bc22a7cdc173fedebb73ef81e07faef93692b8c1ad3733b67e31e1b6e1b8", size = 353747, upload-time = "2025-11-30T20:23:54.036Z" }, 2043 - { url = "https://files.pythonhosted.org/packages/ab/00/ba2e50183dbd9abcce9497fa5149c62b4ff3e22d338a30d690f9af970561/rpds_py-0.30.0-cp314-cp314t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0d08f00679177226c4cb8c5265012eea897c8ca3b93f429e546600c971bcbae7", size = 383795, upload-time = "2025-11-30T20:23:55.556Z" }, 2044 - { url = "https://files.pythonhosted.org/packages/05/6f/86f0272b84926bcb0e4c972262f54223e8ecc556b3224d281e6598fc9268/rpds_py-0.30.0-cp314-cp314t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:5965af57d5848192c13534f90f9dd16464f3c37aaf166cc1da1cae1fd5a34898", size = 393330, upload-time = "2025-11-30T20:23:57.033Z" }, 2045 - { url = "https://files.pythonhosted.org/packages/cb/e9/0e02bb2e6dc63d212641da45df2b0bf29699d01715913e0d0f017ee29438/rpds_py-0.30.0-cp314-cp314t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9a4e86e34e9ab6b667c27f3211ca48f73dba7cd3d90f8d5b11be56e5dbc3fb4e", size = 518194, upload-time = "2025-11-30T20:23:58.637Z" }, 2046 - { url = "https://files.pythonhosted.org/packages/ee/ca/be7bca14cf21513bdf9c0606aba17d1f389ea2b6987035eb4f62bd923f25/rpds_py-0.30.0-cp314-cp314t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e5d3e6b26f2c785d65cc25ef1e5267ccbe1b069c5c21b8cc724efee290554419", size = 408340, upload-time = "2025-11-30T20:24:00.2Z" }, 2047 - { url = "https://files.pythonhosted.org/packages/c2/c7/736e00ebf39ed81d75544c0da6ef7b0998f8201b369acf842f9a90dc8fce/rpds_py-0.30.0-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:626a7433c34566535b6e56a1b39a7b17ba961e97ce3b80ec62e6f1312c025551", size = 383765, upload-time = "2025-11-30T20:24:01.759Z" }, 2048 - { url = "https://files.pythonhosted.org/packages/4a/3f/da50dfde9956aaf365c4adc9533b100008ed31aea635f2b8d7b627e25b49/rpds_py-0.30.0-cp314-cp314t-manylinux_2_31_riscv64.whl", hash = "sha256:acd7eb3f4471577b9b5a41baf02a978e8bdeb08b4b355273994f8b87032000a8", size = 396834, upload-time = "2025-11-30T20:24:03.687Z" }, 2049 - { url = "https://files.pythonhosted.org/packages/4e/00/34bcc2565b6020eab2623349efbdec810676ad571995911f1abdae62a3a0/rpds_py-0.30.0-cp314-cp314t-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:fe5fa731a1fa8a0a56b0977413f8cacac1768dad38d16b3a296712709476fbd5", size = 415470, upload-time = "2025-11-30T20:24:05.232Z" }, 2050 - { url = "https://files.pythonhosted.org/packages/8c/28/882e72b5b3e6f718d5453bd4d0d9cf8df36fddeb4ddbbab17869d5868616/rpds_py-0.30.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:74a3243a411126362712ee1524dfc90c650a503502f135d54d1b352bd01f2404", size = 565630, upload-time = "2025-11-30T20:24:06.878Z" }, 2051 - { url = "https://files.pythonhosted.org/packages/3b/97/04a65539c17692de5b85c6e293520fd01317fd878ea1995f0367d4532fb1/rpds_py-0.30.0-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:3e8eeb0544f2eb0d2581774be4c3410356eba189529a6b3e36bbbf9696175856", size = 591148, upload-time = "2025-11-30T20:24:08.445Z" }, 2052 - { url = "https://files.pythonhosted.org/packages/85/70/92482ccffb96f5441aab93e26c4d66489eb599efdcf96fad90c14bbfb976/rpds_py-0.30.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:dbd936cde57abfee19ab3213cf9c26be06d60750e60a8e4dd85d1ab12c8b1f40", size = 556030, upload-time = "2025-11-30T20:24:10.956Z" }, 2053 - { url = "https://files.pythonhosted.org/packages/20/53/7c7e784abfa500a2b6b583b147ee4bb5a2b3747a9166bab52fec4b5b5e7d/rpds_py-0.30.0-cp314-cp314t-win32.whl", hash = "sha256:dc824125c72246d924f7f796b4f63c1e9dc810c7d9e2355864b3c3a73d59ade0", size = 211570, upload-time = "2025-11-30T20:24:12.735Z" }, 2054 - { url = "https://files.pythonhosted.org/packages/d0/02/fa464cdfbe6b26e0600b62c528b72d8608f5cc49f96b8d6e38c95d60c676/rpds_py-0.30.0-cp314-cp314t-win_amd64.whl", hash = "sha256:27f4b0e92de5bfbc6f86e43959e6edd1425c33b5e69aab0984a72047f2bcf1e3", size = 226532, upload-time = "2025-11-30T20:24:14.634Z" }, 2055 - ] 2056 - 2057 - [[package]] 2058 - name = "s3transfer" 2059 - version = "0.16.0" 2060 - source = { registry = "https://pypi.org/simple" } 2061 - dependencies = [ 2062 - { name = "botocore" }, 2063 - ] 2064 - sdist = { url = "https://files.pythonhosted.org/packages/05/04/74127fc843314818edfa81b5540e26dd537353b123a4edc563109d8f17dd/s3transfer-0.16.0.tar.gz", hash = "sha256:8e990f13268025792229cd52fa10cb7163744bf56e719e0b9cb925ab79abf920", size = 153827, upload-time = "2025-12-01T02:30:59.114Z" } 2065 - wheels = [ 2066 - { url = "https://files.pythonhosted.org/packages/fc/51/727abb13f44c1fcf6d145979e1535a35794db0f6e450a0cb46aa24732fe2/s3transfer-0.16.0-py3-none-any.whl", hash = "sha256:18e25d66fed509e3868dc1572b3f427ff947dd2c56f844a5bf09481ad3f3b2fe", size = 86830, upload-time = "2025-12-01T02:30:57.729Z" }, 2067 - ] 2068 - 2069 - [[package]] 2070 - name = "secretstorage" 2071 - version = "3.5.0" 2072 - source = { registry = "https://pypi.org/simple" } 2073 - dependencies = [ 2074 - { name = "cryptography" }, 2075 - { name = "jeepney" }, 2076 - ] 2077 - sdist = { url = "https://files.pythonhosted.org/packages/1c/03/e834bcd866f2f8a49a85eaff47340affa3bfa391ee9912a952a1faa68c7b/secretstorage-3.5.0.tar.gz", hash = "sha256:f04b8e4689cbce351744d5537bf6b1329c6fc68f91fa666f60a380edddcd11be", size = 19884, upload-time = "2025-11-23T19:02:53.191Z" } 2078 - wheels = [ 2079 - { url = "https://files.pythonhosted.org/packages/b7/46/f5af3402b579fd5e11573ce652019a67074317e18c1935cc0b4ba9b35552/secretstorage-3.5.0-py3-none-any.whl", hash = "sha256:0ce65888c0725fcb2c5bc0fdb8e5438eece02c523557ea40ce0703c266248137", size = 15554, upload-time = "2025-11-23T19:02:51.545Z" }, 2080 - ] 2081 - 2082 - [[package]] 2083 - name = "shellingham" 2084 - version = "1.5.4" 2085 - source = { registry = "https://pypi.org/simple" } 2086 - sdist = { url = "https://files.pythonhosted.org/packages/58/15/8b3609fd3830ef7b27b655beb4b4e9c62313a4e8da8c676e142cc210d58e/shellingham-1.5.4.tar.gz", hash = "sha256:8dbca0739d487e5bd35ab3ca4b36e11c4078f3a234bfce294b0a0291363404de", size = 10310, upload-time = "2023-10-24T04:13:40.426Z" } 2087 - wheels = [ 2088 - { url = "https://files.pythonhosted.org/packages/e0/f9/0595336914c5619e5f28a1fb793285925a8cd4b432c9da0a987836c7f822/shellingham-1.5.4-py2.py3-none-any.whl", hash = "sha256:7ecfff8f2fd72616f7481040475a65b2bf8af90a56c89140852d1120324e8686", size = 9755, upload-time = "2023-10-24T04:13:38.866Z" }, 2089 - ] 2090 - 2091 - [[package]] 2092 - name = "six" 2093 - version = "1.17.0" 2094 - source = { registry = "https://pypi.org/simple" } 2095 - sdist = { url = "https://files.pythonhosted.org/packages/94/e7/b2c673351809dca68a0e064b6af791aa332cf192da575fd474ed7d6f16a2/six-1.17.0.tar.gz", hash = "sha256:ff70335d468e7eb6ec65b95b99d3a2836546063f63acc5171de367e834932a81", size = 34031, upload-time = "2024-12-04T17:35:28.174Z" } 2096 - wheels = [ 2097 - { url = "https://files.pythonhosted.org/packages/b7/ce/149a00dd41f10bc29e5921b496af8b574d8413afcd5e30dfa0ed46c2cc5e/six-1.17.0-py2.py3-none-any.whl", hash = "sha256:4721f391ed90541fddacab5acf947aa0d3dc7d27b2e1e8eda2be8970586c3274", size = 11050, upload-time = "2024-12-04T17:35:26.475Z" }, 2098 - ] 2099 - 2100 - [[package]] 2101 - name = "sniffio" 2102 - version = "1.3.1" 2103 - source = { registry = "https://pypi.org/simple" } 2104 - sdist = { url = "https://files.pythonhosted.org/packages/a2/87/a6771e1546d97e7e041b6ae58d80074f81b7d5121207425c964ddf5cfdbd/sniffio-1.3.1.tar.gz", hash = "sha256:f4324edc670a0f49750a81b895f35c3adb843cca46f0530f79fc1babb23789dc", size = 20372, upload-time = "2024-02-25T23:20:04.057Z" } 2105 - wheels = [ 2106 - { url = "https://files.pythonhosted.org/packages/e9/44/75a9c9421471a6c4805dbf2356f7c181a29c1879239abab1ea2cc8f38b40/sniffio-1.3.1-py3-none-any.whl", hash = "sha256:2f6da418d1f1e0fddd844478f41680e794e6051915791a034ff65e5f100525a2", size = 10235, upload-time = "2024-02-25T23:20:01.196Z" }, 2107 - ] 2108 - 2109 - [[package]] 2110 - name = "sse-starlette" 2111 - version = "3.3.4" 2112 - source = { registry = "https://pypi.org/simple" } 2113 - dependencies = [ 2114 - { name = "anyio" }, 2115 - { name = "starlette" }, 2116 - ] 2117 - sdist = { url = "https://files.pythonhosted.org/packages/26/8c/f9290339ef6d79badbc010f067cd769d6601ec11a57d78569c683fb4dd87/sse_starlette-3.3.4.tar.gz", hash = "sha256:aaf92fc067af8a5427192895ac028e947b484ac01edbc3caf00e7e7137c7bef1", size = 32427, upload-time = "2026-03-29T09:00:23.307Z" } 2118 - wheels = [ 2119 - { url = "https://files.pythonhosted.org/packages/f8/7f/3de5402f39890ac5660b86bcf5c03f9d855dad5c4ed764866d7b592b46fd/sse_starlette-3.3.4-py3-none-any.whl", hash = "sha256:84bb06e58939a8b38d8341f1bc9792f06c2b53f48c608dd207582b664fc8f3c1", size = 14330, upload-time = "2026-03-29T09:00:21.846Z" }, 2120 - ] 2121 - 2122 - [[package]] 2123 - name = "starlette" 2124 - version = "1.0.0" 2125 - source = { registry = "https://pypi.org/simple" } 2126 - dependencies = [ 2127 - { name = "anyio" }, 2128 - ] 2129 - sdist = { url = "https://files.pythonhosted.org/packages/81/69/17425771797c36cded50b7fe44e850315d039f28b15901ab44839e70b593/starlette-1.0.0.tar.gz", hash = "sha256:6a4beaf1f81bb472fd19ea9b918b50dc3a77a6f2e190a12954b25e6ed5eea149", size = 2655289, upload-time = "2026-03-22T18:29:46.779Z" } 2130 - wheels = [ 2131 - { url = "https://files.pythonhosted.org/packages/0b/c9/584bc9651441b4ba60cc4d557d8a547b5aff901af35bda3a4ee30c819b82/starlette-1.0.0-py3-none-any.whl", hash = "sha256:d3ec55e0bb321692d275455ddfd3df75fff145d009685eb40dc91fc66b03d38b", size = 72651, upload-time = "2026-03-22T18:29:45.111Z" }, 2132 - ] 2133 - 2134 - [[package]] 2135 - name = "temporalio" 2136 - version = "1.25.0" 2137 - source = { registry = "https://pypi.org/simple" } 2138 - dependencies = [ 2139 - { name = "nexus-rpc" }, 2140 - { name = "protobuf" }, 2141 - { name = "types-protobuf" }, 2142 - { name = "typing-extensions" }, 2143 - ] 2144 - sdist = { url = "https://files.pythonhosted.org/packages/de/9c/3782bab0bf11a40b550147c19a5d1a476c17405391751982408902d9f138/temporalio-1.25.0.tar.gz", hash = "sha256:a3bbec1dcc904f674402cfa4faae480fda490b1c53ea5440c1f1996c562016fb", size = 2152534, upload-time = "2026-04-08T18:53:55.388Z" } 2145 - wheels = [ 2146 - { url = "https://files.pythonhosted.org/packages/19/e3/5676dd10d1164b6d6ca8752314054097b89c5da931e936af402a7b15236c/temporalio-1.25.0-cp310-abi3-macosx_10_12_x86_64.whl", hash = "sha256:6dc1bc8e1773b1a833d86a7ede2dd90ef4e031ced5b748b59e7f09a5bf9b327d", size = 13943906, upload-time = "2026-04-08T18:53:30.022Z" }, 2147 - { url = "https://files.pythonhosted.org/packages/89/50/7cbf7f845973be986ec165348f72f7a409750842a04d554965a39be5cb4f/temporalio-1.25.0-cp310-abi3-macosx_11_0_arm64.whl", hash = "sha256:3c8fdcf79ea5ae8ae2cf6f48072e4a86c3e0f4778f6a8a066c6ff1d336587db4", size = 13298719, upload-time = "2026-04-08T18:53:35.95Z" }, 2148 - { url = "https://files.pythonhosted.org/packages/d2/31/d474bab8535552add6ed289911bf1ffae5d7071823ece1069842190fcaed/temporalio-1.25.0-cp310-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:141f37aaafd7d090ba5c8776e4e9bc60df1fbc64b9f50c8f00e905a436588ddc", size = 13555435, upload-time = "2026-04-08T18:53:41.36Z" }, 2149 - { url = "https://files.pythonhosted.org/packages/2a/c8/e7dc053d6107bf2a037a3c9fe7b86639a25dcb888bde0e1ca366901ee47f/temporalio-1.25.0-cp310-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ff7ca5bb80264976477d4dc7a839b3d22af8577ae92306526a061481db49bf92", size = 14052050, upload-time = "2026-04-08T18:53:46.44Z" }, 2150 - { url = "https://files.pythonhosted.org/packages/08/70/9340ed3a578321cbc153041d34834bb1ec3f1f3e3d9cded47cd1b7c3e403/temporalio-1.25.0-cp310-abi3-win_amd64.whl", hash = "sha256:9411534279a2e64847231b6059c214bff4d57cfd1532bd09f333d0b1603daa7f", size = 14299684, upload-time = "2026-04-08T18:53:52.482Z" }, 2151 - ] 2152 - 2153 - [[package]] 2154 - name = "tenacity" 2155 - version = "9.1.4" 2156 - source = { registry = "https://pypi.org/simple" } 2157 - sdist = { url = "https://files.pythonhosted.org/packages/47/c6/ee486fd809e357697ee8a44d3d69222b344920433d3b6666ccd9b374630c/tenacity-9.1.4.tar.gz", hash = "sha256:adb31d4c263f2bd041081ab33b498309a57c77f9acf2db65aadf0898179cf93a", size = 49413, upload-time = "2026-02-07T10:45:33.841Z" } 2158 - wheels = [ 2159 - { url = "https://files.pythonhosted.org/packages/d7/c1/eb8f9debc45d3b7918a32ab756658a0904732f75e555402972246b0b8e71/tenacity-9.1.4-py3-none-any.whl", hash = "sha256:6095a360c919085f28c6527de529e76a06ad89b23659fa881ae0649b867a9d55", size = 28926, upload-time = "2026-02-07T10:45:32.24Z" }, 2160 - ] 2161 - 2162 - [[package]] 2163 - name = "tiktoken" 2164 - version = "0.12.0" 2165 - source = { registry = "https://pypi.org/simple" } 2166 - dependencies = [ 2167 - { name = "regex" }, 2168 - { name = "requests" }, 2169 - ] 2170 - sdist = { url = "https://files.pythonhosted.org/packages/7d/ab/4d017d0f76ec3171d469d80fc03dfbb4e48a4bcaddaa831b31d526f05edc/tiktoken-0.12.0.tar.gz", hash = "sha256:b18ba7ee2b093863978fcb14f74b3707cdc8d4d4d3836853ce7ec60772139931", size = 37806, upload-time = "2025-10-06T20:22:45.419Z" } 2171 - wheels = [ 2172 - { url = "https://files.pythonhosted.org/packages/72/05/3abc1db5d2c9aadc4d2c76fa5640134e475e58d9fbb82b5c535dc0de9b01/tiktoken-0.12.0-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:a90388128df3b3abeb2bfd1895b0681412a8d7dc644142519e6f0a97c2111646", size = 1050188, upload-time = "2025-10-06T20:22:19.563Z" }, 2173 - { url = "https://files.pythonhosted.org/packages/e3/7b/50c2f060412202d6c95f32b20755c7a6273543b125c0985d6fa9465105af/tiktoken-0.12.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:da900aa0ad52247d8794e307d6446bd3cdea8e192769b56276695d34d2c9aa88", size = 993978, upload-time = "2025-10-06T20:22:20.702Z" }, 2174 - { url = "https://files.pythonhosted.org/packages/14/27/bf795595a2b897e271771cd31cb847d479073497344c637966bdf2853da1/tiktoken-0.12.0-cp314-cp314-manylinux_2_28_aarch64.whl", hash = "sha256:285ba9d73ea0d6171e7f9407039a290ca77efcdb026be7769dccc01d2c8d7fff", size = 1129271, upload-time = "2025-10-06T20:22:22.06Z" }, 2175 - { url = "https://files.pythonhosted.org/packages/f5/de/9341a6d7a8f1b448573bbf3425fa57669ac58258a667eb48a25dfe916d70/tiktoken-0.12.0-cp314-cp314-manylinux_2_28_x86_64.whl", hash = "sha256:d186a5c60c6a0213f04a7a802264083dea1bbde92a2d4c7069e1a56630aef830", size = 1151216, upload-time = "2025-10-06T20:22:23.085Z" }, 2176 - { url = "https://files.pythonhosted.org/packages/75/0d/881866647b8d1be4d67cb24e50d0c26f9f807f994aa1510cb9ba2fe5f612/tiktoken-0.12.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:604831189bd05480f2b885ecd2d1986dc7686f609de48208ebbbddeea071fc0b", size = 1194860, upload-time = "2025-10-06T20:22:24.602Z" }, 2177 - { url = "https://files.pythonhosted.org/packages/b3/1e/b651ec3059474dab649b8d5b69f5c65cd8fcd8918568c1935bd4136c9392/tiktoken-0.12.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:8f317e8530bb3a222547b85a58583238c8f74fd7a7408305f9f63246d1a0958b", size = 1254567, upload-time = "2025-10-06T20:22:25.671Z" }, 2178 - { url = "https://files.pythonhosted.org/packages/80/57/ce64fd16ac390fafde001268c364d559447ba09b509181b2808622420eec/tiktoken-0.12.0-cp314-cp314-win_amd64.whl", hash = "sha256:399c3dd672a6406719d84442299a490420b458c44d3ae65516302a99675888f3", size = 921067, upload-time = "2025-10-06T20:22:26.753Z" }, 2179 - { url = "https://files.pythonhosted.org/packages/ac/a4/72eed53e8976a099539cdd5eb36f241987212c29629d0a52c305173e0a68/tiktoken-0.12.0-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:c2c714c72bc00a38ca969dae79e8266ddec999c7ceccd603cc4f0d04ccd76365", size = 1050473, upload-time = "2025-10-06T20:22:27.775Z" }, 2180 - { url = "https://files.pythonhosted.org/packages/e6/d7/0110b8f54c008466b19672c615f2168896b83706a6611ba6e47313dbc6e9/tiktoken-0.12.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:cbb9a3ba275165a2cb0f9a83f5d7025afe6b9d0ab01a22b50f0e74fee2ad253e", size = 993855, upload-time = "2025-10-06T20:22:28.799Z" }, 2181 - { url = "https://files.pythonhosted.org/packages/5f/77/4f268c41a3957c418b084dd576ea2fad2e95da0d8e1ab705372892c2ca22/tiktoken-0.12.0-cp314-cp314t-manylinux_2_28_aarch64.whl", hash = "sha256:dfdfaa5ffff8993a3af94d1125870b1d27aed7cb97aa7eb8c1cefdbc87dbee63", size = 1129022, upload-time = "2025-10-06T20:22:29.981Z" }, 2182 - { url = "https://files.pythonhosted.org/packages/4e/2b/fc46c90fe5028bd094cd6ee25a7db321cb91d45dc87531e2bdbb26b4867a/tiktoken-0.12.0-cp314-cp314t-manylinux_2_28_x86_64.whl", hash = "sha256:584c3ad3d0c74f5269906eb8a659c8bfc6144a52895d9261cdaf90a0ae5f4de0", size = 1150736, upload-time = "2025-10-06T20:22:30.996Z" }, 2183 - { url = "https://files.pythonhosted.org/packages/28/c0/3c7a39ff68022ddfd7d93f3337ad90389a342f761c4d71de99a3ccc57857/tiktoken-0.12.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:54c891b416a0e36b8e2045b12b33dd66fb34a4fe7965565f1b482da50da3e86a", size = 1194908, upload-time = "2025-10-06T20:22:32.073Z" }, 2184 - { url = "https://files.pythonhosted.org/packages/ab/0d/c1ad6f4016a3968c048545f5d9b8ffebf577774b2ede3e2e352553b685fe/tiktoken-0.12.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:5edb8743b88d5be814b1a8a8854494719080c28faaa1ccbef02e87354fe71ef0", size = 1253706, upload-time = "2025-10-06T20:22:33.385Z" }, 2185 - { url = "https://files.pythonhosted.org/packages/af/df/c7891ef9d2712ad774777271d39fdef63941ffba0a9d59b7ad1fd2765e57/tiktoken-0.12.0-cp314-cp314t-win_amd64.whl", hash = "sha256:f61c0aea5565ac82e2ec50a05e02a6c44734e91b51c10510b084ea1b8e633a71", size = 920667, upload-time = "2025-10-06T20:22:34.444Z" }, 2186 - ] 2187 - 2188 - [[package]] 2189 - name = "tokenizers" 2190 - version = "0.22.2" 2191 - source = { registry = "https://pypi.org/simple" } 2192 - dependencies = [ 2193 - { name = "huggingface-hub" }, 2194 - ] 2195 - sdist = { url = "https://files.pythonhosted.org/packages/73/6f/f80cfef4a312e1fb34baf7d85c72d4411afde10978d4657f8cdd811d3ccc/tokenizers-0.22.2.tar.gz", hash = "sha256:473b83b915e547aa366d1eee11806deaf419e17be16310ac0a14077f1e28f917", size = 372115, upload-time = "2026-01-05T10:45:15.988Z" } 2196 - wheels = [ 2197 - { url = "https://files.pythonhosted.org/packages/92/97/5dbfabf04c7e348e655e907ed27913e03db0923abb5dfdd120d7b25630e1/tokenizers-0.22.2-cp39-abi3-macosx_10_12_x86_64.whl", hash = "sha256:544dd704ae7238755d790de45ba8da072e9af3eea688f698b137915ae959281c", size = 3100275, upload-time = "2026-01-05T10:41:02.158Z" }, 2198 - { url = "https://files.pythonhosted.org/packages/2e/47/174dca0502ef88b28f1c9e06b73ce33500eedfac7a7692108aec220464e7/tokenizers-0.22.2-cp39-abi3-macosx_11_0_arm64.whl", hash = "sha256:1e418a55456beedca4621dbab65a318981467a2b188e982a23e117f115ce5001", size = 2981472, upload-time = "2026-01-05T10:41:00.276Z" }, 2199 - { url = "https://files.pythonhosted.org/packages/d6/84/7990e799f1309a8b87af6b948f31edaa12a3ed22d11b352eaf4f4b2e5753/tokenizers-0.22.2-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2249487018adec45d6e3554c71d46eb39fa8ea67156c640f7513eb26f318cec7", size = 3290736, upload-time = "2026-01-05T10:40:32.165Z" }, 2200 - { url = "https://files.pythonhosted.org/packages/78/59/09d0d9ba94dcd5f4f1368d4858d24546b4bdc0231c2354aa31d6199f0399/tokenizers-0.22.2-cp39-abi3-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:25b85325d0815e86e0bac263506dd114578953b7b53d7de09a6485e4a160a7dd", size = 3168835, upload-time = "2026-01-05T10:40:38.847Z" }, 2201 - { url = "https://files.pythonhosted.org/packages/47/50/b3ebb4243e7160bda8d34b731e54dd8ab8b133e50775872e7a434e524c28/tokenizers-0.22.2-cp39-abi3-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:bfb88f22a209ff7b40a576d5324bf8286b519d7358663db21d6246fb17eea2d5", size = 3521673, upload-time = "2026-01-05T10:40:56.614Z" }, 2202 - { url = "https://files.pythonhosted.org/packages/e0/fa/89f4cb9e08df770b57adb96f8cbb7e22695a4cb6c2bd5f0c4f0ebcf33b66/tokenizers-0.22.2-cp39-abi3-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1c774b1276f71e1ef716e5486f21e76333464f47bece56bbd554485982a9e03e", size = 3724818, upload-time = "2026-01-05T10:40:44.507Z" }, 2203 - { url = "https://files.pythonhosted.org/packages/64/04/ca2363f0bfbe3b3d36e95bf67e56a4c88c8e3362b658e616d1ac185d47f2/tokenizers-0.22.2-cp39-abi3-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:df6c4265b289083bf710dff49bc51ef252f9d5be33a45ee2bed151114a56207b", size = 3379195, upload-time = "2026-01-05T10:40:51.139Z" }, 2204 - { url = "https://files.pythonhosted.org/packages/2e/76/932be4b50ef6ccedf9d3c6639b056a967a86258c6d9200643f01269211ca/tokenizers-0.22.2-cp39-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:369cc9fc8cc10cb24143873a0d95438bb8ee257bb80c71989e3ee290e8d72c67", size = 3274982, upload-time = "2026-01-05T10:40:58.331Z" }, 2205 - { url = "https://files.pythonhosted.org/packages/1d/28/5f9f5a4cc211b69e89420980e483831bcc29dade307955cc9dc858a40f01/tokenizers-0.22.2-cp39-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:29c30b83d8dcd061078b05ae0cb94d3c710555fbb44861139f9f83dcca3dc3e4", size = 9478245, upload-time = "2026-01-05T10:41:04.053Z" }, 2206 - { url = "https://files.pythonhosted.org/packages/6c/fb/66e2da4704d6aadebf8cb39f1d6d1957df667ab24cff2326b77cda0dcb85/tokenizers-0.22.2-cp39-abi3-musllinux_1_2_armv7l.whl", hash = "sha256:37ae80a28c1d3265bb1f22464c856bd23c02a05bb211e56d0c5301a435be6c1a", size = 9560069, upload-time = "2026-01-05T10:45:10.673Z" }, 2207 - { url = "https://files.pythonhosted.org/packages/16/04/fed398b05caa87ce9b1a1bb5166645e38196081b225059a6edaff6440fac/tokenizers-0.22.2-cp39-abi3-musllinux_1_2_i686.whl", hash = "sha256:791135ee325f2336f498590eb2f11dc5c295232f288e75c99a36c5dbce63088a", size = 9899263, upload-time = "2026-01-05T10:45:12.559Z" }, 2208 - { url = "https://files.pythonhosted.org/packages/05/a1/d62dfe7376beaaf1394917e0f8e93ee5f67fea8fcf4107501db35996586b/tokenizers-0.22.2-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:38337540fbbddff8e999d59970f3c6f35a82de10053206a7562f1ea02d046fa5", size = 10033429, upload-time = "2026-01-05T10:45:14.333Z" }, 2209 - { url = "https://files.pythonhosted.org/packages/fd/18/a545c4ea42af3df6effd7d13d250ba77a0a86fb20393143bbb9a92e434d4/tokenizers-0.22.2-cp39-abi3-win32.whl", hash = "sha256:a6bf3f88c554a2b653af81f3204491c818ae2ac6fbc09e76ef4773351292bc92", size = 2502363, upload-time = "2026-01-05T10:45:20.593Z" }, 2210 - { url = "https://files.pythonhosted.org/packages/65/71/0670843133a43d43070abeb1949abfdef12a86d490bea9cd9e18e37c5ff7/tokenizers-0.22.2-cp39-abi3-win_amd64.whl", hash = "sha256:c9ea31edff2968b44a88f97d784c2f16dc0729b8b143ed004699ebca91f05c48", size = 2747786, upload-time = "2026-01-05T10:45:18.411Z" }, 2211 - { url = "https://files.pythonhosted.org/packages/72/f4/0de46cfa12cdcbcd464cc59fde36912af405696f687e53a091fb432f694c/tokenizers-0.22.2-cp39-abi3-win_arm64.whl", hash = "sha256:9ce725d22864a1e965217204946f830c37876eee3b2ba6fc6255e8e903d5fcbc", size = 2612133, upload-time = "2026-01-05T10:45:17.232Z" }, 2212 - ] 2213 - 2214 - [[package]] 2215 - name = "tqdm" 2216 - version = "4.67.3" 2217 - source = { registry = "https://pypi.org/simple" } 2218 - dependencies = [ 2219 - { name = "colorama", marker = "sys_platform == 'win32'" }, 2220 - ] 2221 - sdist = { url = "https://files.pythonhosted.org/packages/09/a9/6ba95a270c6f1fbcd8dac228323f2777d886cb206987444e4bce66338dd4/tqdm-4.67.3.tar.gz", hash = "sha256:7d825f03f89244ef73f1d4ce193cb1774a8179fd96f31d7e1dcde62092b960bb", size = 169598, upload-time = "2026-02-03T17:35:53.048Z" } 2222 - wheels = [ 2223 - { url = "https://files.pythonhosted.org/packages/16/e1/3079a9ff9b8e11b846c6ac5c8b5bfb7ff225eee721825310c91b3b50304f/tqdm-4.67.3-py3-none-any.whl", hash = "sha256:ee1e4c0e59148062281c49d80b25b67771a127c85fc9676d3be5f243206826bf", size = 78374, upload-time = "2026-02-03T17:35:50.982Z" }, 2224 - ] 2225 - 2226 - [[package]] 2227 - name = "typer" 2228 - version = "0.24.1" 2229 - source = { registry = "https://pypi.org/simple" } 2230 - dependencies = [ 2231 - { name = "annotated-doc" }, 2232 - { name = "click" }, 2233 - { name = "rich" }, 2234 - { name = "shellingham" }, 2235 - ] 2236 - sdist = { url = "https://files.pythonhosted.org/packages/f5/24/cb09efec5cc954f7f9b930bf8279447d24618bb6758d4f6adf2574c41780/typer-0.24.1.tar.gz", hash = "sha256:e39b4732d65fbdcde189ae76cf7cd48aeae72919dea1fdfc16593be016256b45", size = 118613, upload-time = "2026-02-21T16:54:40.609Z" } 2237 - wheels = [ 2238 - { url = "https://files.pythonhosted.org/packages/4a/91/48db081e7a63bb37284f9fbcefda7c44c277b18b0e13fbc36ea2335b71e6/typer-0.24.1-py3-none-any.whl", hash = "sha256:112c1f0ce578bfb4cab9ffdabc68f031416ebcc216536611ba21f04e9aa84c9e", size = 56085, upload-time = "2026-02-21T16:54:41.616Z" }, 2239 - ] 2240 - 2241 - [[package]] 2242 - name = "types-protobuf" 2243 - version = "6.32.1.20260221" 2244 - source = { registry = "https://pypi.org/simple" } 2245 - 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" } 2246 - wheels = [ 2247 - { 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" }, 2248 - ] 2249 - 2250 - [[package]] 2251 - name = "types-requests" 2252 - version = "2.33.0.20260408" 2253 - source = { registry = "https://pypi.org/simple" } 2254 - dependencies = [ 2255 - { name = "urllib3" }, 2256 - ] 2257 - sdist = { url = "https://files.pythonhosted.org/packages/69/6a/749dc53a54a3f35842c1f8197b3ca6b54af6d7458a1bfc75f6629b6da666/types_requests-2.33.0.20260408.tar.gz", hash = "sha256:95b9a86376807a216b2fb412b47617b202091c3ea7c078f47cc358d5528ccb7b", size = 23882, upload-time = "2026-04-08T04:34:49.33Z" } 2258 - wheels = [ 2259 - { url = "https://files.pythonhosted.org/packages/90/b8/78fd6c037de4788c040fdd323b3369804400351b7827473920f6c1d03c10/types_requests-2.33.0.20260408-py3-none-any.whl", hash = "sha256:81f31d5ea4acb39f03be7bc8bed569ba6d5a9c5d97e89f45ac43d819b68ca50f", size = 20739, upload-time = "2026-04-08T04:34:48.325Z" }, 2260 - ] 2261 - 2262 - [[package]] 2263 - name = "typing-extensions" 2264 - version = "4.15.0" 2265 - source = { registry = "https://pypi.org/simple" } 2266 - sdist = { url = "https://files.pythonhosted.org/packages/72/94/1a15dd82efb362ac84269196e94cf00f187f7ed21c242792a923cdb1c61f/typing_extensions-4.15.0.tar.gz", hash = "sha256:0cea48d173cc12fa28ecabc3b837ea3cf6f38c6d1136f85cbaaf598984861466", size = 109391, upload-time = "2025-08-25T13:49:26.313Z" } 2267 - wheels = [ 2268 - { url = "https://files.pythonhosted.org/packages/18/67/36e9267722cc04a6b9f15c7f3441c2363321a3ea07da7ae0c0707beb2a9c/typing_extensions-4.15.0-py3-none-any.whl", hash = "sha256:f0fa19c6845758ab08074a0cfa8b7aecb71c999ca73d62883bc25cc018c4e548", size = 44614, upload-time = "2025-08-25T13:49:24.86Z" }, 2269 - ] 2270 - 2271 - [[package]] 2272 - name = "typing-inspection" 2273 - version = "0.4.2" 2274 - source = { registry = "https://pypi.org/simple" } 2275 - dependencies = [ 2276 - { name = "typing-extensions" }, 2277 - ] 2278 - sdist = { url = "https://files.pythonhosted.org/packages/55/e3/70399cb7dd41c10ac53367ae42139cf4b1ca5f36bb3dc6c9d33acdb43655/typing_inspection-0.4.2.tar.gz", hash = "sha256:ba561c48a67c5958007083d386c3295464928b01faa735ab8547c5692e87f464", size = 75949, upload-time = "2025-10-01T02:14:41.687Z" } 2279 - wheels = [ 2280 - { url = "https://files.pythonhosted.org/packages/dc/9b/47798a6c91d8bdb567fe2698fe81e0c6b7cb7ef4d13da4114b41d239f65d/typing_inspection-0.4.2-py3-none-any.whl", hash = "sha256:4ed1cacbdc298c220f1bd249ed5287caa16f34d44ef4e9c3d0cbad5b521545e7", size = 14611, upload-time = "2025-10-01T02:14:40.154Z" }, 2281 - ] 2282 - 2283 - [[package]] 2284 - name = "uncalled-for" 2285 - version = "0.3.1" 2286 - source = { registry = "https://pypi.org/simple" } 2287 - sdist = { url = "https://files.pythonhosted.org/packages/e1/68/35c1d87e608940badbcfeb630347aa0509897284684f61fab6423d02b253/uncalled_for-0.3.1.tar.gz", hash = "sha256:5e412ac6708f04b56bef5867b5dcf6690ebce4eb7316058d9c50787492bb4bca", size = 49693, upload-time = "2026-04-07T13:05:06.462Z" } 2288 - wheels = [ 2289 - { url = "https://files.pythonhosted.org/packages/11/e1/7ec67882ad8fc9f86384bef6421fa252c9cbe5744f8df6ce77afc9eca1f5/uncalled_for-0.3.1-py3-none-any.whl", hash = "sha256:074cdc92da8356278f93d0ded6f2a66dd883dbecaf9bc89437646ee2289cc200", size = 11361, upload-time = "2026-04-07T13:05:05.341Z" }, 2290 - ] 2291 - 2292 - [[package]] 2293 - name = "urllib3" 2294 - version = "2.6.3" 2295 - source = { registry = "https://pypi.org/simple" } 2296 - sdist = { url = "https://files.pythonhosted.org/packages/c7/24/5f1b3bdffd70275f6661c76461e25f024d5a38a46f04aaca912426a2b1d3/urllib3-2.6.3.tar.gz", hash = "sha256:1b62b6884944a57dbe321509ab94fd4d3b307075e0c2eae991ac71ee15ad38ed", size = 435556, upload-time = "2026-01-07T16:24:43.925Z" } 2297 - wheels = [ 2298 - { url = "https://files.pythonhosted.org/packages/39/08/aaaad47bc4e9dc8c725e68f9d04865dbcb2052843ff09c97b08904852d84/urllib3-2.6.3-py3-none-any.whl", hash = "sha256:bf272323e553dfb2e87d9bfd225ca7b0f467b919d7bbd355436d3fd37cb0acd4", size = 131584, upload-time = "2026-01-07T16:24:42.685Z" }, 2299 - ] 2300 - 2301 - [[package]] 2302 - name = "uvicorn" 2303 - version = "0.44.0" 2304 - source = { registry = "https://pypi.org/simple" } 2305 - dependencies = [ 2306 - { name = "click" }, 2307 - { name = "h11" }, 2308 - ] 2309 - sdist = { url = "https://files.pythonhosted.org/packages/5e/da/6eee1ff8b6cbeed47eeb5229749168e81eb4b7b999a1a15a7176e51410c9/uvicorn-0.44.0.tar.gz", hash = "sha256:6c942071b68f07e178264b9152f1f16dfac5da85880c4ce06366a96d70d4f31e", size = 86947, upload-time = "2026-04-06T09:23:22.826Z" } 2310 - wheels = [ 2311 - { url = "https://files.pythonhosted.org/packages/b7/23/a5bbd9600dd607411fa644c06ff4951bec3a4d82c4b852374024359c19c0/uvicorn-0.44.0-py3-none-any.whl", hash = "sha256:ce937c99a2cc70279556967274414c087888e8cec9f9c94644dfca11bd3ced89", size = 69425, upload-time = "2026-04-06T09:23:21.524Z" }, 2312 - ] 2313 - 2314 - [package.optional-dependencies] 2315 - standard = [ 2316 - { name = "colorama", marker = "sys_platform == 'win32'" }, 2317 - { name = "httptools" }, 2318 - { name = "python-dotenv" }, 2319 - { name = "pyyaml" }, 2320 - { name = "uvloop", marker = "platform_python_implementation != 'PyPy' and sys_platform != 'cygwin' and sys_platform != 'win32'" }, 2321 - { name = "watchfiles" }, 2322 - { name = "websockets" }, 2323 - ] 2324 - 2325 - [[package]] 2326 - name = "uvloop" 2327 - version = "0.22.1" 2328 - source = { registry = "https://pypi.org/simple" } 2329 - sdist = { url = "https://files.pythonhosted.org/packages/06/f0/18d39dbd1971d6d62c4629cc7fa67f74821b0dc1f5a77af43719de7936a7/uvloop-0.22.1.tar.gz", hash = "sha256:6c84bae345b9147082b17371e3dd5d42775bddce91f885499017f4607fdaf39f", size = 2443250, upload-time = "2025-10-16T22:17:19.342Z" } 2330 - wheels = [ 2331 - { url = "https://files.pythonhosted.org/packages/90/cd/b62bdeaa429758aee8de8b00ac0dd26593a9de93d302bff3d21439e9791d/uvloop-0.22.1-cp314-cp314-macosx_10_13_universal2.whl", hash = "sha256:3879b88423ec7e97cd4eba2a443aa26ed4e59b45e6b76aabf13fe2f27023a142", size = 1362067, upload-time = "2025-10-16T22:16:44.503Z" }, 2332 - { url = "https://files.pythonhosted.org/packages/0d/f8/a132124dfda0777e489ca86732e85e69afcd1ff7686647000050ba670689/uvloop-0.22.1-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:4baa86acedf1d62115c1dc6ad1e17134476688f08c6efd8a2ab076e815665c74", size = 752423, upload-time = "2025-10-16T22:16:45.968Z" }, 2333 - { url = "https://files.pythonhosted.org/packages/a3/94/94af78c156f88da4b3a733773ad5ba0b164393e357cc4bd0ab2e2677a7d6/uvloop-0.22.1-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:297c27d8003520596236bdb2335e6b3f649480bd09e00d1e3a99144b691d2a35", size = 4272437, upload-time = "2025-10-16T22:16:47.451Z" }, 2334 - { url = "https://files.pythonhosted.org/packages/b5/35/60249e9fd07b32c665192cec7af29e06c7cd96fa1d08b84f012a56a0b38e/uvloop-0.22.1-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c1955d5a1dd43198244d47664a5858082a3239766a839b2102a269aaff7a4e25", size = 4292101, upload-time = "2025-10-16T22:16:49.318Z" }, 2335 - { url = "https://files.pythonhosted.org/packages/02/62/67d382dfcb25d0a98ce73c11ed1a6fba5037a1a1d533dcbb7cab033a2636/uvloop-0.22.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:b31dc2fccbd42adc73bc4e7cdbae4fc5086cf378979e53ca5d0301838c5682c6", size = 4114158, upload-time = "2025-10-16T22:16:50.517Z" }, 2336 - { url = "https://files.pythonhosted.org/packages/f0/7a/f1171b4a882a5d13c8b7576f348acfe6074d72eaf52cccef752f748d4a9f/uvloop-0.22.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:93f617675b2d03af4e72a5333ef89450dfaa5321303ede6e67ba9c9d26878079", size = 4177360, upload-time = "2025-10-16T22:16:52.646Z" }, 2337 - { url = "https://files.pythonhosted.org/packages/79/7b/b01414f31546caf0919da80ad57cbfe24c56b151d12af68cee1b04922ca8/uvloop-0.22.1-cp314-cp314t-macosx_10_13_universal2.whl", hash = "sha256:37554f70528f60cad66945b885eb01f1bb514f132d92b6eeed1c90fd54ed6289", size = 1454790, upload-time = "2025-10-16T22:16:54.355Z" }, 2338 - { url = "https://files.pythonhosted.org/packages/d4/31/0bb232318dd838cad3fa8fb0c68c8b40e1145b32025581975e18b11fab40/uvloop-0.22.1-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:b76324e2dc033a0b2f435f33eb88ff9913c156ef78e153fb210e03c13da746b3", size = 796783, upload-time = "2025-10-16T22:16:55.906Z" }, 2339 - { url = "https://files.pythonhosted.org/packages/42/38/c9b09f3271a7a723a5de69f8e237ab8e7803183131bc57c890db0b6bb872/uvloop-0.22.1-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:badb4d8e58ee08dad957002027830d5c3b06aea446a6a3744483c2b3b745345c", size = 4647548, upload-time = "2025-10-16T22:16:57.008Z" }, 2340 - { url = "https://files.pythonhosted.org/packages/c1/37/945b4ca0ac27e3dc4952642d4c900edd030b3da6c9634875af6e13ae80e5/uvloop-0.22.1-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b91328c72635f6f9e0282e4a57da7470c7350ab1c9f48546c0f2866205349d21", size = 4467065, upload-time = "2025-10-16T22:16:58.206Z" }, 2341 - { url = "https://files.pythonhosted.org/packages/97/cc/48d232f33d60e2e2e0b42f4e73455b146b76ebe216487e862700457fbf3c/uvloop-0.22.1-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:daf620c2995d193449393d6c62131b3fbd40a63bf7b307a1527856ace637fe88", size = 4328384, upload-time = "2025-10-16T22:16:59.36Z" }, 2342 - { url = "https://files.pythonhosted.org/packages/e4/16/c1fd27e9549f3c4baf1dc9c20c456cd2f822dbf8de9f463824b0c0357e06/uvloop-0.22.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:6cde23eeda1a25c75b2e07d39970f3374105d5eafbaab2a4482be82f272d5a5e", size = 4296730, upload-time = "2025-10-16T22:17:00.744Z" }, 2343 - ] 2344 - 2345 - [[package]] 2346 - name = "watchfiles" 2347 - version = "1.1.1" 2348 - source = { registry = "https://pypi.org/simple" } 2349 - dependencies = [ 2350 - { name = "anyio" }, 2351 - ] 2352 - sdist = { url = "https://files.pythonhosted.org/packages/c2/c9/8869df9b2a2d6c59d79220a4db37679e74f807c559ffe5265e08b227a210/watchfiles-1.1.1.tar.gz", hash = "sha256:a173cb5c16c4f40ab19cecf48a534c409f7ea983ab8fed0741304a1c0a31b3f2", size = 94440, upload-time = "2025-10-14T15:06:21.08Z" } 2353 - wheels = [ 2354 - { url = "https://files.pythonhosted.org/packages/c3/f4/0872229324ef69b2c3edec35e84bd57a1289e7d3fe74588048ed8947a323/watchfiles-1.1.1-cp314-cp314-macosx_10_12_x86_64.whl", hash = "sha256:d1715143123baeeaeadec0528bb7441103979a1d5f6fd0e1f915383fea7ea6d5", size = 404315, upload-time = "2025-10-14T15:05:26.501Z" }, 2355 - { url = "https://files.pythonhosted.org/packages/7b/22/16d5331eaed1cb107b873f6ae1b69e9ced582fcf0c59a50cd84f403b1c32/watchfiles-1.1.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:39574d6370c4579d7f5d0ad940ce5b20db0e4117444e39b6d8f99db5676c52fd", size = 390869, upload-time = "2025-10-14T15:05:27.649Z" }, 2356 - { url = "https://files.pythonhosted.org/packages/b2/7e/5643bfff5acb6539b18483128fdc0ef2cccc94a5b8fbda130c823e8ed636/watchfiles-1.1.1-cp314-cp314-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7365b92c2e69ee952902e8f70f3ba6360d0d596d9299d55d7d386df84b6941fb", size = 449919, upload-time = "2025-10-14T15:05:28.701Z" }, 2357 - { url = "https://files.pythonhosted.org/packages/51/2e/c410993ba5025a9f9357c376f48976ef0e1b1aefb73b97a5ae01a5972755/watchfiles-1.1.1-cp314-cp314-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:bfff9740c69c0e4ed32416f013f3c45e2ae42ccedd1167ef2d805c000b6c71a5", size = 460845, upload-time = "2025-10-14T15:05:30.064Z" }, 2358 - { url = "https://files.pythonhosted.org/packages/8e/a4/2df3b404469122e8680f0fcd06079317e48db58a2da2950fb45020947734/watchfiles-1.1.1-cp314-cp314-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b27cf2eb1dda37b2089e3907d8ea92922b673c0c427886d4edc6b94d8dfe5db3", size = 489027, upload-time = "2025-10-14T15:05:31.064Z" }, 2359 - { url = "https://files.pythonhosted.org/packages/ea/84/4587ba5b1f267167ee715b7f66e6382cca6938e0a4b870adad93e44747e6/watchfiles-1.1.1-cp314-cp314-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:526e86aced14a65a5b0ec50827c745597c782ff46b571dbfe46192ab9e0b3c33", size = 595615, upload-time = "2025-10-14T15:05:32.074Z" }, 2360 - { url = "https://files.pythonhosted.org/packages/6a/0f/c6988c91d06e93cd0bb3d4a808bcf32375ca1904609835c3031799e3ecae/watchfiles-1.1.1-cp314-cp314-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:04e78dd0b6352db95507fd8cb46f39d185cf8c74e4cf1e4fbad1d3df96faf510", size = 474836, upload-time = "2025-10-14T15:05:33.209Z" }, 2361 - { url = "https://files.pythonhosted.org/packages/b4/36/ded8aebea91919485b7bbabbd14f5f359326cb5ec218cd67074d1e426d74/watchfiles-1.1.1-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5c85794a4cfa094714fb9c08d4a218375b2b95b8ed1666e8677c349906246c05", size = 455099, upload-time = "2025-10-14T15:05:34.189Z" }, 2362 - { url = "https://files.pythonhosted.org/packages/98/e0/8c9bdba88af756a2fce230dd365fab2baf927ba42cd47521ee7498fd5211/watchfiles-1.1.1-cp314-cp314-musllinux_1_1_aarch64.whl", hash = "sha256:74d5012b7630714b66be7b7b7a78855ef7ad58e8650c73afc4c076a1f480a8d6", size = 630626, upload-time = "2025-10-14T15:05:35.216Z" }, 2363 - { url = "https://files.pythonhosted.org/packages/2a/84/a95db05354bf2d19e438520d92a8ca475e578c647f78f53197f5a2f17aaf/watchfiles-1.1.1-cp314-cp314-musllinux_1_1_x86_64.whl", hash = "sha256:8fbe85cb3201c7d380d3d0b90e63d520f15d6afe217165d7f98c9c649654db81", size = 622519, upload-time = "2025-10-14T15:05:36.259Z" }, 2364 - { url = "https://files.pythonhosted.org/packages/1d/ce/d8acdc8de545de995c339be67711e474c77d643555a9bb74a9334252bd55/watchfiles-1.1.1-cp314-cp314-win32.whl", hash = "sha256:3fa0b59c92278b5a7800d3ee7733da9d096d4aabcfabb9a928918bd276ef9b9b", size = 272078, upload-time = "2025-10-14T15:05:37.63Z" }, 2365 - { url = "https://files.pythonhosted.org/packages/c4/c9/a74487f72d0451524be827e8edec251da0cc1fcf111646a511ae752e1a3d/watchfiles-1.1.1-cp314-cp314-win_amd64.whl", hash = "sha256:c2047d0b6cea13b3316bdbafbfa0c4228ae593d995030fda39089d36e64fc03a", size = 287664, upload-time = "2025-10-14T15:05:38.95Z" }, 2366 - { url = "https://files.pythonhosted.org/packages/df/b8/8ac000702cdd496cdce998c6f4ee0ca1f15977bba51bdf07d872ebdfc34c/watchfiles-1.1.1-cp314-cp314-win_arm64.whl", hash = "sha256:842178b126593addc05acf6fce960d28bc5fae7afbaa2c6c1b3a7b9460e5be02", size = 277154, upload-time = "2025-10-14T15:05:39.954Z" }, 2367 - { url = "https://files.pythonhosted.org/packages/47/a8/e3af2184707c29f0f14b1963c0aace6529f9d1b8582d5b99f31bbf42f59e/watchfiles-1.1.1-cp314-cp314t-macosx_10_12_x86_64.whl", hash = "sha256:88863fbbc1a7312972f1c511f202eb30866370ebb8493aef2812b9ff28156a21", size = 403820, upload-time = "2025-10-14T15:05:40.932Z" }, 2368 - { url = "https://files.pythonhosted.org/packages/c0/ec/e47e307c2f4bd75f9f9e8afbe3876679b18e1bcec449beca132a1c5ffb2d/watchfiles-1.1.1-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:55c7475190662e202c08c6c0f4d9e345a29367438cf8e8037f3155e10a88d5a5", size = 390510, upload-time = "2025-10-14T15:05:41.945Z" }, 2369 - { url = "https://files.pythonhosted.org/packages/d5/a0/ad235642118090f66e7b2f18fd5c42082418404a79205cdfca50b6309c13/watchfiles-1.1.1-cp314-cp314t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3f53fa183d53a1d7a8852277c92b967ae99c2d4dcee2bfacff8868e6e30b15f7", size = 448408, upload-time = "2025-10-14T15:05:43.385Z" }, 2370 - { url = "https://files.pythonhosted.org/packages/df/85/97fa10fd5ff3332ae17e7e40e20784e419e28521549780869f1413742e9d/watchfiles-1.1.1-cp314-cp314t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:6aae418a8b323732fa89721d86f39ec8f092fc2af67f4217a2b07fd3e93c6101", size = 458968, upload-time = "2025-10-14T15:05:44.404Z" }, 2371 - { url = "https://files.pythonhosted.org/packages/47/c2/9059c2e8966ea5ce678166617a7f75ecba6164375f3b288e50a40dc6d489/watchfiles-1.1.1-cp314-cp314t-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f096076119da54a6080e8920cbdaac3dbee667eb91dcc5e5b78840b87415bd44", size = 488096, upload-time = "2025-10-14T15:05:45.398Z" }, 2372 - { url = "https://files.pythonhosted.org/packages/94/44/d90a9ec8ac309bc26db808a13e7bfc0e4e78b6fc051078a554e132e80160/watchfiles-1.1.1-cp314-cp314t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:00485f441d183717038ed2e887a7c868154f216877653121068107b227a2f64c", size = 596040, upload-time = "2025-10-14T15:05:46.502Z" }, 2373 - { url = "https://files.pythonhosted.org/packages/95/68/4e3479b20ca305cfc561db3ed207a8a1c745ee32bf24f2026a129d0ddb6e/watchfiles-1.1.1-cp314-cp314t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a55f3e9e493158d7bfdb60a1165035f1cf7d320914e7b7ea83fe22c6023b58fc", size = 473847, upload-time = "2025-10-14T15:05:47.484Z" }, 2374 - { url = "https://files.pythonhosted.org/packages/4f/55/2af26693fd15165c4ff7857e38330e1b61ab8c37d15dc79118cdba115b7a/watchfiles-1.1.1-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8c91ed27800188c2ae96d16e3149f199d62f86c7af5f5f4d2c61a3ed8cd3666c", size = 455072, upload-time = "2025-10-14T15:05:48.928Z" }, 2375 - { url = "https://files.pythonhosted.org/packages/66/1d/d0d200b10c9311ec25d2273f8aad8c3ef7cc7ea11808022501811208a750/watchfiles-1.1.1-cp314-cp314t-musllinux_1_1_aarch64.whl", hash = "sha256:311ff15a0bae3714ffb603e6ba6dbfba4065ab60865d15a6ec544133bdb21099", size = 629104, upload-time = "2025-10-14T15:05:49.908Z" }, 2376 - { url = "https://files.pythonhosted.org/packages/e3/bd/fa9bb053192491b3867ba07d2343d9f2252e00811567d30ae8d0f78136fe/watchfiles-1.1.1-cp314-cp314t-musllinux_1_1_x86_64.whl", hash = "sha256:a916a2932da8f8ab582f242c065f5c81bed3462849ca79ee357dd9551b0e9b01", size = 622112, upload-time = "2025-10-14T15:05:50.941Z" }, 2377 - ] 2378 - 2379 - [[package]] 2380 - name = "wcwidth" 2381 - version = "0.6.0" 2382 - source = { registry = "https://pypi.org/simple" } 2383 - sdist = { url = "https://files.pythonhosted.org/packages/35/a2/8e3becb46433538a38726c948d3399905a4c7cabd0df578ede5dc51f0ec2/wcwidth-0.6.0.tar.gz", hash = "sha256:cdc4e4262d6ef9a1a57e018384cbeb1208d8abbc64176027e2c2455c81313159", size = 159684, upload-time = "2026-02-06T19:19:40.919Z" } 2384 - wheels = [ 2385 - { url = "https://files.pythonhosted.org/packages/68/5a/199c59e0a824a3db2b89c5d2dade7ab5f9624dbf6448dc291b46d5ec94d3/wcwidth-0.6.0-py3-none-any.whl", hash = "sha256:1a3a1e510b553315f8e146c54764f4fb6264ffad731b3d78088cdb1478ffbdad", size = 94189, upload-time = "2026-02-06T19:19:39.646Z" }, 2386 - ] 2387 - 2388 - [[package]] 2389 - name = "websockets" 2390 - version = "15.0.1" 2391 - source = { registry = "https://pypi.org/simple" } 2392 - sdist = { url = "https://files.pythonhosted.org/packages/21/e6/26d09fab466b7ca9c7737474c52be4f76a40301b08362eb2dbc19dcc16c1/websockets-15.0.1.tar.gz", hash = "sha256:82544de02076bafba038ce055ee6412d68da13ab47f0c60cab827346de828dee", size = 177016, upload-time = "2025-03-05T20:03:41.606Z" } 2393 - wheels = [ 2394 - { url = "https://files.pythonhosted.org/packages/fa/a8/5b41e0da817d64113292ab1f8247140aac61cbf6cfd085d6a0fa77f4984f/websockets-15.0.1-py3-none-any.whl", hash = "sha256:f7a866fbc1e97b5c617ee4116daaa09b722101d4a3c170c787450ba409f9736f", size = 169743, upload-time = "2025-03-05T20:03:39.41Z" }, 2395 - ] 2396 - 2397 - [[package]] 2398 - name = "wrapt" 2399 - version = "1.17.3" 2400 - source = { registry = "https://pypi.org/simple" } 2401 - sdist = { url = "https://files.pythonhosted.org/packages/95/8f/aeb76c5b46e273670962298c23e7ddde79916cb74db802131d49a85e4b7d/wrapt-1.17.3.tar.gz", hash = "sha256:f66eb08feaa410fe4eebd17f2a2c8e2e46d3476e9f8c783daa8e09e0faa666d0", size = 55547, upload-time = "2025-08-12T05:53:21.714Z" } 2402 - wheels = [ 2403 - { url = "https://files.pythonhosted.org/packages/02/a2/cd864b2a14f20d14f4c496fab97802001560f9f41554eef6df201cd7f76c/wrapt-1.17.3-cp314-cp314-macosx_10_13_universal2.whl", hash = "sha256:cf30f6e3c077c8e6a9a7809c94551203c8843e74ba0c960f4a98cd80d4665d39", size = 54132, upload-time = "2025-08-12T05:51:49.864Z" }, 2404 - { url = "https://files.pythonhosted.org/packages/d5/46/d011725b0c89e853dc44cceb738a307cde5d240d023d6d40a82d1b4e1182/wrapt-1.17.3-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:e228514a06843cae89621384cfe3a80418f3c04aadf8a3b14e46a7be704e4235", size = 39091, upload-time = "2025-08-12T05:51:38.935Z" }, 2405 - { url = "https://files.pythonhosted.org/packages/2e/9e/3ad852d77c35aae7ddebdbc3b6d35ec8013af7d7dddad0ad911f3d891dae/wrapt-1.17.3-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:5ea5eb3c0c071862997d6f3e02af1d055f381b1d25b286b9d6644b79db77657c", size = 39172, upload-time = "2025-08-12T05:51:59.365Z" }, 2406 - { url = "https://files.pythonhosted.org/packages/c3/f7/c983d2762bcce2326c317c26a6a1e7016f7eb039c27cdf5c4e30f4160f31/wrapt-1.17.3-cp314-cp314-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:281262213373b6d5e4bb4353bc36d1ba4084e6d6b5d242863721ef2bf2c2930b", size = 87163, upload-time = "2025-08-12T05:52:40.965Z" }, 2407 - { url = "https://files.pythonhosted.org/packages/e4/0f/f673f75d489c7f22d17fe0193e84b41540d962f75fce579cf6873167c29b/wrapt-1.17.3-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:dc4a8d2b25efb6681ecacad42fca8859f88092d8732b170de6a5dddd80a1c8fa", size = 87963, upload-time = "2025-08-12T05:52:20.326Z" }, 2408 - { url = "https://files.pythonhosted.org/packages/df/61/515ad6caca68995da2fac7a6af97faab8f78ebe3bf4f761e1b77efbc47b5/wrapt-1.17.3-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:373342dd05b1d07d752cecbec0c41817231f29f3a89aa8b8843f7b95992ed0c7", size = 86945, upload-time = "2025-08-12T05:52:21.581Z" }, 2409 - { url = "https://files.pythonhosted.org/packages/d3/bd/4e70162ce398462a467bc09e768bee112f1412e563620adc353de9055d33/wrapt-1.17.3-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:d40770d7c0fd5cbed9d84b2c3f2e156431a12c9a37dc6284060fb4bec0b7ffd4", size = 86857, upload-time = "2025-08-12T05:52:43.043Z" }, 2410 - { url = "https://files.pythonhosted.org/packages/2b/b8/da8560695e9284810b8d3df8a19396a6e40e7518059584a1a394a2b35e0a/wrapt-1.17.3-cp314-cp314-win32.whl", hash = "sha256:fbd3c8319de8e1dc79d346929cd71d523622da527cca14e0c1d257e31c2b8b10", size = 37178, upload-time = "2025-08-12T05:53:12.605Z" }, 2411 - { url = "https://files.pythonhosted.org/packages/db/c8/b71eeb192c440d67a5a0449aaee2310a1a1e8eca41676046f99ed2487e9f/wrapt-1.17.3-cp314-cp314-win_amd64.whl", hash = "sha256:e1a4120ae5705f673727d3253de3ed0e016f7cd78dc463db1b31e2463e1f3cf6", size = 39310, upload-time = "2025-08-12T05:53:11.106Z" }, 2412 - { url = "https://files.pythonhosted.org/packages/45/20/2cda20fd4865fa40f86f6c46ed37a2a8356a7a2fde0773269311f2af56c7/wrapt-1.17.3-cp314-cp314-win_arm64.whl", hash = "sha256:507553480670cab08a800b9463bdb881b2edeed77dc677b0a5915e6106e91a58", size = 37266, upload-time = "2025-08-12T05:52:56.531Z" }, 2413 - { url = "https://files.pythonhosted.org/packages/77/ed/dd5cf21aec36c80443c6f900449260b80e2a65cf963668eaef3b9accce36/wrapt-1.17.3-cp314-cp314t-macosx_10_13_universal2.whl", hash = "sha256:ed7c635ae45cfbc1a7371f708727bf74690daedc49b4dba310590ca0bd28aa8a", size = 56544, upload-time = "2025-08-12T05:51:51.109Z" }, 2414 - { url = "https://files.pythonhosted.org/packages/8d/96/450c651cc753877ad100c7949ab4d2e2ecc4d97157e00fa8f45df682456a/wrapt-1.17.3-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:249f88ed15503f6492a71f01442abddd73856a0032ae860de6d75ca62eed8067", size = 40283, upload-time = "2025-08-12T05:51:39.912Z" }, 2415 - { url = "https://files.pythonhosted.org/packages/d1/86/2fcad95994d9b572db57632acb6f900695a648c3e063f2cd344b3f5c5a37/wrapt-1.17.3-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:5a03a38adec8066d5a37bea22f2ba6bbf39fcdefbe2d91419ab864c3fb515454", size = 40366, upload-time = "2025-08-12T05:52:00.693Z" }, 2416 - { url = "https://files.pythonhosted.org/packages/64/0e/f4472f2fdde2d4617975144311f8800ef73677a159be7fe61fa50997d6c0/wrapt-1.17.3-cp314-cp314t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:5d4478d72eb61c36e5b446e375bbc49ed002430d17cdec3cecb36993398e1a9e", size = 108571, upload-time = "2025-08-12T05:52:44.521Z" }, 2417 - { url = "https://files.pythonhosted.org/packages/cc/01/9b85a99996b0a97c8a17484684f206cbb6ba73c1ce6890ac668bcf3838fb/wrapt-1.17.3-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:223db574bb38637e8230eb14b185565023ab624474df94d2af18f1cdb625216f", size = 113094, upload-time = "2025-08-12T05:52:22.618Z" }, 2418 - { url = "https://files.pythonhosted.org/packages/25/02/78926c1efddcc7b3aa0bc3d6b33a822f7d898059f7cd9ace8c8318e559ef/wrapt-1.17.3-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:e405adefb53a435f01efa7ccdec012c016b5a1d3f35459990afc39b6be4d5056", size = 110659, upload-time = "2025-08-12T05:52:24.057Z" }, 2419 - { url = "https://files.pythonhosted.org/packages/dc/ee/c414501ad518ac3e6fe184753632fe5e5ecacdcf0effc23f31c1e4f7bfcf/wrapt-1.17.3-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:88547535b787a6c9ce4086917b6e1d291aa8ed914fdd3a838b3539dc95c12804", size = 106946, upload-time = "2025-08-12T05:52:45.976Z" }, 2420 - { url = "https://files.pythonhosted.org/packages/be/44/a1bd64b723d13bb151d6cc91b986146a1952385e0392a78567e12149c7b4/wrapt-1.17.3-cp314-cp314t-win32.whl", hash = "sha256:41b1d2bc74c2cac6f9074df52b2efbef2b30bdfe5f40cb78f8ca22963bc62977", size = 38717, upload-time = "2025-08-12T05:53:15.214Z" }, 2421 - { url = "https://files.pythonhosted.org/packages/79/d9/7cfd5a312760ac4dd8bf0184a6ee9e43c33e47f3dadc303032ce012b8fa3/wrapt-1.17.3-cp314-cp314t-win_amd64.whl", hash = "sha256:73d496de46cd2cdbdbcce4ae4bcdb4afb6a11234a1df9c085249d55166b95116", size = 41334, upload-time = "2025-08-12T05:53:14.178Z" }, 2422 - { url = "https://files.pythonhosted.org/packages/46/78/10ad9781128ed2f99dbc474f43283b13fea8ba58723e98844367531c18e9/wrapt-1.17.3-cp314-cp314t-win_arm64.whl", hash = "sha256:f38e60678850c42461d4202739f9bf1e3a737c7ad283638251e79cc49effb6b6", size = 38471, upload-time = "2025-08-12T05:52:57.784Z" }, 2423 - { url = "https://files.pythonhosted.org/packages/1f/f6/a933bd70f98e9cf3e08167fc5cd7aaaca49147e48411c0bd5ae701bb2194/wrapt-1.17.3-py3-none-any.whl", hash = "sha256:7171ae35d2c33d326ac19dd8facb1e82e5fd04ef8c6c0e394d7af55a55051c22", size = 23591, upload-time = "2025-08-12T05:53:20.674Z" }, 2424 - ] 2425 - 2426 - [[package]] 2427 - name = "xai-sdk" 2428 - version = "1.11.0" 2429 - source = { registry = "https://pypi.org/simple" } 2430 - dependencies = [ 2431 - { name = "aiohttp" }, 2432 - { name = "googleapis-common-protos" }, 2433 - { name = "grpcio" }, 2434 - { name = "opentelemetry-sdk" }, 2435 - { name = "packaging" }, 2436 - { name = "protobuf" }, 2437 - { name = "pydantic" }, 2438 - { name = "requests" }, 2439 - ] 2440 - sdist = { url = "https://files.pythonhosted.org/packages/49/32/bb8385f7a3b05ce406b689aa000c9a34289caa1526f1c093a1cefc0d9695/xai_sdk-1.11.0.tar.gz", hash = "sha256:ca87a830d310fb8e06fba44fb2a8c5cdf0d9f716b61126eddd51b7f416a63932", size = 404313, upload-time = "2026-03-27T18:23:10.091Z" } 2441 - wheels = [ 2442 - { url = "https://files.pythonhosted.org/packages/04/76/86d9a3589c725ce825d2ed3e7cb3ecf7f956d3fd015353d52197bb341bcd/xai_sdk-1.11.0-py3-none-any.whl", hash = "sha256:fe58ce6d8f8115ae8bd57ded57bcd847d0bb7cb28bb7b236abefd4626df1ed8d", size = 251388, upload-time = "2026-03-27T18:23:08.573Z" }, 2443 - ] 2444 - 2445 - [[package]] 2446 - name = "yarl" 2447 - version = "1.23.0" 2448 - source = { registry = "https://pypi.org/simple" } 2449 - dependencies = [ 2450 - { name = "idna" }, 2451 - { name = "multidict" }, 2452 - { name = "propcache" }, 2453 - ] 2454 - sdist = { url = "https://files.pythonhosted.org/packages/23/6e/beb1beec874a72f23815c1434518bfc4ed2175065173fb138c3705f658d4/yarl-1.23.0.tar.gz", hash = "sha256:53b1ea6ca88ebd4420379c330aea57e258408dd0df9af0992e5de2078dc9f5d5", size = 194676, upload-time = "2026-03-01T22:07:53.373Z" } 2455 - wheels = [ 2456 - { url = "https://files.pythonhosted.org/packages/90/98/b85a038d65d1b92c3903ab89444f48d3cee490a883477b716d7a24b1a78c/yarl-1.23.0-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:21d1b7305a71a15b4794b5ff22e8eef96ff4a6d7f9657155e5aa419444b28912", size = 124455, upload-time = "2026-03-01T22:06:43.615Z" }, 2457 - { url = "https://files.pythonhosted.org/packages/39/54/bc2b45559f86543d163b6e294417a107bb87557609007c007ad889afec18/yarl-1.23.0-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:85610b4f27f69984932a7abbe52703688de3724d9f72bceb1cca667deff27474", size = 86752, upload-time = "2026-03-01T22:06:45.425Z" }, 2458 - { url = "https://files.pythonhosted.org/packages/24/f9/e8242b68362bffe6fb536c8db5076861466fc780f0f1b479fc4ffbebb128/yarl-1.23.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:23f371bd662cf44a7630d4d113101eafc0cfa7518a2760d20760b26021454719", size = 86291, upload-time = "2026-03-01T22:06:46.974Z" }, 2459 - { url = "https://files.pythonhosted.org/packages/ea/d8/d1cb2378c81dd729e98c716582b1ccb08357e8488e4c24714658cc6630e8/yarl-1.23.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c4a80f77dc1acaaa61f0934176fccca7096d9b1ff08c8ba9cddf5ae034a24319", size = 99026, upload-time = "2026-03-01T22:06:48.459Z" }, 2460 - { url = "https://files.pythonhosted.org/packages/0a/ff/7196790538f31debe3341283b5b0707e7feb947620fc5e8236ef28d44f72/yarl-1.23.0-cp314-cp314-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:bd654fad46d8d9e823afbb4f87c79160b5a374ed1ff5bde24e542e6ba8f41434", size = 92355, upload-time = "2026-03-01T22:06:50.306Z" }, 2461 - { url = "https://files.pythonhosted.org/packages/c1/56/25d58c3eddde825890a5fe6aa1866228377354a3c39262235234ab5f616b/yarl-1.23.0-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:682bae25f0a0dd23a056739f23a134db9f52a63e2afd6bfb37ddc76292bbd723", size = 106417, upload-time = "2026-03-01T22:06:52.1Z" }, 2462 - { url = "https://files.pythonhosted.org/packages/51/8a/882c0e7bc8277eb895b31bce0138f51a1ba551fc2e1ec6753ffc1e7c1377/yarl-1.23.0-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:a82836cab5f197a0514235aaf7ffccdc886ccdaa2324bc0aafdd4ae898103039", size = 106422, upload-time = "2026-03-01T22:06:54.424Z" }, 2463 - { url = "https://files.pythonhosted.org/packages/42/2b/fef67d616931055bf3d6764885990a3ac647d68734a2d6a9e1d13de437a2/yarl-1.23.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:1c57676bdedc94cd3bc37724cf6f8cd2779f02f6aba48de45feca073e714fe52", size = 101915, upload-time = "2026-03-01T22:06:55.895Z" }, 2464 - { url = "https://files.pythonhosted.org/packages/18/6a/530e16aebce27c5937920f3431c628a29a4b6b430fab3fd1c117b26ff3f6/yarl-1.23.0-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:c7f8dc16c498ff06497c015642333219871effba93e4a2e8604a06264aca5c5c", size = 100690, upload-time = "2026-03-01T22:06:58.21Z" }, 2465 - { url = "https://files.pythonhosted.org/packages/88/08/93749219179a45e27b036e03260fda05190b911de8e18225c294ac95bbc9/yarl-1.23.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:5ee586fb17ff8f90c91cf73c6108a434b02d69925f44f5f8e0d7f2f260607eae", size = 98750, upload-time = "2026-03-01T22:06:59.794Z" }, 2466 - { url = "https://files.pythonhosted.org/packages/d9/cf/ea424a004969f5d81a362110a6ac1496d79efdc6d50c2c4b2e3ea0fc2519/yarl-1.23.0-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:17235362f580149742739cc3828b80e24029d08cbb9c4bda0242c7b5bc610a8e", size = 94685, upload-time = "2026-03-01T22:07:01.375Z" }, 2467 - { url = "https://files.pythonhosted.org/packages/e2/b7/14341481fe568e2b0408bcf1484c652accafe06a0ade9387b5d3fd9df446/yarl-1.23.0-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:0793e2bd0cf14234983bbb371591e6bea9e876ddf6896cdcc93450996b0b5c85", size = 106009, upload-time = "2026-03-01T22:07:03.151Z" }, 2468 - { url = "https://files.pythonhosted.org/packages/0a/e6/5c744a9b54f4e8007ad35bce96fbc9218338e84812d36f3390cea616881a/yarl-1.23.0-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:3650dc2480f94f7116c364096bc84b1d602f44224ef7d5c7208425915c0475dd", size = 100033, upload-time = "2026-03-01T22:07:04.701Z" }, 2469 - { url = "https://files.pythonhosted.org/packages/0c/23/e3bfc188d0b400f025bc49d99793d02c9abe15752138dcc27e4eaf0c4a9e/yarl-1.23.0-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:f40e782d49630ad384db66d4d8b73ff4f1b8955dc12e26b09a3e3af064b3b9d6", size = 106483, upload-time = "2026-03-01T22:07:06.231Z" }, 2470 - { url = "https://files.pythonhosted.org/packages/72/42/f0505f949a90b3f8b7a363d6cbdf398f6e6c58946d85c6d3a3bc70595b26/yarl-1.23.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:94f8575fbdf81749008d980c17796097e645574a3b8c28ee313931068dad14fe", size = 102175, upload-time = "2026-03-01T22:07:08.4Z" }, 2471 - { url = "https://files.pythonhosted.org/packages/aa/65/b39290f1d892a9dd671d1c722014ca062a9c35d60885d57e5375db0404b5/yarl-1.23.0-cp314-cp314-win32.whl", hash = "sha256:c8aa34a5c864db1087d911a0b902d60d203ea3607d91f615acd3f3108ac32169", size = 83871, upload-time = "2026-03-01T22:07:09.968Z" }, 2472 - { url = "https://files.pythonhosted.org/packages/a9/5b/9b92f54c784c26e2a422e55a8d2607ab15b7ea3349e28359282f84f01d43/yarl-1.23.0-cp314-cp314-win_amd64.whl", hash = "sha256:63e92247f383c85ab00dd0091e8c3fa331a96e865459f5ee80353c70a4a42d70", size = 89093, upload-time = "2026-03-01T22:07:11.501Z" }, 2473 - { url = "https://files.pythonhosted.org/packages/e0/7d/8a84dc9381fd4412d5e7ff04926f9865f6372b4c2fd91e10092e65d29eb8/yarl-1.23.0-cp314-cp314-win_arm64.whl", hash = "sha256:70efd20be968c76ece7baa8dafe04c5be06abc57f754d6f36f3741f7aa7a208e", size = 83384, upload-time = "2026-03-01T22:07:13.069Z" }, 2474 - { url = "https://files.pythonhosted.org/packages/dd/8d/d2fad34b1c08aa161b74394183daa7d800141aaaee207317e82c790b418d/yarl-1.23.0-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:9a18d6f9359e45722c064c97464ec883eb0e0366d33eda61cb19a244bf222679", size = 131019, upload-time = "2026-03-01T22:07:14.903Z" }, 2475 - { url = "https://files.pythonhosted.org/packages/19/ff/33009a39d3ccf4b94d7d7880dfe17fb5816c5a4fe0096d9b56abceea9ac7/yarl-1.23.0-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:2803ed8b21ca47a43da80a6fd1ed3019d30061f7061daa35ac54f63933409412", size = 89894, upload-time = "2026-03-01T22:07:17.372Z" }, 2476 - { url = "https://files.pythonhosted.org/packages/0c/f1/dab7ac5e7306fb79c0190766a3c00b4cb8d09a1f390ded68c85a5934faf5/yarl-1.23.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:394906945aa8b19fc14a61cf69743a868bb8c465efe85eee687109cc540b98f4", size = 89979, upload-time = "2026-03-01T22:07:19.361Z" }, 2477 - { url = "https://files.pythonhosted.org/packages/aa/b1/08e95f3caee1fad6e65017b9f26c1d79877b502622d60e517de01e72f95d/yarl-1.23.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:71d006bee8397a4a89f469b8deb22469fe7508132d3c17fa6ed871e79832691c", size = 95943, upload-time = "2026-03-01T22:07:21.266Z" }, 2478 - { url = "https://files.pythonhosted.org/packages/c0/cc/6409f9018864a6aa186c61175b977131f373f1988e198e031236916e87e4/yarl-1.23.0-cp314-cp314t-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:62694e275c93d54f7ccedcfef57d42761b2aad5234b6be1f3e3026cae4001cd4", size = 88786, upload-time = "2026-03-01T22:07:23.129Z" }, 2479 - { url = "https://files.pythonhosted.org/packages/76/40/cc22d1d7714b717fde2006fad2ced5efe5580606cb059ae42117542122f3/yarl-1.23.0-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:a31de1613658308efdb21ada98cbc86a97c181aa050ba22a808120bb5be3ab94", size = 101307, upload-time = "2026-03-01T22:07:24.689Z" }, 2480 - { url = "https://files.pythonhosted.org/packages/8f/0d/476c38e85ddb4c6ec6b20b815bdd779aa386a013f3d8b85516feee55c8dc/yarl-1.23.0-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:fb1e8b8d66c278b21d13b0a7ca22c41dd757a7c209c6b12c313e445c31dd3b28", size = 100904, upload-time = "2026-03-01T22:07:26.287Z" }, 2481 - { url = "https://files.pythonhosted.org/packages/72/32/0abe4a76d59adf2081dcb0397168553ece4616ada1c54d1c49d8936c74f8/yarl-1.23.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:50f9d8d531dfb767c565f348f33dd5139a6c43f5cbdf3f67da40d54241df93f6", size = 97728, upload-time = "2026-03-01T22:07:27.906Z" }, 2482 - { url = "https://files.pythonhosted.org/packages/b7/35/7b30f4810fba112f60f5a43237545867504e15b1c7647a785fbaf588fac2/yarl-1.23.0-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:575aa4405a656e61a540f4a80eaa5260f2a38fff7bfdc4b5f611840d76e9e277", size = 95964, upload-time = "2026-03-01T22:07:30.198Z" }, 2483 - { url = "https://files.pythonhosted.org/packages/2d/86/ed7a73ab85ef00e8bb70b0cb5421d8a2a625b81a333941a469a6f4022828/yarl-1.23.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:041b1a4cefacf65840b4e295c6985f334ba83c30607441ae3cf206a0eed1a2e4", size = 95882, upload-time = "2026-03-01T22:07:32.132Z" }, 2484 - { url = "https://files.pythonhosted.org/packages/19/90/d56967f61a29d8498efb7afb651e0b2b422a1e9b47b0ab5f4e40a19b699b/yarl-1.23.0-cp314-cp314t-musllinux_1_2_armv7l.whl", hash = "sha256:d38c1e8231722c4ce40d7593f28d92b5fc72f3e9774fe73d7e800ec32299f63a", size = 90797, upload-time = "2026-03-01T22:07:34.404Z" }, 2485 - { url = "https://files.pythonhosted.org/packages/72/00/8b8f76909259f56647adb1011d7ed8b321bcf97e464515c65016a47ecdf0/yarl-1.23.0-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:d53834e23c015ee83a99377db6e5e37d8484f333edb03bd15b4bc312cc7254fb", size = 101023, upload-time = "2026-03-01T22:07:35.953Z" }, 2486 - { url = "https://files.pythonhosted.org/packages/ac/e2/cab11b126fb7d440281b7df8e9ddbe4851e70a4dde47a202b6642586b8d9/yarl-1.23.0-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:2e27c8841126e017dd2a054a95771569e6070b9ee1b133366d8b31beb5018a41", size = 96227, upload-time = "2026-03-01T22:07:37.594Z" }, 2487 - { url = "https://files.pythonhosted.org/packages/c2/9b/2c893e16bfc50e6b2edf76c1a9eb6cb0c744346197e74c65e99ad8d634d0/yarl-1.23.0-cp314-cp314t-musllinux_1_2_s390x.whl", hash = "sha256:76855800ac56f878847a09ce6dba727c93ca2d89c9e9d63002d26b916810b0a2", size = 100302, upload-time = "2026-03-01T22:07:39.334Z" }, 2488 - { url = "https://files.pythonhosted.org/packages/28/ec/5498c4e3a6d5f1003beb23405671c2eb9cdbf3067d1c80f15eeafe301010/yarl-1.23.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:e09fd068c2e169a7070d83d3bde728a4d48de0549f975290be3c108c02e499b4", size = 98202, upload-time = "2026-03-01T22:07:41.717Z" }, 2489 - { url = "https://files.pythonhosted.org/packages/fe/c3/cd737e2d45e70717907f83e146f6949f20cc23cd4bf7b2688727763aa458/yarl-1.23.0-cp314-cp314t-win32.whl", hash = "sha256:73309162a6a571d4cbd3b6a1dcc703c7311843ae0d1578df6f09be4e98df38d4", size = 90558, upload-time = "2026-03-01T22:07:43.433Z" }, 2490 - { url = "https://files.pythonhosted.org/packages/e1/19/3774d162f6732d1cfb0b47b4140a942a35ca82bb19b6db1f80e9e7bdc8f8/yarl-1.23.0-cp314-cp314t-win_amd64.whl", hash = "sha256:4503053d296bc6e4cbd1fad61cf3b6e33b939886c4f249ba7c78b602214fabe2", size = 97610, upload-time = "2026-03-01T22:07:45.773Z" }, 2491 - { url = "https://files.pythonhosted.org/packages/51/47/3fa2286c3cb162c71cdb34c4224d5745a1ceceb391b2bd9b19b668a8d724/yarl-1.23.0-cp314-cp314t-win_arm64.whl", hash = "sha256:44bb7bef4ea409384e3f8bc36c063d77ea1b8d4a5b2706956c0d6695f07dcc25", size = 86041, upload-time = "2026-03-01T22:07:49.026Z" }, 2492 - { url = "https://files.pythonhosted.org/packages/69/68/c8739671f5699c7dc470580a4f821ef37c32c4cb0b047ce223a7f115757f/yarl-1.23.0-py3-none-any.whl", hash = "sha256:a2df6afe50dea8ae15fa34c9f824a3ee958d785fd5d089063d960bae1daa0a3f", size = 48288, upload-time = "2026-03-01T22:07:51.388Z" }, 2493 - ] 2494 - 2495 - [[package]] 2496 - name = "zipp" 2497 - version = "3.23.1" 2498 - source = { registry = "https://pypi.org/simple" } 2499 - sdist = { url = "https://files.pythonhosted.org/packages/30/21/093488dfc7cc8964ded15ab726fad40f25fd3d788fd741cc1c5a17d78ee8/zipp-3.23.1.tar.gz", hash = "sha256:32120e378d32cd9714ad503c1d024619063ec28aad2248dc6672ad13edfa5110", size = 25965, upload-time = "2026-04-13T23:21:46.6Z" } 2500 - wheels = [ 2501 - { url = "https://files.pythonhosted.org/packages/08/8a/0861bec20485572fbddf3dfba2910e38fe249796cb73ecdeb74e07eeb8d3/zipp-3.23.1-py3-none-any.whl", hash = "sha256:0b3596c50a5c700c9cb40ba8d86d9f2cc4807e9bedb06bcdf7fac85633e444dc", size = 10378, upload-time = "2026-04-13T23:21:45.386Z" }, 2502 - ]