diff --git a/README.md b/README.md index c718f39..0917852 100644 --- a/README.md +++ b/README.md @@ -3,7 +3,7 @@ -ShellSage is an AI-powered command-line assistant that integrates seamlessly with your terminal workflow through tmux. It provides contextual help for shell operations, making it easier to navigate complex command-line tasks, debug scripts, and manage your system. +ShellSage is an AI-powered command-line assistant that integrates seamlessly with your terminal workflow through tmux, macOS Ghostty, Terminal.app, or iTerm2. It provides contextual help for shell operations, making it easier to navigate complex command-line tasks, debug scripts, and manage your system. [![PyPI version](https://badge.fury.io/py/shell-sage.svg)](https://badge.fury.io/py/shell-sage) [![Python 3.8+](https://img.shields.io/badge/python-3.10+-blue.svg)](https://www.python.org/downloads/) @@ -12,9 +12,9 @@ ShellSage is an AI-powered command-line assistant that integrates seamlessly wit ## Overview -ShellSage is an AI-powered command-line assistant that integrates seamlessly with your terminal workflow through tmux. It provides contextual help for shell operations, making it easier to navigate complex command-line tasks, debug scripts, and manage your system. +ShellSage is an AI-powered command-line assistant that integrates seamlessly with your terminal workflow through tmux, macOS Ghostty, Terminal.app, or iTerm2. It provides contextual help for shell operations, making it easier to navigate complex command-line tasks, debug scripts, and manage your system. -ShellSage works with multiple LLM providers including Claude, GPT, and Ollama. It uses tmux to automatically read your terminal history or multiple pane histories to provide contextual assistance. You can pipe command output or file contents directly to ShellSage, and it can view files, search code, create files, and make edits with your permission. When needed, it can even search the internet for up-to-date information. You can also log all your interactions directly to SQLite for later reference. +ShellSage works with multiple LLM providers including Claude, GPT, and Ollama. It reads recent terminal context from tmux, or on macOS from Ghostty, Terminal.app, or iTerm2; tmux can also provide specific pane histories. You can pipe command output or file contents directly to ShellSage, and it can view files, search code, create files, and make edits with your permission. When needed, it can search the internet for up-to-date information. You can also log all your interactions directly to SQLite for later reference. ## Installation @@ -82,6 +82,8 @@ export OPENAI_API_KEY=sk... ShellSage works best with a properly configured tmux environment. I’ve created a preconfigured [tmux configuration](tmux.conf) that works well with ShellSage. This configuration enables mouse support, adds pane IDs to your status bar so you can quickly reference them when having ShellSage read from specific panes, turns off alternative-screen so editor content like vim stays in the tmux buffer where ShellSage can see it, and adds a convenient shortcut (CTRL+B+E followed by the index number) for automatically extracting code fence blocks into your command prompt. +On macOS, ShellSage can also read the front, focused Ghostty, Terminal.app, or iTerm2 terminal; see [Using Terminal Context](#using-terminal-context) for provider details and limitations. + ## Getting Started ### Your First Command @@ -154,7 +156,7 @@ ShellSage will provide the command, explain how it works, and give you practical ### Using Terminal Context -ShellSage automatically reads your tmux history to understand what you’re working on: +ShellSage automatically reads terminal history to understand what you’re working on. It uses tmux first. Outside tmux, it can use macOS Ghostty (`TERM_PROGRAM=ghostty` or `TERM` starts with `xterm-ghostty`) or Terminal.app/iTerm2 (`TERM_PROGRAM=Apple_Terminal`/`iTerm.app`). macOS terminal capture is focused-terminal only: pane IDs and `--pid all` remain tmux-only, and Linux/GTK Ghostty is not supported yet. Ghostty 1.3+ requires AppleScript enabled (`macos-applescript = true`) and captures its full screen buffer: scrollback plus visible content, including alternate-screen apps. Its capture action temporarily replaces the macOS clipboard with a screen-file path; ShellSage restores previous text only if that path is still present and attempts to scrub the captured file after reading, but cannot preserve non-text clipboard formats. If capture fails, `ssage` continues without terminal context: ``` python # After running some commands that produced errors (e.g. find -name "*.tmp" .) @@ -204,6 +206,26 @@ ShellSage automatically reads your tmux history to understand what you’re work Tip: If you forget the path, find defaults to the current directory, so find -name "*.tmp" also works! +### Clearing Context + +Ctrl-L looks like it clears the terminal, but it only wipes the visible screen – tmux’s history still holds everything, so ShellSage keeps seeing it. The [`ssage_clear`](https://github.com/AnswerDotAI/shell_sage/blob/main/ssage_clear.sh) binding gives ctrl-L real “clear” semantics for ShellSage without destroying anything: it scrolls the screen into history and records a mark, and ShellSage captures never reach past the most recent clear, while your own scrollback stays intact. + +Save the script from the link above, then add to your `.bashrc`: + +``` sh +source /path/to/ssage_clear.sh +bind -x '"\C-l": ssage_clear' +``` + +Or to your `.zshrc`: + +``` sh +source /path/to/ssage_clear.sh +zle -N ssage_clear && bindkey '^L' ssage_clear +``` + +The binding is only active at the shell prompt, so full-screen apps still receive a plain ctrl-L, and outside tmux it falls back to a normal clear. + ### Piping Content for Analysis One of ShellSage’s most powerful features is analyzing piped input: @@ -255,7 +277,7 @@ One of ShellSage’s most powerful features is analyzing piped input: ### Working with Multiple Tmux Panes -When you have multiple panes open, you can reference specific ones by their ID (shown in your status bar): +When you have multiple tmux panes open, you can reference specific ones by their ID (shown in your status bar). This is tmux-only; Ghostty, Terminal.app, and iTerm2 macOS support capture only the front, focused terminal for now. ![btop output](./screenshots/btop_output.png) @@ -328,7 +350,7 @@ ShellSage can be customized through a configuration file located at `~/.config/s base_url = '' # Alternative API base URL api_key = '' # API key override instead of the default env var vendor_name = '' # Explicit fastllm vendor name when needed - history_lines = -1 # Lines of terminal history to include. -1 means include all + history_lines = -1 # Lines of terminal history; -1 uses tmux limit or 3000 outside tmux code_theme = "monokai" # Syntax highlighting theme code_lexer = "python" # Default lexer for inline code blocks log = False # Enable SQLite logging (required for code extraction) diff --git a/nbs/00_core.ipynb b/nbs/00_core.ipynb index e2e62d5..61df7c2 100644 --- a/nbs/00_core.ipynb +++ b/nbs/00_core.ipynb @@ -38,10 +38,14 @@ "from datetime import datetime\n", "from fastcore.script import *\n", "from fastcore.tools import *\n", + "try: from fastcore.tools import view_file, create_file, file_str_replace, file_insert_line\n", + "except ImportError:\n", + " from fastcore.tools import view as view_file, create as create_file\n", + " from fastcore.tools import str_replace as file_str_replace, insert as file_insert_line\n", "from fastcore.utils import *\n", "from fastcore.meta import delegates\n", "from fastlite import database\n", - "from functools import partial, wraps\n", + "from functools import lru_cache, partial, wraps\n", "from rich.live import Live\n", "from rich.spinner import Spinner\n", "from rich.console import Console\n", @@ -54,7 +58,7 @@ "from fastllm.chat import AsyncChat\n", "\n", "import rgapi\n", - "import asyncio,os,pyperclip,re,subprocess,sys,builtins\n", + "import asyncio,os,pyperclip,re,stat,subprocess,sys,tempfile,time,builtins\n", "from typing import Annotated" ] }, @@ -493,6 +497,163 @@ "outputs": [], "source": [ "#| export\n", + "_DEFAULT_TERMINAL_HISTORY_LINES = 3000\n", + "_GHOSTTY_HISTORY_MAX_BYTES = 10 * 1024 * 1024\n", + "_GHOSTTY_POLL_INTERVAL = 0.05\n", + "\n", + "GHOSTTY_SCREEN_SCRIPT = \"\"\"\n", + "tell application \"Ghostty\"\n", + " perform action \"write_screen_file:copy,plain\" on focused terminal of selected tab of front window\n", + "end tell\n", + "\"\"\"\n", + "\n", + "MACOS_TERMINAL_HISTORY_SCRIPTS = {\n", + " 'Apple_Terminal': 'tell application \"Terminal\" to get the contents of the selected tab of the front window',\n", + " 'iTerm.app': 'tell application id \"com.googlecode.iterm2\" to get contents of current session of current tab of current window',\n", + "}\n", + "\n", + "\n", + "def _pbpaste():\n", + " try:\n", + " return co(['pbpaste'], text=True, stderr=DEVNULL)\n", + " except Exception:\n", + " return None\n", + "\n", + "\n", + "def _pbcopy(clip):\n", + " if clip is None:\n", + " return\n", + " try:\n", + " subprocess.run(['pbcopy'], input=clip, text=True, check=False, stdout=DEVNULL, stderr=DEVNULL)\n", + " except Exception:\n", + " pass\n", + "\n", + "\n", + "@lru_cache(maxsize=1)\n", + "def _ghostty_history_temp_roots():\n", + " roots = {tempfile.gettempdir(), '/var/folders', '/private/var/folders'}\n", + " roots |= {'/private' + root for root in roots if root.startswith('/var/')}\n", + " roots |= {root.removeprefix('/private') for root in roots if root.startswith('/private/')}\n", + " return tuple(Path(root).resolve() for root in roots if root)\n", + "\n", + "\n", + "def _valid_ghostty_history_path(candidate):\n", + " try:\n", + " path = Path(str(candidate).strip())\n", + " info = path.stat()\n", + " owner_uid = getattr(os, 'getuid', lambda: info.st_uid)()\n", + " if (path.name != 'screen.txt' or not stat.S_ISREG(info.st_mode) or\n", + " info.st_size > _GHOSTTY_HISTORY_MAX_BYTES or info.st_nlink != 1 or info.st_uid != owner_uid):\n", + " return False\n", + " path = path.resolve()\n", + " return any(os.path.commonpath([str(path), str(root)]) == str(root) for root in _ghostty_history_temp_roots())\n", + " except Exception:\n", + " return False\n", + "\n", + "\n", + "def _wait_for_ghostty_history_path(old_clip=None, timeout=1.0):\n", + " start = time.time()\n", + " deadline = start + timeout\n", + " old_clip = (old_clip or '').strip()\n", + " while time.time() < deadline:\n", + " candidate = (_pbpaste() or '').strip()\n", + " if _valid_ghostty_history_path(candidate):\n", + " if candidate != old_clip:\n", + " return candidate\n", + " try:\n", + " if Path(candidate).stat().st_mtime >= start:\n", + " return candidate\n", + " except OSError:\n", + " pass\n", + " time.sleep(_GHOSTTY_POLL_INTERVAL)\n", + " return None\n", + "\n", + "\n", + "def _tail_lines(text, n):\n", + " if n is None or n < 0:\n", + " return text\n", + " if n == 0:\n", + " return ''\n", + " return '\\n'.join(text.splitlines()[-n:])\n", + "\n", + "\n", + "# [tag:ghostty_history_fd_validation] Validate and read the same descriptor so path swaps cannot escape temp roots.\n", + "def _read_ghostty_history_file(candidate, n):\n", + " descriptor = None\n", + " validated = False\n", + " try:\n", + " path = Path(str(candidate).strip())\n", + " if path.name != 'screen.txt':\n", + " return None\n", + " flags = os.O_RDWR | getattr(os, 'O_CLOEXEC', 0) | getattr(os, 'O_NOFOLLOW', 0)\n", + " descriptor = os.open(path, flags)\n", + " opened = os.fstat(descriptor)\n", + " owner_uid = getattr(os, 'getuid', lambda: opened.st_uid)()\n", + " if (not stat.S_ISREG(opened.st_mode) or opened.st_size > _GHOSTTY_HISTORY_MAX_BYTES or\n", + " opened.st_nlink != 1 or opened.st_uid != owner_uid):\n", + " return None\n", + "\n", + " resolved = path.resolve(strict=True)\n", + " if not any(os.path.commonpath([str(resolved), str(root)]) == str(root) for root in _ghostty_history_temp_roots()):\n", + " return None\n", + " current = resolved.stat()\n", + " if ((opened.st_dev, opened.st_ino) != (current.st_dev, current.st_ino) or\n", + " current.st_nlink != 1 or current.st_uid != owner_uid):\n", + " return None\n", + " validated = True\n", + "\n", + " chunks = []\n", + " remaining = _GHOSTTY_HISTORY_MAX_BYTES + 1\n", + " while remaining:\n", + " chunk = os.read(descriptor, min(64 * 1024, remaining))\n", + " if not chunk:\n", + " break\n", + " chunks.append(chunk)\n", + " remaining -= len(chunk)\n", + " data = b''.join(chunks)\n", + " if len(data) > _GHOSTTY_HISTORY_MAX_BYTES:\n", + " return None\n", + " return _tail_lines(data.decode('utf-8', errors='replace'), n)\n", + " except Exception:\n", + " return None\n", + " finally:\n", + " if descriptor is not None:\n", + " # [tag:ghostty_history_cleanup] Scrub the validated descriptor; unlinking by path would reintroduce a swap race.\n", + " if validated:\n", + " try:\n", + " latest = os.fstat(descriptor)\n", + " if latest.st_nlink == 1 and latest.st_uid == owner_uid:\n", + " os.ftruncate(descriptor, 0)\n", + " except OSError:\n", + " pass\n", + " try:\n", + " os.close(descriptor)\n", + " except OSError:\n", + " pass\n", + "\n", + "\n", + "def get_ghostty_history_macos(n):\n", + " old_clip = _pbpaste()\n", + " history_path = None\n", + " try:\n", + " subprocess.run(['osascript', '-e', GHOSTTY_SCREEN_SCRIPT], text=True, check=True, stdout=DEVNULL, stderr=DEVNULL)\n", + " history_path = _wait_for_ghostty_history_path(old_clip)\n", + " if not history_path:\n", + " return None\n", + " return _read_ghostty_history_file(history_path, n)\n", + " except Exception:\n", + " return None\n", + " finally:\n", + " # [tag:ghostty_clipboard_restore] Never overwrite clipboard content changed while capture was running.\n", + " current_clip = _pbpaste() if old_clip is not None and history_path else None\n", + " if current_clip is not None and current_clip.strip() == str(history_path).strip():\n", + " _pbcopy(old_clip)\n", + "\n", + "\n", + "def is_ghostty():\n", + " return os.environ.get('TERM_PROGRAM') == 'ghostty' or os.environ.get('TERM', '').startswith('xterm-ghostty')\n", + "\n", + "\n", "def get_hist_tmux(n, pid='current'):\n", " if not os.environ.get('TMUX'): return None\n", " try:\n", @@ -526,12 +687,8 @@ "source": [ "#| export\n", "def get_hist_osa(n, pid=''):\n", - " script = ({\n", - " 'Apple_Terminal':'tell application \"Terminal\" to get the contents of the selected tab of the front window',\n", - " 'iTerm.app':'tell application id \"com.googlecode.iterm2\" to get text of current session of current tab of current window',\n", - " }).get(os.getenv('TERM_PROGRAM'))\n", - " if not script: return None\n", - " return \"\\n\".join(co(['osascript', '-e', script], text=True).splitlines()[:n])" + " \"Backwards-compatible wrapper for macOS terminal history capture.\"\n", + " return get_macos_terminal_history(n) if _is_current_macos_terminal(pid or 'current') else None" ] }, { @@ -553,7 +710,33 @@ "source": [ "#| export\n", "def get_history(n, pid='current'):\n", - " return get_hist_tmux(n, pid) or get_hist_osa(n)" + " \"Backwards-compatible entry point for terminal history dispatch.\"\n", + " return get_terminal_history(n, pid)\n", + "\n", + "\n", + "def _is_current_macos_terminal(pid):\n", + " return sys.platform == 'darwin' and pid in ('current', None)\n", + "\n", + "\n", + "def get_macos_terminal_history(n):\n", + " script = MACOS_TERMINAL_HISTORY_SCRIPTS.get(os.environ.get('TERM_PROGRAM'))\n", + " if not script or sys.platform != 'darwin':\n", + " return None\n", + " try:\n", + " return _tail_lines(co(['osascript', '-e', script], text=True, stderr=DEVNULL), n)\n", + " except Exception:\n", + " return None\n", + "\n", + "\n", + "def get_terminal_history(n, pid='current'):\n", + " if os.environ.get('TMUX'):\n", + " n = tmux_history_lim() if n is None or n < 0 else n\n", + " return get_hist_tmux(n, pid)\n", + " if not _is_current_macos_terminal(pid):\n", + " return None\n", + "\n", + " n = _DEFAULT_TERMINAL_HISTORY_LINES if n is None or n < 0 else n\n", + " return get_ghostty_history_macos(n) if is_ghostty() else get_macos_terminal_history(n)" ] }, { @@ -1046,7 +1229,7 @@ " v: Annotated[str, \"Print version\", dict(action='version')] = '%(prog)s ' + __version__,\n", " pid: str = 'current', # `current`, `all` or tmux pane_id (e.g. %0) for context\n", " skip_system: bool = False, # Whether to skip system information in the AI's context\n", - " history_lines: int = None, # Number of history lines. Defaults to tmux scrollback history length\n", + " history_lines: int = None, # Number of terminal history lines; defaults to tmux limit or 3000 outside tmux\n", " mode: str = 'default', # Available ShellSage modes: ['default', 'sassy']\n", " model: str = None, # The LLM model that will be invoked on the LLM provider\n", " vendor_name: str = None, # Vendor name for non auto-resolved models (e.g. 'codex', 'fireworks_ai', 'moonshot', 'deepseek', ...)\n", @@ -1082,10 +1265,7 @@ " query = ' '.join(query)\n", " ctxt = '' if skip_system else _sys_info()\n", "\n", - " # Get tmux history if in a tmux session\n", - " if opts.history_lines is None or opts.history_lines < 0:\n", - " opts.history_lines = tmux_history_lim()\n", - " history = get_history(opts.history_lines, pid)\n", + " history = get_terminal_history(opts.history_lines, pid)\n", " if history: ctxt += f'\\n{history}\\n'\n", "\n", " # Read from redirect stdin if available\n", diff --git a/nbs/index.ipynb b/nbs/index.ipynb index d50f74e..55eac8f 100644 --- a/nbs/index.ipynb +++ b/nbs/index.ipynb @@ -15,7 +15,7 @@ "id": "4038037e", "metadata": {}, "source": [ - "ShellSage is an AI-powered command-line assistant that integrates seamlessly with your terminal workflow through tmux. It provides contextual help for shell operations, making it easier to navigate complex command-line tasks, debug scripts, and manage your system.\n", + "ShellSage is an AI-powered command-line assistant that integrates seamlessly with your terminal workflow through tmux, macOS Ghostty, Terminal.app, or iTerm2. It provides contextual help for shell operations, making it easier to navigate complex command-line tasks, debug scripts, and manage your system.\n", "\n", "[![PyPI version](https://badge.fury.io/py/shell-sage.svg)](https://badge.fury.io/py/shell-sage)\n", "[![Python 3.8+](https://img.shields.io/badge/python-3.10+-blue.svg)](https://www.python.org/downloads/)\n", @@ -36,9 +36,9 @@ "id": "0c0e8bb9", "metadata": {}, "source": [ - "ShellSage is an AI-powered command-line assistant that integrates seamlessly with your terminal workflow through tmux. It provides contextual help for shell operations, making it easier to navigate complex command-line tasks, debug scripts, and manage your system.\n", + "ShellSage is an AI-powered command-line assistant that integrates seamlessly with your terminal workflow through tmux, macOS Ghostty, Terminal.app, or iTerm2. It provides contextual help for shell operations, making it easier to navigate complex command-line tasks, debug scripts, and manage your system.\n", "\n", - "ShellSage works with multiple LLM providers including Claude, GPT, and Ollama. It uses tmux to automatically read your terminal history or multiple pane histories to provide contextual assistance. You can pipe command output or file contents directly to ShellSage, and it can view files, search code, create files, and make edits with your permission. When needed, it can even search the internet for up-to-date information. You can also log all your interactions directly to SQLite for later reference." + "ShellSage works with multiple LLM providers including Claude, GPT, and Ollama. It reads recent terminal context from tmux, or on macOS from Ghostty, Terminal.app, or iTerm2; tmux can also provide specific pane histories. You can pipe command output or file contents directly to ShellSage, and it can view files, search code, create files, and make edits with your permission. When needed, it can search the internet for up-to-date information. You can also log all your interactions directly to SQLite for later reference." ] }, { @@ -158,7 +158,9 @@ "export OPENAI_API_KEY=sk...\n", "```\n", "\n", - "ShellSage works best with a properly configured tmux environment. I've created a preconfigured [tmux configuration](tmux.conf) that works well with ShellSage. This configuration enables mouse support, adds pane IDs to your status bar so you can quickly reference them when having ShellSage read from specific panes, turns off alternative-screen so editor content like vim stays in the tmux buffer where ShellSage can see it, and adds a convenient shortcut (CTRL+B+E followed by the index number) for automatically extracting code fence blocks into your command prompt." + "ShellSage works best with a properly configured tmux environment. I've created a preconfigured [tmux configuration](tmux.conf) that works well with ShellSage. This configuration enables mouse support, adds pane IDs to your status bar so you can quickly reference them when having ShellSage read from specific panes, turns off alternative-screen so editor content like vim stays in the tmux buffer where ShellSage can see it, and adds a convenient shortcut (CTRL+B+E followed by the index number) for automatically extracting code fence blocks into your command prompt.\n", + "\n", + "On macOS, ShellSage can also read the front, focused Ghostty, Terminal.app, or iTerm2 terminal; see [Using Terminal Context](#using-terminal-context) for provider details and limitations." ] }, { @@ -310,7 +312,7 @@ "id": "c98cda12", "metadata": {}, "source": [ - "ShellSage automatically reads your tmux history to understand what you're working on:" + "ShellSage automatically reads terminal history to understand what you're working on. It uses tmux first. Outside tmux, it can use macOS Ghostty (`TERM_PROGRAM=ghostty` or `TERM` starts with `xterm-ghostty`) or Terminal.app/iTerm2 (`TERM_PROGRAM=Apple_Terminal`/`iTerm.app`). macOS terminal capture is focused-terminal only: pane IDs and `--pid all` remain tmux-only, and Linux/GTK Ghostty is not supported yet. Ghostty 1.3+ requires AppleScript enabled (`macos-applescript = true`) and captures its full screen buffer: scrollback plus visible content, including alternate-screen apps. Its capture action temporarily replaces the macOS clipboard with a screen-file path; ShellSage restores previous text only if that path is still present and attempts to scrub the captured file after reading, but cannot preserve non-text clipboard formats. If capture fails, `ssage` continues without terminal context:" ] }, { @@ -485,7 +487,7 @@ "id": "e64a6ecb", "metadata": {}, "source": [ - "When you have multiple panes open, you can reference specific ones by their ID (shown in your status bar):\n", + "When you have multiple tmux panes open, you can reference specific ones by their ID (shown in your status bar). This is tmux-only; Ghostty, Terminal.app, and iTerm2 macOS support capture only the front, focused terminal for now.\n", "\n", "![btop output](./screenshots/btop_output.png)" ] @@ -589,7 +591,7 @@ "base_url = '' # Alternative API base URL\n", "api_key = '' # API key override instead of the default env var\n", "vendor_name = '' # Explicit fastllm vendor name when needed\n", - "history_lines = -1 # Lines of terminal history to include. -1 means include all\n", + "history_lines = -1 # Lines of terminal history; -1 uses tmux limit or 3000 outside tmux\n", "code_theme = \"monokai\" # Syntax highlighting theme\n", "code_lexer = \"python\" # Default lexer for inline code blocks\n", "log = False # Enable SQLite logging (required for code extraction)\n", diff --git a/shell_sage/_modidx.py b/shell_sage/_modidx.py index 0c1df96..0f2c8d5 100644 --- a/shell_sage/_modidx.py +++ b/shell_sage/_modidx.py @@ -12,20 +12,38 @@ 'shell_sage/core.py'), 'shell_sage.core.Log': ('core.html#log', 'shell_sage/core.py'), 'shell_sage.core._aliases': ('core.html#_aliases', 'shell_sage/core.py'), + 'shell_sage.core._ghostty_history_temp_roots': ( 'core.html#_ghostty_history_temp_roots', + 'shell_sage/core.py'), + 'shell_sage.core._is_current_macos_terminal': ( 'core.html#_is_current_macos_terminal', + 'shell_sage/core.py'), 'shell_sage.core._pause_live': ('core.html#_pause_live', 'shell_sage/core.py'), + 'shell_sage.core._pbcopy': ('core.html#_pbcopy', 'shell_sage/core.py'), + 'shell_sage.core._pbpaste': ('core.html#_pbpaste', 'shell_sage/core.py'), + 'shell_sage.core._read_ghostty_history_file': ( 'core.html#_read_ghostty_history_file', + 'shell_sage/core.py'), 'shell_sage.core._sys_info': ('core.html#_sys_info', 'shell_sage/core.py'), + 'shell_sage.core._tail_lines': ('core.html#_tail_lines', 'shell_sage/core.py'), 'shell_sage.core._tmux_fmt': ('core.html#_tmux_fmt', 'shell_sage/core.py'), + 'shell_sage.core._valid_ghostty_history_path': ( 'core.html#_valid_ghostty_history_path', + 'shell_sage/core.py'), + 'shell_sage.core._wait_for_ghostty_history_path': ( 'core.html#_wait_for_ghostty_history_path', + 'shell_sage/core.py'), 'shell_sage.core.extract': ('core.html#extract', 'shell_sage/core.py'), 'shell_sage.core.extract_cf': ('core.html#extract_cf', 'shell_sage/core.py'), 'shell_sage.core.fd': ('core.html#fd', 'shell_sage/core.py'), + 'shell_sage.core.get_ghostty_history_macos': ('core.html#get_ghostty_history_macos', 'shell_sage/core.py'), 'shell_sage.core.get_hist_osa': ('core.html#get_hist_osa', 'shell_sage/core.py'), 'shell_sage.core.get_hist_tmux': ('core.html#get_hist_tmux', 'shell_sage/core.py'), 'shell_sage.core.get_history': ('core.html#get_history', 'shell_sage/core.py'), + 'shell_sage.core.get_macos_terminal_history': ( 'core.html#get_macos_terminal_history', + 'shell_sage/core.py'), 'shell_sage.core.get_opts': ('core.html#get_opts', 'shell_sage/core.py'), 'shell_sage.core.get_pane': ('core.html#get_pane', 'shell_sage/core.py'), 'shell_sage.core.get_panes': ('core.html#get_panes', 'shell_sage/core.py'), 'shell_sage.core.get_res': ('core.html#get_res', 'shell_sage/core.py'), 'shell_sage.core.get_sage': ('core.html#get_sage', 'shell_sage/core.py'), + 'shell_sage.core.get_terminal_history': ('core.html#get_terminal_history', 'shell_sage/core.py'), + 'shell_sage.core.is_ghostty': ('core.html#is_ghostty', 'shell_sage/core.py'), 'shell_sage.core.ls': ('core.html#ls', 'shell_sage/core.py'), 'shell_sage.core.main': ('core.html#main', 'shell_sage/core.py'), 'shell_sage.core.mk_db': ('core.html#mk_db', 'shell_sage/core.py'), diff --git a/shell_sage/core.py b/shell_sage/core.py index 5637ad0..1be7c1d 100644 --- a/shell_sage/core.py +++ b/shell_sage/core.py @@ -3,19 +3,25 @@ # AUTOGENERATED! DO NOT EDIT! File to edit: ../nbs/00_core.ipynb. # %% auto #0 -__all__ = ['console', 'print', 'sp', 'ssp', 'default_cfg', 'tools', 'sps', 'log_path', 'pane_mark', 'get_pane', 'get_panes', - 'tmux_history_lim', 'get_hist_tmux', 'get_hist_osa', 'get_history', 'get_opts', 'with_permission', 'rg', - 'ls', 'fd', 'get_sage', 'get_res', 'Log', 'mk_db', 'main', 'extract_cf', 'extract'] +__all__ = ['console', 'print', 'sp', 'ssp', 'GHOSTTY_SCREEN_SCRIPT', 'MACOS_TERMINAL_HISTORY_SCRIPTS', 'default_cfg', 'tools', + 'sps', 'log_path', 'pane_mark', 'get_pane', 'get_panes', 'tmux_history_lim', 'get_ghostty_history_macos', + 'is_ghostty', 'get_hist_tmux', 'get_hist_osa', 'get_history', 'get_macos_terminal_history', + 'get_terminal_history', 'get_opts', 'with_permission', 'rg', 'ls', 'fd', 'get_sage', 'get_res', 'Log', + 'mk_db', 'main', 'extract_cf', 'extract'] # %% ../nbs/00_core.ipynb #d7c5634a from contextlib import contextmanager from datetime import datetime from fastcore.script import * from fastcore.tools import * +try: from fastcore.tools import view_file, create_file, file_str_replace, file_insert_line +except ImportError: + from fastcore.tools import view as view_file, create as create_file + from fastcore.tools import str_replace as file_str_replace, insert as file_insert_line from fastcore.utils import * from fastcore.meta import delegates from fastlite import database -from functools import partial, wraps +from functools import lru_cache, partial, wraps from rich.live import Live from rich.spinner import Spinner from rich.console import Console @@ -28,7 +34,7 @@ from fastllm.chat import AsyncChat import rgapi -import asyncio,os,pyperclip,re,subprocess,sys,builtins +import asyncio,os,pyperclip,re,stat,subprocess,sys,tempfile,time,builtins from typing import Annotated # %% ../nbs/00_core.ipynb #4d0676fd @@ -180,6 +186,163 @@ def tmux_history_lim(): # %% ../nbs/00_core.ipynb #0d70591e +_DEFAULT_TERMINAL_HISTORY_LINES = 3000 +_GHOSTTY_HISTORY_MAX_BYTES = 10 * 1024 * 1024 +_GHOSTTY_POLL_INTERVAL = 0.05 + +GHOSTTY_SCREEN_SCRIPT = """ +tell application "Ghostty" + perform action "write_screen_file:copy,plain" on focused terminal of selected tab of front window +end tell +""" + +MACOS_TERMINAL_HISTORY_SCRIPTS = { + 'Apple_Terminal': 'tell application "Terminal" to get the contents of the selected tab of the front window', + 'iTerm.app': 'tell application id "com.googlecode.iterm2" to get contents of current session of current tab of current window', +} + + +def _pbpaste(): + try: + return co(['pbpaste'], text=True, stderr=DEVNULL) + except Exception: + return None + + +def _pbcopy(clip): + if clip is None: + return + try: + subprocess.run(['pbcopy'], input=clip, text=True, check=False, stdout=DEVNULL, stderr=DEVNULL) + except Exception: + pass + + +@lru_cache(maxsize=1) +def _ghostty_history_temp_roots(): + roots = {tempfile.gettempdir(), '/var/folders', '/private/var/folders'} + roots |= {'/private' + root for root in roots if root.startswith('/var/')} + roots |= {root.removeprefix('/private') for root in roots if root.startswith('/private/')} + return tuple(Path(root).resolve() for root in roots if root) + + +def _valid_ghostty_history_path(candidate): + try: + path = Path(str(candidate).strip()) + info = path.stat() + owner_uid = getattr(os, 'getuid', lambda: info.st_uid)() + if (path.name != 'screen.txt' or not stat.S_ISREG(info.st_mode) or + info.st_size > _GHOSTTY_HISTORY_MAX_BYTES or info.st_nlink != 1 or info.st_uid != owner_uid): + return False + path = path.resolve() + return any(os.path.commonpath([str(path), str(root)]) == str(root) for root in _ghostty_history_temp_roots()) + except Exception: + return False + + +def _wait_for_ghostty_history_path(old_clip=None, timeout=1.0): + start = time.time() + deadline = start + timeout + old_clip = (old_clip or '').strip() + while time.time() < deadline: + candidate = (_pbpaste() or '').strip() + if _valid_ghostty_history_path(candidate): + if candidate != old_clip: + return candidate + try: + if Path(candidate).stat().st_mtime >= start: + return candidate + except OSError: + pass + time.sleep(_GHOSTTY_POLL_INTERVAL) + return None + + +def _tail_lines(text, n): + if n is None or n < 0: + return text + if n == 0: + return '' + return '\n'.join(text.splitlines()[-n:]) + + +# [tag:ghostty_history_fd_validation] Validate and read the same descriptor so path swaps cannot escape temp roots. +def _read_ghostty_history_file(candidate, n): + descriptor = None + validated = False + try: + path = Path(str(candidate).strip()) + if path.name != 'screen.txt': + return None + flags = os.O_RDWR | getattr(os, 'O_CLOEXEC', 0) | getattr(os, 'O_NOFOLLOW', 0) + descriptor = os.open(path, flags) + opened = os.fstat(descriptor) + owner_uid = getattr(os, 'getuid', lambda: opened.st_uid)() + if (not stat.S_ISREG(opened.st_mode) or opened.st_size > _GHOSTTY_HISTORY_MAX_BYTES or + opened.st_nlink != 1 or opened.st_uid != owner_uid): + return None + + resolved = path.resolve(strict=True) + if not any(os.path.commonpath([str(resolved), str(root)]) == str(root) for root in _ghostty_history_temp_roots()): + return None + current = resolved.stat() + if ((opened.st_dev, opened.st_ino) != (current.st_dev, current.st_ino) or + current.st_nlink != 1 or current.st_uid != owner_uid): + return None + validated = True + + chunks = [] + remaining = _GHOSTTY_HISTORY_MAX_BYTES + 1 + while remaining: + chunk = os.read(descriptor, min(64 * 1024, remaining)) + if not chunk: + break + chunks.append(chunk) + remaining -= len(chunk) + data = b''.join(chunks) + if len(data) > _GHOSTTY_HISTORY_MAX_BYTES: + return None + return _tail_lines(data.decode('utf-8', errors='replace'), n) + except Exception: + return None + finally: + if descriptor is not None: + # [tag:ghostty_history_cleanup] Scrub the validated descriptor; unlinking by path would reintroduce a swap race. + if validated: + try: + latest = os.fstat(descriptor) + if latest.st_nlink == 1 and latest.st_uid == owner_uid: + os.ftruncate(descriptor, 0) + except OSError: + pass + try: + os.close(descriptor) + except OSError: + pass + + +def get_ghostty_history_macos(n): + old_clip = _pbpaste() + history_path = None + try: + subprocess.run(['osascript', '-e', GHOSTTY_SCREEN_SCRIPT], text=True, check=True, stdout=DEVNULL, stderr=DEVNULL) + history_path = _wait_for_ghostty_history_path(old_clip) + if not history_path: + return None + return _read_ghostty_history_file(history_path, n) + except Exception: + return None + finally: + # [tag:ghostty_clipboard_restore] Never overwrite clipboard content changed while capture was running. + current_clip = _pbpaste() if old_clip is not None and history_path else None + if current_clip is not None and current_clip.strip() == str(history_path).strip(): + _pbcopy(old_clip) + + +def is_ghostty(): + return os.environ.get('TERM_PROGRAM') == 'ghostty' or os.environ.get('TERM', '').startswith('xterm-ghostty') + + def get_hist_tmux(n, pid='current'): if not os.environ.get('TMUX'): return None try: @@ -190,16 +353,38 @@ def get_hist_tmux(n, pid='current'): # %% ../nbs/00_core.ipynb #0eda0216 def get_hist_osa(n, pid=''): - script = ({ - 'Apple_Terminal':'tell application "Terminal" to get the contents of the selected tab of the front window', - 'iTerm.app':'tell application id "com.googlecode.iterm2" to get text of current session of current tab of current window', - }).get(os.getenv('TERM_PROGRAM')) - if not script: return None - return "\n".join(co(['osascript', '-e', script], text=True).splitlines()[:n]) + "Backwards-compatible wrapper for macOS terminal history capture." + return get_macos_terminal_history(n) if _is_current_macos_terminal(pid or 'current') else None # %% ../nbs/00_core.ipynb #5344a2bd def get_history(n, pid='current'): - return get_hist_tmux(n, pid) or get_hist_osa(n) + "Backwards-compatible entry point for terminal history dispatch." + return get_terminal_history(n, pid) + + +def _is_current_macos_terminal(pid): + return sys.platform == 'darwin' and pid in ('current', None) + + +def get_macos_terminal_history(n): + script = MACOS_TERMINAL_HISTORY_SCRIPTS.get(os.environ.get('TERM_PROGRAM')) + if not script or sys.platform != 'darwin': + return None + try: + return _tail_lines(co(['osascript', '-e', script], text=True, stderr=DEVNULL), n) + except Exception: + return None + + +def get_terminal_history(n, pid='current'): + if os.environ.get('TMUX'): + n = tmux_history_lim() if n is None or n < 0 else n + return get_hist_tmux(n, pid) + if not _is_current_macos_terminal(pid): + return None + + n = _DEFAULT_TERMINAL_HISTORY_LINES if n is None or n < 0 else n + return get_ghostty_history_macos(n) if is_ghostty() else get_macos_terminal_history(n) # %% ../nbs/00_core.ipynb #0dcbb503 default_cfg = asdict(ShellSageConfig()) @@ -324,7 +509,7 @@ async def main( v: Annotated[str, "Print version", dict(action='version')] = '%(prog)s ' + __version__, pid: str = 'current', # `current`, `all` or tmux pane_id (e.g. %0) for context skip_system: bool = False, # Whether to skip system information in the AI's context - history_lines: int = None, # Number of history lines. Defaults to tmux scrollback history length + history_lines: int = None, # Number of terminal history lines; defaults to tmux limit or 3000 outside tmux mode: str = 'default', # Available ShellSage modes: ['default', 'sassy'] model: str = None, # The LLM model that will be invoked on the LLM provider vendor_name: str = None, # Vendor name for non auto-resolved models (e.g. 'codex', 'fireworks_ai', 'moonshot', 'deepseek', ...) @@ -360,10 +545,7 @@ async def main( query = ' '.join(query) ctxt = '' if skip_system else _sys_info() - # Get tmux history if in a tmux session - if opts.history_lines is None or opts.history_lines < 0: - opts.history_lines = tmux_history_lim() - history = get_history(opts.history_lines, pid) + history = get_terminal_history(opts.history_lines, pid) if history: ctxt += f'\n{history}\n' # Read from redirect stdin if available diff --git a/tests/__init__.py b/tests/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/tests/test_entrypoint.py b/tests/test_entrypoint.py new file mode 100644 index 0000000..886fe1e --- /dev/null +++ b/tests/test_entrypoint.py @@ -0,0 +1,17 @@ +import subprocess +import sys +import unittest + + +class EntrypointTests(unittest.TestCase): + def test_core_imports_with_installed_dependencies(self): + result = subprocess.run( + [sys.executable, '-c', 'import shell_sage.core'], + capture_output=True, + text=True, + ) + self.assertEqual(result.returncode, 0, result.stderr) + + +if __name__ == '__main__': + unittest.main() diff --git a/tests/test_terminal_history_dispatch.py b/tests/test_terminal_history_dispatch.py new file mode 100644 index 0000000..70010ea --- /dev/null +++ b/tests/test_terminal_history_dispatch.py @@ -0,0 +1,497 @@ +import errno +import importlib +import sys +import tempfile +import types +import unittest +from dataclasses import asdict, dataclass +from pathlib import Path +from unittest.mock import patch + + +def _module(name, **attrs): + module = types.ModuleType(name) + for key, value in attrs.items(): + setattr(module, key, value) + return module + + +_MISSING = object() +_STUB_MODULES = [ + 'fastcore', 'fastcore.script', 'fastcore.tools', 'fastcore.utils', 'fastcore.meta', + 'fastlite', 'rich', 'rich.live', 'rich.spinner', 'rich.console', 'rich.markdown', + 'rich.syntax', 'shell_sage.config', 'safecmd', 'pyperclip', 'rgapi', 'fastllm', + 'fastllm.chat', 'shell_sage.core', +] + + +def _save_modules(): + return {name: sys.modules.get(name, _MISSING) for name in _STUB_MODULES} + + +def _restore_modules(saved): + for name, module in saved.items(): + if module is _MISSING: + sys.modules.pop(name, None) + else: + sys.modules[name] = module + + +def _install_import_stubs(): + """Import shell_sage.core without loading optional runtime dependencies.""" + fastcore_script = _module('fastcore.script', call_parse=lambda f: f) + fastcore_tools = _module( + 'fastcore.tools', + rg=lambda *args, **kwargs: None, + view_file=lambda *args, **kwargs: None, + create_file=lambda *args, **kwargs: None, + file_str_replace=lambda *args, **kwargs: None, + file_insert_line=lambda *args, **kwargs: None, + ) + fastcore_utils = _module( + 'fastcore.utils', + patch=lambda f: f, + IN_NOTEBOOK=False, + noop=lambda x=None, *args, **kwargs: x, + asdict=asdict, + Path=Path, + ) + + class AttrDict(dict): + def __getattr__(self, key): return self[key] + def __setattr__(self, key, value): self[key] = value + + fastcore_utils.AttrDict = AttrDict + fastcore_meta = _module('fastcore.meta', delegates=lambda *args, **kwargs: lambda f: f) + fastcore = _module( + 'fastcore', script=fastcore_script, tools=fastcore_tools, + utils=fastcore_utils, meta=fastcore_meta, + ) + fastcore.__path__ = [] + + class Console: + def print(self, *args, **kwargs): pass + + rich_live = _module('rich.live', Live=object) + rich_spinner = _module('rich.spinner', Spinner=object) + rich_console = _module('rich.console', Console=Console) + rich_markdown = _module( + 'rich.markdown', + CodeBlock=type('CodeBlock', (), {}), + Markdown=lambda *args, **kwargs: args[0] if args else '', + ) + rich_syntax = _module('rich.syntax', Syntax=lambda *args, **kwargs: args[0] if args else '') + rich = _module( + 'rich', live=rich_live, spinner=rich_spinner, console=rich_console, + markdown=rich_markdown, syntax=rich_syntax, + ) + rich.__path__ = [] + + @dataclass + class ShellSageConfig: + model: str = 'test-model' + search: str = '' + think: str = '' + trust: str = '' + mode: str = 'default' + base_url: str = '' + api_key: str = '' + vendor_name: str = '' + history_lines: int = -1 + code_theme: str = 'monokai' + code_lexer: str = 'python' + log: bool = False + safecmd: bool = False + custom_instructions: str = '' + + shell_config = _module( + 'shell_sage.config', ShellSageConfig=ShellSageConfig, get_cfg=lambda: {}, + ) + + class AsyncChat: + def __init__(self, *args, **kwargs): pass + def _call(self, *args, **kwargs): pass + + fastllm_chat = _module('fastllm.chat', AsyncChat=AsyncChat) + fastllm = _module('fastllm', chat=fastllm_chat) + fastllm.__path__ = [] + + modules = { + 'fastcore': fastcore, + 'fastcore.script': fastcore_script, + 'fastcore.tools': fastcore_tools, + 'fastcore.utils': fastcore_utils, + 'fastcore.meta': fastcore_meta, + 'fastlite': _module('fastlite', database=lambda *args, **kwargs: None), + 'rich': rich, + 'rich.live': rich_live, + 'rich.spinner': rich_spinner, + 'rich.console': rich_console, + 'rich.markdown': rich_markdown, + 'rich.syntax': rich_syntax, + 'shell_sage.config': shell_config, + 'safecmd': _module('safecmd', bash=lambda *args, **kwargs: None), + 'pyperclip': _module('pyperclip', copy=lambda *args, **kwargs: None), + 'rgapi': _module( + 'rgapi', + rg=lambda *args, **kwargs: None, + ls=lambda *args, **kwargs: None, + fd=lambda *args, **kwargs: [], + ), + 'fastllm': fastllm, + 'fastllm.chat': fastllm_chat, + } + sys.modules.update(modules) + + +def import_core(): + saved_modules = _save_modules() + _install_import_stubs() + sys.modules.pop('shell_sage.core', None) + return importlib.import_module('shell_sage.core'), saved_modules + + +class TerminalHistoryDispatchTests(unittest.TestCase): + def setUp(self): + self.core, self._saved_modules = import_core() + + def tearDown(self): + _restore_modules(self._saved_modules) + + def test_tmux_provider_wins_and_defaults_to_tmux_history_limit(self): + with patch.dict(self.core.os.environ, {'TMUX': '/tmp/tmux', 'TERM_PROGRAM': 'ghostty'}, clear=True), \ + patch.object(self.core, 'tmux_history_lim', return_value=123) as history_lim, \ + patch.object(self.core, 'get_hist_tmux', return_value='tmux history') as get_tmux, \ + patch.object(self.core, 'get_ghostty_history_macos') as get_ghostty, \ + patch.object(self.core, 'get_macos_terminal_history') as get_terminal: + self.assertEqual(self.core.get_terminal_history(None, 'all'), 'tmux history') + history_lim.assert_called_once_with() + get_tmux.assert_called_once_with(123, 'all') + get_ghostty.assert_not_called() + get_terminal.assert_not_called() + + def test_explicit_history_lines_pass_through_to_tmux(self): + with patch.dict(self.core.os.environ, {'TMUX': '/tmp/tmux'}, clear=True), \ + patch.object(self.core, 'tmux_history_lim') as history_lim, \ + patch.object(self.core, 'get_hist_tmux', return_value='tmux history') as get_tmux: + self.assertEqual(self.core.get_terminal_history(42, '%1'), 'tmux history') + history_lim.assert_not_called() + get_tmux.assert_called_once_with(42, '%1') + + def test_get_history_delegates_to_canonical_dispatcher(self): + with patch.object(self.core, 'get_terminal_history', return_value='terminal history') as get_terminal: + self.assertEqual(self.core.get_history(12, 'current'), 'terminal history') + get_terminal.assert_called_once_with(12, 'current') + + def test_get_hist_osa_remains_compatible(self): + with patch.object(self.core.sys, 'platform', 'darwin'), \ + patch.object(self.core, 'get_macos_terminal_history', return_value='terminal history') as get_terminal: + self.assertEqual(self.core.get_hist_osa(12), 'terminal history') + self.assertIsNone(self.core.get_hist_osa(12, '%2')) + get_terminal.assert_called_once_with(12) + + def test_ghostty_detection_uses_term_program_or_term(self): + with patch.dict(self.core.os.environ, {'TERM_PROGRAM': 'ghostty'}, clear=True): + self.assertTrue(self.core.is_ghostty()) + with patch.dict(self.core.os.environ, {'TERM': 'xterm-ghostty'}, clear=True): + self.assertTrue(self.core.is_ghostty()) + with patch.dict( + self.core.os.environ, + {'TERM_PROGRAM': 'Apple_Terminal', 'TERM': 'xterm-256color'}, + clear=True, + ): + self.assertFalse(self.core.is_ghostty()) + + def test_non_tmux_providers_use_default_history_lines(self): + cases = [ + ('ghostty', 'get_ghostty_history_macos', 'get_macos_terminal_history', 'ghostty history'), + ('Apple_Terminal', 'get_macos_terminal_history', 'get_ghostty_history_macos', 'terminal history'), + ] + for term, provider, other, expected in cases: + with self.subTest(term=term), \ + patch.dict(self.core.os.environ, {'TERM_PROGRAM': term}, clear=True), \ + patch.object(self.core.sys, 'platform', 'darwin'), \ + patch.object(self.core, provider, return_value=expected) as provider_fn, \ + patch.object(self.core, other) as other_fn: + self.assertEqual(self.core.get_terminal_history(-1, 'current'), expected) + provider_fn.assert_called_once_with(3000) + other_fn.assert_not_called() + + def test_macos_terminal_history_reads_recent_lines_from_osascript(self): + cases = [ + ('Apple_Terminal', 2, 'one\ntwo\nthree\n', 'two\nthree'), + ('iTerm.app', 1, 'alpha\nbeta\ngamma\n', 'gamma'), + ] + for term, n, output, expected in cases: + with self.subTest(term=term), \ + patch.dict(self.core.os.environ, {'TERM_PROGRAM': term}, clear=True), \ + patch.object(self.core.sys, 'platform', 'darwin'), \ + patch.object(self.core, 'co', return_value=output) as co: + self.assertEqual(self.core.get_macos_terminal_history(n), expected) + co.assert_called_once_with( + ['osascript', '-e', self.core.MACOS_TERMINAL_HISTORY_SCRIPTS[term]], + text=True, + stderr=self.core.DEVNULL, + ) + + def test_non_tmux_rejects_unsupported_pid_or_platform_before_capture(self): + for term, provider in [ + ('Apple_Terminal', 'get_macos_terminal_history'), + ('ghostty', 'get_ghostty_history_macos'), + ]: + with self.subTest(term=term), \ + patch.dict(self.core.os.environ, {'TERM_PROGRAM': term}, clear=True), \ + patch.object(self.core.sys, 'platform', 'darwin'), \ + patch.object(self.core, provider) as capture: + self.assertIsNone(self.core.get_terminal_history(10, 'all')) + self.assertIsNone(self.core.get_terminal_history(10, '%2')) + capture.assert_not_called() + + with patch.dict(self.core.os.environ, {'TERM_PROGRAM': 'Apple_Terminal'}, clear=True), \ + patch.object(self.core.sys, 'platform', 'linux'), \ + patch.object(self.core, 'get_macos_terminal_history') as capture: + self.assertIsNone(self.core.get_terminal_history(10)) + capture.assert_not_called() + + def test_macos_terminal_failed_capture_returns_none(self): + with patch.dict(self.core.os.environ, {'TERM_PROGRAM': 'Apple_Terminal'}, clear=True), \ + patch.object(self.core.sys, 'platform', 'darwin'), \ + patch.object(self.core, 'co', side_effect=Exception('boom')): + self.assertIsNone(self.core.get_terminal_history(10)) + with patch.dict(self.core.os.environ, {'TERM_PROGRAM': 'Apple_Terminal'}, clear=True), \ + patch.object(self.core.sys, 'platform', 'linux'), \ + patch.object(self.core, 'co') as co: + self.assertIsNone(self.core.get_macos_terminal_history(10)) + co.assert_not_called() + + # [ref:ghostty_history_cleanup] + def test_ghostty_capture_scrubs_consumed_history_file(self): + with tempfile.TemporaryDirectory(dir=tempfile.gettempdir()) as directory: + history_path = Path(directory) / 'screen.txt' + history_path.write_text('history', encoding='utf-8') + with patch.object(self.core, '_pbpaste', side_effect=['original clip', str(history_path), str(history_path)]), \ + patch.object(self.core, '_pbcopy'), \ + patch.object(self.core.subprocess, 'run'): + self.assertEqual(self.core.get_ghostty_history_macos(10), 'history') + self.assertTrue(history_path.exists()) + self.assertEqual(history_path.read_bytes(), b'') + + # [ref:ghostty_history_cleanup] + def test_ghostty_cleanup_uses_descriptor_not_replacement_path(self): + with tempfile.TemporaryDirectory(dir=tempfile.gettempdir()) as directory, \ + tempfile.TemporaryDirectory(dir=Path.cwd()) as outside_directory: + history_path = Path(directory) / 'screen.txt' + history_path.write_text('history', encoding='utf-8') + replacement = Path(outside_directory) / 'replacement.txt' + replacement.write_text('do not scrub', encoding='utf-8') + original_read = self.core.os.read + swapped = False + + def swap_path_then_read(descriptor, size): + nonlocal swapped + if not swapped: + history_path.unlink() + history_path.symlink_to(replacement) + swapped = True + return original_read(descriptor, size) + + with patch.object(self.core, '_pbpaste', side_effect=['original clip', str(history_path), str(history_path)]), \ + patch.object(self.core, '_pbcopy'), \ + patch.object(self.core.subprocess, 'run'), \ + patch.object(self.core.os, 'read', side_effect=swap_path_then_read): + self.assertEqual(self.core.get_ghostty_history_macos(10), 'history') + self.assertTrue(history_path.is_symlink()) + self.assertEqual(replacement.read_text(encoding='utf-8'), 'do not scrub') + + def test_ghostty_cleanup_rejects_preexisting_hard_link(self): + with tempfile.TemporaryDirectory(dir=tempfile.gettempdir()) as directory, \ + tempfile.TemporaryDirectory(dir=Path.cwd()) as outside_directory: + outside = Path(outside_directory) / 'outside.txt' + outside.write_text('do not scrub', encoding='utf-8') + history_path = Path(directory) / 'screen.txt' + try: + self.core.os.link(outside, history_path) + except OSError as error: + if error.errno in (errno.EXDEV, errno.EPERM, errno.EOPNOTSUPP): + self.skipTest(f'hard links unavailable: {error}') + raise + + self.assertIsNone(self.core._read_ghostty_history_file(history_path, 10)) + self.assertEqual(outside.read_text(encoding='utf-8'), 'do not scrub') + self.assertFalse(self.core._valid_ghostty_history_path(history_path)) + + def test_ghostty_cleanup_skips_scrub_if_hard_link_appears_during_read(self): + with tempfile.TemporaryDirectory(dir=tempfile.gettempdir()) as directory, \ + tempfile.TemporaryDirectory(dir=Path.cwd()) as outside_directory: + history_path = Path(directory) / 'screen.txt' + history_path.write_text('history', encoding='utf-8') + alias = Path(outside_directory) / 'alias.txt' + probe = Path(outside_directory) / 'probe.txt' + try: + self.core.os.link(history_path, probe) + except OSError as error: + if error.errno in (errno.EXDEV, errno.EPERM, errno.EOPNOTSUPP): + self.skipTest(f'hard links unavailable: {error}') + raise + probe.unlink() + original_read = self.core.os.read + linked = False + + def link_then_read(descriptor, size): + nonlocal linked + if not linked: + self.core.os.link(history_path, alias) + linked = True + return original_read(descriptor, size) + + with patch.object(self.core.os, 'read', side_effect=link_then_read): + self.assertEqual(self.core._read_ghostty_history_file(history_path, 10), 'history') + self.assertEqual(history_path.read_text(encoding='utf-8'), 'history') + self.assertEqual(alias.read_text(encoding='utf-8'), 'history') + + def test_ghostty_scrub_failure_does_not_mask_history_or_clipboard_restore(self): + with tempfile.TemporaryDirectory(dir=tempfile.gettempdir()) as directory: + history_path = Path(directory) / 'screen.txt' + history_path.write_text('history', encoding='utf-8') + with patch.object(self.core, '_pbpaste', side_effect=['original clip', str(history_path), str(history_path)]), \ + patch.object(self.core, '_pbcopy') as pbcopy, \ + patch.object(self.core.subprocess, 'run'), \ + patch.object(self.core.os, 'ftruncate', side_effect=OSError('cannot scrub')): + self.assertEqual(self.core.get_ghostty_history_macos(10), 'history') + pbcopy.assert_called_once_with('original clip') + self.assertEqual(history_path.read_text(encoding='utf-8'), 'history') + + def test_ghostty_macos_happy_path_reads_temp_history_and_restores_clipboard(self): + with tempfile.TemporaryDirectory(dir=tempfile.gettempdir()) as directory: + history_path = Path(directory) / 'screen.txt' + history_path.write_text('one\ntwo\nthree\nfour', encoding='utf-8') + with patch.object(self.core, '_pbpaste', side_effect=['original clip', str(history_path), str(history_path)]), \ + patch.object(self.core, '_pbcopy') as pbcopy, \ + patch.object(self.core.subprocess, 'run') as run: + self.assertEqual(self.core.get_ghostty_history_macos(2), 'three\nfour') + self.assertIn('perform action "write_screen_file:copy,plain"', self.core.GHOSTTY_SCREEN_SCRIPT) + run.assert_called_once_with( + ['osascript', '-e', self.core.GHOSTTY_SCREEN_SCRIPT], + text=True, + check=True, + stdout=self.core.DEVNULL, + stderr=self.core.DEVNULL, + ) + pbcopy.assert_called_once_with('original clip') + + def test_ghostty_macos_invalid_path_does_not_restore_clipboard(self): + clipboard_values = iter(['original clip', 'not a path']) + + def fake_pbpaste(): return next(clipboard_values, 'not a path') + + with patch.object(self.core, '_pbpaste', side_effect=fake_pbpaste), \ + patch.object(self.core, '_pbcopy') as pbcopy, \ + patch.object(self.core.subprocess, 'run'), \ + patch.object(self.core.time, 'sleep'), \ + patch.object(self.core.time, 'time', side_effect=[0, 0, 2]): + self.assertIsNone(self.core.get_ghostty_history_macos(10)) + pbcopy.assert_not_called() + + # [ref:ghostty_clipboard_restore] + def test_ghostty_capture_does_not_overwrite_concurrent_clipboard_change(self): + with tempfile.TemporaryDirectory(dir=tempfile.gettempdir()) as directory: + history_path = Path(directory) / 'screen.txt' + history_path.write_text('history', encoding='utf-8') + with patch.object( + self.core, + '_pbpaste', + side_effect=['original clip', str(history_path), 'new user clip'], + ), patch.object(self.core, '_pbcopy') as pbcopy, \ + patch.object(self.core.subprocess, 'run'): + self.assertEqual(self.core.get_ghostty_history_macos(10), 'history') + pbcopy.assert_not_called() + + def test_ghostty_capture_does_not_restore_unavailable_clipboard_text(self): + with tempfile.TemporaryDirectory(dir=tempfile.gettempdir()) as directory: + history_path = Path(directory) / 'screen.txt' + history_path.write_text('history', encoding='utf-8') + with patch.object( + self.core, + '_pbpaste', + side_effect=[None, str(history_path), str(history_path)], + ), patch.object(self.core, '_pbcopy') as pbcopy, \ + patch.object(self.core.subprocess, 'run'): + self.assertEqual(self.core.get_ghostty_history_macos(10), 'history') + pbcopy.assert_not_called() + + def test_clipboard_helpers_preserve_unavailable_text_state(self): + with patch.object(self.core, 'co', side_effect=OSError('no text clipboard')): + self.assertIsNone(self.core._pbpaste()) + with patch.object(self.core.subprocess, 'run') as run: + self.core._pbcopy(None) + run.assert_not_called() + + def test_ghostty_macos_read_errors_return_none_and_restore_clipboard(self): + with tempfile.TemporaryDirectory(dir=tempfile.gettempdir()) as directory: + history_path = Path(directory) / 'screen.txt' + history_path.write_text('history', encoding='utf-8') + with patch.object(self.core, '_pbpaste', side_effect=['original clip', str(history_path), str(history_path)]), \ + patch.object(self.core, '_pbcopy') as pbcopy, \ + patch.object(self.core.subprocess, 'run'), \ + patch.object(self.core.os, 'read', side_effect=OSError('boom')): + self.assertIsNone(self.core.get_ghostty_history_macos(10)) + pbcopy.assert_called_once_with('original clip') + self.assertTrue(history_path.exists()) + self.assertEqual(history_path.read_bytes(), b'') + + def test_ghostty_path_validation_enforces_name_root_and_size(self): + with tempfile.TemporaryDirectory(dir=tempfile.gettempdir()) as directory, \ + tempfile.TemporaryDirectory(dir=Path.cwd()) as outside_directory: + valid = Path(directory) / 'screen.txt' + valid.write_text('history', encoding='utf-8') + wrong_name = Path(directory) / 'history.txt' + wrong_name.write_text('history', encoding='utf-8') + oversized_target = Path(directory) / 'screen.txt' + outside = Path(outside_directory) / 'screen.txt' + outside.write_text('outside history', encoding='utf-8') + + self.assertTrue(self.core._valid_ghostty_history_path(str(valid))) + self.assertFalse(self.core._valid_ghostty_history_path(str(wrong_name))) + self.assertFalse(self.core._valid_ghostty_history_path(str(outside))) + self.assertIsNone(self.core._read_ghostty_history_file(outside, 10)) + self.assertEqual(outside.read_text(encoding='utf-8'), 'outside history') + self.assertFalse(self.core._valid_ghostty_history_path('not a path')) + + valid.unlink() + oversized_target.touch() + oversized_target.write_bytes(b'') + with oversized_target.open('r+b') as stream: + stream.truncate(self.core._GHOSTTY_HISTORY_MAX_BYTES + 1) + self.assertFalse(self.core._valid_ghostty_history_path(str(oversized_target))) + self.assertIsNone(self.core._read_ghostty_history_file(oversized_target, 10)) + + # [ref:ghostty_history_fd_validation] + def test_ghostty_safe_read_rejects_symlink_swap_after_validation(self): + with tempfile.TemporaryDirectory(dir=tempfile.gettempdir()) as directory, \ + tempfile.TemporaryDirectory(dir=Path.cwd()) as outside_directory: + safe_target = Path(directory) / 'safe-history.txt' + safe_target.write_text('safe history', encoding='utf-8') + candidate = Path(directory) / 'screen.txt' + candidate.symlink_to(safe_target) + outside = Path(outside_directory) / 'secret.txt' + outside.write_text('secret outside history', encoding='utf-8') + + self.assertTrue(self.core._valid_ghostty_history_path(str(candidate))) + candidate.unlink() + candidate.symlink_to(outside) + + self.assertIsNone(self.core._read_ghostty_history_file(candidate, 10)) + + def test_tail_lines_respects_history_line_count(self): + self.assertEqual(self.core._tail_lines('one\ntwo\nthree', 2), 'two\nthree') + self.assertEqual(self.core._tail_lines('one\ntwo', -1), 'one\ntwo') + self.assertEqual(self.core._tail_lines('one\ntwo', 0), '') + + def test_no_terminal_provider_returns_none(self): + with patch.dict(self.core.os.environ, {}, clear=True), \ + patch.object(self.core.sys, 'platform', 'darwin'): + self.assertIsNone(self.core.get_terminal_history(10, 'current')) + + +if __name__ == '__main__': + unittest.main() diff --git a/uv.lock b/uv.lock index ffd6ff2..c8d7cfc 100644 --- a/uv.lock +++ b/uv.lock @@ -7,34 +7,6 @@ resolution-markers = [ "python_full_version < '3.11'", ] -[[package]] -name = "annotated-types" -version = "0.7.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/ee/67/531ea369ba64dcff5ec9c3402f9f51bf748cec26dde048a2f973a4eea7f5/annotated_types-0.7.0.tar.gz", hash = "sha256:aff07c09a53a08bc8cfccb9c85b05f1aa9a2a6f23728d790723543408344ce89", size = 16081, upload-time = "2024-05-20T21:33:25.928Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/78/b6/6307fbef88d9b5ee7421e68d78a9f162e0da4900bc5f5793f6d3d0e34fb8/annotated_types-0.7.0-py3-none-any.whl", hash = "sha256:1f02e8b43a8fbbc3f3e0d4f0f4bfc8131bcb4eebe8849b8e5c773f3a1c582a53", size = 13643, upload-time = "2024-05-20T21:33:24.1Z" }, -] - -[[package]] -name = "anthropic" -version = "0.68.0" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "anyio" }, - { name = "distro" }, - { name = "docstring-parser" }, - { name = "httpx" }, - { name = "jiter" }, - { name = "pydantic" }, - { name = "sniffio" }, - { name = "typing-extensions" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/64/46/da44bf087ddaf3f7dbe4808c00c7cde466fe68c4fc9fbebdfc231f4ea205/anthropic-0.68.0.tar.gz", hash = "sha256:507e9b5f627d1b249128ff15b21855e718fa4ed8dabc787d0e68860a4b32a7a8", size = 471584, upload-time = "2025-09-17T15:20:19.509Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/60/32/2d7553184b05bdbec61dd600014a55b9028408aee6128b25cb6f20e3002c/anthropic-0.68.0-py3-none-any.whl", hash = "sha256:ac579ea5eca22a7165b1042e6af57c4bf556e51afae3ca80e24768d4756b78c0", size = 325199, upload-time = "2025-09-17T15:20:17.452Z" }, -] - [[package]] name = "anyio" version = "4.11.0" @@ -158,63 +130,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/e5/48/1549795ba7742c948d2ad169c1c8cdbae65bc450d6cd753d124b17c8cd32/certifi-2025.8.3-py3-none-any.whl", hash = "sha256:f6c12493cfb1b06ba2ff328595af9350c65d6644968e5d3a2ffd78699af217a5", size = 161216, upload-time = "2025-08-03T03:07:45.777Z" }, ] -[[package]] -name = "claudette" -version = "0.3.6" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "anthropic" }, - { name = "fastcore" }, - { name = "msglm" }, - { name = "toolslm" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/4d/f2/ffc34c6816b40efa25843f77f72022fa22968df896ae4e1d1ed04a6a1175/claudette-0.3.6.tar.gz", hash = "sha256:5ffcc4722c2f684cc99ddfb22fc6964f777d9c1d1228e8c3c1f315acfa3ee2f1", size = 90821, upload-time = "2025-08-09T22:16:04.797Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/09/f6/4f0e1da162410216ed892152c5cd0656b55e02a169f04f4e017bbee7a58e/claudette-0.3.6-py3-none-any.whl", hash = "sha256:9953215bdc4131718ef26e10d56aeb65bd532568cdee31f868dc9c184f64306c", size = 40498, upload-time = "2025-08-09T22:16:02.7Z" }, -] - -[[package]] -name = "colorama" -version = "0.4.6" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/d8/53/6f443c9a4a8358a93a6792e2acffb9d9d5cb0a5cfd8802644b7b1c9a02e4/colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44", size = 27697, upload-time = "2022-10-25T02:36:22.414Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6", size = 25335, upload-time = "2022-10-25T02:36:20.889Z" }, -] - -[[package]] -name = "cosette" -version = "0.2.3" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "fastcore" }, - { name = "msglm" }, - { name = "openai" }, - { name = "toolslm" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/73/04/64cc39a7567a4f39eacac9a30ef22c16c967bcd83d48ac1b0aad7ade8b08/cosette-0.2.3.tar.gz", hash = "sha256:6d5e18f902f19e12fdd1d1b35a9cbf1ec98741d3f621111f3433f152bb7c98e3", size = 20032, upload-time = "2025-08-09T23:20:21.827Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/94/9e/a86c11c79c63de0c3273831c1e3560fc281758df72b909cd37246a028c19/cosette-0.2.3-py3-none-any.whl", hash = "sha256:a31be26803dc72a9f758c9a819b052459b08364a10b2dda6bde20c3f2c0913bb", size = 15225, upload-time = "2025-08-09T23:20:20.093Z" }, -] - -[[package]] -name = "distro" -version = "1.9.0" -source = { registry = "https://pypi.org/simple" } -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" } -wheels = [ - { 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" }, -] - -[[package]] -name = "docstring-parser" -version = "0.17.0" -source = { registry = "https://pypi.org/simple" } -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" } -wheels = [ - { 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" }, -] - [[package]] name = "exceptiongroup" version = "1.3.0" @@ -229,14 +144,11 @@ wheels = [ [[package]] name = "fastcore" -version = "1.8.11" +version = "2.1.11" source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "packaging" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/39/4e/39b4c5c89019334f0b0179d3f5d4b04e32a5eb68237cf6bcdc7d7d44e24b/fastcore-1.8.11.tar.gz", hash = "sha256:873dbe119293ddfd1b8286eb8a513a22a36efc628759965011925fe4e8beb5a3", size = 78798, upload-time = "2025-09-23T06:05:59.647Z" } +sdist = { url = "https://files.pythonhosted.org/packages/21/75/545775f88cf3c62a3a5ee35c99b3e4b460462e8d63fc63097af6a6478c34/fastcore-2.1.11.tar.gz", hash = "sha256:3e44cd49bc0303a1da224abeeaf1fa9d9261c62c7254f4a2d1ec5b5581d8bb60", size = 116851, upload-time = "2026-07-26T13:18:38.131Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/6e/cb/7a73b18cf24804d5ac35a6859a0b29b0949861caa660b4ae924d92de5940/fastcore-1.8.11-py3-none-any.whl", hash = "sha256:0a5bd1346445eca8d25f102d7ff12ac03b294c322343fc818091930c9e373726", size = 81917, upload-time = "2025-09-23T06:05:57.675Z" }, + { url = "https://files.pythonhosted.org/packages/87/90/859c8a9d3f142cf73f11925eab143e23553b332ef7e696642f26e3a6b489/fastcore-2.1.11-py3-none-any.whl", hash = "sha256:e6c5a2017b57d2a92edee81ed93f9c288df660c0b3df88e72448569531946776", size = 121814, upload-time = "2026-07-26T13:18:36.607Z" }, ] [[package]] @@ -252,6 +164,19 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/86/1d/f09c2994a7e1c9c32dc0453b6fd7d4ae7830d26a17fd4f1e014b10b999ed/fastlite-0.2.1-py3-none-any.whl", hash = "sha256:106488269de593b41fed7e828dd2dc74dafc67ca5f6dc974c5c53f74d7b30be4", size = 17509, upload-time = "2025-07-07T23:57:36.543Z" }, ] +[[package]] +name = "fastspec" +version = "0.1.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "fastcore" }, + { name = "httpx" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/78/48/d7655de96b1076c7efbc5fc8185b338a3c27b5b4f83aa6bc6a2dcb883ad3/fastspec-0.1.2.tar.gz", hash = "sha256:57f2450c9d81c820fb9dd9ca6281ec8addee811426fd8049bbfd816bfc872f0b", size = 27143, upload-time = "2026-07-22T15:42:08.255Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e4/76/d5ebed62edf112697bfc16b398315585ac7daffd7b1951477204f742b9e2/fastspec-0.1.2-py3-none-any.whl", hash = "sha256:65fe5c6cfac585f12170098e86c973a27957e565cc005f70ec57b8fa425e419b", size = 25938, upload-time = "2026-07-22T15:42:06.831Z" }, +] + [[package]] name = "h11" version = "0.16.0" @@ -298,79 +223,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/76/c6/c88e154df9c4e1a2a66ccf0005a88dfb2650c1dffb6f5ce603dfbd452ce3/idna-3.10-py3-none-any.whl", hash = "sha256:946d195a0d259cbba61165e88e65941f16e9b36ea6ddb97f00452bae8b1287d3", size = 70442, upload-time = "2024-09-15T18:07:37.964Z" }, ] -[[package]] -name = "jiter" -version = "0.11.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/9d/c0/a3bb4cc13aced219dd18191ea66e874266bd8aa7b96744e495e1c733aa2d/jiter-0.11.0.tar.gz", hash = "sha256:1d9637eaf8c1d6a63d6562f2a6e5ab3af946c66037eb1b894e8fad75422266e4", size = 167094, upload-time = "2025-09-15T09:20:38.212Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/25/21/7dd1235a19e26979be6098e87e4cced2e061752f3a40a17bbce6dea7fae1/jiter-0.11.0-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:3893ce831e1c0094a83eeaf56c635a167d6fa8cc14393cc14298fd6fdc2a2449", size = 309875, upload-time = "2025-09-15T09:18:48.41Z" }, - { url = "https://files.pythonhosted.org/packages/71/f9/462b54708aa85b135733ccba70529dd68a18511bf367a87c5fd28676c841/jiter-0.11.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:25c625b9b61b5a8725267fdf867ef2e51b429687f6a4eef211f4612e95607179", size = 316505, upload-time = "2025-09-15T09:18:51.057Z" }, - { url = "https://files.pythonhosted.org/packages/bd/40/14e2eeaac6a47bff27d213834795472355fd39769272eb53cb7aa83d5aa8/jiter-0.11.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:dd4ca85fb6a62cf72e1c7f5e34ddef1b660ce4ed0886ec94a1ef9777d35eaa1f", size = 337613, upload-time = "2025-09-15T09:18:52.358Z" }, - { url = "https://files.pythonhosted.org/packages/d3/ed/a5f1f8419c92b150a7c7fb5ccba1fb1e192887ad713d780e70874f0ce996/jiter-0.11.0-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:572208127034725e79c28437b82414028c3562335f2b4f451d98136d0fc5f9cd", size = 361438, upload-time = "2025-09-15T09:18:54.637Z" }, - { url = "https://files.pythonhosted.org/packages/dd/f5/70682c023dfcdd463a53faf5d30205a7d99c51d70d3e303c932d0936e5a2/jiter-0.11.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:494ba627c7f550ad3dabb21862864b8f2216098dc18ff62f37b37796f2f7c325", size = 486180, upload-time = "2025-09-15T09:18:56.158Z" }, - { url = "https://files.pythonhosted.org/packages/7c/39/020d08cbab4eab48142ad88b837c41eb08a15c0767fdb7c0d3265128a44b/jiter-0.11.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b8da18a99f58bca3ecc2d2bba99cac000a924e115b6c4f0a2b98f752b6fbf39a", size = 376681, upload-time = "2025-09-15T09:18:57.553Z" }, - { url = "https://files.pythonhosted.org/packages/52/10/b86733f6e594cf51dd142f37c602d8df87c554c5844958deaab0de30eb5d/jiter-0.11.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e4ffd3b0fff3fabbb02cc09910c08144db6bb5697a98d227a074401e01ee63dd", size = 348685, upload-time = "2025-09-15T09:18:59.208Z" }, - { url = "https://files.pythonhosted.org/packages/fb/ee/8861665e83a9e703aa5f65fddddb6225428e163e6b0baa95a7f9a8fb9aae/jiter-0.11.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:8fe6530aa738a4f7d4e4702aa8f9581425d04036a5f9e25af65ebe1f708f23be", size = 385573, upload-time = "2025-09-15T09:19:00.593Z" }, - { url = "https://files.pythonhosted.org/packages/25/74/05afec03600951f128293813b5a208c9ba1bf587c57a344c05a42a69e1b1/jiter-0.11.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:e35d66681c133a03d7e974e7eedae89720fe8ca3bd09f01a4909b86a8adf31f5", size = 516669, upload-time = "2025-09-15T09:19:02.369Z" }, - { url = "https://files.pythonhosted.org/packages/93/d1/2e5bfe147cfbc2a5eef7f73eb75dc5c6669da4fa10fc7937181d93af9495/jiter-0.11.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:c59459beca2fbc9718b6f1acb7bfb59ebc3eb4294fa4d40e9cb679dafdcc6c60", size = 508767, upload-time = "2025-09-15T09:19:04.011Z" }, - { url = "https://files.pythonhosted.org/packages/87/50/597f71307e10426b5c082fd05d38c615ddbdd08c3348d8502963307f0652/jiter-0.11.0-cp310-cp310-win32.whl", hash = "sha256:b7b0178417b0dcfc5f259edbc6db2b1f5896093ed9035ee7bab0f2be8854726d", size = 205476, upload-time = "2025-09-15T09:19:05.594Z" }, - { url = "https://files.pythonhosted.org/packages/c7/86/1e5214b3272e311754da26e63edec93a183811d4fc2e0118addec365df8b/jiter-0.11.0-cp310-cp310-win_amd64.whl", hash = "sha256:11df2bf99fb4754abddd7f5d940a48e51f9d11624d6313ca4314145fcad347f0", size = 204708, upload-time = "2025-09-15T09:19:06.955Z" }, - { url = "https://files.pythonhosted.org/packages/38/55/a69fefeef09c2eaabae44b935a1aa81517e49639c0a0c25d861cb18cd7ac/jiter-0.11.0-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:cb5d9db02979c3f49071fce51a48f4b4e4cf574175fb2b11c7a535fa4867b222", size = 309503, upload-time = "2025-09-15T09:19:08.191Z" }, - { url = "https://files.pythonhosted.org/packages/bd/d5/a6aba9e6551f32f9c127184f398208e4eddb96c59ac065c8a92056089d28/jiter-0.11.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:1dc6a123f3471c4730db7ca8ba75f1bb3dcb6faeb8d46dd781083e7dee88b32d", size = 317688, upload-time = "2025-09-15T09:19:09.918Z" }, - { url = "https://files.pythonhosted.org/packages/bb/f3/5e86f57c1883971cdc8535d0429c2787bf734840a231da30a3be12850562/jiter-0.11.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:09858f8d230f031c7b8e557429102bf050eea29c77ad9c34c8fe253c5329acb7", size = 337418, upload-time = "2025-09-15T09:19:11.078Z" }, - { url = "https://files.pythonhosted.org/packages/5e/4f/a71d8a24c2a70664970574a8e0b766663f5ef788f7fe1cc20ee0c016d488/jiter-0.11.0-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:dbe2196c4a0ce760925a74ab4456bf644748ab0979762139626ad138f6dac72d", size = 361423, upload-time = "2025-09-15T09:19:13.286Z" }, - { url = "https://files.pythonhosted.org/packages/8f/e5/b09076f4e7fd9471b91e16f9f3dc7330b161b738f3b39b2c37054a36e26a/jiter-0.11.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5beb56d22b63647bafd0b74979216fdee80c580c0c63410be8c11053860ffd09", size = 486367, upload-time = "2025-09-15T09:19:14.546Z" }, - { url = "https://files.pythonhosted.org/packages/fb/f1/98cb3a36f5e62f80cd860f0179f948d9eab5a316d55d3e1bab98d9767af5/jiter-0.11.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:97025d09ef549795d8dc720a824312cee3253c890ac73c621721ddfc75066789", size = 376335, upload-time = "2025-09-15T09:19:15.939Z" }, - { url = "https://files.pythonhosted.org/packages/9f/d8/ec74886497ea393c29dbd7651ddecc1899e86404a6b1f84a3ddab0ab59fd/jiter-0.11.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d50880a6da65d8c23a2cf53c412847d9757e74cc9a3b95c5704a1d1a24667347", size = 348981, upload-time = "2025-09-15T09:19:17.568Z" }, - { url = "https://files.pythonhosted.org/packages/24/93/d22ad7fa3b86ade66c86153ceea73094fc2af8b20c59cb7fceab9fea4704/jiter-0.11.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:452d80a1c86c095a242007bd9fc5d21b8a8442307193378f891cb8727e469648", size = 385797, upload-time = "2025-09-15T09:19:19.121Z" }, - { url = "https://files.pythonhosted.org/packages/c8/bd/e25ff4a4df226e9b885f7cb01ee4b9dc74e3000e612d6f723860d71a1f34/jiter-0.11.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:e84e58198d4894668eec2da660ffff60e0f3e60afa790ecc50cb12b0e02ca1d4", size = 516597, upload-time = "2025-09-15T09:19:20.301Z" }, - { url = "https://files.pythonhosted.org/packages/be/fb/beda613db7d93ffa2fdd2683f90f2f5dce8daf4bc2d0d2829e7de35308c6/jiter-0.11.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:df64edcfc5dd5279a791eea52aa113d432c933119a025b0b5739f90d2e4e75f1", size = 508853, upload-time = "2025-09-15T09:19:22.075Z" }, - { url = "https://files.pythonhosted.org/packages/20/64/c5b0d93490634e41e38e2a15de5d54fdbd2c9f64a19abb0f95305b63373c/jiter-0.11.0-cp311-cp311-win32.whl", hash = "sha256:144fc21337d21b1d048f7f44bf70881e1586401d405ed3a98c95a114a9994982", size = 205140, upload-time = "2025-09-15T09:19:23.351Z" }, - { url = "https://files.pythonhosted.org/packages/a1/e6/c347c0e6f5796e97d4356b7e5ff0ce336498b7f4ef848fae621a56f1ccf3/jiter-0.11.0-cp311-cp311-win_amd64.whl", hash = "sha256:b0f32e644d241293b892b1a6dd8f0b9cc029bfd94c97376b2681c36548aabab7", size = 204311, upload-time = "2025-09-15T09:19:24.591Z" }, - { url = "https://files.pythonhosted.org/packages/ba/b5/3009b112b8f673e568ef79af9863d8309a15f0a8cdcc06ed6092051f377e/jiter-0.11.0-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:2fb7b377688cc3850bbe5c192a6bd493562a0bc50cbc8b047316428fbae00ada", size = 305510, upload-time = "2025-09-15T09:19:25.893Z" }, - { url = "https://files.pythonhosted.org/packages/fe/82/15514244e03b9e71e086bbe2a6de3e4616b48f07d5f834200c873956fb8c/jiter-0.11.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:a1b7cbe3f25bd0d8abb468ba4302a5d45617ee61b2a7a638f63fee1dc086be99", size = 316521, upload-time = "2025-09-15T09:19:27.525Z" }, - { url = "https://files.pythonhosted.org/packages/92/94/7a2e905f40ad2d6d660e00b68d818f9e29fb87ffe82774f06191e93cbe4a/jiter-0.11.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c0a7f0ec81d5b7588c5cade1eb1925b91436ae6726dc2df2348524aeabad5de6", size = 338214, upload-time = "2025-09-15T09:19:28.727Z" }, - { url = "https://files.pythonhosted.org/packages/a8/9c/5791ed5bdc76f12110158d3316a7a3ec0b1413d018b41c5ed399549d3ad5/jiter-0.11.0-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:07630bb46ea2a6b9c6ed986c6e17e35b26148cce2c535454b26ee3f0e8dcaba1", size = 361280, upload-time = "2025-09-15T09:19:30.013Z" }, - { url = "https://files.pythonhosted.org/packages/d4/7f/b7d82d77ff0d2cb06424141000176b53a9e6b16a1125525bb51ea4990c2e/jiter-0.11.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:7764f27d28cd4a9cbc61704dfcd80c903ce3aad106a37902d3270cd6673d17f4", size = 487895, upload-time = "2025-09-15T09:19:31.424Z" }, - { url = "https://files.pythonhosted.org/packages/42/44/10a1475d46f1fc1fd5cc2e82c58e7bca0ce5852208e0fa5df2f949353321/jiter-0.11.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:1d4a6c4a737d486f77f842aeb22807edecb4a9417e6700c7b981e16d34ba7c72", size = 378421, upload-time = "2025-09-15T09:19:32.746Z" }, - { url = "https://files.pythonhosted.org/packages/9a/5f/0dc34563d8164d31d07bc09d141d3da08157a68dcd1f9b886fa4e917805b/jiter-0.11.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cf408d2a0abd919b60de8c2e7bc5eeab72d4dafd18784152acc7c9adc3291591", size = 347932, upload-time = "2025-09-15T09:19:34.612Z" }, - { url = "https://files.pythonhosted.org/packages/f7/de/b68f32a4fcb7b4a682b37c73a0e5dae32180140cd1caf11aef6ad40ddbf2/jiter-0.11.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:cdef53eda7d18e799625023e1e250dbc18fbc275153039b873ec74d7e8883e09", size = 386959, upload-time = "2025-09-15T09:19:35.994Z" }, - { url = "https://files.pythonhosted.org/packages/76/0a/c08c92e713b6e28972a846a81ce374883dac2f78ec6f39a0dad9f2339c3a/jiter-0.11.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:53933a38ef7b551dd9c7f1064f9d7bb235bb3168d0fa5f14f0798d1b7ea0d9c5", size = 517187, upload-time = "2025-09-15T09:19:37.426Z" }, - { url = "https://files.pythonhosted.org/packages/89/b5/4a283bec43b15aad54fcae18d951f06a2ec3f78db5708d3b59a48e9c3fbd/jiter-0.11.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:11840d2324c9ab5162fc1abba23bc922124fedcff0d7b7f85fffa291e2f69206", size = 509461, upload-time = "2025-09-15T09:19:38.761Z" }, - { url = "https://files.pythonhosted.org/packages/34/a5/f8bad793010534ea73c985caaeef8cc22dfb1fedb15220ecdf15c623c07a/jiter-0.11.0-cp312-cp312-win32.whl", hash = "sha256:4f01a744d24a5f2bb4a11657a1b27b61dc038ae2e674621a74020406e08f749b", size = 206664, upload-time = "2025-09-15T09:19:40.096Z" }, - { url = "https://files.pythonhosted.org/packages/ed/42/5823ec2b1469395a160b4bf5f14326b4a098f3b6898fbd327366789fa5d3/jiter-0.11.0-cp312-cp312-win_amd64.whl", hash = "sha256:29fff31190ab3a26de026da2f187814f4b9c6695361e20a9ac2123e4d4378a4c", size = 203520, upload-time = "2025-09-15T09:19:41.798Z" }, - { url = "https://files.pythonhosted.org/packages/97/c4/d530e514d0f4f29b2b68145e7b389cbc7cac7f9c8c23df43b04d3d10fa3e/jiter-0.11.0-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:4441a91b80a80249f9a6452c14b2c24708f139f64de959943dfeaa6cb915e8eb", size = 305021, upload-time = "2025-09-15T09:19:43.523Z" }, - { url = "https://files.pythonhosted.org/packages/7a/77/796a19c567c5734cbfc736a6f987affc0d5f240af8e12063c0fb93990ffa/jiter-0.11.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:ff85fc6d2a431251ad82dbd1ea953affb5a60376b62e7d6809c5cd058bb39471", size = 314384, upload-time = "2025-09-15T09:19:44.849Z" }, - { url = "https://files.pythonhosted.org/packages/14/9c/824334de0b037b91b6f3fa9fe5a191c83977c7ec4abe17795d3cb6d174cf/jiter-0.11.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c5e86126d64706fd28dfc46f910d496923c6f95b395138c02d0e252947f452bd", size = 337389, upload-time = "2025-09-15T09:19:46.094Z" }, - { url = "https://files.pythonhosted.org/packages/a2/95/ed4feab69e6cf9b2176ea29d4ef9d01a01db210a3a2c8a31a44ecdc68c38/jiter-0.11.0-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:4ad8bd82165961867a10f52010590ce0b7a8c53da5ddd8bbb62fef68c181b921", size = 360519, upload-time = "2025-09-15T09:19:47.494Z" }, - { url = "https://files.pythonhosted.org/packages/b5/0c/2ad00f38d3e583caba3909d95b7da1c3a7cd82c0aa81ff4317a8016fb581/jiter-0.11.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b42c2cd74273455ce439fd9528db0c6e84b5623cb74572305bdd9f2f2961d3df", size = 487198, upload-time = "2025-09-15T09:19:49.116Z" }, - { url = "https://files.pythonhosted.org/packages/ea/8b/919b64cf3499b79bdfba6036da7b0cac5d62d5c75a28fb45bad7819e22f0/jiter-0.11.0-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f0062dab98172dd0599fcdbf90214d0dcde070b1ff38a00cc1b90e111f071982", size = 377835, upload-time = "2025-09-15T09:19:50.468Z" }, - { url = "https://files.pythonhosted.org/packages/29/7f/8ebe15b6e0a8026b0d286c083b553779b4dd63db35b43a3f171b544de91d/jiter-0.11.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bb948402821bc76d1f6ef0f9e19b816f9b09f8577844ba7140f0b6afe994bc64", size = 347655, upload-time = "2025-09-15T09:19:51.726Z" }, - { url = "https://files.pythonhosted.org/packages/8e/64/332127cef7e94ac75719dda07b9a472af6158ba819088d87f17f3226a769/jiter-0.11.0-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:25a5b1110cca7329fd0daf5060faa1234be5c11e988948e4f1a1923b6a457fe1", size = 386135, upload-time = "2025-09-15T09:19:53.075Z" }, - { url = "https://files.pythonhosted.org/packages/20/c8/557b63527442f84c14774159948262a9d4fabb0d61166f11568f22fc60d2/jiter-0.11.0-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:bf11807e802a214daf6c485037778843fadd3e2ec29377ae17e0706ec1a25758", size = 516063, upload-time = "2025-09-15T09:19:54.447Z" }, - { url = "https://files.pythonhosted.org/packages/86/13/4164c819df4a43cdc8047f9a42880f0ceef5afeb22e8b9675c0528ebdccd/jiter-0.11.0-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:dbb57da40631c267861dd0090461222060960012d70fd6e4c799b0f62d0ba166", size = 508139, upload-time = "2025-09-15T09:19:55.764Z" }, - { url = "https://files.pythonhosted.org/packages/fa/70/6e06929b401b331d41ddb4afb9f91cd1168218e3371972f0afa51c9f3c31/jiter-0.11.0-cp313-cp313-win32.whl", hash = "sha256:8e36924dad32c48d3c5e188d169e71dc6e84d6cb8dedefea089de5739d1d2f80", size = 206369, upload-time = "2025-09-15T09:19:57.048Z" }, - { url = "https://files.pythonhosted.org/packages/f4/0d/8185b8e15de6dce24f6afae63380e16377dd75686d56007baa4f29723ea1/jiter-0.11.0-cp313-cp313-win_amd64.whl", hash = "sha256:452d13e4fd59698408087235259cebe67d9d49173b4dacb3e8d35ce4acf385d6", size = 202538, upload-time = "2025-09-15T09:19:58.35Z" }, - { url = "https://files.pythonhosted.org/packages/13/3a/d61707803260d59520721fa326babfae25e9573a88d8b7b9cb54c5423a59/jiter-0.11.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:089f9df9f69532d1339e83142438668f52c97cd22ee2d1195551c2b1a9e6cf33", size = 313737, upload-time = "2025-09-15T09:19:59.638Z" }, - { url = "https://files.pythonhosted.org/packages/cd/cc/c9f0eec5d00f2a1da89f6bdfac12b8afdf8d5ad974184863c75060026457/jiter-0.11.0-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:29ed1fe69a8c69bf0f2a962d8d706c7b89b50f1332cd6b9fbda014f60bd03a03", size = 346183, upload-time = "2025-09-15T09:20:01.442Z" }, - { url = "https://files.pythonhosted.org/packages/a6/87/fc632776344e7aabbab05a95a0075476f418c5d29ab0f2eec672b7a1f0ac/jiter-0.11.0-cp313-cp313t-win_amd64.whl", hash = "sha256:a4d71d7ea6ea8786291423fe209acf6f8d398a0759d03e7f24094acb8ab686ba", size = 204225, upload-time = "2025-09-15T09:20:03.102Z" }, - { url = "https://files.pythonhosted.org/packages/ee/3b/e7f45be7d3969bdf2e3cd4b816a7a1d272507cd0edd2d6dc4b07514f2d9a/jiter-0.11.0-cp314-cp314-macosx_10_12_x86_64.whl", hash = "sha256:9a6dff27eca70930bdbe4cbb7c1a4ba8526e13b63dc808c0670083d2d51a4a72", size = 304414, upload-time = "2025-09-15T09:20:04.357Z" }, - { url = "https://files.pythonhosted.org/packages/06/32/13e8e0d152631fcc1907ceb4943711471be70496d14888ec6e92034e2caf/jiter-0.11.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:b1ae2a7593a62132c7d4c2abbee80bbbb94fdc6d157e2c6cc966250c564ef774", size = 314223, upload-time = "2025-09-15T09:20:05.631Z" }, - { url = "https://files.pythonhosted.org/packages/0c/7e/abedd5b5a20ca083f778d96bba0d2366567fcecb0e6e34ff42640d5d7a18/jiter-0.11.0-cp314-cp314-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7b13a431dba4b059e9e43019d3022346d009baf5066c24dcdea321a303cde9f0", size = 337306, upload-time = "2025-09-15T09:20:06.917Z" }, - { url = "https://files.pythonhosted.org/packages/ac/e2/30d59bdc1204c86aa975ec72c48c482fee6633120ee9c3ab755e4dfefea8/jiter-0.11.0-cp314-cp314-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:af62e84ca3889604ebb645df3b0a3f3bcf6b92babbff642bd214616f57abb93a", size = 360565, upload-time = "2025-09-15T09:20:08.283Z" }, - { url = "https://files.pythonhosted.org/packages/fe/88/567288e0d2ed9fa8f7a3b425fdaf2cb82b998633c24fe0d98f5417321aa8/jiter-0.11.0-cp314-cp314-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c6f3b32bb723246e6b351aecace52aba78adb8eeb4b2391630322dc30ff6c773", size = 486465, upload-time = "2025-09-15T09:20:09.613Z" }, - { url = "https://files.pythonhosted.org/packages/18/6e/7b72d09273214cadd15970e91dd5ed9634bee605176107db21e1e4205eb1/jiter-0.11.0-cp314-cp314-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:adcab442f4a099a358a7f562eaa54ed6456fb866e922c6545a717be51dbed7d7", size = 377581, upload-time = "2025-09-15T09:20:10.884Z" }, - { url = "https://files.pythonhosted.org/packages/58/52/4db456319f9d14deed325f70102577492e9d7e87cf7097bda9769a1fcacb/jiter-0.11.0-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c9967c2ab338ee2b2c0102fd379ec2693c496abf71ffd47e4d791d1f593b68e2", size = 347102, upload-time = "2025-09-15T09:20:12.175Z" }, - { url = "https://files.pythonhosted.org/packages/ce/b4/433d5703c38b26083aec7a733eb5be96f9c6085d0e270a87ca6482cbf049/jiter-0.11.0-cp314-cp314-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:e7d0bed3b187af8b47a981d9742ddfc1d9b252a7235471ad6078e7e4e5fe75c2", size = 386477, upload-time = "2025-09-15T09:20:13.428Z" }, - { url = "https://files.pythonhosted.org/packages/c8/7a/a60bfd9c55b55b07c5c441c5085f06420b6d493ce9db28d069cc5b45d9f3/jiter-0.11.0-cp314-cp314-musllinux_1_1_aarch64.whl", hash = "sha256:f6fe0283e903ebc55f1a6cc569b8c1f3bf4abd026fed85e3ff8598a9e6f982f0", size = 516004, upload-time = "2025-09-15T09:20:14.848Z" }, - { url = "https://files.pythonhosted.org/packages/2e/46/f8363e5ecc179b4ed0ca6cb0a6d3bfc266078578c71ff30642ea2ce2f203/jiter-0.11.0-cp314-cp314-musllinux_1_1_x86_64.whl", hash = "sha256:4ee5821e3d66606b29ae5b497230b304f1376f38137d69e35f8d2bd5f310ff73", size = 507855, upload-time = "2025-09-15T09:20:16.176Z" }, - { url = "https://files.pythonhosted.org/packages/90/33/396083357d51d7ff0f9805852c288af47480d30dd31d8abc74909b020761/jiter-0.11.0-cp314-cp314-win32.whl", hash = "sha256:c2d13ba7567ca8799f17c76ed56b1d49be30df996eb7fa33e46b62800562a5e2", size = 205802, upload-time = "2025-09-15T09:20:17.661Z" }, - { url = "https://files.pythonhosted.org/packages/e7/ab/eb06ca556b2551d41de7d03bf2ee24285fa3d0c58c5f8d95c64c9c3281b1/jiter-0.11.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:fb4790497369d134a07fc763cc88888c46f734abdd66f9fdf7865038bf3a8f40", size = 313405, upload-time = "2025-09-15T09:20:18.918Z" }, - { url = "https://files.pythonhosted.org/packages/af/22/7ab7b4ec3a1c1f03aef376af11d23b05abcca3fb31fbca1e7557053b1ba2/jiter-0.11.0-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6e2bbf24f16ba5ad4441a9845e40e4ea0cb9eed00e76ba94050664ef53ef4406", size = 347102, upload-time = "2025-09-15T09:20:20.16Z" }, - { url = "https://files.pythonhosted.org/packages/70/f3/ce100253c80063a7b8b406e1d1562657fd4b9b4e1b562db40e68645342fb/jiter-0.11.0-graalpy311-graalpy242_311_native-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:902b43386c04739229076bd1c4c69de5d115553d982ab442a8ae82947c72ede7", size = 336380, upload-time = "2025-09-15T09:20:36.867Z" }, -] - [[package]] name = "markdown-it-py" version = "4.0.0" @@ -393,154 +245,148 @@ wheels = [ ] [[package]] -name = "msglm" -version = "0.0.12" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "fastcore" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/70/6e/2cd814e9a86959b08c125ccfeb0f232a500917414f2dfa374400e5eed1db/msglm-0.0.12.tar.gz", hash = "sha256:4a1cf1a1b76710931759c7d8e8b2a4ef2046e0882348682e7cce3967438adc46", size = 12308, upload-time = "2025-08-19T19:47:22.266Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/d0/6a/c3b4fc7612e8687750d3f817f707a0d18d3ff5117438a4ebd02633b793d5/msglm-0.0.12-py3-none-any.whl", hash = "sha256:a519747e119a91fe7eb969792213af5c0a304ac73785551d9f3f3ced6571b6b2", size = 10875, upload-time = "2025-08-19T19:47:21.23Z" }, +name = "pillow" +version = "12.3.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/1c/3d/bb7fca845737cf9d7dbde16ed1843984665ff2e0a518f5db43e77ec540b9/pillow-12.3.0.tar.gz", hash = "sha256:3b8182a766685eaa002637e28b4ec8d6b18819a0c71f579bf0dbaa5830297cce", size = 47025035, upload-time = "2026-07-01T11:56:38.965Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/25/c2/669d88644cddb1485bd9534e63e8cf476c8e51cb3c3a1297677023505c0e/pillow-12.3.0-cp310-cp310-macosx_10_10_x86_64.whl", hash = "sha256:6c0016e7b354317c4e9e525b937ac8596c38d2d232b419529b9cd7a1cd46e39a", size = 5392418, upload-time = "2026-07-01T11:53:27.808Z" }, + { url = "https://files.pythonhosted.org/packages/6b/ba/3762f376a2948e3036488d773a146e0ae6ecc2ca03ac20e2615bd0b2ba02/pillow-12.3.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:bcc33feacfaefce60c12fd500a277533bdc02b10a19f7f6d348763d8140bbba7", size = 4785287, upload-time = "2026-07-01T11:53:29.761Z" }, + { url = "https://files.pythonhosted.org/packages/07/50/b5d688cc9c52d4482f3d5bcab6ce20bc2a74a85d2343841c907444a3be2c/pillow-12.3.0-cp310-cp310-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5594fc43d548a7ed94949d139aa1341b270f1863f11cfd37f5a6c8b778a6b67f", size = 6253754, upload-time = "2026-07-01T11:53:32.298Z" }, + { url = "https://files.pythonhosted.org/packages/4e/89/36f4cd76cf4baf05c50ababb976249153f18c959171c7f6ba09a6f217260/pillow-12.3.0-cp310-cp310-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:f0606c8bf2cdefea14a43530f7657cbbb7ecf1c4222512492ef4a4434a9501ec", size = 6925605, upload-time = "2026-07-01T11:53:34.487Z" }, + { url = "https://files.pythonhosted.org/packages/eb/c0/4de58cf6633b9e3a6061ef4be6fb91fc3c90b812ece886f531e3c523d777/pillow-12.3.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:85f998ea1848bc6757289e739cfbdda3a04adfd58b02fc018ce54d754a5ce468", size = 6327788, upload-time = "2026-07-01T11:53:36.433Z" }, + { url = "https://files.pythonhosted.org/packages/87/3c/14d53682a19550dbbaf3b598f807d5457646c510805a44c7d7891cd1cd1a/pillow-12.3.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:25b9b82bb22e6e2b3cd07b39c68b7b862001226cb3dff7130d1cb914121b39ed", size = 7036288, upload-time = "2026-07-01T11:53:38.712Z" }, + { url = "https://files.pythonhosted.org/packages/38/1d/36279e3c77efe034e4cc2b0393ee74ffdb5a62391dacbf9b916154f5f0b8/pillow-12.3.0-cp310-cp310-win32.whl", hash = "sha256:37dc8f7bbb66efe481bb60defacef820c950c24713fb44962ed6aa2a50966de1", size = 6472396, upload-time = "2026-07-01T11:53:40.781Z" }, + { url = "https://files.pythonhosted.org/packages/48/7c/8fa0039574c476d7c6fa57dd7c32a130436877c6ec1e5ce1cc8ec44878c1/pillow-12.3.0-cp310-cp310-win_amd64.whl", hash = "sha256:300557495eb45ebb8aec96c2da9c4be642fbf7cd937278b4013ba894ea8eb0eb", size = 7226887, upload-time = "2026-07-01T11:53:42.764Z" }, + { url = "https://files.pythonhosted.org/packages/fa/17/e324be141d173c1c919428066c3259f21c1b8982e564e01a4a81e96dbdcf/pillow-12.3.0-cp310-cp310-win_arm64.whl", hash = "sha256:514435a37670e3e5e08f3945b68718b6ed329bb84367777e16f9f4dfe1e61a0f", size = 2568039, upload-time = "2026-07-01T11:53:45.372Z" }, + { url = "https://files.pythonhosted.org/packages/fb/c8/0a78b0e02d7ac54bc03e5321c9220da52f0c2ea83b21f7c40e7f3169c502/pillow-12.3.0-cp311-cp311-macosx_10_10_x86_64.whl", hash = "sha256:00808c5e14ef63ac5161091d242999076604ff74b883423a11e5d7bbb38bf756", size = 5392415, upload-time = "2026-07-01T11:53:47.162Z" }, + { url = "https://files.pythonhosted.org/packages/b2/5b/a02d30018abd97ced9f5a6c63d28597694a00d066516b9c1c6de45859fc9/pillow-12.3.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:37d6d0a00072fd2948eb22bce7e1475f34569d90c87c59f7a2ec59541b77f7a6", size = 4785266, upload-time = "2026-07-01T11:53:49.079Z" }, + { url = "https://files.pythonhosted.org/packages/c8/98/766667a4be768150a202836acd9fad19c06824ca86c4286d3cf6b274964e/pillow-12.3.0-cp311-cp311-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:bcb46e2f9feff8d06323983bd83ed00c201fdcab3d74973e7072a889b3979fcd", size = 6263814, upload-time = "2026-07-01T11:53:51.32Z" }, + { url = "https://files.pythonhosted.org/packages/3b/2d/ede717bc1144f63886c21fd349bb95860b0d1a21149ff16f2bb362b612b6/pillow-12.3.0-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:23d27a3e0307ec2244cc51e7287b919aa68d097504ebe19df4e76a98a3eea5bd", size = 6934408, upload-time = "2026-07-01T11:53:53.487Z" }, + { url = "https://files.pythonhosted.org/packages/a3/48/9c58b685e69d49c31af6c8eb9012055fab7e665785165c84796e2c73ce72/pillow-12.3.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:4f883547d4b7f0495ebe7056b0cc2aea76094e7a4abc8e933540f3271df27d9c", size = 6337160, upload-time = "2026-07-01T11:53:55.457Z" }, + { url = "https://files.pythonhosted.org/packages/ff/fa/dc2a5c0ba6df93f67c31d34b808b7ce440b40cdbf96f0b81cde1d1e6fa93/pillow-12.3.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:236ff70b9312fb68943c703aa842ca6a758abfa45ac187a5e7c1452e96ef72b5", size = 7045172, upload-time = "2026-07-01T11:53:57.736Z" }, + { url = "https://files.pythonhosted.org/packages/86/a5/444817a4d4c4c2417df00513086ca196f388d8f9ef40c2e4ccd1ad1af54b/pillow-12.3.0-cp311-cp311-win32.whl", hash = "sha256:10e41f0fbf1eec8cfd234b8fe17a4caac7c9d0db4c204d3c173a8f9f6ef3232b", size = 6472232, upload-time = "2026-07-01T11:53:59.767Z" }, + { url = "https://files.pythonhosted.org/packages/63/c6/4bad1b18d132a50b27e1365e1ab163616f7a5bb56d330f66f9d1d9d4f9d4/pillow-12.3.0-cp311-cp311-win_amd64.whl", hash = "sha256:8e95e1385e4998ae9694eeaa4730ba5457ff61185b3a55e2e7bea0880aef452a", size = 7233653, upload-time = "2026-07-01T11:54:02.066Z" }, + { url = "https://files.pythonhosted.org/packages/fd/16/00f91ab7760dc842f5aad55217e80fc4a7067a0604535249bc8a2d6d9870/pillow-12.3.0-cp311-cp311-win_arm64.whl", hash = "sha256:ebaea975e03d3141d9d3a507df75c9b3ec90fa9d2ffd07567b3a978d9d790b26", size = 2568195, upload-time = "2026-07-01T11:54:04.622Z" }, + { url = "https://files.pythonhosted.org/packages/37/bf/fb3ebff8ddcb76aac5a01389251bbbb9519922a9b520d8247c1ca864a25d/pillow-12.3.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:ba09209fbe443b4acccebe845d8a138b89a8f4fbaeedd44953490b5315d5e965", size = 5345969, upload-time = "2026-07-01T11:54:06.397Z" }, + { url = "https://files.pythonhosted.org/packages/d8/66/9a386a92561f402389a4fc70c18838bf6d35eb5eb5c6850b4b2dc64f5048/pillow-12.3.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:ffd0c5368496f41b0944be820fcb7a838aa6e623d250b01acf2643939c3f99d7", size = 4780323, upload-time = "2026-07-01T11:54:09.351Z" }, + { url = "https://files.pythonhosted.org/packages/25/27/ac8f99618ffd3dde21db0f4d4b1d2ab00c0880595bfd17df103f7f39fd0c/pillow-12.3.0-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d9c7f76c0673154f044e9d78c8655fb4213f6ca31a836df48b40fe5d187717b9", size = 6266838, upload-time = "2026-07-01T11:54:11.71Z" }, + { url = "https://files.pythonhosted.org/packages/84/21/a35af28dcc61f37ed850a2d64c65c701321dfbf25085e469d5559360cbbf/pillow-12.3.0-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:78cb2c6865a35ab8ff8b75fd122f6033b92a62c82801110e48ddd6c936a45d91", size = 6940830, upload-time = "2026-07-01T11:54:13.732Z" }, + { url = "https://files.pythonhosted.org/packages/eb/51/8b08617af3ad95e33ce6d7dd2c99ed6c8298f7fb131636303956be022e25/pillow-12.3.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:e491916b378fba47242221bb9ead245211b70d504f495d105d17b14a24b4907c", size = 6344383, upload-time = "2026-07-01T11:54:15.756Z" }, + { url = "https://files.pythonhosted.org/packages/1d/72/cf78ac9780bb93c28328f408973845a309d4d145041665f734572ced1b52/pillow-12.3.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:0dd2064cbc55aaec028ef5fbb60fa47bb6c3e7918e07ff17935284b227a9d2df", size = 7052934, upload-time = "2026-07-01T11:54:17.721Z" }, + { url = "https://files.pythonhosted.org/packages/20/20/25e0f4dc178a6bc0696793720055519a0de89e7661dae886992decbd2f81/pillow-12.3.0-cp312-cp312-win32.whl", hash = "sha256:dbce0b29841537a2fa4a214c2bbf14de3587c9680caa9b4e217568472490b28f", size = 6472684, upload-time = "2026-07-01T11:54:19.839Z" }, + { url = "https://files.pythonhosted.org/packages/45/89/da2f7971a317f83d807fdd4065c0af40208e59e692cc43d315a71a0e96d1/pillow-12.3.0-cp312-cp312-win_amd64.whl", hash = "sha256:a2b55dd6b2a4c4b7d87ffa56bdb33fdc5fdb9a462173861a7bc097f17d91cb09", size = 7227137, upload-time = "2026-07-01T11:54:22.025Z" }, + { url = "https://files.pythonhosted.org/packages/de/47/4845a0a6c0dbf1db8456bd9fc791f13c5ced7ced20606d08a0aacfd25b49/pillow-12.3.0-cp312-cp312-win_arm64.whl", hash = "sha256:331b624368d4f1d069149002f25f44bc61c8919ce8ddb3c45bdad8f6e2d89510", size = 2568267, upload-time = "2026-07-01T11:54:24.051Z" }, + { url = "https://files.pythonhosted.org/packages/9d/ac/31fb64e1e7efb5a4b50cd3d92049ba89ac6e4d8d3bb6a74e15048ca3353e/pillow-12.3.0-cp313-cp313-ios_13_0_arm64_iphoneos.whl", hash = "sha256:21900ce7ba264168cd50defae43cd75d25c833ad4ad6e73ffc5596d12e25ac89", size = 4161684, upload-time = "2026-07-01T11:54:25.934Z" }, + { url = "https://files.pythonhosted.org/packages/87/b4/9805e23d2b4d77842b468513841fda254ee42f0289d25088340e4ff46e2d/pillow-12.3.0-cp313-cp313-ios_13_0_arm64_iphonesimulator.whl", hash = "sha256:4e8c2a84d977f50b9daed6eeaf3baef67d00d5d74d932288f02cb94518ee3ace", size = 4255487, upload-time = "2026-07-01T11:54:27.935Z" }, + { url = "https://files.pythonhosted.org/packages/df/39/ecf519435a200c693fe053a6ee4d835b41cf963a4dfc2551c4e637cb2a71/pillow-12.3.0-cp313-cp313-ios_13_0_x86_64_iphonesimulator.whl", hash = "sha256:ae26d61dfa7a47befdc7572b521024e8745f3d809bd95ca9505a7bba9ef849ec", size = 3696433, upload-time = "2026-07-01T11:54:29.813Z" }, + { url = "https://files.pythonhosted.org/packages/42/92/2fc3ffad878ae8dd5469ec1bc8eb83b71f48e13efdf68f02709003982a32/pillow-12.3.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:7a743ff716f746fc19a9557f60dab1600d4613255f8a7aeb3cdde4db7eb15a66", size = 5345889, upload-time = "2026-07-01T11:54:31.97Z" }, + { url = "https://files.pythonhosted.org/packages/10/76/8803c13605b763d33d156c4678fc77f8443389c0c51c8aef707bb02015f4/pillow-12.3.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:d69141514cc30b774ceea5e3ed3a6635c8d8a96edf664689b890f4089111fb35", size = 4780109, upload-time = "2026-07-01T11:54:34.026Z" }, + { url = "https://files.pythonhosted.org/packages/1f/01/e18aff37cb0b4aac47ac90f016d347a49aca667ef97f190b06ac2aabc928/pillow-12.3.0-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f7401aebd7f581d7f83a439d87d474999317ee099218e5ad25d125290990ba65", size = 6263736, upload-time = "2026-07-01T11:54:36.131Z" }, + { url = "https://files.pythonhosted.org/packages/f7/62/de5bdd77d935331f4f802edc11e4d82950f642caad6cb2f949837b8560e2/pillow-12.3.0-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0847a763afefb695bc912d7c131e7e0632d4edc1d8698f58ddabec8e46b8b6d3", size = 6937129, upload-time = "2026-07-01T11:54:38.216Z" }, + { url = "https://files.pythonhosted.org/packages/70/4d/105627a13300c5e0df1d174230b32fd1273062c96f7745fd552b945d1e1d/pillow-12.3.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:571b9fcb07b97ef3a492028fb3d2dc0993ca23a06138b0315286566d29ef718a", size = 6339562, upload-time = "2026-07-01T11:54:40.354Z" }, + { url = "https://files.pythonhosted.org/packages/6b/1d/f13de01a553988ab895ba1c722e06cf3144d4f57656fd5b81b6d881f1179/pillow-12.3.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:756c768d0c9c2955feb7a56c37ea24aea2e369f8d36a88da270b6a9f19e62b5e", size = 7049439, upload-time = "2026-07-01T11:54:42.489Z" }, + { url = "https://files.pythonhosted.org/packages/c9/f9/066794cca041b969964f779ee5fa66a9498bbf34248ac39c5d7954e4198f/pillow-12.3.0-cp313-cp313-win32.whl", hash = "sha256:a876864214e136f0eb367788dbd7df045f4806801518e2cfe9e13229cfe06d8f", size = 6473287, upload-time = "2026-07-01T11:54:44.9Z" }, + { url = "https://files.pythonhosted.org/packages/a6/9b/7a58e61d62be561da3a356fe2384d4059a6345fc130e23ef1c36a5b81d24/pillow-12.3.0-cp313-cp313-win_amd64.whl", hash = "sha256:1cca606cd25738df4ed873d5ad46bbdb3d83b5cbca291f6b4ff13a4df6b0bbe8", size = 7239691, upload-time = "2026-07-01T11:54:47.141Z" }, + { url = "https://files.pythonhosted.org/packages/aa/b0/c4ed4f0ef8f8fa5ee8351537db6650bb8189f7e118842978dd6589065692/pillow-12.3.0-cp313-cp313-win_arm64.whl", hash = "sha256:b629de27fda84b42cde7edef0d85f13b958b47f6e9bbcbba9b673c562a89bd8b", size = 2568185, upload-time = "2026-07-01T11:54:49.137Z" }, + { url = "https://files.pythonhosted.org/packages/dc/01/001f65b68192f0228cc1dbbc8d2530ab5d58b61037ba0587f946fea607cd/pillow-12.3.0-cp314-cp314-ios_13_0_arm64_iphoneos.whl", hash = "sha256:9cf95fe4d0f84c82d282745d9bb08ad9f926efa00be4697e767b814ce40d4330", size = 4161736, upload-time = "2026-07-01T11:54:51.156Z" }, + { url = "https://files.pythonhosted.org/packages/1a/d2/0219746d0fd16fc8a84498e79452375be3797d3ce4044596ce565164b84f/pillow-12.3.0-cp314-cp314-ios_13_0_arm64_iphonesimulator.whl", hash = "sha256:8728f216dcdb6e6d555cf971cb34076139ad74b31fc2c14da4fafc741c5f6217", size = 4255435, upload-time = "2026-07-01T11:54:53.414Z" }, + { url = "https://files.pythonhosted.org/packages/c8/02/8d0bc62ef0302318c46ff2a512822d2610e81c7aa46c9b3abe6cbaca5ad0/pillow-12.3.0-cp314-cp314-ios_13_0_x86_64_iphonesimulator.whl", hash = "sha256:a45650e8ce7fafffd731db8550230db6b0d306d181a90b67d3e6bca2f1990930", size = 3696262, upload-time = "2026-07-01T11:54:55.739Z" }, + { url = "https://files.pythonhosted.org/packages/85/e2/73c77d218410b14f5f2d565e8a998d5317b7b9c75368d29985139f7a46f0/pillow-12.3.0-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:ba54cfebe86920a559a7c4d6b9050791c20513650a1952ebe3368c7dc70306f8", size = 5350344, upload-time = "2026-07-01T11:54:57.657Z" }, + { url = "https://files.pythonhosted.org/packages/c7/da/32c752228ae345f489e3a42499d817b6c3996da7e8a3bc7a04fc806b243b/pillow-12.3.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:e158cb00350dc278f3b91551101aa7d12415a66ebf2c91d8d5ac14e56ddd3ad0", size = 4780131, upload-time = "2026-07-01T11:54:59.713Z" }, + { url = "https://files.pythonhosted.org/packages/b1/9d/8b2c807dbef61a5197c047afe99823787eb66f63daf9fb2432f91d6f0462/pillow-12.3.0-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e9aeb04d6aef139de265b29683e119b638208f88cf73cdd1658aa07221165321", size = 6263757, upload-time = "2026-07-01T11:55:01.778Z" }, + { url = "https://files.pythonhosted.org/packages/5c/44/c85361f65dbe00eea8576ee467c768d25129989efb76e94f205e9ca9bb46/pillow-12.3.0-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:251bf95b67017e27b13d82f5b326234ca62d70f9cf4c2b9032de2358a3b12c7b", size = 6936962, upload-time = "2026-07-01T11:55:03.93Z" }, + { url = "https://files.pythonhosted.org/packages/18/7e/e483414b35800b86b6f08dbbc7803fb5cd52c4d6f897f47d53ea2c7e6f65/pillow-12.3.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:fe3cca2e4e8a592be0f269a1ca4835c25199d9f3ce815c8491048f785b0a0198", size = 6339171, upload-time = "2026-07-01T11:55:05.989Z" }, + { url = "https://files.pythonhosted.org/packages/f0/f4/68c491844841ede6bed70189546b3ee9731cf9f2cbad396faff5e1ccba45/pillow-12.3.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:23aceaa007d6172b02c277f0cd359c79492bbb14f7072b4ede9fbcaf20648130", size = 7048116, upload-time = "2026-07-01T11:55:08.131Z" }, + { url = "https://files.pythonhosted.org/packages/a3/34/77f3f793fed8efc7d243f21b33c5a3f0d1c97ee70346d3db855587e155ff/pillow-12.3.0-cp314-cp314-win32.whl", hash = "sha256:af8d94b0db561cf68b88a267c5c44b49e134f525d0dc2cb7ed413a66bc23559a", size = 6467209, upload-time = "2026-07-01T11:55:10.408Z" }, + { url = "https://files.pythonhosted.org/packages/f1/e0/492879f69d94f91f60fc8cd05ba03650e9520afebb2fb7aa12777d7c7f38/pillow-12.3.0-cp314-cp314-win_amd64.whl", hash = "sha256:fdafc9cce40277e0f7a0feabce0ee50dd2fa1800f3b38015e51296b5e814048d", size = 7237707, upload-time = "2026-07-01T11:55:12.745Z" }, + { url = "https://files.pythonhosted.org/packages/c9/ac/6b11f2875f1c2ac040d84e1bbf9cf22a88038f901ca1037898b280b38365/pillow-12.3.0-cp314-cp314-win_arm64.whl", hash = "sha256:e91206ee562682b51b98ef4b26a6ef48fd84e15fd4c4bc5ec768eb641d206838", size = 2565995, upload-time = "2026-07-01T11:55:14.736Z" }, + { url = "https://files.pythonhosted.org/packages/52/69/c2208e56af9bfc1913afb24020297a691eb1d4ef688474c8a04913f65e04/pillow-12.3.0-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:164b31cd1a0490ab6efae01aa5df49da7061be0af1b30e035b6e9a1bfe34ee6e", size = 5352503, upload-time = "2026-07-01T11:55:17.076Z" }, + { url = "https://files.pythonhosted.org/packages/07/70/e5686d753e898a45d778ff1718dba8516ead6ab6b95d85fc8c4b70650cf2/pillow-12.3.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:5afb51d599ea772b8365ae807ae557f18bccfe46ab261fd1c2a9ed700fc6eb17", size = 4782956, upload-time = "2026-07-01T11:55:19.448Z" }, + { url = "https://files.pythonhosted.org/packages/d5/37/25c6692f06927ee973ff18c8d9ee98ad0b4d84ee67a09610c2dd1447958e/pillow-12.3.0-cp314-cp314t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3edce1d53195db527e0191f84b71d02022de0540bf43a16ed734ed7537b07385", size = 6322855, upload-time = "2026-07-01T11:55:21.613Z" }, + { url = "https://files.pythonhosted.org/packages/cc/91/420637fcb8f1bc11029e403b4538e6694744428d8246118e45719f944556/pillow-12.3.0-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:bf16ba1b4d0b6b7c8e534936632270cf70eb00dbe09005bc345b2677b726855c", size = 6989642, upload-time = "2026-07-01T11:55:24.006Z" }, + { url = "https://files.pythonhosted.org/packages/10/08/b94d7811281ccf0d143a1cf768d1c49e1e54af63e7b708ab2ee3eb87face/pillow-12.3.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:24870b09b224f7ae3c39ed07d10e819d06f8720bc551847b1d623832b5b0e28d", size = 6391281, upload-time = "2026-07-01T11:55:26.252Z" }, + { url = "https://files.pythonhosted.org/packages/d2/87/24233f785f55474dc02ce3e739c5528a77e3a862e9333d1dd7a25cc31f70/pillow-12.3.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:30f2aa603c41533cc25c05acd0da21636e84a315768feb631c937177db558931", size = 7096716, upload-time = "2026-07-01T11:55:28.318Z" }, + { url = "https://files.pythonhosted.org/packages/23/26/fcb2f6e37175b04f53570b59937867e2b80ee1685e744023153028fc14f9/pillow-12.3.0-cp314-cp314t-win32.whl", hash = "sha256:4b0a7fe987b14c31ebda6083f74f22b561fd3739bc0ac51e019622e3d72668c7", size = 6474125, upload-time = "2026-07-01T11:55:30.956Z" }, + { url = "https://files.pythonhosted.org/packages/90/de/3634abee5f1c9e13c56787b7d5517b0ba8d6de51700b95578cf338349c9f/pillow-12.3.0-cp314-cp314t-win_amd64.whl", hash = "sha256:962864dc93511324d51ddbb5b9f8731bf71675b93ca612a07441896f4688fb8c", size = 7242939, upload-time = "2026-07-01T11:55:34.044Z" }, + { url = "https://files.pythonhosted.org/packages/ce/2a/fd13f8eb24de5714a6eb444a3d67e2842c6c576e159a43793adf23051351/pillow-12.3.0-cp314-cp314t-win_arm64.whl", hash = "sha256:0740a512dc522224c77d9aa5a8d70d8b7d73fb91f2c21125d8d025d3b8990e45", size = 2567506, upload-time = "2026-07-01T11:55:35.988Z" }, + { url = "https://files.pythonhosted.org/packages/5d/dc/8fdce34ec725a33c81c6ba122b904d6b9024e50ea9ac7bede62fab54506c/pillow-12.3.0-cp315-cp315-ios_13_0_arm64_iphoneos.whl", hash = "sha256:0feb2e9d6ad6c9e3c06effe9d00f3f1e618a6643273576b016f591e9315a7139", size = 4162063, upload-time = "2026-07-01T11:55:37.941Z" }, + { url = "https://files.pythonhosted.org/packages/76/66/2044b9a63d3b84ff048228dfcb7cd9bf0df983e8470971bf7d4c57b693de/pillow-12.3.0-cp315-cp315-ios_13_0_arm64_iphonesimulator.whl", hash = "sha256:9e881fca225083806662a5c43d627d215f258ff43c890f831966c7d7ba9c7402", size = 4255549, upload-time = "2026-07-01T11:55:40.022Z" }, + { url = "https://files.pythonhosted.org/packages/52/7e/1f67e6f4ece6b582ee4b539decbcc9f848dc245a93ed8cd7338bafef72f1/pillow-12.3.0-cp315-cp315-ios_13_0_x86_64_iphonesimulator.whl", hash = "sha256:4998562bf62a445225f22e07c896bb04b35b1b1f2eb6d760584c9c51d7a5f78c", size = 3696331, upload-time = "2026-07-01T11:55:41.98Z" }, + { url = "https://files.pythonhosted.org/packages/12/40/d306fc2c8e4d45d7f175c77edca7063be7b86fe7fe6e68f4353bf71d808c/pillow-12.3.0-cp315-cp315-macosx_10_15_x86_64.whl", hash = "sha256:dc624f6bc473dacdf7ef7eb8678d0d08edf15cd94fad6ae5c7d6cc67a4e4902f", size = 5350370, upload-time = "2026-07-01T11:55:44.028Z" }, + { url = "https://files.pythonhosted.org/packages/dd/44/668fb1437e8ce420f62d6106eb66e44a5971602a4d794615bdf79315d82d/pillow-12.3.0-cp315-cp315-macosx_11_0_arm64.whl", hash = "sha256:71d6097b330eea8fd15097780c8e89cb1a8ce7838669f48c5bacd6f663dd4701", size = 4780147, upload-time = "2026-07-01T11:55:46.073Z" }, + { url = "https://files.pythonhosted.org/packages/0c/08/93fa2e70e30a2d81547e481b6ee2bb9522117221fb1e0ce4b5df70967677/pillow-12.3.0-cp315-cp315-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:28ce87c5ab450a9dd970b52e5aca5fe63ed432d18a2eaddd1979a00a1ba24ace", size = 6273659, upload-time = "2026-07-01T11:55:48.264Z" }, + { url = "https://files.pythonhosted.org/packages/f8/6d/043e96ff814fc31a33077e4cba86082167db520c93632afdf2042febbb0c/pillow-12.3.0-cp315-cp315-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6b02afb9b97f65fbca5f31db6a2a3ba21aa93030225f150fa3f249717e938fb4", size = 6947439, upload-time = "2026-07-01T11:55:50.503Z" }, + { url = "https://files.pythonhosted.org/packages/af/92/ba71d2ee2ac0edf3fa33bd9d5ee9ee080da70b1766f3ca3934f9938ddac9/pillow-12.3.0-cp315-cp315-musllinux_1_2_aarch64.whl", hash = "sha256:1182d52bc2d5e5d7d0949503aa7e36d12f42205dc287e4883f407b1988820d39", size = 6353577, upload-time = "2026-07-01T11:55:52.697Z" }, + { url = "https://files.pythonhosted.org/packages/0f/ce/e63064e2122923ff687c8ad792d0d736a7b3920a56a46982e81a7fdd25d6/pillow-12.3.0-cp315-cp315-musllinux_1_2_x86_64.whl", hash = "sha256:e795b7eb908249c4e43c7c99fac7c2c75dab0c43566e37db472a355f63693d71", size = 7060394, upload-time = "2026-07-01T11:55:55.149Z" }, + { url = "https://files.pythonhosted.org/packages/54/76/a09cc3ccc8d773a7283d34c38bec1708f9e3cc932093cbc4c5e71ac4060b/pillow-12.3.0-cp315-cp315-win32.whl", hash = "sha256:57b3d78c95ba9059768b10e28b813002261d3f3dfc55cc48b0c988f625175827", size = 6467375, upload-time = "2026-07-01T11:55:57.769Z" }, + { url = "https://files.pythonhosted.org/packages/3e/03/1846c49ba3b1d5550392a4bbd06d6fb4578e1cd91a803198b5c90f5f7d53/pillow-12.3.0-cp315-cp315-win_amd64.whl", hash = "sha256:fa4ecea169a355be7a3ade2c783e2ed12f0e40d2c5621cda8b3297faf7fbb9f5", size = 7237048, upload-time = "2026-07-01T11:55:59.975Z" }, + { url = "https://files.pythonhosted.org/packages/fb/bb/89f35dcc79610423f9f195504d7def7f0d1416a711541b42867e25fe3412/pillow-12.3.0-cp315-cp315-win_arm64.whl", hash = "sha256:877c3f311ff35410f690861c4409e7ccbf0cd2f878e50628a28e5a0bb689e658", size = 2566006, upload-time = "2026-07-01T11:56:02.143Z" }, + { url = "https://files.pythonhosted.org/packages/30/88/707027ba09942dfa2c28759b5c222d769290a41c6d20ea60ec250801941f/pillow-12.3.0-cp315-cp315t-macosx_10_15_x86_64.whl", hash = "sha256:e9871b1ffbfa9656b60aeee92ed5136a5742696006fa322b29ea3d8da0ecc9cf", size = 5352509, upload-time = "2026-07-01T11:56:04.2Z" }, + { url = "https://files.pythonhosted.org/packages/b0/6d/00352fa25332c2569cd387851f568cc5a4b75a9adbfb37ac4fbce4c02eec/pillow-12.3.0-cp315-cp315t-macosx_11_0_arm64.whl", hash = "sha256:53aa02d20d10c3d814d536aa4e5ac9b84ca0ff5a88377963b085ad6822f93e64", size = 4783167, upload-time = "2026-07-01T11:56:06.631Z" }, + { url = "https://files.pythonhosted.org/packages/13/4f/9e049dfa21af7c22427275720e2490267ba8138120add5c4c574deb69782/pillow-12.3.0-cp315-cp315t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:446c34dcc4324b084a53b705127dc15717b22c5e140ae0a3c38349d4efec071e", size = 6329237, upload-time = "2026-07-01T11:56:08.868Z" }, + { url = "https://files.pythonhosted.org/packages/36/16/cf6eeaae8d0fce8dd390a33437cf68c5d5bd73834a2bc6e2f14efda0ab45/pillow-12.3.0-cp315-cp315t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:cf1845d02ad822a369a49f2bb9345b1614744267682e7a03527dc3bf6eea1777", size = 6997047, upload-time = "2026-07-01T11:56:11.379Z" }, + { url = "https://files.pythonhosted.org/packages/1e/69/dbf769bdd55f48bf5733cac28edc6364ffaa072ec9ba336266e4fe66be55/pillow-12.3.0-cp315-cp315t-musllinux_1_2_aarch64.whl", hash = "sha256:186941b6aef820ad110fb01fb06eb925374dc3a21b17e37ec9a53b250c6fe2d1", size = 6400440, upload-time = "2026-07-01T11:56:13.908Z" }, + { url = "https://files.pythonhosted.org/packages/a0/e1/ffc9cfc2eea0d178da8018e18e959301ad9d6bc9f3edb7181e748a474b97/pillow-12.3.0-cp315-cp315t-musllinux_1_2_x86_64.whl", hash = "sha256:f13c32a3abd6079a66d9526e18dad9b6d280384d49d7c54040cd57b6424041d9", size = 7105895, upload-time = "2026-07-01T11:56:16.575Z" }, + { url = "https://files.pythonhosted.org/packages/18/f0/a5595c1e8c3ae44b9828cb2f0fa8155e5095ef04d6327b8f61cf44a3df85/pillow-12.3.0-cp315-cp315t-win32.whl", hash = "sha256:1657923d2d45afb66526e5b933e5b3052e6bdea196c90d3abb2424e18c77dae8", size = 6474384, upload-time = "2026-07-01T11:56:18.855Z" }, + { url = "https://files.pythonhosted.org/packages/e4/04/62bcd9f844984c5938d3b05264a61d797a29d3e0812341a8204af70bbdee/pillow-12.3.0-cp315-cp315t-win_amd64.whl", hash = "sha256:8cd2f7bdda092d99c9fc2fb7391354f306d01443d22785d0cbfafa2e2c8bb418", size = 7243537, upload-time = "2026-07-01T11:56:21.214Z" }, + { url = "https://files.pythonhosted.org/packages/3d/68/1f3066acedf37673694a7141381d8f811ae97f30d34413d236abe7d489f1/pillow-12.3.0-cp315-cp315t-win_arm64.whl", hash = "sha256:06ff022112bc9cbf83b60f8e028d94ad87b60621706487e65f673de61610ab59", size = 2567491, upload-time = "2026-07-01T11:56:23.506Z" }, + { url = "https://files.pythonhosted.org/packages/75/18/2e8b40223153ccbc60df07f9e8928dc0c76202aa4e55ae9f53962b6510d6/pillow-12.3.0-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:b3c777e849237620b022f7f297dd67705f9f5cf1685f09f02e46f93e92725468", size = 5302510, upload-time = "2026-07-01T11:56:25.736Z" }, + { url = "https://files.pythonhosted.org/packages/46/3e/51fabf59d5ab801ceab709453d3ab6b180083496579549de4c45ced6528a/pillow-12.3.0-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:b343699e8308bdc51978310e1c959c584e7869cc8c40780058c87da7781a1e94", size = 4736058, upload-time = "2026-07-01T11:56:28.041Z" }, + { url = "https://files.pythonhosted.org/packages/bf/20/22fe9384b7949e25fb1293bcfc84fb82590ff4ea6b37c95b24d26d793d86/pillow-12.3.0-pp311-pypy311_pp73-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:fbd139c8447d25dd750ab79ee274cc5e1fe80fc56340ab10b18a195e1b6eca3e", size = 5237776, upload-time = "2026-07-01T11:56:30.263Z" }, + { url = "https://files.pythonhosted.org/packages/08/14/f6ba68107680ffa74b39985f3f30884e41318fbc4250caa423c79b4788bb/pillow-12.3.0-pp311-pypy311_pp73-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e7e480451b9fa137494bccd3a7d69adbe8ac65a87d97be61e11f1b1050a5bac3", size = 5860358, upload-time = "2026-07-01T11:56:32.68Z" }, + { url = "https://files.pythonhosted.org/packages/36/54/0169bc772ec491108b62f644f8ecf1fe5d8ae5ebafde2ee2142210166903/pillow-12.3.0-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:04f01d28a6aaff387bf842a13be313df23ba0597a44f1a976c9feb3c6ff4711a", size = 7231786, upload-time = "2026-07-01T11:56:35.046Z" }, ] [[package]] -name = "openai" -version = "1.109.1" +name = "pygments" +version = "2.19.2" source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "anyio" }, - { name = "distro" }, - { name = "httpx" }, - { name = "jiter" }, - { name = "pydantic" }, - { name = "sniffio" }, - { name = "tqdm" }, - { name = "typing-extensions" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/c6/a1/a303104dc55fc546a3f6914c842d3da471c64eec92043aef8f652eb6c524/openai-1.109.1.tar.gz", hash = "sha256:d173ed8dbca665892a6db099b4a2dfac624f94d20a93f46eb0b56aae940ed869", size = 564133, upload-time = "2025-09-24T13:00:53.075Z" } +sdist = { url = "https://files.pythonhosted.org/packages/b0/77/a5b8c569bf593b0140bde72ea885a803b82086995367bf2037de0159d924/pygments-2.19.2.tar.gz", hash = "sha256:636cb2477cec7f8952536970bc533bc43743542f70392ae026374600add5b887", size = 4968631, upload-time = "2025-06-21T13:39:12.283Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/1d/2a/7dd3d207ec669cacc1f186fd856a0f61dbc255d24f6fdc1a6715d6051b0f/openai-1.109.1-py3-none-any.whl", hash = "sha256:6bcaf57086cf59159b8e27447e4e7dd019db5d29a438072fbd49c290c7e65315", size = 948627, upload-time = "2025-09-24T13:00:50.754Z" }, + { url = "https://files.pythonhosted.org/packages/c7/21/705964c7812476f378728bdf590ca4b771ec72385c533964653c68e86bdc/pygments-2.19.2-py3-none-any.whl", hash = "sha256:86540386c03d588bb81d44bc3928634ff26449851e99741617ecb9037ee5ec0b", size = 1225217, upload-time = "2025-06-21T13:39:07.939Z" }, ] [[package]] -name = "packaging" -version = "25.0" +name = "pyperclip" +version = "1.11.0" source = { registry = "https://pypi.org/simple" } -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" } +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" } wheels = [ - { 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" }, + { 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" }, ] [[package]] -name = "pydantic" -version = "2.11.9" +name = "python-fastllm" +version = "0.0.31" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "annotated-types" }, - { name = "pydantic-core" }, - { name = "typing-extensions" }, - { name = "typing-inspection" }, + { name = "fastspec" }, + { name = "pillow" }, + { name = "toolslm" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/ff/5d/09a551ba512d7ca404d785072700d3f6727a02f6f3c24ecfd081c7cf0aa8/pydantic-2.11.9.tar.gz", hash = "sha256:6b8ffda597a14812a7975c90b82a8a2e777d9257aba3453f973acd3c032a18e2", size = 788495, upload-time = "2025-09-13T11:26:39.325Z" } +sdist = { url = "https://files.pythonhosted.org/packages/20/02/8c807fe9b71287ba7d057762cfe97c0684d2206227a17812d11ccacccfe3/python_fastllm-0.0.31.tar.gz", hash = "sha256:b9487cc0fc417a47996565cbc2edaf081e917d285bb42798c0994f264cbee04d", size = 779627, upload-time = "2026-07-24T13:18:54.264Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/3e/d3/108f2006987c58e76691d5ae5d200dd3e0f532cb4e5fa3560751c3a1feba/pydantic-2.11.9-py3-none-any.whl", hash = "sha256:c42dd626f5cfc1c6950ce6205ea58c93efa406da65f479dcb4029d5934857da2", size = 444855, upload-time = "2025-09-13T11:26:36.909Z" }, + { url = "https://files.pythonhosted.org/packages/a8/40/b797dcc259fb92ccc5c5fcd1bbea3f5c4ddba088664c003c26f93b0377e7/python_fastllm-0.0.31-py3-none-any.whl", hash = "sha256:80d4ed02e04e4842352d1097f487f720974ce06958e52eff374d1001f393635e", size = 792418, upload-time = "2026-07-24T13:18:52.414Z" }, ] [[package]] -name = "pydantic-core" -version = "2.33.2" +name = "rgapi" +version = "0.1.17" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "typing-extensions" }, + { name = "fastcore" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/ad/88/5f2260bdfae97aabf98f1778d43f69574390ad787afb646292a638c923d4/pydantic_core-2.33.2.tar.gz", hash = "sha256:7cb8bc3605c29176e1b105350d2e6474142d7c1bd1d9327c4a9bdb46bf827acc", size = 435195, upload-time = "2025-04-23T18:33:52.104Z" } +sdist = { url = "https://files.pythonhosted.org/packages/9a/05/f75fa4978aa7a538a9b05e49693646398238e62772d51b8856123dc22580/rgapi-0.1.17.tar.gz", hash = "sha256:454098af79b3a6bde0cd6fc242c04cfaf8bf132b9ec7a14a297b27661c125831", size = 46651, upload-time = "2026-07-22T06:08:07.777Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/e5/92/b31726561b5dae176c2d2c2dc43a9c5bfba5d32f96f8b4c0a600dd492447/pydantic_core-2.33.2-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:2b3d326aaef0c0399d9afffeb6367d5e26ddc24d351dbc9c636840ac355dc5d8", size = 2028817, upload-time = "2025-04-23T18:30:43.919Z" }, - { url = "https://files.pythonhosted.org/packages/a3/44/3f0b95fafdaca04a483c4e685fe437c6891001bf3ce8b2fded82b9ea3aa1/pydantic_core-2.33.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:0e5b2671f05ba48b94cb90ce55d8bdcaaedb8ba00cc5359f6810fc918713983d", size = 1861357, upload-time = "2025-04-23T18:30:46.372Z" }, - { url = "https://files.pythonhosted.org/packages/30/97/e8f13b55766234caae05372826e8e4b3b96e7b248be3157f53237682e43c/pydantic_core-2.33.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0069c9acc3f3981b9ff4cdfaf088e98d83440a4c7ea1bc07460af3d4dc22e72d", size = 1898011, upload-time = "2025-04-23T18:30:47.591Z" }, - { url = "https://files.pythonhosted.org/packages/9b/a3/99c48cf7bafc991cc3ee66fd544c0aae8dc907b752f1dad2d79b1b5a471f/pydantic_core-2.33.2-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:d53b22f2032c42eaaf025f7c40c2e3b94568ae077a606f006d206a463bc69572", size = 1982730, upload-time = "2025-04-23T18:30:49.328Z" }, - { url = "https://files.pythonhosted.org/packages/de/8e/a5b882ec4307010a840fb8b58bd9bf65d1840c92eae7534c7441709bf54b/pydantic_core-2.33.2-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:0405262705a123b7ce9f0b92f123334d67b70fd1f20a9372b907ce1080c7ba02", size = 2136178, upload-time = "2025-04-23T18:30:50.907Z" }, - { url = "https://files.pythonhosted.org/packages/e4/bb/71e35fc3ed05af6834e890edb75968e2802fe98778971ab5cba20a162315/pydantic_core-2.33.2-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:4b25d91e288e2c4e0662b8038a28c6a07eaac3e196cfc4ff69de4ea3db992a1b", size = 2736462, upload-time = "2025-04-23T18:30:52.083Z" }, - { url = "https://files.pythonhosted.org/packages/31/0d/c8f7593e6bc7066289bbc366f2235701dcbebcd1ff0ef8e64f6f239fb47d/pydantic_core-2.33.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6bdfe4b3789761f3bcb4b1ddf33355a71079858958e3a552f16d5af19768fef2", size = 2005652, upload-time = "2025-04-23T18:30:53.389Z" }, - { url = "https://files.pythonhosted.org/packages/d2/7a/996d8bd75f3eda405e3dd219ff5ff0a283cd8e34add39d8ef9157e722867/pydantic_core-2.33.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:efec8db3266b76ef9607c2c4c419bdb06bf335ae433b80816089ea7585816f6a", size = 2113306, upload-time = "2025-04-23T18:30:54.661Z" }, - { url = "https://files.pythonhosted.org/packages/ff/84/daf2a6fb2db40ffda6578a7e8c5a6e9c8affb251a05c233ae37098118788/pydantic_core-2.33.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:031c57d67ca86902726e0fae2214ce6770bbe2f710dc33063187a68744a5ecac", size = 2073720, upload-time = "2025-04-23T18:30:56.11Z" }, - { url = "https://files.pythonhosted.org/packages/77/fb/2258da019f4825128445ae79456a5499c032b55849dbd5bed78c95ccf163/pydantic_core-2.33.2-cp310-cp310-musllinux_1_1_armv7l.whl", hash = "sha256:f8de619080e944347f5f20de29a975c2d815d9ddd8be9b9b7268e2e3ef68605a", size = 2244915, upload-time = "2025-04-23T18:30:57.501Z" }, - { url = "https://files.pythonhosted.org/packages/d8/7a/925ff73756031289468326e355b6fa8316960d0d65f8b5d6b3a3e7866de7/pydantic_core-2.33.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:73662edf539e72a9440129f231ed3757faab89630d291b784ca99237fb94db2b", size = 2241884, upload-time = "2025-04-23T18:30:58.867Z" }, - { url = "https://files.pythonhosted.org/packages/0b/b0/249ee6d2646f1cdadcb813805fe76265745c4010cf20a8eba7b0e639d9b2/pydantic_core-2.33.2-cp310-cp310-win32.whl", hash = "sha256:0a39979dcbb70998b0e505fb1556a1d550a0781463ce84ebf915ba293ccb7e22", size = 1910496, upload-time = "2025-04-23T18:31:00.078Z" }, - { url = "https://files.pythonhosted.org/packages/66/ff/172ba8f12a42d4b552917aa65d1f2328990d3ccfc01d5b7c943ec084299f/pydantic_core-2.33.2-cp310-cp310-win_amd64.whl", hash = "sha256:b0379a2b24882fef529ec3b4987cb5d003b9cda32256024e6fe1586ac45fc640", size = 1955019, upload-time = "2025-04-23T18:31:01.335Z" }, - { url = "https://files.pythonhosted.org/packages/3f/8d/71db63483d518cbbf290261a1fc2839d17ff89fce7089e08cad07ccfce67/pydantic_core-2.33.2-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:4c5b0a576fb381edd6d27f0a85915c6daf2f8138dc5c267a57c08a62900758c7", size = 2028584, upload-time = "2025-04-23T18:31:03.106Z" }, - { url = "https://files.pythonhosted.org/packages/24/2f/3cfa7244ae292dd850989f328722d2aef313f74ffc471184dc509e1e4e5a/pydantic_core-2.33.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:e799c050df38a639db758c617ec771fd8fb7a5f8eaaa4b27b101f266b216a246", size = 1855071, upload-time = "2025-04-23T18:31:04.621Z" }, - { url = "https://files.pythonhosted.org/packages/b3/d3/4ae42d33f5e3f50dd467761304be2fa0a9417fbf09735bc2cce003480f2a/pydantic_core-2.33.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:dc46a01bf8d62f227d5ecee74178ffc448ff4e5197c756331f71efcc66dc980f", size = 1897823, upload-time = "2025-04-23T18:31:06.377Z" }, - { url = "https://files.pythonhosted.org/packages/f4/f3/aa5976e8352b7695ff808599794b1fba2a9ae2ee954a3426855935799488/pydantic_core-2.33.2-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:a144d4f717285c6d9234a66778059f33a89096dfb9b39117663fd8413d582dcc", size = 1983792, upload-time = "2025-04-23T18:31:07.93Z" }, - { url = "https://files.pythonhosted.org/packages/d5/7a/cda9b5a23c552037717f2b2a5257e9b2bfe45e687386df9591eff7b46d28/pydantic_core-2.33.2-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:73cf6373c21bc80b2e0dc88444f41ae60b2f070ed02095754eb5a01df12256de", size = 2136338, upload-time = "2025-04-23T18:31:09.283Z" }, - { url = "https://files.pythonhosted.org/packages/2b/9f/b8f9ec8dd1417eb9da784e91e1667d58a2a4a7b7b34cf4af765ef663a7e5/pydantic_core-2.33.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3dc625f4aa79713512d1976fe9f0bc99f706a9dee21dfd1810b4bbbf228d0e8a", size = 2730998, upload-time = "2025-04-23T18:31:11.7Z" }, - { url = "https://files.pythonhosted.org/packages/47/bc/cd720e078576bdb8255d5032c5d63ee5c0bf4b7173dd955185a1d658c456/pydantic_core-2.33.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:881b21b5549499972441da4758d662aeea93f1923f953e9cbaff14b8b9565aef", size = 2003200, upload-time = "2025-04-23T18:31:13.536Z" }, - { url = "https://files.pythonhosted.org/packages/ca/22/3602b895ee2cd29d11a2b349372446ae9727c32e78a94b3d588a40fdf187/pydantic_core-2.33.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:bdc25f3681f7b78572699569514036afe3c243bc3059d3942624e936ec93450e", size = 2113890, upload-time = "2025-04-23T18:31:15.011Z" }, - { url = "https://files.pythonhosted.org/packages/ff/e6/e3c5908c03cf00d629eb38393a98fccc38ee0ce8ecce32f69fc7d7b558a7/pydantic_core-2.33.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:fe5b32187cbc0c862ee201ad66c30cf218e5ed468ec8dc1cf49dec66e160cc4d", size = 2073359, upload-time = "2025-04-23T18:31:16.393Z" }, - { url = "https://files.pythonhosted.org/packages/12/e7/6a36a07c59ebefc8777d1ffdaf5ae71b06b21952582e4b07eba88a421c79/pydantic_core-2.33.2-cp311-cp311-musllinux_1_1_armv7l.whl", hash = "sha256:bc7aee6f634a6f4a95676fcb5d6559a2c2a390330098dba5e5a5f28a2e4ada30", size = 2245883, upload-time = "2025-04-23T18:31:17.892Z" }, - { url = "https://files.pythonhosted.org/packages/16/3f/59b3187aaa6cc0c1e6616e8045b284de2b6a87b027cce2ffcea073adf1d2/pydantic_core-2.33.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:235f45e5dbcccf6bd99f9f472858849f73d11120d76ea8707115415f8e5ebebf", size = 2241074, upload-time = "2025-04-23T18:31:19.205Z" }, - { url = "https://files.pythonhosted.org/packages/e0/ed/55532bb88f674d5d8f67ab121a2a13c385df382de2a1677f30ad385f7438/pydantic_core-2.33.2-cp311-cp311-win32.whl", hash = "sha256:6368900c2d3ef09b69cb0b913f9f8263b03786e5b2a387706c5afb66800efd51", size = 1910538, upload-time = "2025-04-23T18:31:20.541Z" }, - { url = "https://files.pythonhosted.org/packages/fe/1b/25b7cccd4519c0b23c2dd636ad39d381abf113085ce4f7bec2b0dc755eb1/pydantic_core-2.33.2-cp311-cp311-win_amd64.whl", hash = "sha256:1e063337ef9e9820c77acc768546325ebe04ee38b08703244c1309cccc4f1bab", size = 1952909, upload-time = "2025-04-23T18:31:22.371Z" }, - { url = "https://files.pythonhosted.org/packages/49/a9/d809358e49126438055884c4366a1f6227f0f84f635a9014e2deb9b9de54/pydantic_core-2.33.2-cp311-cp311-win_arm64.whl", hash = "sha256:6b99022f1d19bc32a4c2a0d544fc9a76e3be90f0b3f4af413f87d38749300e65", size = 1897786, upload-time = "2025-04-23T18:31:24.161Z" }, - { url = "https://files.pythonhosted.org/packages/18/8a/2b41c97f554ec8c71f2a8a5f85cb56a8b0956addfe8b0efb5b3d77e8bdc3/pydantic_core-2.33.2-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:a7ec89dc587667f22b6a0b6579c249fca9026ce7c333fc142ba42411fa243cdc", size = 2009000, upload-time = "2025-04-23T18:31:25.863Z" }, - { url = "https://files.pythonhosted.org/packages/a1/02/6224312aacb3c8ecbaa959897af57181fb6cf3a3d7917fd44d0f2917e6f2/pydantic_core-2.33.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:3c6db6e52c6d70aa0d00d45cdb9b40f0433b96380071ea80b09277dba021ddf7", size = 1847996, upload-time = "2025-04-23T18:31:27.341Z" }, - { url = "https://files.pythonhosted.org/packages/d6/46/6dcdf084a523dbe0a0be59d054734b86a981726f221f4562aed313dbcb49/pydantic_core-2.33.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4e61206137cbc65e6d5256e1166f88331d3b6238e082d9f74613b9b765fb9025", size = 1880957, upload-time = "2025-04-23T18:31:28.956Z" }, - { url = "https://files.pythonhosted.org/packages/ec/6b/1ec2c03837ac00886ba8160ce041ce4e325b41d06a034adbef11339ae422/pydantic_core-2.33.2-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:eb8c529b2819c37140eb51b914153063d27ed88e3bdc31b71198a198e921e011", size = 1964199, upload-time = "2025-04-23T18:31:31.025Z" }, - { url = "https://files.pythonhosted.org/packages/2d/1d/6bf34d6adb9debd9136bd197ca72642203ce9aaaa85cfcbfcf20f9696e83/pydantic_core-2.33.2-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c52b02ad8b4e2cf14ca7b3d918f3eb0ee91e63b3167c32591e57c4317e134f8f", size = 2120296, upload-time = "2025-04-23T18:31:32.514Z" }, - { url = "https://files.pythonhosted.org/packages/e0/94/2bd0aaf5a591e974b32a9f7123f16637776c304471a0ab33cf263cf5591a/pydantic_core-2.33.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:96081f1605125ba0855dfda83f6f3df5ec90c61195421ba72223de35ccfb2f88", size = 2676109, upload-time = "2025-04-23T18:31:33.958Z" }, - { url = "https://files.pythonhosted.org/packages/f9/41/4b043778cf9c4285d59742281a769eac371b9e47e35f98ad321349cc5d61/pydantic_core-2.33.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8f57a69461af2a5fa6e6bbd7a5f60d3b7e6cebb687f55106933188e79ad155c1", size = 2002028, upload-time = "2025-04-23T18:31:39.095Z" }, - { url = "https://files.pythonhosted.org/packages/cb/d5/7bb781bf2748ce3d03af04d5c969fa1308880e1dca35a9bd94e1a96a922e/pydantic_core-2.33.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:572c7e6c8bb4774d2ac88929e3d1f12bc45714ae5ee6d9a788a9fb35e60bb04b", size = 2100044, upload-time = "2025-04-23T18:31:41.034Z" }, - { url = "https://files.pythonhosted.org/packages/fe/36/def5e53e1eb0ad896785702a5bbfd25eed546cdcf4087ad285021a90ed53/pydantic_core-2.33.2-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:db4b41f9bd95fbe5acd76d89920336ba96f03e149097365afe1cb092fceb89a1", size = 2058881, upload-time = "2025-04-23T18:31:42.757Z" }, - { url = "https://files.pythonhosted.org/packages/01/6c/57f8d70b2ee57fc3dc8b9610315949837fa8c11d86927b9bb044f8705419/pydantic_core-2.33.2-cp312-cp312-musllinux_1_1_armv7l.whl", hash = "sha256:fa854f5cf7e33842a892e5c73f45327760bc7bc516339fda888c75ae60edaeb6", size = 2227034, upload-time = "2025-04-23T18:31:44.304Z" }, - { url = "https://files.pythonhosted.org/packages/27/b9/9c17f0396a82b3d5cbea4c24d742083422639e7bb1d5bf600e12cb176a13/pydantic_core-2.33.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:5f483cfb75ff703095c59e365360cb73e00185e01aaea067cd19acffd2ab20ea", size = 2234187, upload-time = "2025-04-23T18:31:45.891Z" }, - { url = "https://files.pythonhosted.org/packages/b0/6a/adf5734ffd52bf86d865093ad70b2ce543415e0e356f6cacabbc0d9ad910/pydantic_core-2.33.2-cp312-cp312-win32.whl", hash = "sha256:9cb1da0f5a471435a7bc7e439b8a728e8b61e59784b2af70d7c169f8dd8ae290", size = 1892628, upload-time = "2025-04-23T18:31:47.819Z" }, - { url = "https://files.pythonhosted.org/packages/43/e4/5479fecb3606c1368d496a825d8411e126133c41224c1e7238be58b87d7e/pydantic_core-2.33.2-cp312-cp312-win_amd64.whl", hash = "sha256:f941635f2a3d96b2973e867144fde513665c87f13fe0e193c158ac51bfaaa7b2", size = 1955866, upload-time = "2025-04-23T18:31:49.635Z" }, - { url = "https://files.pythonhosted.org/packages/0d/24/8b11e8b3e2be9dd82df4b11408a67c61bb4dc4f8e11b5b0fc888b38118b5/pydantic_core-2.33.2-cp312-cp312-win_arm64.whl", hash = "sha256:cca3868ddfaccfbc4bfb1d608e2ccaaebe0ae628e1416aeb9c4d88c001bb45ab", size = 1888894, upload-time = "2025-04-23T18:31:51.609Z" }, - { url = "https://files.pythonhosted.org/packages/46/8c/99040727b41f56616573a28771b1bfa08a3d3fe74d3d513f01251f79f172/pydantic_core-2.33.2-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:1082dd3e2d7109ad8b7da48e1d4710c8d06c253cbc4a27c1cff4fbcaa97a9e3f", size = 2015688, upload-time = "2025-04-23T18:31:53.175Z" }, - { url = "https://files.pythonhosted.org/packages/3a/cc/5999d1eb705a6cefc31f0b4a90e9f7fc400539b1a1030529700cc1b51838/pydantic_core-2.33.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:f517ca031dfc037a9c07e748cefd8d96235088b83b4f4ba8939105d20fa1dcd6", size = 1844808, upload-time = "2025-04-23T18:31:54.79Z" }, - { url = "https://files.pythonhosted.org/packages/6f/5e/a0a7b8885c98889a18b6e376f344da1ef323d270b44edf8174d6bce4d622/pydantic_core-2.33.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0a9f2c9dd19656823cb8250b0724ee9c60a82f3cdf68a080979d13092a3b0fef", size = 1885580, upload-time = "2025-04-23T18:31:57.393Z" }, - { url = "https://files.pythonhosted.org/packages/3b/2a/953581f343c7d11a304581156618c3f592435523dd9d79865903272c256a/pydantic_core-2.33.2-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:2b0a451c263b01acebe51895bfb0e1cc842a5c666efe06cdf13846c7418caa9a", size = 1973859, upload-time = "2025-04-23T18:31:59.065Z" }, - { url = "https://files.pythonhosted.org/packages/e6/55/f1a813904771c03a3f97f676c62cca0c0a4138654107c1b61f19c644868b/pydantic_core-2.33.2-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1ea40a64d23faa25e62a70ad163571c0b342b8bf66d5fa612ac0dec4f069d916", size = 2120810, upload-time = "2025-04-23T18:32:00.78Z" }, - { url = "https://files.pythonhosted.org/packages/aa/c3/053389835a996e18853ba107a63caae0b9deb4a276c6b472931ea9ae6e48/pydantic_core-2.33.2-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:0fb2d542b4d66f9470e8065c5469ec676978d625a8b7a363f07d9a501a9cb36a", size = 2676498, upload-time = "2025-04-23T18:32:02.418Z" }, - { url = "https://files.pythonhosted.org/packages/eb/3c/f4abd740877a35abade05e437245b192f9d0ffb48bbbbd708df33d3cda37/pydantic_core-2.33.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9fdac5d6ffa1b5a83bca06ffe7583f5576555e6c8b3a91fbd25ea7780f825f7d", size = 2000611, upload-time = "2025-04-23T18:32:04.152Z" }, - { url = "https://files.pythonhosted.org/packages/59/a7/63ef2fed1837d1121a894d0ce88439fe3e3b3e48c7543b2a4479eb99c2bd/pydantic_core-2.33.2-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:04a1a413977ab517154eebb2d326da71638271477d6ad87a769102f7c2488c56", size = 2107924, upload-time = "2025-04-23T18:32:06.129Z" }, - { url = "https://files.pythonhosted.org/packages/04/8f/2551964ef045669801675f1cfc3b0d74147f4901c3ffa42be2ddb1f0efc4/pydantic_core-2.33.2-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:c8e7af2f4e0194c22b5b37205bfb293d166a7344a5b0d0eaccebc376546d77d5", size = 2063196, upload-time = "2025-04-23T18:32:08.178Z" }, - { url = "https://files.pythonhosted.org/packages/26/bd/d9602777e77fc6dbb0c7db9ad356e9a985825547dce5ad1d30ee04903918/pydantic_core-2.33.2-cp313-cp313-musllinux_1_1_armv7l.whl", hash = "sha256:5c92edd15cd58b3c2d34873597a1e20f13094f59cf88068adb18947df5455b4e", size = 2236389, upload-time = "2025-04-23T18:32:10.242Z" }, - { url = "https://files.pythonhosted.org/packages/42/db/0e950daa7e2230423ab342ae918a794964b053bec24ba8af013fc7c94846/pydantic_core-2.33.2-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:65132b7b4a1c0beded5e057324b7e16e10910c106d43675d9bd87d4f38dde162", size = 2239223, upload-time = "2025-04-23T18:32:12.382Z" }, - { url = "https://files.pythonhosted.org/packages/58/4d/4f937099c545a8a17eb52cb67fe0447fd9a373b348ccfa9a87f141eeb00f/pydantic_core-2.33.2-cp313-cp313-win32.whl", hash = "sha256:52fb90784e0a242bb96ec53f42196a17278855b0f31ac7c3cc6f5c1ec4811849", size = 1900473, upload-time = "2025-04-23T18:32:14.034Z" }, - { url = "https://files.pythonhosted.org/packages/a0/75/4a0a9bac998d78d889def5e4ef2b065acba8cae8c93696906c3a91f310ca/pydantic_core-2.33.2-cp313-cp313-win_amd64.whl", hash = "sha256:c083a3bdd5a93dfe480f1125926afcdbf2917ae714bdb80b36d34318b2bec5d9", size = 1955269, upload-time = "2025-04-23T18:32:15.783Z" }, - { url = "https://files.pythonhosted.org/packages/f9/86/1beda0576969592f1497b4ce8e7bc8cbdf614c352426271b1b10d5f0aa64/pydantic_core-2.33.2-cp313-cp313-win_arm64.whl", hash = "sha256:e80b087132752f6b3d714f041ccf74403799d3b23a72722ea2e6ba2e892555b9", size = 1893921, upload-time = "2025-04-23T18:32:18.473Z" }, - { url = "https://files.pythonhosted.org/packages/a4/7d/e09391c2eebeab681df2b74bfe6c43422fffede8dc74187b2b0bf6fd7571/pydantic_core-2.33.2-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:61c18fba8e5e9db3ab908620af374db0ac1baa69f0f32df4f61ae23f15e586ac", size = 1806162, upload-time = "2025-04-23T18:32:20.188Z" }, - { url = "https://files.pythonhosted.org/packages/f1/3d/847b6b1fed9f8ed3bb95a9ad04fbd0b212e832d4f0f50ff4d9ee5a9f15cf/pydantic_core-2.33.2-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:95237e53bb015f67b63c91af7518a62a8660376a6a0db19b89acc77a4d6199f5", size = 1981560, upload-time = "2025-04-23T18:32:22.354Z" }, - { url = "https://files.pythonhosted.org/packages/6f/9a/e73262f6c6656262b5fdd723ad90f518f579b7bc8622e43a942eec53c938/pydantic_core-2.33.2-cp313-cp313t-win_amd64.whl", hash = "sha256:c2fc0a768ef76c15ab9238afa6da7f69895bb5d1ee83aeea2e3509af4472d0b9", size = 1935777, upload-time = "2025-04-23T18:32:25.088Z" }, - { url = "https://files.pythonhosted.org/packages/30/68/373d55e58b7e83ce371691f6eaa7175e3a24b956c44628eb25d7da007917/pydantic_core-2.33.2-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:5c4aa4e82353f65e548c476b37e64189783aa5384903bfea4f41580f255fddfa", size = 2023982, upload-time = "2025-04-23T18:32:53.14Z" }, - { url = "https://files.pythonhosted.org/packages/a4/16/145f54ac08c96a63d8ed6442f9dec17b2773d19920b627b18d4f10a061ea/pydantic_core-2.33.2-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:d946c8bf0d5c24bf4fe333af284c59a19358aa3ec18cb3dc4370080da1e8ad29", size = 1858412, upload-time = "2025-04-23T18:32:55.52Z" }, - { url = "https://files.pythonhosted.org/packages/41/b1/c6dc6c3e2de4516c0bb2c46f6a373b91b5660312342a0cf5826e38ad82fa/pydantic_core-2.33.2-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:87b31b6846e361ef83fedb187bb5b4372d0da3f7e28d85415efa92d6125d6e6d", size = 1892749, upload-time = "2025-04-23T18:32:57.546Z" }, - { url = "https://files.pythonhosted.org/packages/12/73/8cd57e20afba760b21b742106f9dbdfa6697f1570b189c7457a1af4cd8a0/pydantic_core-2.33.2-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:aa9d91b338f2df0508606f7009fde642391425189bba6d8c653afd80fd6bb64e", size = 2067527, upload-time = "2025-04-23T18:32:59.771Z" }, - { url = "https://files.pythonhosted.org/packages/e3/d5/0bb5d988cc019b3cba4a78f2d4b3854427fc47ee8ec8e9eaabf787da239c/pydantic_core-2.33.2-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:2058a32994f1fde4ca0480ab9d1e75a0e8c87c22b53a3ae66554f9af78f2fe8c", size = 2108225, upload-time = "2025-04-23T18:33:04.51Z" }, - { url = "https://files.pythonhosted.org/packages/f1/c5/00c02d1571913d496aabf146106ad8239dc132485ee22efe08085084ff7c/pydantic_core-2.33.2-pp310-pypy310_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:0e03262ab796d986f978f79c943fc5f620381be7287148b8010b4097f79a39ec", size = 2069490, upload-time = "2025-04-23T18:33:06.391Z" }, - { url = "https://files.pythonhosted.org/packages/22/a8/dccc38768274d3ed3a59b5d06f59ccb845778687652daa71df0cab4040d7/pydantic_core-2.33.2-pp310-pypy310_pp73-musllinux_1_1_armv7l.whl", hash = "sha256:1a8695a8d00c73e50bff9dfda4d540b7dee29ff9b8053e38380426a85ef10052", size = 2237525, upload-time = "2025-04-23T18:33:08.44Z" }, - { url = "https://files.pythonhosted.org/packages/d4/e7/4f98c0b125dda7cf7ccd14ba936218397b44f50a56dd8c16a3091df116c3/pydantic_core-2.33.2-pp310-pypy310_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:fa754d1850735a0b0e03bcffd9d4b4343eb417e47196e4485d9cca326073a42c", size = 2238446, upload-time = "2025-04-23T18:33:10.313Z" }, - { url = "https://files.pythonhosted.org/packages/ce/91/2ec36480fdb0b783cd9ef6795753c1dea13882f2e68e73bce76ae8c21e6a/pydantic_core-2.33.2-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:a11c8d26a50bfab49002947d3d237abe4d9e4b5bdc8846a63537b6488e197808", size = 2066678, upload-time = "2025-04-23T18:33:12.224Z" }, - { url = "https://files.pythonhosted.org/packages/7b/27/d4ae6487d73948d6f20dddcd94be4ea43e74349b56eba82e9bdee2d7494c/pydantic_core-2.33.2-pp311-pypy311_pp73-macosx_10_12_x86_64.whl", hash = "sha256:dd14041875d09cc0f9308e37a6f8b65f5585cf2598a53aa0123df8b129d481f8", size = 2025200, upload-time = "2025-04-23T18:33:14.199Z" }, - { url = "https://files.pythonhosted.org/packages/f1/b8/b3cb95375f05d33801024079b9392a5ab45267a63400bf1866e7ce0f0de4/pydantic_core-2.33.2-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:d87c561733f66531dced0da6e864f44ebf89a8fba55f31407b00c2f7f9449593", size = 1859123, upload-time = "2025-04-23T18:33:16.555Z" }, - { url = "https://files.pythonhosted.org/packages/05/bc/0d0b5adeda59a261cd30a1235a445bf55c7e46ae44aea28f7bd6ed46e091/pydantic_core-2.33.2-pp311-pypy311_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2f82865531efd18d6e07a04a17331af02cb7a651583c418df8266f17a63c6612", size = 1892852, upload-time = "2025-04-23T18:33:18.513Z" }, - { url = "https://files.pythonhosted.org/packages/3e/11/d37bdebbda2e449cb3f519f6ce950927b56d62f0b84fd9cb9e372a26a3d5/pydantic_core-2.33.2-pp311-pypy311_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2bfb5112df54209d820d7bf9317c7a6c9025ea52e49f46b6a2060104bba37de7", size = 2067484, upload-time = "2025-04-23T18:33:20.475Z" }, - { url = "https://files.pythonhosted.org/packages/8c/55/1f95f0a05ce72ecb02a8a8a1c3be0579bbc29b1d5ab68f1378b7bebc5057/pydantic_core-2.33.2-pp311-pypy311_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:64632ff9d614e5eecfb495796ad51b0ed98c453e447a76bcbeeb69615079fc7e", size = 2108896, upload-time = "2025-04-23T18:33:22.501Z" }, - { url = "https://files.pythonhosted.org/packages/53/89/2b2de6c81fa131f423246a9109d7b2a375e83968ad0800d6e57d0574629b/pydantic_core-2.33.2-pp311-pypy311_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:f889f7a40498cc077332c7ab6b4608d296d852182211787d4f3ee377aaae66e8", size = 2069475, upload-time = "2025-04-23T18:33:24.528Z" }, - { url = "https://files.pythonhosted.org/packages/b8/e9/1f7efbe20d0b2b10f6718944b5d8ece9152390904f29a78e68d4e7961159/pydantic_core-2.33.2-pp311-pypy311_pp73-musllinux_1_1_armv7l.whl", hash = "sha256:de4b83bb311557e439b9e186f733f6c645b9417c84e2eb8203f3f820a4b988bf", size = 2239013, upload-time = "2025-04-23T18:33:26.621Z" }, - { url = "https://files.pythonhosted.org/packages/3c/b2/5309c905a93811524a49b4e031e9851a6b00ff0fb668794472ea7746b448/pydantic_core-2.33.2-pp311-pypy311_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:82f68293f055f51b51ea42fafc74b6aad03e70e191799430b90c13d643059ebb", size = 2238715, upload-time = "2025-04-23T18:33:28.656Z" }, - { url = "https://files.pythonhosted.org/packages/32/56/8a7ca5d2cd2cda1d245d34b1c9a942920a718082ae8e54e5f3e5a58b7add/pydantic_core-2.33.2-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:329467cecfb529c925cf2bbd4d60d2c509bc2fb52a20c1045bf09bb70971a9c1", size = 2066757, upload-time = "2025-04-23T18:33:30.645Z" }, -] - -[[package]] -name = "pygments" -version = "2.19.2" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/b0/77/a5b8c569bf593b0140bde72ea885a803b82086995367bf2037de0159d924/pygments-2.19.2.tar.gz", hash = "sha256:636cb2477cec7f8952536970bc533bc43743542f70392ae026374600add5b887", size = 4968631, upload-time = "2025-06-21T13:39:12.283Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/c7/21/705964c7812476f378728bdf590ca4b771ec72385c533964653c68e86bdc/pygments-2.19.2-py3-none-any.whl", hash = "sha256:86540386c03d588bb81d44bc3928634ff26449851e99741617ecb9037ee5ec0b", size = 1225217, upload-time = "2025-06-21T13:39:07.939Z" }, + { url = "https://files.pythonhosted.org/packages/c4/33/3ec9bf27cd8cf1be1ad43138306817e40b1b631dc6252d57267d07876d70/rgapi-0.1.17-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:ac76c8abf4184e77b5a4e0ea74124c2dea1d99c49e5e753b10312ae0cbb47ec0", size = 1570615, upload-time = "2026-07-22T06:07:55.506Z" }, + { url = "https://files.pythonhosted.org/packages/68/f8/195789deaa50d0e36161f3c61d01dfe6d42ad434d2d36605fc30744693c0/rgapi-0.1.17-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f16aeb3a763911f1de3263b62f2e8fb74fe7e414b552eec7640250f8c13fda94", size = 1746581, upload-time = "2026-07-22T06:07:57.162Z" }, + { url = "https://files.pythonhosted.org/packages/8f/7b/9f19a0ef1e9fe47b5bb076c84aea331e1cdaebad2fe55938219579116b20/rgapi-0.1.17-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:44af27b67d8c702268a9d888389e81b7a44d3c5f8ee45a93b77700ea279b9105", size = 1569096, upload-time = "2026-07-22T06:07:58.456Z" }, + { url = "https://files.pythonhosted.org/packages/7f/63/e5e0482f00af43a644589d1d0af8e25e3691d0a446b4ff7d77104fe4215b/rgapi-0.1.17-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:351a8b05295473430c4aee467763a33b2750b2079fa18e7563a452fe99a0512e", size = 1742796, upload-time = "2026-07-22T06:08:00.102Z" }, + { url = "https://files.pythonhosted.org/packages/b6/44/21193229ed51b698867aaa012a874de86e945d9b36b08f8a2b0c398664d0/rgapi-0.1.17-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:bafab22e0a023b41daf50580d757a8a46633bbfc7f24d35ef05efe5b4e95d97b", size = 1561610, upload-time = "2026-07-22T06:08:01.656Z" }, + { url = "https://files.pythonhosted.org/packages/55/53/b6ef1fa3ca63e9c2948c01cbb056929e02dd11c6f8f320302717659ec008/rgapi-0.1.17-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5011581818889f895df4603929a93719d5531001b9e0e52221afcf6fd694886c", size = 1741487, upload-time = "2026-07-22T06:08:03.575Z" }, + { url = "https://files.pythonhosted.org/packages/ef/47/b2d80fb072b45310a1e5f0cac59b5d02523f68e7a09f74cd9468db306ffb/rgapi-0.1.17-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:ed1faee38c9f746403c07a3d611db02ccdeb0482381783cad63e162fe25c404d", size = 1561510, upload-time = "2026-07-22T06:08:04.883Z" }, + { url = "https://files.pythonhosted.org/packages/b4/5f/c1a2c332a2ffa0c042414a64efb52406bf0ba5d0cac7e0e532acaae76915/rgapi-0.1.17-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b1b715a09dec1e14702f79d4643a789735373db09716014a8f75882c502127df", size = 1741757, upload-time = "2026-07-22T06:08:06.431Z" }, ] [[package]] @@ -556,28 +402,55 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/e3/30/3c4d035596d3cf444529e0b2953ad0466f6049528a879d27534700580395/rich-14.1.0-py3-none-any.whl", hash = "sha256:536f5f1785986d6dbdea3c75205c473f970777b4a0d6c6dd1b696aa05a3fa04f", size = 243368, upload-time = "2025-07-25T07:32:56.73Z" }, ] +[[package]] +name = "safecmd" +version = "0.1.13" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "fastcore" }, + { name = "shfmt-py" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/a3/fd/a2236b3f7c231eed5b1ce930936790a00f7c77468ca41389f80d4956568b/safecmd-0.1.13.tar.gz", hash = "sha256:52a4c234f4a961c995ed68c19c9273f33120f31539ff123f4b5fd9041e73ba68", size = 23930, upload-time = "2026-06-28T05:17:54.908Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/54/7d/00db4edaafabf2abcfdf34a6eccfc2687eb68f462a219ca1403678d4f630/safecmd-0.1.13-py3-none-any.whl", hash = "sha256:cc15c82e9758c857b00caa891c67590058702a13ac78700dde88899040017cc0", size = 21255, upload-time = "2026-06-28T05:17:53.708Z" }, +] + [[package]] name = "shell-sage" source = { editable = "." } dependencies = [ - { name = "claudette" }, - { name = "cosette" }, { name = "fastcore" }, { name = "fastlite" }, - { name = "msglm" }, + { name = "pyperclip" }, + { name = "python-fastllm" }, + { name = "rgapi" }, { name = "rich" }, + { name = "safecmd" }, ] [package.metadata] requires-dist = [ - { name = "claudette" }, - { name = "cosette" }, - { name = "fastcore", specifier = ">=1.7.29" }, + { name = "fastcore", specifier = ">=2.1.3" }, { name = "fastlite" }, - { name = "msglm" }, + { name = "pyperclip" }, + { name = "python-fastllm" }, + { name = "rgapi", specifier = ">=0.1.16" }, { name = "rich" }, + { name = "safecmd" }, +] + +[[package]] +name = "shfmt-py" +version = "4.0.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/06/d5/c2ad5c6593a34da7344cf39bde65763e8cda752589074ba1619e55b317ad/shfmt_py-4.0.0.tar.gz", hash = "sha256:1e5fdacf40aabaa77a97639d52a6220df0893b46658d82b7f136f4e66e2b2fb0", size = 11947, upload-time = "2026-05-13T09:25:50.153Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/5a/1d/8f72824e2a0e06dc0bc2687baacaba0573be7d2e93c01d1e895fddd8c13e/shfmt_py-4.0.0-py2.py3-none-macosx_10_9_x86_64.whl", hash = "sha256:75a4919a03fb3bcff9795e3cc7b971e37e74905654d2f11605001cab42e5f92f", size = 1343695, upload-time = "2026-05-13T09:25:42.969Z" }, + { url = "https://files.pythonhosted.org/packages/a8/82/9564a2c2a76fbec94db1b3a3c37a9a1d00e7eafca2cdd2e0d19082618d7e/shfmt_py-4.0.0-py2.py3-none-macosx_11_0_arm64.whl", hash = "sha256:bb3d236163ff39c7790953e069938caf247e7646399f7a059f00f65d4e6916d6", size = 1237947, upload-time = "2026-05-13T09:25:44.767Z" }, + { url = "https://files.pythonhosted.org/packages/9a/a9/6fce944efa530db941edd11388d70dc7384aaf12169a3b0847b6a6c987b0/shfmt_py-4.0.0-py2.py3-none-manylinux2014_aarch64.whl", hash = "sha256:4701336c3cb5f3959a5e85481b14f02054ea094b3c666f3d04649bbe10de3c25", size = 1218771, upload-time = "2026-05-13T09:25:46.225Z" }, + { url = "https://files.pythonhosted.org/packages/64/43/e3965a25bb39555f2791c6860214f62b6f976f9ac7e9786073364bcdd9a6/shfmt_py-4.0.0-py2.py3-none-manylinux2014_x86_64.whl", hash = "sha256:e57877abe0177a9da7bbb5390fe7e96aa19b00958189a025634039aef8834d44", size = 1350939, upload-time = "2026-05-13T09:25:47.584Z" }, + { url = "https://files.pythonhosted.org/packages/95/20/db2430d9262d2cffadcad2b330441e13031f1ab849ec069659edb7f23257/shfmt_py-4.0.0-py2.py3-none-win_amd64.whl", hash = "sha256:bd4f3d36264d4ba8b014ff73e5e702aaa2345845c021f563480128de3705135b", size = 1427721, upload-time = "2026-05-13T09:25:48.865Z" }, ] -provides-extras = ["dev"] [[package]] name = "sniffio" @@ -601,18 +474,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/e6/4e/8aeb462f455ea41a6d72d649272af95aecbf869d7b42c83b2ea64b2b5cbc/toolslm-0.3.4-py3-none-any.whl", hash = "sha256:58cc074722095c06dde8e81ae9b6860e7f3b04d2a58d0b66cf084a26e01b1dda", size = 17905, upload-time = "2025-09-23T06:04:40.397Z" }, ] -[[package]] -name = "tqdm" -version = "4.67.1" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "colorama", marker = "sys_platform == 'win32'" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/a8/4b/29b4ef32e036bb34e4ab51796dd745cdba7ed47ad142a9f4a1eb8e0c744d/tqdm-4.67.1.tar.gz", hash = "sha256:f8aef9c52c08c13a65f30ea34f4e5aac3fd1a34959879d7e59e63027286627f2", size = 169737, upload-time = "2024-11-24T20:12:22.481Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/d0/30/dc54f88dd4a2b5dc8a0279bdd7270e735851848b762aeb1c1184ed1f6b14/tqdm-4.67.1-py3-none-any.whl", hash = "sha256:26445eca388f82e72884e0d580d5464cd801a3ea01e63e5601bdff9ba6a48de2", size = 78540, upload-time = "2024-11-24T20:12:19.698Z" }, -] - [[package]] name = "typing-extensions" version = "4.15.0" @@ -621,15 +482,3 @@ sdist = { url = "https://files.pythonhosted.org/packages/72/94/1a15dd82efb362ac8 wheels = [ { url = "https://files.pythonhosted.org/packages/18/67/36e9267722cc04a6b9f15c7f3441c2363321a3ea07da7ae0c0707beb2a9c/typing_extensions-4.15.0-py3-none-any.whl", hash = "sha256:f0fa19c6845758ab08074a0cfa8b7aecb71c999ca73d62883bc25cc018c4e548", size = 44614, upload-time = "2025-08-25T13:49:24.86Z" }, ] - -[[package]] -name = "typing-inspection" -version = "0.4.1" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "typing-extensions" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/f8/b1/0c11f5058406b3af7609f121aaa6b609744687f1d158b3c3a5bf4cc94238/typing_inspection-0.4.1.tar.gz", hash = "sha256:6ae134cc0203c33377d43188d4064e9b357dba58cff3185f22924610e70a9d28", size = 75726, upload-time = "2025-05-21T18:55:23.885Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/17/69/cd203477f944c353c31bade965f880aa1061fd6bf05ded0726ca845b6ff7/typing_inspection-0.4.1-py3-none-any.whl", hash = "sha256:389055682238f53b04f7badcb49b989835495a96700ced5dab2d8feae4b26f51", size = 14552, upload-time = "2025-05-21T18:55:22.152Z" }, -]