commit 12c4e5cae0bda1ab17db935133e61112d7d8fd12 Author: Raelon Date: Sat Jul 11 16:12:25 2026 -0400 Initial VimKit extraction diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..c0413a6 --- /dev/null +++ b/.gitignore @@ -0,0 +1,31 @@ +# Python bytecode and caches +__pycache__/ +*.py[oc] +*.pyd +*.pyo +*.so +.pytest_cache/ +.mypy_cache/ +.ruff_cache/ +.coverage +.coverage.* +htmlcov/ + +# Build artifacts +build/ +dist/ +wheels/ +*.egg-info +site/ + +# Virtual environments +.venv +venv/ + +# Editors and local tooling +.DS_Store +.idea/ +.vscode/ + +# uv and local Python state +.python-version diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..237762a --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2026 Raelon + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/README.md b/README.md new file mode 100644 index 0000000..251ed17 --- /dev/null +++ b/README.md @@ -0,0 +1,52 @@ +# VimKit + +Reusable Textual vim-style editor components extracted from Fleet. + +## What is here + +- `VimEditor`: the core modal editor widget +- `VimWorkbench`: editor plus VimKit-owned top bar, command bar, and status bar +- `VimExtension`: extension hook for `:` commands and editor integrations +- `VimHost`: host interface for project-specific behaviors like named directories + +## Status + +The package is source-isolated and can be developed independently from Fleet. + +## Local development + +```bash +uv sync +uv run python -m compileall src +``` + +## Install + +```bash +pip install vimkit +``` + +## Using from another project + +During development, add it as a local path dependency: + +```bash +uv add --editable ../VimKit +``` + +Later, switch to a Git dependency or the published PyPI package. + +## First release flow + +1. Bump the version in `pyproject.toml`. +2. Sync dev tools with `uv sync --dev`. +3. Build distributions with `uv build`. +4. Validate artifacts with `uv run twine check dist/*`. +5. Upload with `uv run twine upload dist/*`. + +PyPI will prompt for your username and password unless you use an API token. + +## Notes + +- VimKit is released under the MIT license. +- If you later mirror this to GitHub, PyPI Trusted Publishing is preferable to long-lived credentials. diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000..1589248 --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,38 @@ +[project] +name = "vimkit" +version = "0.1.0" +description = "Reusable Textual vim-style editor widgets and extension hooks." +readme = "README.md" +authors = [ + { name = "Raelon", email = "tty303@proton.me" } +] +license = "MIT" +license-files = ["LICENSE"] +requires-python = ">=3.14" +keywords = ["textual", "tui", "vim", "editor", "terminal-ui"] +classifiers = [ + "Development Status :: 3 - Alpha", + "Framework :: Textual", + "Intended Audience :: Developers", + "Programming Language :: Python :: 3", + "Programming Language :: Python :: 3.14", + "Topic :: Software Development :: User Interfaces", + "Topic :: Terminals", + "Typing :: Typed", +] +dependencies = [ + "textual[syntax]>=8.2.8", +] + +[project.urls] +Homepage = "https://git.th3r00t.net/th3r00t/VimKit" +Source = "https://git.th3r00t.net/th3r00t/VimKit" + +[dependency-groups] +dev = [ + "twine>=6.1.0", +] + +[build-system] +requires = ["uv_build>=0.11.23,<0.12.0"] +build-backend = "uv_build" diff --git a/src/vimkit/__init__.py b/src/vimkit/__init__.py new file mode 100644 index 0000000..aeebd5f --- /dev/null +++ b/src/vimkit/__init__.py @@ -0,0 +1,41 @@ +"""Reusable VimKit package decoupled from FleetCommand.""" +__version__ = "0.1.0" + +from vimkit.area import VimTextArea +from vimkit.clipboard import copy_text_to_clipboard +from vimkit.commands import COMMANDS, NORMAL_MOTIONS +from vimkit.editor import VimEditor +from vimkit.extensions import VimExtension +from vimkit.host import VimHost +from vimkit.messages import BufferPathChanged, ModeChanged, QuitRequested, Submitted +from vimkit.modes import VimMode +from vimkit.modals import TextModal +from vimkit.panels import CommandBar, FleetPrompt, WhichKeyPanel +from vimkit.state import VimSessionState +from vimkit.theme import register_cyberdream +from vimkit.workbench import VimStatusBar, VimTopBar, VimWorkbench + +__all__ = [ + "BufferPathChanged", + "COMMANDS", + "NORMAL_MOTIONS", + "CommandBar", + "FleetPrompt", + "ModeChanged", + "QuitRequested", + "Submitted", + "TextModal", + "VimHost", + "VimEditor", + "VimMode", + "VimTextArea", + "VimExtension", + "VimSessionState", + "VimStatusBar", + "VimTopBar", + "VimWorkbench", + "WhichKeyPanel", + "__version__", + "copy_text_to_clipboard", + "register_cyberdream", +] diff --git a/src/vimkit/area.py b/src/vimkit/area.py new file mode 100644 index 0000000..10a7cee --- /dev/null +++ b/src/vimkit/area.py @@ -0,0 +1,766 @@ +"""TextArea implementation for vimkit modal editing behavior.""" +from __future__ import annotations + +from typing import TYPE_CHECKING + +from textual.binding import Binding +from textual.events import Key +from textual.widgets import TextArea + +from vimkit.clipboard import copy_text_to_clipboard +from vimkit.modes import VimMode +from vimkit.panels import WhichKeyPanel + +if TYPE_CHECKING: + from vimkit.editor import VimEditor + + +_vim_clipboard: str = "" +_vim_clipboard_linewise: bool = False + + +class VimTextArea(TextArea): + BINDINGS = [ + Binding("ctrl+j", "submit_chat", "Send", show=False, priority=True), + Binding("ctrl+enter", "submit_chat", "Send", show=False, priority=True), + Binding("shift+enter", "submit_chat", "Send", show=False, priority=True), + Binding("enter", "handle_enter", "Enter", show=False, priority=True), + Binding("escape", "to_normal", "Normal", show=False, priority=True), + Binding("ctrl+z", "undo", "Undo", show=False, priority=False), + Binding("ctrl+r", "redo", "Redo", show=False, priority=False), + ] + + def _outer(self) -> "VimEditor | None": + outer = self.parent + from vimkit.editor import VimEditor + + return outer if isinstance(outer, VimEditor) else None + + def action_submit_chat(self) -> None: + outer = self._outer() + if outer is None or outer.mode == VimMode.COMMAND: + return + text = self.text.strip() + if text: + outer.post_message(outer.Submitted(outer, text)) + + def action_newline(self) -> None: + outer = self._outer() + if outer is not None and outer.mode == VimMode.INSERT: + self.insert("\n") + + def action_handle_enter(self) -> None: + outer = self._outer() + if outer is None: + return + if outer.mode == VimMode.INSERT: + self.action_newline() + elif outer.mode == VimMode.NORMAL: + self.action_submit_chat() + elif outer.mode == VimMode.COMMAND: + outer._end_command(execute=True) + + def action_to_normal(self) -> None: + outer = self._outer() + if outer is None: + return + if outer.mode in (VimMode.INSERT, VimMode.VISUAL, VimMode.VISUAL_LINE): + from textual.widgets.text_area import Selection + + self.selection = Selection.cursor(self.cursor_location) + outer.set_mode(VimMode.NORMAL) + elif outer.mode == VimMode.COMMAND: + outer._end_command(execute=False) + + def on_key(self, event: Key) -> None: + outer = self._outer() + if outer is None: + return + mode = outer.mode + k = event.key + ch = event.character or "" + + if mode == VimMode.INSERT and self._is_submit_key(event): + event.prevent_default() + event.stop() + self.action_submit_chat() + return + if mode == VimMode.INSERT and self._is_enter_key(event): + event.prevent_default() + event.stop() + self.action_newline() + return + + if mode == VimMode.NORMAL: + event.prevent_default() + event.stop() + + if outer._find_char_pending and ch and len(ch) == 1: + self._do_find_char(ch, forward=(outer._find_char_pending == "f")) + outer._last_find = (ch, outer._find_char_pending == "f") + outer._find_char_pending = None + return + if outer._replace_pending and ch and len(ch) == 1: + self._do_replace_char(ch) + outer._replace_pending = False + outer._last_action = ("r", ch) + return + if ch and ch.isdigit() and not (ch == "0" and not outer._count_buf): + outer._count_buf += ch + return + + if outer._op_buf or ch in ("g", "c", "d", "y"): + if self._handle_normal_sequence(outer, ch): + return + + if ch == " ": + outer._op_buf = "" + try: + self.app.query_one(WhichKeyPanel).show() + except Exception: + pass + elif ch == "i": + outer._op_buf = "" + outer.set_mode(VimMode.INSERT) + elif ch == "I": + outer._op_buf = "" + self.action_cursor_line_start() + outer.set_mode(VimMode.INSERT) + elif ch == "A": + outer._op_buf = "" + self.action_cursor_line_end() + outer.set_mode(VimMode.INSERT) + elif ch == "a": + outer._op_buf = "" + outer.set_mode(VimMode.INSERT) + self.action_cursor_right() + elif ch == "o": + outer._op_buf = "" + self._open_line_below() + outer.set_mode(VimMode.INSERT) + elif ch == "O": + outer._op_buf = "" + self._open_line_above() + outer.set_mode(VimMode.INSERT) + elif ch == "v": + outer._op_buf = "" + self._visual_anchor = self.cursor_location + outer.set_mode(VimMode.VISUAL) + elif ch == "V": + outer._op_buf = "" + self._visual_anchor = self.cursor_location + outer.set_mode(VimMode.VISUAL_LINE) + elif ch == ":": + outer._op_buf = "" + outer._start_command() + elif self._is_enter_key(event): + outer._op_buf = "" + self.action_submit_chat() + elif ch == "h" or k == "left": + outer._op_buf = "" + self.action_cursor_left() + elif ch == "l" or k == "right": + outer._op_buf = "" + self.action_cursor_right() + elif ch == "j" or k == "down": + outer._op_buf = "" + self.action_cursor_down() + elif ch == "k" or k == "up": + outer._op_buf = "" + self.action_cursor_up() + elif ch == "0": + outer._op_buf = "" + self.action_cursor_line_start() + elif ch == "$": + outer._op_buf = "" + self.action_cursor_line_end() + elif ch == "G": + outer._op_buf = "" + self._move_doc_end() + elif ch == "w": + outer._op_buf = "" + self._move_word_forward() + elif ch == "b": + outer._op_buf = "" + self._move_word_backward() + elif ch == "e": + outer._op_buf = "" + self._move_word_end() + elif ch == "x": + outer._op_buf = "" + self.read_only = False + self.action_delete_right() + self.read_only = True + outer._last_action = ("x",) + elif ch == "D": + outer._op_buf = "" + self._delete_to_line_end() + elif ch == "C": + outer._op_buf = "" + self._delete_to_line_end() + outer.set_mode(VimMode.INSERT) + elif ch == "p": + outer._op_buf = "" + self._paste_after() + elif ch == "P": + outer._op_buf = "" + self._paste_before() + elif ch == "u": + outer._op_buf = "" + self.read_only = False + self.action_undo() + self.read_only = True + elif k == "ctrl+r": + outer._op_buf = "" + self.read_only = False + self.action_redo() + self.read_only = True + elif ch == "f": + outer._op_buf = "" + outer._find_char_pending = "f" + elif ch == "F": + outer._op_buf = "" + outer._find_char_pending = "F" + elif ch == ";": + outer._op_buf = "" + if outer._last_find: + self._do_find_char(outer._last_find[0], forward=outer._last_find[1]) + elif ch == ",": + outer._op_buf = "" + if outer._last_find: + self._do_find_char(outer._last_find[0], forward=not outer._last_find[1]) + elif ch == "r": + outer._op_buf = "" + outer._replace_pending = True + elif ch == ".": + outer._op_buf = "" + if outer._last_action: + self._replay_last_action(outer) + else: + outer._op_buf = "" + outer._count_buf = "" + + elif mode in (VimMode.VISUAL, VimMode.VISUAL_LINE): + event.prevent_default() + event.stop() + anchor = getattr(self, "_visual_anchor", self.cursor_location) + + if k == "escape": + from textual.widgets.text_area import Selection + + self.selection = Selection.cursor(self.cursor_location) + outer.set_mode(VimMode.NORMAL) + elif ch == "v": + from textual.widgets.text_area import Selection + + if mode == VimMode.VISUAL: + self.selection = Selection.cursor(self.cursor_location) + outer.set_mode(VimMode.NORMAL) + else: + outer.set_mode(VimMode.VISUAL) + self._update_visual_selection(anchor, VimMode.VISUAL) + elif ch == "V": + if mode == VimMode.VISUAL_LINE: + from textual.widgets.text_area import Selection + + self.selection = Selection.cursor(self.cursor_location) + outer.set_mode(VimMode.NORMAL) + else: + outer.set_mode(VimMode.VISUAL_LINE) + self._update_visual_selection(anchor, VimMode.VISUAL_LINE) + elif ch in "hjkl" or k in ("left", "right", "up", "down"): + if ch == "h" or k == "left": + self.action_cursor_left() + elif ch == "l" or k == "right": + self.action_cursor_right() + elif ch == "j" or k == "down": + self.action_cursor_down() + elif ch == "k" or k == "up": + self.action_cursor_up() + self._update_visual_selection(anchor, mode) + elif ch == "0": + self.action_cursor_line_start() + self._update_visual_selection(anchor, mode) + elif ch == "$": + self.action_cursor_line_end() + self._update_visual_selection(anchor, mode) + elif ch == "G": + self._move_doc_end() + self._update_visual_selection(anchor, mode) + elif ch == "w": + self._move_word_forward() + self._update_visual_selection(anchor, mode) + elif ch == "b": + self._move_word_backward() + self._update_visual_selection(anchor, mode) + elif ch == "y": + self._yank_selection(mode) + from textual.widgets.text_area import Selection + + self.selection = Selection.cursor(self.cursor_location) + outer.set_mode(VimMode.NORMAL) + elif ch == "d": + self._delete_selection() + outer.set_mode(VimMode.NORMAL) + elif ch == "c": + self._delete_selection() + outer.set_mode(VimMode.INSERT) + + elif mode == VimMode.COMMAND: + if k == "escape": + return + event.prevent_default() + event.stop() + if self._is_enter_key(event) or k in ("shift+enter", "shift_enter"): + outer._end_command(execute=True) + elif k in ("up", "ctrl+p"): + if outer._cmd_history: + outer._cmd_hist_idx = min(outer._cmd_hist_idx + 1, len(outer._cmd_history) - 1) + outer._cmd_buf = outer._cmd_history[-(outer._cmd_hist_idx + 1)] + outer._refresh_cmd() + elif k in ("down", "ctrl+n"): + if outer._cmd_hist_idx > 0: + outer._cmd_hist_idx -= 1 + outer._cmd_buf = outer._cmd_history[-(outer._cmd_hist_idx + 1)] + else: + outer._cmd_hist_idx = -1 + outer._cmd_buf = "" + outer._refresh_cmd() + elif k == "backspace": + if outer._cmd_buf: + outer._cmd_buf = outer._cmd_buf[:-1] + outer._refresh_cmd() + if not outer._cmd_buf: + outer._end_command(execute=False) + elif k == "tab": + outer._apply_completion() + if outer._completions: + outer._comp_idx = (outer._comp_idx + 1) % len(outer._completions) + outer._update_command_bar() + elif k == "shift+tab": + if outer._completions: + outer._comp_idx = (outer._comp_idx - 1) % len(outer._completions) + outer._apply_completion() + outer._update_command_bar() + elif ch and ch.isprintable(): + outer._cmd_buf += ch + outer._refresh_cmd() + + def _is_submit_key(self, event: Key) -> bool: + if event.key in ("ctrl+j", "ctrl+enter", "shift+enter", "shift_enter"): + return True + if event.key == "enter" and getattr(event, "shift", False): + return True + return False + + def _is_enter_key(self, event: Key) -> bool: + return event.key in ("enter", "return", "ctrl+m") + + def _handle_normal_sequence(self, outer: "VimEditor", ch: str) -> bool: + if ch not in ("g", "c", "d", "i", "w", "y"): + return False + outer._op_buf += ch + seq = outer._op_buf + if seq == "gg": + self.move_cursor((0, 0), center=True) + outer._op_buf = "" + return True + if seq == "dd": + self._delete_line() + outer._last_action = ("dd",) + outer._op_buf = "" + return True + if seq == "yy": + self._yank_line() + outer._op_buf = "" + return True + if seq == "cc": + self._delete_line_content() + outer.set_mode(VimMode.INSERT) + outer._op_buf = "" + return True + if seq == "ciw": + if self._delete_inner_word(): + outer.set_mode(VimMode.INSERT) + outer._op_buf = "" + return True + if seq == "diw": + self._delete_inner_word() + outer._op_buf = "" + return True + if seq == "dw": + self._delete_word_forward() + outer._last_action = ("dw",) + outer._op_buf = "" + return True + if seq == "cw": + self._delete_word_forward() + outer._last_action = ("cw",) + outer.set_mode(VimMode.INSERT) + outer._op_buf = "" + return True + if seq == "yw": + self._yank_word_forward() + outer._op_buf = "" + return True + if seq in ("g", "c", "d", "y", "ci", "di"): + return True + outer._op_buf = "" + return False + + def _delete_inner_word(self) -> bool: + bounds = self._inner_word_bounds() + if bounds is None: + return False + start, end = bounds + self.read_only = False + try: + self.delete(start, end) + self.move_cursor(start) + finally: + self.read_only = True + return True + + def _inner_word_bounds(self) -> tuple[tuple[int, int], tuple[int, int]] | None: + row, col = self.cursor_location + lines = self.text.splitlines() or [""] + if row >= len(lines): + return None + line = lines[row] + if not line: + return None + + def is_word_char(idx: int) -> bool: + return line[idx].isalnum() or line[idx] == "_" + + if col >= len(line): + col = max(len(line) - 1, 0) + idx = col + if not is_word_char(idx): + right = idx + while right < len(line) and not is_word_char(right): + right += 1 + if right < len(line): + idx = right + else: + left = idx + while left >= 0 and not is_word_char(left): + left -= 1 + if left < 0: + return None + idx = left + start = idx + while start > 0 and is_word_char(start - 1): + start -= 1 + end = idx + 1 + while end < len(line) and is_word_char(end): + end += 1 + return (row, start), (row, end) + + def _move_doc_end(self) -> None: + lines = self.text.splitlines() or [""] + row = max(len(lines) - 1, 0) + self.move_cursor((row, len(lines[row])), center=True) + + def _move_word_forward(self) -> None: + row, col = self.cursor_location + lines = self.text.splitlines() or [""] + if row >= len(lines): + return + line = lines[row] + while col < len(line) and (line[col].isalnum() or line[col] == "_"): + col += 1 + while col < len(line) and not (line[col].isalnum() or line[col] == "_"): + col += 1 + if col >= len(line) and row + 1 < len(lines): + row += 1 + col = 0 + self.move_cursor((row, col)) + + def _move_word_backward(self) -> None: + row, col = self.cursor_location + lines = self.text.splitlines() or [""] + if col == 0: + if row > 0: + row -= 1 + col = len(lines[row]) + else: + return + line = lines[row] + col -= 1 + while col > 0 and not (line[col].isalnum() or line[col] == "_"): + col -= 1 + while col > 0 and (line[col - 1].isalnum() or line[col - 1] == "_"): + col -= 1 + self.move_cursor((row, col)) + + def _move_word_end(self) -> None: + row, col = self.cursor_location + lines = self.text.splitlines() or [""] + if row >= len(lines): + return + line = lines[row] + if col + 1 < len(line): + col += 1 + while col < len(line) and not (line[col].isalnum() or line[col] == "_"): + col += 1 + while col + 1 < len(line) and (line[col + 1].isalnum() or line[col + 1] == "_"): + col += 1 + self.move_cursor((row, min(col, max(len(line) - 1, 0)))) + + def _do_find_char(self, ch: str, forward: bool) -> None: + row, col = self.cursor_location + lines = self.text.splitlines() or [""] + if row >= len(lines): + return + line = lines[row] + idx = line.find(ch, col + 1) if forward else line.rfind(ch, 0, col) + if idx != -1: + self.move_cursor((row, idx)) + + def _do_replace_char(self, ch: str) -> None: + row, col = self.cursor_location + lines = self.text.splitlines() or [""] + if row >= len(lines) or col >= len(lines[row]): + return + self.read_only = False + try: + self.delete((row, col), (row, col + 1)) + self.insert(ch, location=(row, col)) + self.move_cursor((row, col)) + finally: + self.read_only = True + + def _delete_word_forward(self) -> None: + global _vim_clipboard, _vim_clipboard_linewise + row, col = self.cursor_location + lines = self.text.splitlines() or [""] + if row >= len(lines): + return + line = lines[row] + end = col + while end < len(line) and (line[end].isalnum() or line[end] == "_"): + end += 1 + if end == col: + while end < len(line) and not (line[end].isalnum() or line[end] == "_"): + end += 1 + _vim_clipboard = line[col:end] + _vim_clipboard_linewise = False + self.read_only = False + try: + self.delete((row, col), (row, end)) + finally: + self.read_only = True + + def _yank_word_forward(self) -> None: + global _vim_clipboard, _vim_clipboard_linewise + row, col = self.cursor_location + lines = self.text.splitlines() or [""] + if row >= len(lines): + return + line = lines[row] + end = col + while end < len(line) and (line[end].isalnum() or line[end] == "_"): + end += 1 + _vim_clipboard = line[col:end] + _vim_clipboard_linewise = False + + def _replay_last_action(self, outer: "VimEditor") -> None: + act = outer._last_action + if not act: + return + if act[0] == "dd": + self._delete_line() + elif act[0] == "x": + self.read_only = False + self.action_delete_right() + self.read_only = True + elif act[0] == "r": + self._do_replace_char(act[1]) + elif act[0] == "dw": + self._delete_word_forward() + elif act[0] == "cw": + self._delete_word_forward() + outer.set_mode(VimMode.INSERT) + + def _open_line_below(self) -> None: + self.action_cursor_line_end() + self.read_only = False + try: + self.insert("\n") + finally: + self.read_only = True + + def _open_line_above(self) -> None: + row, _ = self.cursor_location + self.read_only = False + try: + if row == 0: + self.insert("\n", location=(0, 0)) + self.move_cursor((0, 0)) + else: + self.insert("\n", location=(row, 0)) + self.move_cursor((row, 0)) + finally: + self.read_only = True + + def _delete_line(self) -> None: + global _vim_clipboard, _vim_clipboard_linewise + row, _ = self.cursor_location + lines = self.text.splitlines(keepends=True) + if row >= len(lines): + return + _vim_clipboard = lines[row].rstrip("\n\r") + _vim_clipboard_linewise = True + self.read_only = False + try: + line_start = (row, 0) + if row + 1 < len(lines): + line_end = (row + 1, 0) + else: + raw = lines[row].rstrip("\n\r") + line_end = (row, len(raw)) + self.delete(line_start, line_end) + finally: + self.read_only = True + + def _yank_line(self) -> None: + global _vim_clipboard, _vim_clipboard_linewise + row, _ = self.cursor_location + lines = self.text.splitlines() + if row >= len(lines): + return + _vim_clipboard = lines[row] + _vim_clipboard_linewise = True + + def _delete_line_content(self) -> None: + global _vim_clipboard, _vim_clipboard_linewise + row, _ = self.cursor_location + lines = self.text.splitlines() + if row >= len(lines): + return + _vim_clipboard = lines[row] + _vim_clipboard_linewise = False + self.read_only = False + try: + self.delete((row, 0), (row, len(lines[row]))) + finally: + self.read_only = True + + def _delete_to_line_end(self) -> None: + global _vim_clipboard, _vim_clipboard_linewise + row, col = self.cursor_location + lines = self.text.splitlines() + if row >= len(lines): + return + line = lines[row] + _vim_clipboard = line[col:] + _vim_clipboard_linewise = False + self.read_only = False + try: + self.delete((row, col), (row, len(line))) + finally: + self.read_only = True + + def _paste_after(self) -> None: + global _vim_clipboard, _vim_clipboard_linewise + if not _vim_clipboard: + return + row, col = self.cursor_location + self.read_only = False + try: + if _vim_clipboard_linewise: + lines = self.text.splitlines(keepends=True) + pos = (row + 1, 0) if row < len(lines) else (row, col) + self.insert(_vim_clipboard + "\n", location=pos) + self.move_cursor((row + 1, 0)) + else: + all_lines = self.text.splitlines() or [""] + line = all_lines[row] if row < len(all_lines) else "" + insert_col = min(col + 1, len(line)) + self.insert(_vim_clipboard, location=(row, insert_col)) + finally: + self.read_only = True + + def _paste_before(self) -> None: + global _vim_clipboard, _vim_clipboard_linewise + if not _vim_clipboard: + return + row, col = self.cursor_location + self.read_only = False + try: + if _vim_clipboard_linewise: + self.insert(_vim_clipboard + "\n", location=(row, 0)) + self.move_cursor((row, 0)) + else: + self.insert(_vim_clipboard, location=(row, col)) + finally: + self.read_only = True + + def _update_visual_selection(self, anchor: tuple[int, int], mode: VimMode) -> None: + from textual.widgets.text_area import Selection + + current = self.cursor_location + if mode == VimMode.VISUAL_LINE: + start_row = min(anchor[0], current[0]) + end_row = max(anchor[0], current[0]) + lines = self.text.splitlines() or [""] + end_col = len(lines[end_row]) if end_row < len(lines) else 0 + self.selection = Selection((start_row, 0), (end_row, end_col)) + else: + if anchor <= current: + end_col = current[1] + 1 + all_lines = self.text.splitlines() or [""] + line = all_lines[current[0]] if current[0] < len(all_lines) else "" + end_col = min(end_col, len(line)) + self.selection = Selection(anchor, (current[0], end_col)) + else: + end_col = anchor[1] + 1 + all_lines = self.text.splitlines() or [""] + line = all_lines[anchor[0]] if anchor[0] < len(all_lines) else "" + end_col = min(end_col, len(line)) + self.selection = Selection(current, (anchor[0], end_col)) + + def _yank_selection(self, mode: VimMode) -> None: + global _vim_clipboard, _vim_clipboard_linewise + sel = self.selection + start, end = sel.start, sel.end + if start > end: + start, end = end, start + lines = self.text.splitlines() or [""] + if start[0] == end[0]: + line = lines[start[0]] if start[0] < len(lines) else "" + _vim_clipboard = line[start[1]:end[1]] + _vim_clipboard_linewise = False + else: + parts = [] + for r in range(start[0], end[0] + 1): + if r >= len(lines): + break + line = lines[r] + if r == start[0]: + parts.append(line[start[1]:]) + elif r == end[0]: + parts.append(line[:end[1]]) + else: + parts.append(line) + _vim_clipboard = "\n".join(parts) + _vim_clipboard_linewise = mode == VimMode.VISUAL_LINE + try: + copy_text_to_clipboard(self.app, _vim_clipboard) + except Exception: + pass + + def _delete_selection(self) -> None: + self._yank_selection(VimMode.VISUAL) + sel = self.selection + start, end = sel.start, sel.end + if start > end: + start, end = end, start + if start == end: + return + self.read_only = False + try: + self.delete(start, end) + finally: + self.read_only = True diff --git a/src/vimkit/clipboard.py b/src/vimkit/clipboard.py new file mode 100644 index 0000000..d3d1876 --- /dev/null +++ b/src/vimkit/clipboard.py @@ -0,0 +1,40 @@ +"""Clipboard helpers for VimKit.""" +from __future__ import annotations + +import shutil +import subprocess + +from textual.app import App + + +def copy_text_to_clipboard(app: App, text: str) -> bool: + """Copy text through Textual OSC52 and local clipboard fallbacks.""" + try: + app.copy_to_clipboard(text) + except Exception: + pass + + copied_somewhere = getattr(app, "_clipboard", None) == text + + commands = ( + ("wl-copy",), + ("xclip", "-selection", "clipboard"), + ("xsel", "--clipboard", "--input"), + ("pbcopy",), + ) + for cmd in commands: + if shutil.which(cmd[0]) is None: + continue + try: + proc = subprocess.run( + list(cmd), + input=text, + text=True, + capture_output=True, + timeout=2, + ) + if proc.returncode == 0: + return True + except Exception: + continue + return copied_somewhere diff --git a/src/vimkit/command_executor.py b/src/vimkit/command_executor.py new file mode 100644 index 0000000..1696b9b --- /dev/null +++ b/src/vimkit/command_executor.py @@ -0,0 +1,187 @@ +"""Command execution helpers for VimEditor.""" +from __future__ import annotations + +import shlex +from pathlib import Path +from typing import TYPE_CHECKING + +if TYPE_CHECKING: + from vimkit.editor import VimEditor + + +def resolve_editor_path(editor: "VimEditor", path: str) -> Path: + candidate = Path(path).expanduser() + return candidate if candidate.is_absolute() else editor._cwd / candidate + + +def run_command(editor: "VimEditor", cmd: str) -> None: + if cmd.startswith("!"): + run_passthrough_command(editor, cmd[1:].strip()) + return + + if cmd.startswith("e "): + target = resolve_editor_path(editor, cmd[2:].strip()) + if target.exists(): + try: + editor.load_text(target.read_text(encoding="utf-8")) + editor.set_buffer_path(target) + editor._cwd = target.parent + editor.app.notify(f"Loaded {target.name}", timeout=2) + except Exception as exc: + editor.app.notify(str(exc), severity="error", timeout=3) + else: + editor.load_text("") + editor.set_buffer_path(target) + editor.app.notify(f"New buffer → {target}", severity="information", timeout=3) + return + + if cmd == "w": + if editor.current_path is None: + editor.app.notify(":w requires a filename or existing buffer path", severity="warning", timeout=4) + else: + editor._write_to_path(editor.current_path) + return + + if cmd.startswith("w "): + path_arg = cmd[2:].strip() + if not path_arg: + editor.app.notify(":w requires a filename", severity="warning", timeout=4) + else: + editor._write_to_path(resolve_editor_path(editor, path_arg)) + return + + if cmd.startswith("cd "): + path = cmd[3:].strip() + named = editor.host.named_directory(path) if editor.host is not None else None + if named is not None: + editor._cwd = named + editor.app.notify(f"cwd → {editor._cwd}", timeout=2) + else: + target = resolve_editor_path(editor, path) if path else Path.home() + try: + editor._cwd = target.resolve() + editor.app.notify(f"cwd → {editor._cwd}", timeout=2) + except Exception as exc: + editor.app.notify(str(exc), severity="error", timeout=3) + return + + if cmd == "cd": + editor._cwd = Path.home() + editor.app.notify("cwd → home (hint: :cd projects | :cd notebooks)", timeout=4) + return + + if cmd in ("q", "q!"): + editor.post_message(editor.QuitRequested(editor, force=(cmd == "q!"))) + return + + if cmd.startswith("wq "): + path_arg = cmd[3:].strip() + if not path_arg: + editor.app.notify(":wq requires a filename", severity="warning", timeout=4) + return + try: + editor._write_to_path(resolve_editor_path(editor, path_arg)) + editor.post_message(editor.QuitRequested(editor, force=False)) + except Exception as exc: + editor.app.notify(f"Write failed: {exc}", severity="error", timeout=6) + return + + if cmd == "wq": + if editor.current_path is not None: + try: + editor._write_to_path(editor.current_path) + except Exception as exc: + editor.app.notify(f"Write failed: {exc}", severity="error", timeout=6) + return + editor.post_message(editor.QuitRequested(editor, force=False)) + return + + for extension in editor.extensions: + if extension.handles(editor, cmd): + return + + if cmd == "help": + try: + from vimkit.panels import WhichKeyPanel + + editor.app.query_one(WhichKeyPanel).show() + except Exception: + pass + return + + editor.app.notify(f"Unknown command: :{cmd}", severity="warning", timeout=4) + + +def run_passthrough_command(editor: "VimEditor", raw: str) -> None: + if not raw: + editor.app.notify(":! requires a command", severity="warning", timeout=3) + return + try: + parts = shlex.split(raw) + except ValueError as exc: + editor.app.notify(f"Parse failed: {exc}", severity="error", timeout=4) + return + if not parts: + editor.app.notify(":! requires a command", severity="warning", timeout=3) + return + + cmd, *args = parts + if cmd == "cd": + target_arg = args[0] if args else "" + run_command(editor, f"cd {target_arg}".rstrip()) + return + + if cmd == "mkdir": + parents = False + paths: list[str] = [] + for arg in args: + if arg == "-p": + parents = True + elif arg.startswith("-"): + editor.app.notify(f"Unsupported mkdir option: {arg}", severity="warning", timeout=4) + return + else: + paths.append(arg) + if not paths: + editor.app.notify(":!mkdir requires a path", severity="warning", timeout=3) + return + created: list[str] = [] + for path_arg in paths: + target = resolve_editor_path(editor, path_arg) + target.mkdir(parents=parents, exist_ok=parents) + created.append(str(target)) + editor.app.notify(f"mkdir → {', '.join(created[:3])}", severity="information", timeout=4) + return + + if cmd == "touch": + if not args: + editor.app.notify(":!touch requires a path", severity="warning", timeout=3) + return + touched: list[str] = [] + for path_arg in args: + target = resolve_editor_path(editor, path_arg) + target.parent.mkdir(parents=True, exist_ok=True) + target.touch(exist_ok=True) + touched.append(str(target)) + editor.app.notify(f"touch → {', '.join(touched[:3])}", severity="information", timeout=4) + return + + if cmd == "ls": + target = resolve_editor_path(editor, args[0]) if args else editor._cwd + if not target.exists(): + editor.app.notify(f"No such path: {target}", severity="error", timeout=4) + return + base = target if target.is_dir() else target.parent + entries = sorted(base.iterdir(), key=lambda path: (not path.is_dir(), path.name.lower())) + lines = [f"{path.name}/" if path.is_dir() else path.name for path in entries[:200]] + body = "\n".join(lines) if lines else "(empty)" + from vimkit.modals import TextModal + + editor.app.push_screen(TextModal(f":!ls {base}", body)) + return + + editor.app.notify( + "Supported passthrough commands: :!cd, :!mkdir, :!touch, :!ls", + severity="warning", + timeout=5, + ) diff --git a/src/vimkit/commands.py b/src/vimkit/commands.py new file mode 100644 index 0000000..fe1000d --- /dev/null +++ b/src/vimkit/commands.py @@ -0,0 +1,51 @@ +"""Reusable command and motion metadata for VimEditor surfaces.""" +from __future__ import annotations + + +COMMANDS: dict[str, str] = { + "e": "load file into prompt", + "w": "save prompt to file", + "wq": "write then quit, requires filename", + "q": "quit FleetCommand", + "q!": "quit FleetCommand", + "cd": "change command working directory", + "new": "start a new chat session", + "model": "open orchestrator model picker", + "chat": "show chat view", + "agents": "show agents view", + "panel": "toggle logs/system panel", + "clear": "clear current chat", + "manifest": "open memory manifest screen", + "notes": "open Obsidian notes browser", + "note": "fuzzy-find and open an Obsidian note", + "projectnote": "open the dedicated Obsidian note for this project", + "OrderTemplate": "create a seeded navigator task plan in .fleet/plans", + "engineer": "open engineer monitor screen", + "health": "run project health checks", + "theme": "pick UI colour theme", + "help": "show command and motion reference", +} + + +NORMAL_MOTIONS: dict[str, str] = { + "h/j/k/l": "cursor left/down/up/right", + "w/b/e": "word forward/backward/end", + "0/$": "line start/end", + "gg/G": "doc start/end", + "i/a/A": "insert at/after/line-end", + "I/o/O": "insert line-start/below/above", + "dd/yy/cc": "delete/yank/change line", + "dw/cw/yw": "delete/change/yank word", + "ciw/diw": "change/delete inner word", + "D/C": "delete/change to line-end", + "p/P": "paste after/before", + "f/F": "find char forward/backward", + "r": "replace char under cursor", + "3j/5w": "move N lines/words (count prefix)", + ";/,": "repeat last find fwd/bwd", + ".": "repeat last change", + "u/ctrl+r": "undo/redo", + "v/V": "visual char/line mode", + "Space": "open WhichKey reference panel", + ":": "open command bar", +} diff --git a/src/vimkit/completion.py b/src/vimkit/completion.py new file mode 100644 index 0000000..e6179c3 --- /dev/null +++ b/src/vimkit/completion.py @@ -0,0 +1,67 @@ +"""Command completion helpers for VimEditor.""" +from __future__ import annotations + +from pathlib import Path + +from vimkit.commands import COMMANDS + + +def list_path_matches(cwd: Path, partial: str) -> list[str]: + try: + if "/" in partial: + dir_part, name_part = partial.rsplit("/", 1) + base = (cwd / dir_part).expanduser().resolve() + prefix = dir_part + "/" + lower = name_part.lower() + else: + base = cwd + prefix = "" + lower = partial.lower() + if not base.is_dir(): + return [] + return sorted( + prefix + path.name + ("/" if path.is_dir() else "") + for path in base.iterdir() + if lower in path.name.lower() + )[:20] + except Exception: + return [] + + +def compute_command_completions(cmd_buf: str, cwd: Path, commands: dict[str, str] | None = None) -> list[str]: + commands = commands or COMMANDS + stripped = cmd_buf.lstrip() + if not stripped or " " not in stripped: + lower = stripped.lower() + return [ + cmd + (" " if cmd in ("e", "w", "wq", "cd") else "") + for cmd in commands + if lower in cmd.lower() + ] + + if stripped.startswith("e ") or stripped.startswith("w ") or stripped.startswith("wq "): + partial = cmd_buf[2:] + if stripped.startswith("wq "): + partial = cmd_buf[3:] + return list_path_matches(cwd, partial) + + if stripped.startswith("cd "): + partial = cmd_buf[3:] + named = [name for name in ("projects", "notebooks") if partial == "" or name.startswith(partial.lower())] + file_matches = list_path_matches(cwd, partial) if partial else [] + return named + [match for match in file_matches if match not in named] + + return [] + + +def apply_completion_text(cmd_buf: str, completions: list[str], index: int) -> str: + if not completions: + return cmd_buf + stripped = cmd_buf.lstrip() + selected = completions[index % len(completions)] + if " " not in stripped: + return selected + for prefix in ("e ", "w ", "wq ", "cd "): + if stripped.startswith(prefix): + return prefix + selected + return cmd_buf diff --git a/src/vimkit/editor.py b/src/vimkit/editor.py new file mode 100644 index 0000000..b5e0f1d --- /dev/null +++ b/src/vimkit/editor.py @@ -0,0 +1,486 @@ +"""Core vim-modal editor widget for VimKit.""" +from __future__ import annotations + +from pathlib import Path + +from textual.binding import Binding +from textual.reactive import reactive +from textual.widget import Widget +from textual.widgets import Label + +from vimkit.area import VimTextArea +from vimkit.command_executor import run_command, run_passthrough_command +from vimkit.completion import apply_completion_text, compute_command_completions +from vimkit.commands import COMMANDS +from vimkit.extensions import VimExtension +from vimkit.host import VimHost +from vimkit.messages import BufferPathChanged, ModeChanged, QuitRequested, Submitted +from vimkit.modes import VimMode +from vimkit.panels import CommandBar +from vimkit.state import VimSessionState +from vimkit.theme import register_cyberdream + + +class VimEditor(Widget): + """Unified vim modal editor — used for chat input, notes, and anywhere else.""" + + BINDINGS = [ + Binding("enter", "handle_enter", "Enter", show=False, priority=True), + Binding("ctrl+m", "handle_enter", "Enter", show=False, priority=True), + ] + + Submitted = Submitted + ModeChanged = ModeChanged + QuitRequested = QuitRequested + BufferPathChanged = BufferPathChanged + _Area = VimTextArea + + mode: reactive[VimMode] = reactive(VimMode.INSERT) + + DEFAULT_CSS = """ + VimEditor { + width: 1fr; + height: auto; + min-height: 1; + max-height: 5; + background: $surface; + } + VimEditor TextArea { + background: transparent; + border: none; + height: auto; + min-height: 1; + padding: 0 1; + } + VimEditor TextArea:focus { + border: none; + } + VimEditor #comp-bar { + display: none; + } + VimEditor #cmd-line { + display: none; + } + VimEditor.busy { + opacity: 0.6; + } + """ + + def __init__( + self, + notes_mode: bool = False, + extensions: list[VimExtension] | None = None, + host: VimHost | None = None, + **kwargs, + ) -> None: + super().__init__(**kwargs) + self._state = VimSessionState(notes_mode=notes_mode, extensions=list(extensions or [])) + self._saved_text = "" + self._host = host + + @property + def host(self) -> VimHost | None: + return self._host + + @property + def _cmd_buf(self) -> str: + return self._state.cmd_buf + + @_cmd_buf.setter + def _cmd_buf(self, value: str) -> None: + self._state.cmd_buf = value + + @property + def _op_buf(self) -> str: + return self._state.op_buf + + @_op_buf.setter + def _op_buf(self, value: str) -> None: + self._state.op_buf = value + + @property + def _rows(self) -> int: + return self._state.rows + + @_rows.setter + def _rows(self, value: int) -> None: + self._state.rows = value + + @property + def _cwd(self) -> Path: + return self._state.cwd + + @_cwd.setter + def _cwd(self, value: Path) -> None: + self._state.cwd = value + + @property + def _completions(self) -> list[str]: + return self._state.completions + + @_completions.setter + def _completions(self, value: list[str]) -> None: + self._state.completions = value + + @property + def _comp_idx(self) -> int: + return self._state.comp_idx + + @_comp_idx.setter + def _comp_idx(self, value: int) -> None: + self._state.comp_idx = value + + @property + def _notes_mode(self) -> bool: + return self._state.notes_mode + + @property + def _count_buf(self) -> str: + return self._state.count_buf + + @_count_buf.setter + def _count_buf(self, value: str) -> None: + self._state.count_buf = value + + @property + def _find_char_pending(self) -> str | None: + return self._state.find_char_pending + + @_find_char_pending.setter + def _find_char_pending(self, value: str | None) -> None: + self._state.find_char_pending = value + + @property + def _last_find(self) -> tuple[str, bool] | None: + return self._state.last_find + + @_last_find.setter + def _last_find(self, value: tuple[str, bool] | None) -> None: + self._state.last_find = value + + @property + def _replace_pending(self) -> bool: + return self._state.replace_pending + + @_replace_pending.setter + def _replace_pending(self, value: bool) -> None: + self._state.replace_pending = value + + @property + def _last_action(self) -> tuple | None: + return self._state.last_action + + @_last_action.setter + def _last_action(self, value: tuple | None) -> None: + self._state.last_action = value + + @property + def _cmd_history(self) -> list[str]: + return self._state.cmd_history + + @property + def _cmd_hist_idx(self) -> int: + return self._state.cmd_hist_idx + + @_cmd_hist_idx.setter + def _cmd_hist_idx(self, value: int) -> None: + self._state.cmd_hist_idx = value + + @property + def _buffer_path(self) -> Path | None: + return self._state.buffer_path + + @_buffer_path.setter + def _buffer_path(self, value: Path | None) -> None: + self._state.buffer_path = value + + def compose(self): + if self._notes_mode: + yield VimTextArea( + "", + language="markdown", + theme="vscode_dark", + show_line_numbers=True, + tab_behavior="indent", + soft_wrap=True, + highlight_cursor_line=True, + ) + else: + yield VimTextArea( + "", + language="markdown", + theme="vscode_dark", + show_line_numbers=False, + tab_behavior="focus", + soft_wrap=True, + highlight_cursor_line=False, + ) + yield Label("", id="comp-bar", markup=False) + yield Label("", id="cmd-line", markup=False) + + def watch_mode(self, mode: VimMode) -> None: + try: + self.query_one(VimTextArea).read_only = (mode not in (VimMode.INSERT,)) + except Exception: + pass + self.post_message(VimEditor.ModeChanged(self, mode)) + + def set_mode(self, mode: VimMode) -> None: + self.mode = mode + + def action_handle_enter(self) -> None: + try: + area = self.query_one(VimTextArea) + except Exception: + return + if self.mode == VimMode.INSERT: + area.action_newline() + elif self.mode == VimMode.NORMAL: + area.action_submit_chat() + elif self.mode == VimMode.COMMAND: + self._end_command(execute=True) + + def _start_command(self) -> None: + self._cmd_buf = "" + self.mode = VimMode.COMMAND + command_bar = self._command_bar() + if command_bar is not None: + try: + command_bar.show_command("") + except Exception: + pass + + def _refresh_cmd(self) -> None: + self._recompute_completions() + self._update_command_bar() + + def _recompute_completions(self) -> None: + self._completions = compute_command_completions(self._cmd_buf, self._cwd, self.command_descriptions) + self._comp_idx = 0 + + def _update_command_bar(self) -> None: + command_bar = self._command_bar() + if command_bar is None: + return + try: + command_bar.show_command(self._cmd_buf, self._completions, self._comp_idx) + except Exception: + pass + + def _apply_completion(self) -> None: + if not self._completions: + return + self._cmd_buf = apply_completion_text(self._cmd_buf, self._completions, self._comp_idx) + if self._cmd_buf.endswith("/"): + self._recompute_completions() + command_bar = self._command_bar() + if command_bar is not None: + try: + command_bar.update_command(self._cmd_buf) + except Exception: + pass + self._update_command_bar() + + def _end_command(self, execute: bool) -> None: + cmd = self._cmd_buf.strip() + if execute: + self._state.remember_command(cmd) + self._state.reset_command_mode() + command_bar = self._command_bar() + if command_bar is not None: + try: + command_bar.hide_command() + except Exception: + pass + self.mode = VimMode.NORMAL + if execute and cmd: + self._run_cmd(cmd) + + def _run_cmd(self, cmd: str) -> None: + run_command(self, cmd) + + @property + def extensions(self) -> list[VimExtension]: + return self._state.extensions + + @property + def command_descriptions(self) -> dict[str, str]: + merged = dict(COMMANDS) + for extension in self.extensions: + merged.update(extension.commands) + return merged + + def _command_bar(self) -> CommandBar | None: + parent = self.parent + while parent is not None: + try: + return parent.query_one(CommandBar) + except Exception: + parent = parent.parent + try: + return self.app.query_one(CommandBar) + except Exception: + return None + + def on_mount(self) -> None: + self._apply_rows() + self._apply_cyberdream_ta() + + def _apply_cyberdream_ta(self) -> None: + register_cyberdream(self.app) + ta_theme = getattr(register_cyberdream, "_ta_theme", None) + if ta_theme is None: + return + try: + area = self.query_one(VimTextArea) + area.register_theme(ta_theme) + area.theme = "cyberdream" + except Exception: + pass + + def _apply_rows(self) -> None: + if self._notes_mode: + self.styles.min_height = 3 + self.styles.height = "1fr" + try: + self.query_one(VimTextArea).styles.height = "1fr" + except Exception: + pass + else: + self.styles.min_height = self._rows + self.styles.max_height = self._rows + try: + self.query_one(VimTextArea).styles.height = self._rows + except Exception: + pass + + @property + def rows(self) -> int: + return self._rows + + def set_rows(self, rows: int) -> None: + self._rows = max(1, min(int(rows), 20)) + self._apply_rows() + + def grow(self) -> None: + self.set_rows(self._rows + 1) + + def shrink(self) -> None: + self.set_rows(self._rows - 1) + + def toggle_pin(self) -> bool: + pinned = not self._state.pinned + self._state.pinned = pinned + if pinned: + self.styles.min_height = 1 + self.styles.max_height = 1 + try: + self.query_one(VimTextArea).styles.height = 1 + except Exception: + pass + else: + self._apply_rows() + return pinned + + @property + def text(self) -> str: + try: + return self.query_one(VimTextArea).text + except Exception: + return "" + + @property + def cursor_location(self) -> tuple[int, int]: + try: + return self.query_one(VimTextArea).cursor_location + except Exception: + return (0, 0) + + @property + def is_modified(self) -> bool: + return self.text != self._saved_text + + @property + def cwd(self) -> Path: + return self._cwd + + @property + def current_path(self) -> Path | None: + return self._buffer_path + + def set_buffer_path(self, path: Path | None) -> None: + self._buffer_path = path.resolve() if isinstance(path, Path) and path.exists() else path + try: + self.post_message(self.BufferPathChanged(self, self._buffer_path)) + except Exception: + pass + + def load_text(self, s: str) -> None: + try: + area = self.query_one(VimTextArea) + was_read_only = area.read_only + area.read_only = False + try: + area.load_text(s) + area.move_cursor((0, 0), center=True) + finally: + area.read_only = was_read_only + except Exception: + pass + self._saved_text = s + self.set_buffer_path(None) + + def insert_text(self, s: str) -> None: + try: + area = self.query_one(VimTextArea) + was_read_only = area.read_only + area.read_only = False + try: + area.insert(s, location=area.cursor_location) + finally: + area.read_only = was_read_only + except Exception: + pass + + def _write_to_path(self, path: Path) -> None: + content = self.text + target = path.expanduser() + target.parent.mkdir(parents=True, exist_ok=True) + target.write_text(content, encoding="utf-8") + self._saved_text = content + self.set_buffer_path(target.resolve()) + self._cwd = target.parent.resolve() + self.app.notify(f"Saved {len(content)} chars → {target}", severity="information", timeout=4) + + def _run_passthrough_command(self, raw: str) -> None: + run_passthrough_command(self, raw) + + @property + def disabled(self) -> bool: + return "busy" in self.classes + + @disabled.setter + def disabled(self, value: bool) -> None: + try: + self.query_one(VimTextArea).disabled = value + except Exception: + pass + if value: + self.add_class("busy") + else: + self.remove_class("busy") + + def focus(self, scroll_visible: bool = True) -> "VimEditor": + try: + self.query_one(VimTextArea).focus(scroll_visible) + except Exception: + pass + return self + + def blur(self) -> "VimEditor": + try: + self.query_one(VimTextArea).blur() + except Exception: + pass + return self + diff --git a/src/vimkit/extensions.py b/src/vimkit/extensions.py new file mode 100644 index 0000000..953eb85 --- /dev/null +++ b/src/vimkit/extensions.py @@ -0,0 +1,25 @@ +"""Command extension primitives for VimEditor.""" +from __future__ import annotations + +from dataclasses import dataclass, field +from typing import TYPE_CHECKING, Callable + +if TYPE_CHECKING: + from vimkit.editor import VimEditor + + +CommandHandler = Callable[["VimEditor", str], bool] + + +@dataclass(slots=True) +class VimExtension: + """Named bundle of commands that can be attached to a VimEditor instance.""" + + name: str + commands: dict[str, str] = field(default_factory=dict) + handler: CommandHandler | None = None + + def handles(self, editor: "VimEditor", cmd: str) -> bool: + if self.handler is None: + return False + return self.handler(editor, cmd) diff --git a/src/vimkit/host.py b/src/vimkit/host.py new file mode 100644 index 0000000..7289891 --- /dev/null +++ b/src/vimkit/host.py @@ -0,0 +1,37 @@ +"""Host adapter protocol for vimkit integrations.""" +from __future__ import annotations + +from pathlib import Path +from typing import Protocol + + +class VimHost(Protocol): + """Optional host API that lets applications provide app-specific actions.""" + + def named_directory(self, name: str) -> Path | None: ... + + def action_new_chat(self) -> None: ... + + def action_pick_model(self) -> None: ... + + def action_show_chat(self) -> None: ... + + def action_toggle_log(self) -> None: ... + + def action_clear_chat(self) -> None: ... + + def action_open_manifest(self) -> None: ... + + def action_open_notes(self) -> None: ... + + def action_pick_note(self) -> None: ... + + def action_open_project_note(self) -> None: ... + + def action_open_order_template(self) -> None: ... + + def action_open_engineer(self) -> None: ... + + def action_open_health(self) -> None: ... + + def action_pick_theme(self) -> None: ... diff --git a/src/vimkit/messages.py b/src/vimkit/messages.py new file mode 100644 index 0000000..e5d44b6 --- /dev/null +++ b/src/vimkit/messages.py @@ -0,0 +1,50 @@ +"""Public message types emitted by VimEditor widgets.""" +from __future__ import annotations + +from pathlib import Path +from typing import TYPE_CHECKING + +from textual.message import Message + +from vimkit.modes import VimMode + +if TYPE_CHECKING: + from vimkit.editor import VimEditor + + +class VimWidgetMessage(Message): + """Base message that exposes the originating VimEditor control.""" + + def __init__(self, sender: "VimEditor") -> None: + super().__init__() + self._sender = sender + + @property + def control(self) -> "VimEditor": + return self._sender + + +class Submitted(VimWidgetMessage): + def __init__(self, sender: "VimEditor", value: str) -> None: + super().__init__(sender) + self.value = value + + +class ModeChanged(VimWidgetMessage): + def __init__(self, sender: "VimEditor", mode: VimMode) -> None: + super().__init__(sender) + self.mode = mode + + +class QuitRequested(VimWidgetMessage): + """Emitted when :q or :q! is invoked. Parent decides whether to quit or dismiss.""" + + def __init__(self, sender: "VimEditor", force: bool = False) -> None: + super().__init__(sender) + self.force = force + + +class BufferPathChanged(VimWidgetMessage): + def __init__(self, sender: "VimEditor", path: Path | None) -> None: + super().__init__(sender) + self.path = path diff --git a/src/vimkit/modals.py b/src/vimkit/modals.py new file mode 100644 index 0000000..2bfbdff --- /dev/null +++ b/src/vimkit/modals.py @@ -0,0 +1,61 @@ +"""Generic modal surfaces owned by the reusable VimKit package.""" +from __future__ import annotations + +from textual import on +from textual.app import ComposeResult +from textual.binding import Binding +from textual.containers import Vertical +from textual.screen import ModalScreen +from textual.widgets import Button, RichLog + + +class TextModal(ModalScreen[None]): + """Simple read-only text viewer for builtin Vim commands like :!ls.""" + + CSS = """ + TextModal { + align: center middle; + } + TextModal #tm-dialog { + width: 100; + height: auto; + max-height: 30; + background: $surface; + border: thick $primary; + padding: 1 2; + } + TextModal RichLog { + height: 1fr; + background: transparent; + border: solid $panel; + padding: 0 1; + } + TextModal Button { + width: 10; + margin-top: 1; + align-horizontal: right; + } + """ + + BINDINGS = [Binding("escape", "close", "Close"), Binding("q", "close", "Close", show=False)] + + def __init__(self, title: str, body: str) -> None: + super().__init__() + self._title = title + self._body = body + + def compose(self) -> ComposeResult: + with Vertical(id="tm-dialog"): + yield RichLog(id="tm-log", highlight=False, markup=False, wrap=False) + yield Button("Close", id="tm-close") + + def on_mount(self) -> None: + self.sub_title = self._title + self.query_one("#tm-log", RichLog).write(self._body or "(empty)") + + @on(Button.Pressed, "#tm-close") + def _close_pressed(self, _: Button.Pressed) -> None: + self.dismiss(None) + + def action_close(self) -> None: + self.dismiss(None) diff --git a/src/vimkit/modes.py b/src/vimkit/modes.py new file mode 100644 index 0000000..347e91e --- /dev/null +++ b/src/vimkit/modes.py @@ -0,0 +1,12 @@ +"""Core mode definitions for the reusable VimEditor subsystem.""" +from __future__ import annotations + +from enum import Enum + + +class VimMode(Enum): + INSERT = "I" + NORMAL = "N" + COMMAND = "C" + VISUAL = "V" + VISUAL_LINE = "VL" diff --git a/src/vimkit/panels.py b/src/vimkit/panels.py new file mode 100644 index 0000000..fcff0af --- /dev/null +++ b/src/vimkit/panels.py @@ -0,0 +1,242 @@ +"""Reusable presentational widgets for VimEditor surfaces.""" +from __future__ import annotations + +from textual.containers import Vertical +from textual.events import Key +from textual.reactive import reactive +from textual.widget import Widget +from textual.widgets import Input, Label, Static + +from vimkit.commands import COMMANDS, NORMAL_MOTIONS +from vimkit.prompt_render import WAVE_COLORS, wave + + +class FleetPrompt(Widget): + """Colour-wave animated ⬡ fleet ❯ prompt indicator.""" + + DEFAULT_CSS = """ + FleetPrompt { + width: auto; + height: 1; + padding: 0 1; + background: transparent; + content-align: left middle; + } + FleetPrompt > Static { + background: transparent; + content-align: left middle; + height: 1; + } + """ + + frame: reactive[int] = reactive(0) + + def compose(self): + yield Static(wave(0), markup=True) + + def on_mount(self) -> None: + self.set_interval(0.12, self._tick) + + def _tick(self) -> None: + self.frame = (self.frame + 1) % len(WAVE_COLORS) + + def watch_frame(self, frame: int) -> None: + self.query_one(Static).update(wave(frame)) + + +class WhichKeyPanel(Widget): + """Floating help/discovery panel showing commands and normal-mode motions.""" + + DEFAULT_CSS = """ + WhichKeyPanel { + height: auto; max-height: 16; display: none; + dock: bottom; background: $surface-darken-1; + border: solid $primary; margin: 0 1 1 1; + } + WhichKeyPanel #wk-title { + height: 1; background: $surface-darken-2; color: $primary; + text-style: bold; content-align: center middle; + } + WhichKeyPanel #wk-filter { + height: 1; background: $surface-darken-2; border: none; padding: 0 1; + border-top: none !important; border-bottom: none !important; + } + WhichKeyPanel #wk-grid { + layout: grid; grid-size: 2; height: auto; overflow-y: auto; + } + WhichKeyPanel .wk-item { padding: 0 1; height: 1; } + WhichKeyPanel .wk-item.-selected { background: $boost; } + """ + + def __init__(self, **kwargs: object) -> None: + super().__init__(**kwargs) + self._prev_focus: Widget | None = None + self._all_items: list[tuple[str, str]] = [] + self._selected_idx: int = 0 + + def compose(self): + yield Label(" VimKit — Commands & Motions (Esc to close)", id="wk-title") + yield Input(placeholder="filter…", id="wk-filter") + yield Vertical(id="wk-grid") + + def show(self, section: str = "all") -> None: + self._prev_focus = self.app.focused + self._all_items = [] + command_map = dict(COMMANDS) + try: + from vimkit.editor import VimEditor + + vim = self.app.query_one(VimEditor) + command_map = vim.command_descriptions + except Exception: + pass + if section in ("all", "commands"): + for key, desc in command_map.items(): + self._all_items.append((f":{key}", desc)) + if section in ("all", "motions"): + for key, desc in NORMAL_MOTIONS.items(): + self._all_items.append((key, desc)) + self._selected_idx = 0 + self.display = True + self._rebuild_grid(self._all_items) + try: + self.query_one("#wk-filter", Input).value = "" + self.query_one("#wk-filter", Input).focus() + except Exception: + pass + + def dismiss_panel(self) -> None: + self.display = False + if self._prev_focus is not None: + try: + self._prev_focus.focus() + except Exception: + pass + self._prev_focus = None + + def _active_vim(self): + node = self._prev_focus or self.app.focused + while node is not None: + try: + from vimkit.editor import VimEditor + + if isinstance(node, VimEditor): + return node + except Exception: + return None + node = getattr(node, "parent", None) + return None + + def _rebuild_grid(self, items: list[tuple[str, str]]) -> None: + grid = self.query_one("#wk-grid", Vertical) + grid.remove_children() + for i, (key, desc) in enumerate(items): + lbl = Label(f"[bold]{key}[/] {desc}", classes="wk-item") + if i == self._selected_idx: + lbl.add_class("-selected") + grid.mount(lbl) + + def _filtered_items(self) -> list[tuple[str, str]]: + try: + q = self.query_one("#wk-filter", Input).value.lower() + except Exception: + q = "" + if not q: + return self._all_items + return [(k, d) for k, d in self._all_items if q in k.lower() or q in d.lower()] + + def on_input_changed(self, _: Input.Changed) -> None: + self._selected_idx = 0 + self._rebuild_grid(self._filtered_items()) + + def on_key(self, event: Key) -> None: + k = event.key + if k in ("escape", "ctrl+c"): + self.dismiss_panel() + event.stop() + elif k in ("up", "ctrl+p", "shift+tab"): + items = self._filtered_items() + if items: + self._selected_idx = max(0, self._selected_idx - 1) + self._rebuild_grid(items) + event.stop() + event.prevent_default() + elif k in ("down", "ctrl+n", "tab"): + items = self._filtered_items() + if items: + self._selected_idx = min(len(items) - 1, self._selected_idx + 1) + self._rebuild_grid(items) + event.stop() + event.prevent_default() + elif k == "enter": + items = self._filtered_items() + if items and 0 <= self._selected_idx < len(items): + chosen_key = items[self._selected_idx][0] + if chosen_key.startswith(":"): + cmd = chosen_key[1:] + self.dismiss_panel() + try: + vim = self._active_vim() + if vim is not None: + vim._run_cmd(cmd) + except Exception: + pass + else: + self.dismiss_panel() + event.stop() + + +class CommandBar(Widget): + """Vim-style command line anchored above the status bar.""" + + DEFAULT_CSS = """ + CommandBar { + height: 2; + display: none; + background: $surface-darken-1; + } + CommandBar #cmd-completions { + height: 1; + color: $text-muted; + background: $surface-darken-1; + padding: 0 1; + } + CommandBar #cmd-input { + height: 1; + color: $accent; + background: $surface-darken-2; + padding: 0 1; + } + """ + + def compose(self): + yield Label("", id="cmd-completions", markup=False) + yield Label(":", id="cmd-input", markup=False) + + def show_command(self, text: str = "", completions: list[str] | None = None, index: int = 0) -> None: + self.display = True + self.update_command(text) + self.update_completions(completions or [], index) + + def hide_command(self) -> None: + self.display = False + self.update_command("") + self.update_completions([], 0) + + def update_command(self, text: str) -> None: + try: + self.query_one("#cmd-input", Label).update(f":{text}") + except Exception: + pass + + def update_completions(self, completions: list[str], index: int = 0) -> None: + try: + lbl = self.query_one("#cmd-completions", Label) + if not completions: + lbl.update("") + return + idx = index % len(completions) + parts = [f"[{item}]" if i == idx else item for i, item in enumerate(completions[:10])] + lbl.update(" ".join(parts)) + except Exception: + pass diff --git a/src/vimkit/prompt_render.py b/src/vimkit/prompt_render.py new file mode 100644 index 0000000..e108be1 --- /dev/null +++ b/src/vimkit/prompt_render.py @@ -0,0 +1,12 @@ +"""Rendering helpers for portable VimEditor prompt widgets.""" +from __future__ import annotations + +WAVE_COLORS = ["#ff6b6b", "#ffa06a", "#ffd93d", "#6bcb77", "#4d96ff", "#c77dff"] + + +def wave(frame: int) -> str: + glyphs = "⬡ fleet ❯" + return "".join( + f"[bold {WAVE_COLORS[(i + frame) % len(WAVE_COLORS)]}]{ch}[/]" + for i, ch in enumerate(glyphs) + ) diff --git a/src/vimkit/state.py b/src/vimkit/state.py new file mode 100644 index 0000000..8345504 --- /dev/null +++ b/src/vimkit/state.py @@ -0,0 +1,44 @@ +"""Mutable session state for VimEditor widgets.""" +from __future__ import annotations + +from dataclasses import dataclass, field +from pathlib import Path + +from vimkit.extensions import VimExtension + + +@dataclass +class VimSessionState: + """Editor-local state separate from widget composition and rendering.""" + + notes_mode: bool = False + rows: int = 5 + cwd: Path = field(default_factory=Path.cwd) + cmd_buf: str = "" + op_buf: str = "" + completions: list[str] = field(default_factory=list) + comp_idx: int = 0 + count_buf: str = "" + find_char_pending: str | None = None + last_find: tuple[str, bool] | None = None + replace_pending: bool = False + last_action: tuple | None = None + cmd_history: list[str] = field(default_factory=list) + cmd_hist_idx: int = -1 + buffer_path: Path | None = None + pinned: bool = False + extensions: list[VimExtension] = field(default_factory=list) + + def remember_command(self, cmd: str, *, max_history: int = 50) -> None: + if not cmd: + return + if not self.cmd_history or self.cmd_history[-1] != cmd: + self.cmd_history.append(cmd) + if len(self.cmd_history) > max_history: + self.cmd_history.pop(0) + + def reset_command_mode(self) -> None: + self.cmd_hist_idx = -1 + self.cmd_buf = "" + self.completions.clear() + self.comp_idx = 0 diff --git a/src/vimkit/theme.py b/src/vimkit/theme.py new file mode 100644 index 0000000..625c48d --- /dev/null +++ b/src/vimkit/theme.py @@ -0,0 +1,98 @@ +"""Theme registration helpers for VimKit.""" +from __future__ import annotations + +from textual.app import App + + +def register_cyberdream(app: App) -> None: + """Register the default VimKit app theme and TextArea syntax theme.""" + from rich.style import Style as _S + from textual._text_area_theme import TextAreaTheme as _TAT + from textual.theme import Theme as _Theme + + cyber_app = _Theme( + name="cyberdream", + primary="#5ea1ff", + secondary="#bd5eff", + warning="#ffbd5e", + error="#ff6e5e", + success="#5eff6c", + accent="#5ef1ff", + foreground="#ffffff", + background="#16181a", + surface="#1e2124", + panel="#3c4048", + boost="#3c4048", + dark=True, + variables={ + "primary-darken-1": "#4d8fe0", + "primary-darken-2": "#3b7abf", + "primary-darken-3": "#2a659f", + "primary-lighten-1": "#7ab5ff", + "primary-lighten-2": "#9ac8ff", + "surface-darken-1": "#16181a", + "surface-darken-2": "#121416", + "text-muted": "#7b8496", + }, + ) + app.register_theme(cyber_app) + + cyber_ta = _TAT( + name="cyberdream", + base_style=_S(color="#ffffff", bgcolor="#16181a"), + gutter_style=_S(color="#7b8496", bgcolor="#1e2124"), + cursor_style=_S(color="#16181a", bgcolor="#5ea1ff"), + cursor_line_style=_S(bgcolor="#1e2124"), + cursor_line_gutter_style=_S(color="#ffffff", bgcolor="#1e2124"), + bracket_matching_style=_S(bgcolor="#3c4048", bold=True), + selection_style=_S(bgcolor="#3c4048"), + syntax_styles={ + "string": _S(color="#5eff6c"), + "string.documentation": _S(color="#5eff6c"), + "comment": _S(color="#7b8496", italic=True), + "keyword": _S(color="#ff5ef1", bold=True), + "keyword.function": _S(color="#ff5ef1", bold=True), + "keyword.return": _S(color="#ff5ef1"), + "keyword.operator": _S(color="#5ef1ff"), + "operator": _S(color="#5ef1ff"), + "repeat": _S(color="#ff5ef1"), + "exception": _S(color="#ff6e5e"), + "include": _S(color="#bd5eff"), + "conditional": _S(color="#ff5ef1"), + "number": _S(color="#ffbd5e"), + "float": _S(color="#ffbd5e"), + "boolean": _S(color="#ffbd5e"), + "constant.builtin": _S(color="#ffbd5e"), + "class": _S(color="#f1ff5e", bold=True), + "type": _S(color="#f1ff5e"), + "type.class": _S(color="#f1ff5e", bold=True), + "type.builtin": _S(color="#5ef1ff"), + "function": _S(color="#5ea1ff"), + "function.call": _S(color="#5ea1ff"), + "method": _S(color="#5ea1ff"), + "method.call": _S(color="#5ea1ff"), + "variable.builtin": _S(color="#ff5ea0"), + "tag": _S(color="#5ea1ff"), + "yaml.field": _S(color="#5ef1ff"), + "json.label": _S(color="#5ef1ff"), + "json.null": _S(color="#ffbd5e"), + "toml.type": _S(color="#ff5ef1"), + "toml.datetime": _S(color="#ffbd5e"), + "css.property": _S(color="#5ea1ff"), + "regex.punctuation.bracket": _S(color="#5ef1ff"), + "regex.operator": _S(color="#ff5ea0"), + "heading": _S(color="#5ea1ff", bold=True), + "heading.marker": _S(color="#bd5eff"), + "bold": _S(bold=True), + "italic": _S(italic=True), + "strikethrough": _S(strike=True), + "link.label": _S(color="#5ea1ff"), + "link.uri": _S(color="#5ef1ff"), + "list.marker": _S(color="#bd5eff"), + "inline_code": _S(color="#5eff6c", bgcolor="#1e2124"), + "punctuation.bracket": _S(color="#7b8496"), + "punctuation.delimiter": _S(color="#7b8496"), + "punctuation.special": _S(color="#ff5ea0"), + }, + ) + register_cyberdream._ta_theme = cyber_ta diff --git a/src/vimkit/workbench.py b/src/vimkit/workbench.py new file mode 100644 index 0000000..a09bcdd --- /dev/null +++ b/src/vimkit/workbench.py @@ -0,0 +1,133 @@ +"""Composite vimkit widgets that add editor-local chrome around VimEditor.""" +from __future__ import annotations + +from textual import on +from textual.app import ComposeResult +from textual.containers import Vertical +from textual.widget import Widget +from textual.widgets import Static + +from vimkit.editor import VimEditor +from vimkit.extensions import VimExtension +from vimkit.host import VimHost +from vimkit.messages import BufferPathChanged, ModeChanged +from vimkit.panels import CommandBar + + +class VimTopBar(Widget): + """Editor-local header showing file, mode, cwd, and dirty state.""" + + DEFAULT_CSS = """ + VimTopBar { + height: 1; + background: $surface-darken-2; + color: $text-muted; + padding: 0 1; + } + """ + + def compose(self) -> ComposeResult: + yield Static("", id="vim-topbar-label") + + def update_editor(self, editor: VimEditor) -> None: + path = editor.current_path.name if editor.current_path is not None else "[No Name]" + mode = editor.mode.value + dirty = " [+]" if editor.is_modified else "" + cwd = editor.cwd.name or str(editor.cwd) + self.query_one("#vim-topbar-label", Static).update(f"{mode} {path}{dirty} · {cwd}") + + +class VimStatusBar(Widget): + """Editor-local status line showing cursor, command hints, and file state.""" + + DEFAULT_CSS = """ + VimStatusBar { + height: 1; + background: $surface-darken-1; + color: $text-muted; + padding: 0 1; + } + """ + + def compose(self) -> ComposeResult: + yield Static("", id="vim-statusbar-label") + + def update_editor(self, editor: VimEditor) -> None: + row, col = editor.cursor_location + mode = editor.mode.name.lower() + file_label = editor.current_path.name if editor.current_path is not None else "scratch" + dirty = "modified" if editor.is_modified else "saved" + self.query_one("#vim-statusbar-label", Static).update( + f"{file_label} · {mode} · {dirty} · ln {row + 1}, col {col + 1}" + ) + + +class VimWorkbench(Widget): + """Composite editor surface with optional vimkit-owned chrome.""" + + DEFAULT_CSS = """ + VimWorkbench { + width: 1fr; + height: auto; + background: $surface; + layout: vertical; + } + VimWorkbench.-full { + height: 1fr; + } + VimWorkbench.-compact { + min-height: 4; + } + """ + + def __init__( + self, + *, + editor_id: str, + notes_mode: bool = False, + extensions: list[VimExtension] | None = None, + host: VimHost | None = None, + chrome: str = "compact", + **kwargs, + ) -> None: + super().__init__(**kwargs) + self._editor_id = editor_id + self._notes_mode = notes_mode + self._extensions = list(extensions or []) + self._host = host + self._chrome = chrome + + def compose(self) -> ComposeResult: + if self._chrome == "full": + yield VimTopBar(id="vim-topbar") + yield VimEditor( + notes_mode=self._notes_mode, + id=self._editor_id, + extensions=self._extensions, + host=self._host, + ) + if self._chrome in ("full", "compact"): + yield CommandBar(id="vim-command-bar") + yield VimStatusBar(id="vim-statusbar") + + def on_mount(self) -> None: + self.set_class(self._chrome == "full", "-full") + self.set_class(self._chrome == "compact", "-compact") + self.set_interval(0.15, self._refresh_chrome) + self._refresh_chrome() + + @property + def editor(self) -> VimEditor: + return self.query_one(f"#{self._editor_id}", VimEditor) + + def _refresh_chrome(self) -> None: + editor = self.editor + if self._chrome == "full": + self.query_one("#vim-topbar", VimTopBar).update_editor(editor) + if self._chrome in ("full", "compact"): + self.query_one("#vim-statusbar", VimStatusBar).update_editor(editor) + + @on(ModeChanged) + @on(BufferPathChanged) + def _editor_changed(self, _: object) -> None: + self._refresh_chrome() diff --git a/uv.lock b/uv.lock new file mode 100644 index 0000000..d665fc3 --- /dev/null +++ b/uv.lock @@ -0,0 +1,773 @@ +version = 1 +revision = 3 +requires-python = ">=3.14" + +[[package]] +name = "certifi" +version = "2026.6.17" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/c9/c7/424b75da314c1045981bd9777432fad05a9e0c69daa4ed7e308bbaffe405/certifi-2026.6.17.tar.gz", hash = "sha256:024c88eeec92ca068db80f02b8b07c9cef7b9fe261d1d535abfd5abd6f6af432", size = 134594, upload-time = "2026-06-17T10:31:07.894Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ef/2f/c5464532e965badff2f4c4c1a3a83f5697f0d7c407ed0cda44aaa99bb451/certifi-2026.6.17-py3-none-any.whl", hash = "sha256:2227dcbaafe0d2f59279d1762ddddc37783ed4354594f194ffc31d20f41fc3db", size = 133289, upload-time = "2026-06-17T10:31:06.348Z" }, +] + +[[package]] +name = "cffi" +version = "2.1.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pycparser", marker = "implementation_name != 'PyPy'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/57/5f/ff100cae70ebe9d8df1c01a00e510e45d9adb5c1fdda84791b199141de97/cffi-2.1.0.tar.gz", hash = "sha256:efc1cdd798b1aaf39b4610bba7aad28c9bea9b910f25c784ccf9ec1fa719d1f9", size = 531036, upload-time = "2026-07-06T21:34:30.382Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d8/f0/81478e482afa03f6d18dc8f2afb5edc45b3080853b634b5ed91961be0998/cffi-2.1.0-cp314-cp314-ios_13_0_arm64_iphoneos.whl", hash = "sha256:d2117334c3af3bdcb9a88522b844a2bdb5efdc4f71c6c822df55486ae1c3347a", size = 194142, upload-time = "2026-07-06T21:33:23.657Z" }, + { url = "https://files.pythonhosted.org/packages/7d/95/8de304305cd9204974b0ca051b86d307cafca13aa575a0ef1b44d92c0d8c/cffi-2.1.0-cp314-cp314-ios_13_0_arm64_iphonesimulator.whl", hash = "sha256:702c436735fbe99d59ada02a1f65cfc0d31c0ee8b7290912f8fbc5cd1e4b16c3", size = 196819, upload-time = "2026-07-06T21:33:25.007Z" }, + { url = "https://files.pythonhosted.org/packages/2e/d2/065fcae1c73979fac8e054462478d0ff8a29c40cdc2ed7ea5676a061df53/cffi-2.1.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:276f20fffd7b396e12516ba8edf9509210ac248cbbc5acbc39cd512f9f59ebe6", size = 222353, upload-time = "2026-07-06T21:33:29.178Z" }, + { url = "https://files.pythonhosted.org/packages/cc/d7/97d3136f81db489ec8d1d67748c110d6c994268fd7528014aa9f2b085e4e/cffi-2.1.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:d53d10f7da99ae46f7373b9150393e9c5eab9b224909982b43832668de4779f5", size = 221593, upload-time = "2026-07-06T21:33:33.044Z" }, + { url = "https://files.pythonhosted.org/packages/d3/27/93195977168ee63aed233a1a0993a2178798654d1f4bddcdd321d6fd3b21/cffi-2.1.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:c351efb95e832a853a29361675f33a7ce53de1a109cd73fd47af0712213aa4ce", size = 225146, upload-time = "2026-07-06T21:33:34.224Z" }, + { url = "https://files.pythonhosted.org/packages/b3/c1/6dbd291ee2ae5a50a034aa057207081f545923bbf15dad4511e985aafff5/cffi-2.1.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:dbf7c7a88e2bac086f06d14577332760bdeecc42bdec8ac4077f6260557d9326", size = 223240, upload-time = "2026-07-06T21:33:35.57Z" }, + { url = "https://files.pythonhosted.org/packages/14/d0/117dcd9209255ad8571fbc8c92ef32593a1d294dcec91ddc4e4db50606f2/cffi-2.1.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:eb4e8997a49aa2c08a3e43c9045d224448b8941d88e7ac163c7d383e560cbf98", size = 223899, upload-time = "2026-07-06T21:33:39.514Z" }, + { url = "https://files.pythonhosted.org/packages/cc/82/3d5c705acb7abbba9bbd7d79b8e62e0f25b6120eb7ae6ac49f1b721722fe/cffi-2.1.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:ac0f1a2d0cfa7eea3f2aaf006ab6e70e8feeb16b75d65b7e5939982ca2f11056", size = 223933, upload-time = "2026-07-06T21:33:43.603Z" }, + { url = "https://files.pythonhosted.org/packages/6c/d0/47e338384ab6b1004241002fa616301020cea4fc95f283506565d252f276/cffi-2.1.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:c16914df9fb7f500e440e6875fa23ff5e0b31db01fa9c06af98d59a91f0dc2e4", size = 226749, upload-time = "2026-07-06T21:33:45.046Z" }, + { url = "https://files.pythonhosted.org/packages/70/25/65bd5b58ea4bfdfc15cde02cb5365f89ef8ab8b2adfb8fe5c4bd4233382f/cffi-2.1.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:5ecbd0499275d57506d397eebe1981cee87b47fcd9ef5c22cab7ed7644a39a94", size = 225703, upload-time = "2026-07-06T21:33:46.374Z" }, + { url = "https://files.pythonhosted.org/packages/55/c7/8c8c50cb11c6750051daf12164098a9a6f027ac4356967fd4d800a07f242/cffi-2.1.0-cp315-cp315-ios_13_0_arm64_iphoneos.whl", hash = "sha256:2e9dabb9abcb7ad15938c7196ad5c1718a4e6d33cc79b4c0209bdb64c4a54a5c", size = 194121, upload-time = "2026-07-06T21:33:56.109Z" }, + { url = "https://files.pythonhosted.org/packages/99/e2/67680bf19a6b60d2bb7ff83baefa2a4c3d2d7dc0f3277034b802e1fc504c/cffi-2.1.0-cp315-cp315-ios_13_0_arm64_iphonesimulator.whl", hash = "sha256:37f525a7e7e50c017fdebe58b787be310ad59357ae43a053943a6e1a6c526001", size = 196820, upload-time = "2026-07-06T21:33:57.288Z" }, + { url = "https://files.pythonhosted.org/packages/ef/c3/ad299dc38f3583f8d916b299f028af418a9ec98bc695fcbebeae7420691c/cffi-2.1.0-cp315-cp315-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:90bec57cf82089383bd06a605b3eb8daebf7e5a668520beaf6e327a83a947699", size = 222342, upload-time = "2026-07-06T21:34:01.814Z" }, + { url = "https://files.pythonhosted.org/packages/e6/3f/0b04a700dd64f465c93020253a793a82c9b4dff9961f48facd0df945d9b8/cffi-2.1.0-cp315-cp315-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:7d3538f9c0e50670f4deb93dbb696576e60590369cae2faf7de681e597a8a1f1", size = 221649, upload-time = "2026-07-06T21:34:06.157Z" }, + { url = "https://files.pythonhosted.org/packages/5d/7c/b7379a5704c79eda57ce075869ba70a0368d1c850f803b3c0d078d39dcaf/cffi-2.1.0-cp315-cp315-musllinux_1_2_aarch64.whl", hash = "sha256:8f9ec95b8a043d3dfbc74d9abc6f7baf524dd27a8dc160b0a32ff9cdab650c28", size = 225203, upload-time = "2026-07-06T21:34:07.489Z" }, + { url = "https://files.pythonhosted.org/packages/5a/02/d5e6c43ea85c41bda2a184a3418f195fe7cf602967a8d2b94e085b83deef/cffi-2.1.0-cp315-cp315-musllinux_1_2_x86_64.whl", hash = "sha256:af5e2915d41fe6c961694d7bfdc8562942638200f3ce2765dfb8b745cf997629", size = 223263, upload-time = "2026-07-06T21:34:08.712Z" }, + { url = "https://files.pythonhosted.org/packages/e0/27/1d0b408497e41a74795af122d7b603c418c5fed0171450f899afd04e594f/cffi-2.1.0-cp315-cp315t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:0520e1f4c35f44e209cbbb421b67eec42e6a157f59444dfb6058874ff3610e5d", size = 223904, upload-time = "2026-07-06T21:34:12.606Z" }, + { url = "https://files.pythonhosted.org/packages/19/e5/d3cc82a4a0be7902af279c04181ad038449c096734464a5ae1de3e1401bd/cffi-2.1.0-cp315-cp315t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:0611e7ebf90573a535ebdc33ae9da222d037853983e13359f580fab781ca017f", size = 223843, upload-time = "2026-07-06T21:34:17.509Z" }, + { url = "https://files.pythonhosted.org/packages/b9/65/b434abc97ce7cecc2c640fde160507c0ecc7e21544b483ba3325d2e2ea17/cffi-2.1.0-cp315-cp315t-musllinux_1_2_aarch64.whl", hash = "sha256:86cf8755a791f72c85dc287128cc62d4f24d392e3f1e15837245623f4a33cccc", size = 226773, upload-time = "2026-07-06T21:34:19.05Z" }, + { url = "https://files.pythonhosted.org/packages/b5/9f/d4dc66ca651eb1145a133314cda721abf13cfac3d28c4a0402263ae6ad75/cffi-2.1.0-cp315-cp315t-musllinux_1_2_x86_64.whl", hash = "sha256:ba00f661f8ba35d075c937174e27c2c421cec3942fd2e0ea3e66996757c0fdd9", size = 225719, upload-time = "2026-07-06T21:34:20.576Z" }, +] + +[[package]] +name = "charset-normalizer" +version = "3.4.9" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/bd/2a/23f34ec9d04624958e137efdc394888716353190e75f25dd22c7a2c7a8aa/charset_normalizer-3.4.9.tar.gz", hash = "sha256:673611bbd43f0810bec0b0f028ddeaaa501190339cac411f347ac76917c3ae7b", size = 152439, upload-time = "2026-07-07T14:34:58.454Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/7e/8d/496817fa0944239ecae662dd57ea765cfeaec6a735f9f025d4b7b72e7143/charset_normalizer-3.4.9-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:0327fcd59a935777d83410750c50600ee9571af2846f71ce40f25b13da1ef380", size = 317253, upload-time = "2026-07-07T14:33:54.994Z" }, + { url = "https://files.pythonhosted.org/packages/2b/f9/ef4a69ea338ad3c0deceea0f5f7d2380ae8b52132b06d652cb0d2cd86706/charset_normalizer-3.4.9-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:8a79d9f4d8001473a30c163556b3c3bfebec837495a412dde78b51672f6134f9", size = 215898, upload-time = "2026-07-07T14:33:56.334Z" }, + { url = "https://files.pythonhosted.org/packages/8c/e7/5ddfd76fc061eb52de219658a4aa431cbacadf0a0219c8854f00da50d289/charset_normalizer-3.4.9-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:33bdcc2a32c0a0e861f60841a512c8acc658c87c2ac59d89e3a46dacf7d866e4", size = 236718, upload-time = "2026-07-07T14:33:57.9Z" }, + { url = "https://files.pythonhosted.org/packages/49/ba/768fa3f36048d81c477a0ce61f813bc1454d80917ccfe550abd9f44f5e24/charset_normalizer-3.4.9-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:f840ed6d8ecba8255df8c42b87fadeda98ddfc6eeec05e2dc66e26d46dd6f58a", size = 232519, upload-time = "2026-07-07T14:33:59.811Z" }, + { url = "https://files.pythonhosted.org/packages/f4/c4/b3e049d2aa3766180c78507110543d9d50894cc97f57de543f1be521dcdc/charset_normalizer-3.4.9-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c25fe15c70c59eb7c5ce8c06a1f3fa1da0ecc5ea1e7a5922c40fd2fa9b0d5046", size = 223143, upload-time = "2026-07-07T14:34:01.517Z" }, + { url = "https://files.pythonhosted.org/packages/19/79/55c32d06d76ae4feafe053f061f3e3ab70bcf19f4007797ce8c3efda7830/charset_normalizer-3.4.9-cp314-cp314-manylinux_2_31_armv7l.whl", hash = "sha256:f7fb7d750cfa0a070d2c24e831fd3481019a60dd317ea2b39acbcebc08b6ed81", size = 206742, upload-time = "2026-07-07T14:34:03.04Z" }, + { url = "https://files.pythonhosted.org/packages/10/e0/47c079dd82d217c807479cd59ffd30af56307ea31c108b75758970459ad3/charset_normalizer-3.4.9-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:4d1c96a7a18b9690a4d46df09e3e3382406ae3213727cd1019ebade1c4a81917", size = 219191, upload-time = "2026-07-07T14:34:04.657Z" }, + { url = "https://files.pythonhosted.org/packages/42/ab/b9bc2e77d6b44a7e46ef62ec5cac1c9a6ba7b9135a5d560f002696ec9995/charset_normalizer-3.4.9-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:a4cfde78a9f2880208d16a93b795726a3017d5977e08d1e162a7a31322479c41", size = 218328, upload-time = "2026-07-07T14:34:06.115Z" }, + { url = "https://files.pythonhosted.org/packages/f1/78/c9c71d599f5aa2d42bcdd35cbbd46d7f535351a57e40ff7d8e5a7e219401/charset_normalizer-3.4.9-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:d4d6fcde76f94f5cb9e43e9e9a61f16dacefd228cbbf6f1a09bd9b219a92f1a1", size = 207406, upload-time = "2026-07-07T14:34:07.554Z" }, + { url = "https://files.pythonhosted.org/packages/f6/39/c914445c321a845097ce4f6ac7de9a18228a77b766272125a1ce00d851eb/charset_normalizer-3.4.9-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:898f0e9068ca27d37f8e83a5b962821df851532e6c4a7d615c1c033f9da6eedf", size = 225157, upload-time = "2026-07-07T14:34:09.061Z" }, + { url = "https://files.pythonhosted.org/packages/9b/f2/c0d4b8508565a36bc5c624e88ed297f5b0b1095011034d7f5b83a69908b5/charset_normalizer-3.4.9-cp314-cp314-win32.whl", hash = "sha256:c1c948747b03be832dceed96ca815cef7360de9aa19d37c730f8e3f6101aca48", size = 151095, upload-time = "2026-07-07T14:34:10.901Z" }, + { url = "https://files.pythonhosted.org/packages/49/fd/a1d26144398c67486422a72bf5812cda22cb4ccfcd95a290fb41ceb4b8e2/charset_normalizer-3.4.9-cp314-cp314-win_amd64.whl", hash = "sha256:16b65ea0f2465b6fb52aa22de5eca612aa964ddfec00a912e26f4656cbef890b", size = 162796, upload-time = "2026-07-07T14:34:12.47Z" }, + { url = "https://files.pythonhosted.org/packages/20/95/d75e82f8ce9fd323ebf059c16c9aadefb22a1ecde13b7840b35835e4886c/charset_normalizer-3.4.9-cp314-cp314-win_arm64.whl", hash = "sha256:40a126142a56b2dfc0aacbad1de8310cbf60da7656db0e6b16eebd48e3e93519", size = 153334, upload-time = "2026-07-07T14:34:14.044Z" }, + { url = "https://files.pythonhosted.org/packages/00/5e/17398df3a139985ba9d11ed072531986f408c8fca952835ef1ab1820c02b/charset_normalizer-3.4.9-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:609b3ba8fcc0fb5ab7af00719d0fb6ad0cb518e48e7712d12fd68f1327951198", size = 338848, upload-time = "2026-07-07T14:34:15.688Z" }, + { url = "https://files.pythonhosted.org/packages/cd/91/7253a32e86b7e1d1239b1b36ba6dd0f021a21107ab33054b53119cc083b9/charset_normalizer-3.4.9-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:51447e9aa2684679af07ca5021c3db526e0284347ebf4ffcec1154c3350cfe32", size = 223022, upload-time = "2026-07-07T14:34:17.248Z" }, + { url = "https://files.pythonhosted.org/packages/cb/32/2e64bd2be10e89c61e57ebe6a93fd98ae88eb7ebe414b5121f22c96c69eb/charset_normalizer-3.4.9-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:cc1b0fff8ead343dae06305f954eb8468ba0ec1a97881f42489d198e4ce3c632", size = 241590, upload-time = "2026-07-07T14:34:18.813Z" }, + { url = "https://files.pythonhosted.org/packages/3d/ef/d96ec496cfea0c21db43b0ad03891308b02388d054cc902cf0e5a1ad6a88/charset_normalizer-3.4.9-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:fa36ec09ef71d158186bc79e359ff5fdd6e7996fe8ab638f00d6b93139ba4fcf", size = 239584, upload-time = "2026-07-07T14:34:20.52Z" }, + { url = "https://files.pythonhosted.org/packages/d4/ce/9af95f7876194bd7a14e3dfe4a4de2e0bff02666a3910d72beafd06cc297/charset_normalizer-3.4.9-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:df115d4d83168fdf2cae48ef1ff6d1cb4c466364e30861b37121de0f3bf1b990", size = 230224, upload-time = "2026-07-07T14:34:22.189Z" }, + { url = "https://files.pythonhosted.org/packages/52/94/af74dde74a3996bd959c350709bfe50e297823d70a8c1cbd54b838880863/charset_normalizer-3.4.9-cp314-cp314t-manylinux_2_31_armv7l.whl", hash = "sha256:f86c6358749bd4fda175388691e3ba8c46e24c5347d0afd20f9b7edfc9faf07d", size = 212667, upload-time = "2026-07-07T14:34:23.857Z" }, + { url = "https://files.pythonhosted.org/packages/ee/f0/f1c4fe746c395922961b5916ed1d7d6e7d4c84851d19ed43cc89980ec953/charset_normalizer-3.4.9-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:32286a2c8d167e897177b673176c1e3e00d4057caf5d2b64eef9a3666b03018e", size = 227179, upload-time = "2026-07-07T14:34:25.586Z" }, + { url = "https://files.pythonhosted.org/packages/e4/56/6c745619ac397e8871e2bcd3cea1eec86b877488f33888b3aef5c3ed506e/charset_normalizer-3.4.9-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:83aed2c10721ddd90f68140685391b50811a880af20654c59af6b6c66c40513c", size = 225372, upload-time = "2026-07-07T14:34:27.212Z" }, + { url = "https://files.pythonhosted.org/packages/78/ad/98aae8630ac71f16711968e38a5acfecce41b778bf2f0312851020f565a8/charset_normalizer-3.4.9-cp314-cp314t-musllinux_1_2_armv7l.whl", hash = "sha256:cd6c3d4b783c556fa00bf540854e42f135e2f256abd29669fcd0da0f2dec79c2", size = 215222, upload-time = "2026-07-07T14:34:28.774Z" }, + { url = "https://files.pythonhosted.org/packages/f7/40/9593d54209765207a7f11073c06494c1721e4ca4a0a426c597679bf7f91e/charset_normalizer-3.4.9-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:ee2f2a527e3c1a6e6411eb4209642e138b544a2d72fe5d0d76daf77b24063534", size = 231958, upload-time = "2026-07-07T14:34:30.345Z" }, + { url = "https://files.pythonhosted.org/packages/b1/27/693ee5e8a18191eb38647360c51cd505013e2bd3b366aa43fd5344c21e3c/charset_normalizer-3.4.9-cp314-cp314t-win32.whl", hash = "sha256:0d861473f743244d349b50f850d10eb87aeb22bbdcc8e64f79273c94af5a8226", size = 155580, upload-time = "2026-07-07T14:34:31.884Z" }, + { url = "https://files.pythonhosted.org/packages/80/3f/bd97d3d9c613013d07cb7733d299385b41df37f0471310f5a73dc359f0b8/charset_normalizer-3.4.9-cp314-cp314t-win_amd64.whl", hash = "sha256:9b8e0f3107e2200b76f6054de99016eac3ee6762713587b36baaa7e4bd2ae177", size = 167620, upload-time = "2026-07-07T14:34:33.438Z" }, + { url = "https://files.pythonhosted.org/packages/3d/c6/eee9dca4439b1061f76373f06ea855678cc4a64c1c3c90b50e479edbb8eb/charset_normalizer-3.4.9-cp314-cp314t-win_arm64.whl", hash = "sha256:19ac87f93086ce37b86e098888555c4b4bc48102279bae3350098c0ed664b501", size = 158037, upload-time = "2026-07-07T14:34:35.018Z" }, + { url = "https://files.pythonhosted.org/packages/98/2b/f97f1c193fb855c345d678f5077d6926034db0722df74c8f057020e05a25/charset_normalizer-3.4.9-py3-none-any.whl", hash = "sha256:68e5f26a1ad57ded6d1cfb85331d1c1a195314756471d97758c48498bb4dcdf5", size = 64538, upload-time = "2026-07-07T14:34:56.993Z" }, +] + +[[package]] +name = "cryptography" +version = "49.0.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "cffi", marker = "platform_python_implementation != 'PyPy'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/1f/99/d1c90d6041656cc6ee229dc99cd67fd0cd5aec3c5f7d72fffc27cc750054/cryptography-49.0.0.tar.gz", hash = "sha256:f89660a348f4f78a92366240a61404e337586ef7f5909a2fef59ca88ef505493", size = 854345, upload-time = "2026-06-12T20:02:30.512Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/09/41/3797cfaf69cae04a13ee78ebd83f0678d9c02b4779d21ce24445326f1a69/cryptography-49.0.0-cp311-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:36d1709f992593689b45bda411498d62c6e365f2ca00b84657d4dadd24de16db", size = 4692978, upload-time = "2026-06-12T20:01:21.305Z" }, + { url = "https://files.pythonhosted.org/packages/e6/8b/43011f7ebe515a8aa20d61f290a326cd890c2e738e16e59eaff8d9c3a412/cryptography-49.0.0-cp311-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:0e959b578856a3924bc0cbb710fc12c387b9412a951389f3ca61704a9e25f325", size = 4716422, upload-time = "2026-06-12T20:01:48.566Z" }, + { url = "https://files.pythonhosted.org/packages/4a/91/01ce7303a4579e6d3a6abef01bd322848e9ea7a219adcabc5048b9033571/cryptography-49.0.0-cp311-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:53ecee2e23f7169b6117e99fc8a944e5e50f79e69758a83b52a00cb98ab2b2d2", size = 4700503, upload-time = "2026-06-12T20:02:47.091Z" }, + { url = "https://files.pythonhosted.org/packages/20/2c/0622f20ff02b2ef32558733443805dc82fd4c275be01b2d19d14676f3a1b/cryptography-49.0.0-cp311-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:2afe9051da7ae7bd5905da5a949280c7d2bb75682e188f650a9d0f2756b834c6", size = 4749683, upload-time = "2026-06-12T20:02:03.335Z" }, + { url = "https://files.pythonhosted.org/packages/a3/5b/c5246635d5fd3b64e0d45ae10e99fd32fe9676a79915ccfe5a61ba9af1a5/cryptography-49.0.0-cp311-abi3-manylinux_2_31_armv7l.whl", hash = "sha256:0b82e28ee398a386f0807bba7884d30f25218855690f45115831bcce5d90822c", size = 4337874, upload-time = "2026-06-12T20:02:54.323Z" }, + { url = "https://files.pythonhosted.org/packages/6d/88/05563c7fe2e914e87d1a536d06fe83e66b4e1d95cb593e05aea375531da8/cryptography-49.0.0-cp311-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:ccac2bfebc306b862133e3bb71f3f6ee8bb525240089b2d952e4144b3a6d5da7", size = 4700283, upload-time = "2026-06-12T20:01:34.822Z" }, + { url = "https://files.pythonhosted.org/packages/a9/3c/f3ad17eecc1a57b0ba236dc01f90e783c51f4a2f35f64777cc4f47a184b2/cryptography-49.0.0-cp311-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:cbc77da8c523d5abd028635ba850a6966fcee2c82e2bf65a41d1d8afe0f98be9", size = 4749290, upload-time = "2026-06-12T20:01:30.848Z" }, + { url = "https://files.pythonhosted.org/packages/4f/01/339573cf1023163a400b0b5d16f6d507de413b9f60be6fd1b77feeaf6737/cryptography-49.0.0-cp311-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:b87e65d263b3e5d3bb92a57e2a6638e2f31110fa7aa890c7b2dbba42248d0a3f", size = 4834612, upload-time = "2026-06-12T20:01:29.246Z" }, + { url = "https://files.pythonhosted.org/packages/71/fd/577302e213a1be9468f92d1afef66fcf1ef83d516819d9992ca547f592bd/cryptography-49.0.0-cp311-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:66ec79c3904820572d7e987abdf304281f141d37ad9a489b8e97066e7b9b6459", size = 4980804, upload-time = "2026-06-12T20:01:42.853Z" }, + { url = "https://files.pythonhosted.org/packages/86/12/c48a424f38db03027be9f7ed5c7dc5de9933dbee992865f98b13727a009d/cryptography-49.0.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:196ecd6a36e4e9aa10270393bb98d8df88fccee0bf1e5128b91ae4eb4375896d", size = 4678835, upload-time = "2026-06-12T20:02:48.743Z" }, + { url = "https://files.pythonhosted.org/packages/68/28/8a3ad4653662c93fc44dc4e5d8fd374c25c42e07b34bbfbadf49cf57a5a8/cryptography-49.0.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:7abcee80084cda3f7691f3eb1ce480d8df49cec637b429aa35986c1de71738aa", size = 4697239, upload-time = "2026-06-12T20:02:56.03Z" }, + { url = "https://files.pythonhosted.org/packages/a8/b2/2193fc74f81aee4f9b62733133b73b5176718932ed8f2e4b03fa040480a6/cryptography-49.0.0-cp314-cp314t-manylinux_2_28_aarch64.whl", hash = "sha256:4ae387c9cb68ea569ca17e490d66d8142b81c3cc814bf179974b7d146e490bbb", size = 4685593, upload-time = "2026-06-12T20:02:50.666Z" }, + { url = "https://files.pythonhosted.org/packages/58/39/2d51306721330c486495853eda1c567880ff036de15a14c4b74f399934af/cryptography-49.0.0-cp314-cp314t-manylinux_2_28_x86_64.whl", hash = "sha256:c2bc30226390d60ea19d9f82b19db005fe0452154a23c1c410c12ea801e43561", size = 4731145, upload-time = "2026-06-12T20:02:16.832Z" }, + { url = "https://files.pythonhosted.org/packages/17/50/983e838c7fd0d87fd8c969bcdd328edaf5f756e38df5281637424c155873/cryptography-49.0.0-cp314-cp314t-manylinux_2_31_armv7l.whl", hash = "sha256:07cab27cc7b7e0fd28e5e26bb9eeedde5c135c868b46de4a27845abe94af6122", size = 4321719, upload-time = "2026-06-12T20:02:52.611Z" }, + { url = "https://files.pythonhosted.org/packages/a7/f5/8f571d7e27c55bce9f76f026143bcb1e040a4233149ecca0bea5fa5dd5f7/cryptography-49.0.0-cp314-cp314t-manylinux_2_34_aarch64.whl", hash = "sha256:b20133d204d2bb56ba047642199603876c872026ca53e79c35b83772ab2cc505", size = 4685209, upload-time = "2026-06-12T20:02:07.282Z" }, + { url = "https://files.pythonhosted.org/packages/11/2d/5e1fb307cb5931881516b464c98774b3f2c36b5d4bb9a2830253cf553cad/cryptography-49.0.0-cp314-cp314t-manylinux_2_34_x86_64.whl", hash = "sha256:d8ecde755e2e91bf773fc94e8c9d730cd7f2007004cb492263a794ec3899a1c8", size = 4730441, upload-time = "2026-06-12T20:02:01.469Z" }, + { url = "https://files.pythonhosted.org/packages/e4/c0/bff5a02ee731d207d6a1ed51732549d8c53d2bc8da1d10ec6f2844201d68/cryptography-49.0.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:e3fb64c420688e5319ae25113a354015abbd8dffbfbc41781a1ea66fc7622ac3", size = 4815869, upload-time = "2026-06-12T20:01:36.574Z" }, + { url = "https://files.pythonhosted.org/packages/b9/26/814681d14248d95d73d5c3eea0c39a94eb8302df966f670a2c60de90974b/cryptography-49.0.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:32703d93296f5c1f4b53349ad3a250c2cae0fdecd3a3dd5d47e616d8d616af27", size = 4960948, upload-time = "2026-06-12T20:02:18.688Z" }, + { url = "https://files.pythonhosted.org/packages/3d/df/40577043ca124e17012f408ddddaeb213b856336ac82ddb3bc915f39e29f/cryptography-49.0.0-cp39-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:f78ff2c9ed8dc2d036b0f4d640e22522213d047c1b14e61205a7e55c80a494d4", size = 4692429, upload-time = "2026-06-12T20:01:53.628Z" }, + { url = "https://files.pythonhosted.org/packages/2c/99/2d13299eb3dd27b02dcfaafcc91d6b5cb3329f7cbd6d8f51921acd566c1a/cryptography-49.0.0-cp39-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:35b151772baff2c74cba7fa290ceaff4c3b11c0c881eb93eb5dbc05a7cfbba18", size = 4700968, upload-time = "2026-06-12T20:02:45.383Z" }, + { url = "https://files.pythonhosted.org/packages/a5/4d/9c0cd02f95e2602dd5e563da149ee0830abef3537be8b34dc56281ebe27a/cryptography-49.0.0-cp39-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:0f21641cf4b30fca7aee061ced0ec7ad7b073518088b7c9969a297c0ae796c69", size = 4697758, upload-time = "2026-06-12T20:01:41.13Z" }, + { url = "https://files.pythonhosted.org/packages/b8/7b/62cbbab75d0659865bf0273790031544a0b16c8072d258f9428dcd8190dc/cryptography-49.0.0-cp39-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:6f2debedf9ca60cf1d5bd466475638af5130f89965605cd818484d19987d3a21", size = 4735983, upload-time = "2026-06-12T20:01:50.14Z" }, + { url = "https://files.pythonhosted.org/packages/6c/72/3e798c064bc39e471008075d0f9bc9daf77a80879c092e4a8e170c585ed4/cryptography-49.0.0-cp39-abi3-manylinux_2_31_armv7l.whl", hash = "sha256:8c25ceb16df5b9435f3f6a9829204985b0e0cbee3b48aacd432c7d2c850b44d9", size = 4334173, upload-time = "2026-06-12T20:01:44.743Z" }, + { url = "https://files.pythonhosted.org/packages/f0/ee/6fca21d1ac73e06f8bef71940abfd4d2f6472b4bca284d770f32bd4086f6/cryptography-49.0.0-cp39-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:28d8b15e6275f12c8a207dc309dfa957903c927d08d0cc937ee3f63f200693cc", size = 4697298, upload-time = "2026-06-12T20:02:20.918Z" }, + { url = "https://files.pythonhosted.org/packages/a0/84/84fe36f19caf857d61cb7fc9c63035a47ffabd84ea12d1d393148efa3615/cryptography-49.0.0-cp39-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:2400ef9c9e2299a25614eb1dea3db54a69b1349efd043bfac9c67630d136df36", size = 4735650, upload-time = "2026-06-12T20:02:41.389Z" }, + { url = "https://files.pythonhosted.org/packages/6c/a0/db537264e234f7273a73ec020873d6d6b39dfd8a53db78b550ca8320440e/cryptography-49.0.0-cp39-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:67e1d20ad9ef3a563c59ef22e7a8a0b8210bd26604369ea4a30a7c66aefe504e", size = 4834820, upload-time = "2026-06-12T20:01:51.847Z" }, + { url = "https://files.pythonhosted.org/packages/93/77/8df9eb486495979bccecd1062e2eaf435250e84437040295b57d09048b0b/cryptography-49.0.0-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:42b0684e0e40cf26122427802486f6d93aea593612603a94fbf260c7eb1e9c1b", size = 4967968, upload-time = "2026-06-12T20:02:12.524Z" }, +] + +[[package]] +name = "docutils" +version = "0.23" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/39/a4/5180d9afc57e8fca05601dd652bdff19604c218814037fe90ffc7625a50a/docutils-0.23.tar.gz", hash = "sha256:746f5060322511280a1e50eb76846ed6bf2342984b2ac04dc42caa1a8d78799e", size = 2303823, upload-time = "2026-05-27T17:41:06.934Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/32/91/30151a39f7570f448ed84529390628a651d7f27c87d73c9b887f8189695e/docutils-0.23-py3-none-any.whl", hash = "sha256:25d013af9bf23bc1c7b2b093dff4208166c53a94786c9e447808335ef1185fea", size = 634701, upload-time = "2026-05-27T17:40:58.442Z" }, +] + +[[package]] +name = "id" +version = "1.6.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "urllib3" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/6d/04/c2156091427636080787aac190019dc64096e56a23b7364d3c1764ee3a06/id-1.6.1.tar.gz", hash = "sha256:d0732d624fb46fd4e7bc4e5152f00214450953b9e772c182c1c22964def1a069", size = 18088, upload-time = "2026-02-04T16:19:41.26Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/42/77/de194443bf38daed9452139e960c632b0ef9f9a5dd9ce605fdf18ca9f1b1/id-1.6.1-py3-none-any.whl", hash = "sha256:f5ec41ed2629a508f5d0988eda142e190c9c6da971100612c4de9ad9f9b237ca", size = 14689, upload-time = "2026-02-04T16:19:40.051Z" }, +] + +[[package]] +name = "idna" +version = "3.18" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/cd/63/9496c57188a2ee585e0f1db071d75089a11e98aa86eb99d9d7618fc1edce/idna-3.18.tar.gz", hash = "sha256:ffb385a7e039654cef1ab9ef32c6fafe283c0c0467bba1d9029738ce4a14a848", size = 196711, upload-time = "2026-06-02T14:34:07.794Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/1e/5e/d4e9f1a599fb8e573b7b87160658329fbf28d19eac2718f51fc3def3aa5a/idna-3.18-py3-none-any.whl", hash = "sha256:7f952cbe720b688055e3f87de14f5c3e5fdaa8bc3928985c4077ca689de849a2", size = 65455, upload-time = "2026-06-02T14:34:06.319Z" }, +] + +[[package]] +name = "jaraco-classes" +version = "3.4.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "more-itertools" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/06/c0/ed4a27bc5571b99e3cff68f8a9fa5b56ff7df1c2251cc715a652ddd26402/jaraco.classes-3.4.0.tar.gz", hash = "sha256:47a024b51d0239c0dd8c8540c6c7f484be3b8fcf0b2d85c13825780d3b3f3acd", size = 11780, upload-time = "2024-03-31T07:27:36.643Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/7f/66/b15ce62552d84bbfcec9a4873ab79d993a1dd4edb922cbfccae192bd5b5f/jaraco.classes-3.4.0-py3-none-any.whl", hash = "sha256:f662826b6bed8cace05e7ff873ce0f9283b5c924470fe664fff1c2f00f581790", size = 6777, upload-time = "2024-03-31T07:27:34.792Z" }, +] + +[[package]] +name = "jaraco-context" +version = "6.1.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/af/50/4763cd07e722bb6285316d390a164bc7e479db9d90daa769f22578f698b4/jaraco_context-6.1.2.tar.gz", hash = "sha256:f1a6c9d391e661cc5b8d39861ff077a7dc24dc23833ccee564b234b81c82dfe3", size = 16801, upload-time = "2026-03-20T22:13:33.922Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f2/58/bc8954bda5fcda97bd7c19be11b85f91973d67a706ed4a3aec33e7de22db/jaraco_context-6.1.2-py3-none-any.whl", hash = "sha256:bf8150b79a2d5d91ae48629d8b427a8f7ba0e1097dd6202a9059f29a36379535", size = 7871, upload-time = "2026-03-20T22:13:32.808Z" }, +] + +[[package]] +name = "jaraco-functools" +version = "4.5.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "more-itertools" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/36/cf/ea4ef2920830dea3f5ab2ea4da6fb67724e6dca80ee2553788c3607243d0/jaraco_functools-4.5.0.tar.gz", hash = "sha256:3bb5665ea4a020cf78a7040e89154c77edadb3ca74f366479669c5999aa70b03", size = 20272, upload-time = "2026-05-15T21:34:10.025Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/96/9a/982e48afcffcd727a9144506720ffd4224b6b7e355c98641866f38b7c043/jaraco_functools-4.5.0-py3-none-any.whl", hash = "sha256:79ce39246eddbde4b3a03b77ea5f0f7878dc669b166a66cf3fa8e266aa3fa2f4", size = 10594, upload-time = "2026-05-15T21:34:08.595Z" }, +] + +[[package]] +name = "jeepney" +version = "0.9.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/7b/6f/357efd7602486741aa73ffc0617fb310a29b588ed0fd69c2399acbb85b0c/jeepney-0.9.0.tar.gz", hash = "sha256:cf0e9e845622b81e4a28df94c40345400256ec608d0e55bb8a3feaa9163f5732", size = 106758, upload-time = "2025-02-27T18:51:01.684Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b2/a3/e137168c9c44d18eff0376253da9f1e9234d0239e0ee230d2fee6cea8e55/jeepney-0.9.0-py3-none-any.whl", hash = "sha256:97e5714520c16fc0a45695e5365a2e11b81ea79bba796e26f9f1d178cb182683", size = 49010, upload-time = "2025-02-27T18:51:00.104Z" }, +] + +[[package]] +name = "keyring" +version = "25.7.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "jaraco-classes" }, + { name = "jaraco-context" }, + { name = "jaraco-functools" }, + { name = "jeepney", marker = "sys_platform == 'linux'" }, + { name = "pywin32-ctypes", marker = "sys_platform == 'win32'" }, + { name = "secretstorage", marker = "sys_platform == 'linux'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/43/4b/674af6ef2f97d56f0ab5153bf0bfa28ccb6c3ed4d1babf4305449668807b/keyring-25.7.0.tar.gz", hash = "sha256:fe01bd85eb3f8fb3dd0405defdeac9a5b4f6f0439edbb3149577f244a2e8245b", size = 63516, upload-time = "2025-11-16T16:26:09.482Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/81/db/e655086b7f3a705df045bf0933bdd9c2f79bb3c97bfef1384598bb79a217/keyring-25.7.0-py3-none-any.whl", hash = "sha256:be4a0b195f149690c166e850609a477c532ddbfbaed96a404d4e43f8d5e2689f", size = 39160, upload-time = "2025-11-16T16:26:08.402Z" }, +] + +[[package]] +name = "linkify-it-py" +version = "2.1.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "uc-micro-py" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/2e/c9/06ea13676ef354f0af6169587ae292d3e2406e212876a413bf9eece4eb23/linkify_it_py-2.1.0.tar.gz", hash = "sha256:43360231720999c10e9328dc3691160e27a718e280673d444c38d7d3aaa3b98b", size = 29158, upload-time = "2026-03-01T07:48:47.683Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b4/de/88b3be5c31b22333b3ca2f6ff1de4e863d8fe45aaea7485f591970ec1d3e/linkify_it_py-2.1.0-py3-none-any.whl", hash = "sha256:0d252c1594ecba2ecedc444053db5d3a9b7ec1b0dd929c8f1d74dce89f86c05e", size = 19878, upload-time = "2026-03-01T07:48:46.098Z" }, +] + +[[package]] +name = "markdown-it-py" +version = "4.2.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "mdurl" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/06/ff/7841249c247aa650a76b9ee4bbaeae59370dc8bfd2f6c01f3630c35eb134/markdown_it_py-4.2.0.tar.gz", hash = "sha256:04a21681d6fbb623de53f6f364d352309d4094dd4194040a10fd51833e418d49", size = 82454, upload-time = "2026-05-07T12:08:28.36Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b3/81/4da04ced5a082363ecfa159c010d200ecbd959ae410c10c0264a38cac0f5/markdown_it_py-4.2.0-py3-none-any.whl", hash = "sha256:9f7ebbcd14fe59494226453aed97c1070d83f8d24b6fc3a3bcf9a38092641c4a", size = 91687, upload-time = "2026-05-07T12:08:27.182Z" }, +] + +[package.optional-dependencies] +linkify = [ + { name = "linkify-it-py" }, +] + +[[package]] +name = "mdit-py-plugins" +version = "0.6.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "markdown-it-py" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/59/fc/f8d0863f8862f25602c0404d75568e89fb6b4109804645e5cdfb1be5cf56/mdit_py_plugins-0.6.1.tar.gz", hash = "sha256:a2bca0f039f39dbd35fb74ae1b5f998608c437463371f0ff7f49a19a17a114d0", size = 56114, upload-time = "2026-05-13T09:03:38.91Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a5/69/6da5581c6a7fede7dc261bf4e67d6adca4196f176b43288b55b3db395b6e/mdit_py_plugins-0.6.1-py3-none-any.whl", hash = "sha256:214c82fb2ac524472ab6a5bcab1de80f73b50443e187f401bfd77efbc7c6481d", size = 66663, upload-time = "2026-05-13T09:03:37.76Z" }, +] + +[[package]] +name = "mdurl" +version = "0.1.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/d6/54/cfe61301667036ec958cb99bd3efefba235e65cdeb9c84d24a8293ba1d90/mdurl-0.1.2.tar.gz", hash = "sha256:bb413d29f5eea38f31dd4754dd7377d4465116fb207585f97bf925588687c1ba", size = 8729, upload-time = "2022-08-14T12:40:10.846Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b3/38/89ba8ad64ae25be8de66a6d463314cf1eb366222074cfda9ee839c56a4b4/mdurl-0.1.2-py3-none-any.whl", hash = "sha256:84008a41e51615a49fc9966191ff91509e3c40b939176e643fd50a5c2196b8f8", size = 9979, upload-time = "2022-08-14T12:40:09.779Z" }, +] + +[[package]] +name = "more-itertools" +version = "11.1.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/de/1d/f4da6f02cdffe04d6362210b807146a26044c88d839208aec273bb0d9184/more_itertools-11.1.0.tar.gz", hash = "sha256:48e8f4d9e7e5878571ecf6f2b4e57634f93cd474cc8cfbd2376f2d11b396e30d", size = 145772, upload-time = "2026-05-22T14:14:29.909Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e8/3d/1087453384dbde46a8c7f9356eead2c58be8a7bf156bca40243377c85715/more_itertools-11.1.0-py3-none-any.whl", hash = "sha256:4b65538ae22f6fed0ce4874efd317463a7489796a0939fa66824dd542125a192", size = 72226, upload-time = "2026-05-22T14:14:28.824Z" }, +] + +[[package]] +name = "nh3" +version = "0.3.6" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/5e/1b/ef84624f14954d270f74060a19fc550dd4f06656399447569afb584d8c06/nh3-0.3.6.tar.gz", hash = "sha256:f3736c9dd3d1856f80cd031715b84ca75cda2bbb1ac802c3da26bfce590838d7", size = 24684, upload-time = "2026-06-22T00:47:02.008Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/99/3e/6506aa4f23dc7b7993a2d0a45dca3ce864ec48380adfe15a173e643c63e8/nh3-0.3.6-cp314-cp314t-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl", hash = "sha256:2411e8c3cee81a1ddd62c2a5d50585c28aa5566d373ad1db92536b95ddb24ef2", size = 1421679, upload-time = "2026-06-22T00:46:20.248Z" }, + { url = "https://files.pythonhosted.org/packages/e3/e1/e96e7864a7a53bd6b6fab7e9632467382a2a2c1f3fed951918ad131542fb/nh3-0.3.6-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e196fa70c2ff2eb4de7d3df3108f8f358c1d69dff20d45b11f20a5aa227ffb6d", size = 792570, upload-time = "2026-06-22T00:46:22.179Z" }, + { url = "https://files.pythonhosted.org/packages/59/62/5b6108bedaef2b2637fed04c87bdbcb5967b9961758b41f0e466ef22a022/nh3-0.3.6-cp314-cp314t-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:34d2b0d934156b87ee114f599a3ba9b8b9e17b5d79652ba3a13fa50903de965e", size = 842243, upload-time = "2026-06-22T00:46:23.801Z" }, + { url = "https://files.pythonhosted.org/packages/4b/4a/526f199626bfcb496bc01a268051b44737962005553b158e985ed7e64865/nh3-0.3.6-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:f2f14b7ae1fca99c4a66c981aac3974e7fbc1ca30a12673d223ae1df76680917", size = 1001468, upload-time = "2026-06-22T00:46:25.481Z" }, + { url = "https://files.pythonhosted.org/packages/49/09/0d8e3101636d9ad88cdefb2914e764cb8e876ebdbb4286bfc251277d9c67/nh3-0.3.6-cp314-cp314t-musllinux_1_2_armv7l.whl", hash = "sha256:889932a97fb4abb6f95fef1914c0d269ebfb60011e67121c1163059b9449dbb4", size = 1082933, upload-time = "2026-06-22T00:46:27.15Z" }, + { url = "https://files.pythonhosted.org/packages/09/a1/ea83abe738a3fbaa203dfdb836ca7cbab0e7e9609faaee4fe1d4652599c0/nh3-0.3.6-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:edb2b4a1a27523e6cc7c417f8d21ce3d005243548b93e56b762b66b0c7f589f9", size = 1043120, upload-time = "2026-06-22T00:46:28.89Z" }, + { url = "https://files.pythonhosted.org/packages/66/69/0654482b8635012fbae67826bd6c381abb05d841ac7388b9b4666300fdad/nh3-0.3.6-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:43bc1ed3fa0716295fabee29ba42b2667e4a51d140b0a68e092170a765474fa6", size = 1023824, upload-time = "2026-06-22T00:46:30.453Z" }, + { url = "https://files.pythonhosted.org/packages/ed/a6/1f7285ffadc8307c4dbeb08d21b920536d5117785056d1079e998c4dfa44/nh3-0.3.6-cp314-cp314t-win32.whl", hash = "sha256:597a8e843bea00b2eb5520658dc24a9bb032e7fc9e7c2c0c4cd29420220c9796", size = 599253, upload-time = "2026-06-22T00:46:32.072Z" }, + { url = "https://files.pythonhosted.org/packages/36/ea/5542f3c45da4c00290d9d67a65e996702e23e613c4b627de3e09cb9fe357/nh3-0.3.6-cp314-cp314t-win_amd64.whl", hash = "sha256:4713502748f564fee0633b37b3403783ce0a3af3a3d148ad91025a5bdadb7bc6", size = 612553, upload-time = "2026-06-22T00:46:33.53Z" }, + { url = "https://files.pythonhosted.org/packages/66/35/26bd47e6af5915a628281dccdac354ddf4e32f7397047894270acd8c9870/nh3-0.3.6-cp314-cp314t-win_arm64.whl", hash = "sha256:69bbb92865a693d909db3a700d3c01537533844d0948c1e9323561ce06ecda41", size = 595151, upload-time = "2026-06-22T00:46:34.878Z" }, + { url = "https://files.pythonhosted.org/packages/f3/ab/a7653bce9a3b204be6a6931767a9e23595807bb84790ce6685e4d7e5bd08/nh3-0.3.6-cp38-abi3-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl", hash = "sha256:a43ebd7543555c3ac1bc353023d0794e75cb76f6f18f19c32e95441496c0cc25", size = 1443564, upload-time = "2026-06-22T00:46:36.66Z" }, + { url = "https://files.pythonhosted.org/packages/41/21/e1084ab18eb589506335c7c7576f2d4643e9a0c0e33983ef0e549a256b96/nh3-0.3.6-cp38-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e1b160831c9cdb06a6c79c2f9cdb11386602938f9af260d1c457a85add4f6f69", size = 838002, upload-time = "2026-06-22T00:46:38.101Z" }, + { url = "https://files.pythonhosted.org/packages/b0/94/f48d08e6f72a406300fa11d8acd929fea1a80d4bf750fa292cb10785f126/nh3-0.3.6-cp38-abi3-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:d14bf7982e7a77c0c775634c29c07ce08b38a046df73e1c1f139b3e82f18a38e", size = 823045, upload-time = "2026-06-22T00:46:39.495Z" }, + { url = "https://files.pythonhosted.org/packages/25/bb/431615ba1d1d3eb63cde0f974f2114edf863a8a3f6049a12fed23fc241d3/nh3-0.3.6-cp38-abi3-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:44673b27010051ab5a5e438a86ec31bbda61d4a77d7e900af6b7be3037c1abae", size = 1093171, upload-time = "2026-06-22T00:46:41.21Z" }, + { url = "https://files.pythonhosted.org/packages/0e/24/a0d80182a18919665fefd19c1c06f1d1df1c9a6455d0252de40c034a0bc3/nh3-0.3.6-cp38-abi3-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e6b7beece07525dc6e6b0fc2f104442de2ba328360ad00e50cbe2e1fd620447d", size = 1049217, upload-time = "2026-06-22T00:46:42.804Z" }, + { url = "https://files.pythonhosted.org/packages/0a/13/6f1e302ca674ac74362e150848ad56a1be5145391204f74facdb8e94df12/nh3-0.3.6-cp38-abi3-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:455469a29951edc92bc48b47ac2281c3f2609e6c4f6a047056449f8c2c23facf", size = 917372, upload-time = "2026-06-22T00:46:44.495Z" }, + { url = "https://files.pythonhosted.org/packages/5b/67/314f6151bad77a93d751978a344033e1fc890822f05f0416079338e34231/nh3-0.3.6-cp38-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:905f877dc66dd7aea4a76e54bcb26acb5ff8216f720c0017ccf63e0e6035698e", size = 806699, upload-time = "2026-06-22T00:46:45.99Z" }, + { url = "https://files.pythonhosted.org/packages/3c/a6/bfaa00046e58603507dcfc266c4778e3ab7adf68a5dedd73b6274b8d9314/nh3-0.3.6-cp38-abi3-manylinux_2_31_riscv64.whl", hash = "sha256:25c733bee928530556b1db0ea46c52cf5aa686146e38e60a6fc7cb801ef91cec", size = 835165, upload-time = "2026-06-22T00:46:47.617Z" }, + { url = "https://files.pythonhosted.org/packages/30/a8/fb2c38845efb703a9173bffdfc745fc64d2b0e55cfc73a3647d2f028250c/nh3-0.3.6-cp38-abi3-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:2f90d9a0cfdbee218994fdaaeeb5a0fde62d08f35e4eef0378ec1e2200172fd0", size = 858282, upload-time = "2026-06-22T00:46:49.276Z" }, + { url = "https://files.pythonhosted.org/packages/68/17/06e72a18ee9b572914447338237ca7eb164c0df901f141bc10d1282247a2/nh3-0.3.6-cp38-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:82ca5bf427ad1b216b65ede1a2e2d87dc49bec417ceba0f297213107d3cd9d78", size = 1014328, upload-time = "2026-06-22T00:46:51.026Z" }, + { url = "https://files.pythonhosted.org/packages/11/f9/3966c61455668c08853bf5e33b4bed93c421f3194ce4de896dc248d6f6ce/nh3-0.3.6-cp38-abi3-musllinux_1_2_armv7l.whl", hash = "sha256:f5ed5fe84aee7f39db95c214a7421bf0499fbf500fec6d86a4e29bfc37971438", size = 1098207, upload-time = "2026-06-22T00:46:52.674Z" }, + { url = "https://files.pythonhosted.org/packages/19/d3/479cb4ae440424825735d60525b53e3c77fd60fd6e6afc0e984f00eb0178/nh3-0.3.6-cp38-abi3-musllinux_1_2_i686.whl", hash = "sha256:082675ff87b9385ec430ffe6d5847ba7456cc39b73720cd4add472f9f4cffd56", size = 1056961, upload-time = "2026-06-22T00:46:54.335Z" }, + { url = "https://files.pythonhosted.org/packages/17/0c/6cdb5ee1e127be50dc8391e54bddc1f64e87bf4bfad0c55633320e2e02db/nh3-0.3.6-cp38-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:36d06341bd501240d320f5942481ed5e6846136b666e1ba4faf802b78ebc875f", size = 1033829, upload-time = "2026-06-22T00:46:56.258Z" }, + { url = "https://files.pythonhosted.org/packages/e9/55/9de666ad975d6ccd77d799ea0add55ee2347aa81286ce21b2a97c070746b/nh3-0.3.6-cp38-abi3-win32.whl", hash = "sha256:5276ef17bdba9ad8040575c74072008b13aae429436e9d0429e718bb5f90f4da", size = 609081, upload-time = "2026-06-22T00:46:57.665Z" }, + { url = "https://files.pythonhosted.org/packages/82/fa/2b5d684e3edf1e81bfd02d298c78c3e3da77ca1d8a2be3183a79544a7548/nh3-0.3.6-cp38-abi3-win_amd64.whl", hash = "sha256:f338ac7d594c067679f1e99b4f5ec3906842979560f9d8f15d6bdfa39a353b10", size = 624461, upload-time = "2026-06-22T00:46:59.163Z" }, + { url = "https://files.pythonhosted.org/packages/7b/e5/7cafee2f0413ca4cb0ef3bd111e94d408a48810008b283ad8aee00dd1809/nh3-0.3.6-cp38-abi3-win_arm64.whl", hash = "sha256:69f365963f63a1e9bff53bdbb3c542c7c2efed3e163c9d5d83a772a2ac468c21", size = 603060, upload-time = "2026-06-22T00:47:00.596Z" }, +] + +[[package]] +name = "packaging" +version = "26.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/d7/f1/e7a6dd94a8d4a5626c03e4e99c87f241ba9e350cd9e6d75123f992427270/packaging-26.2.tar.gz", hash = "sha256:ff452ff5a3e828ce110190feff1178bb1f2ea2281fa2075aadb987c2fb221661", size = 228134, upload-time = "2026-04-24T20:15:23.917Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/df/b2/87e62e8c3e2f4b32e5fe99e0b86d576da1312593b39f47d8ceef365e95ed/packaging-26.2-py3-none-any.whl", hash = "sha256:5fc45236b9446107ff2415ce77c807cee2862cb6fac22b8a73826d0693b0980e", size = 100195, upload-time = "2026-04-24T20:15:22.081Z" }, +] + +[[package]] +name = "platformdirs" +version = "4.10.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/d7/47/e4501f49c178ae1d9f4a75073fda4204f52647993f075a9db4d14930e0c5/platformdirs-4.10.0.tar.gz", hash = "sha256:31e761a6a0ca04faf7353ea759bdba55652be214725111e5aac52dfa29d4bef7", size = 31224, upload-time = "2026-05-28T03:32:53.587Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/81/e6/cd9575ac904136b3cbf7aa7ee819ef86eedb7274e46f230e94ea4342e729/platformdirs-4.10.0-py3-none-any.whl", hash = "sha256:fb516cdb12eb0d857d0cd85a7c57cea4d060bee4578d6cf5a14dfdf8cbf8784a", size = 22743, upload-time = "2026-05-28T03:32:52.175Z" }, +] + +[[package]] +name = "pycparser" +version = "3.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/1b/7d/92392ff7815c21062bea51aa7b87d45576f649f16458d78b7cf94b9ab2e6/pycparser-3.0.tar.gz", hash = "sha256:600f49d217304a5902ac3c37e1281c9fe94e4d0489de643a9504c5cdfdfc6b29", size = 103492, upload-time = "2026-01-21T14:26:51.89Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/0c/c3/44f3fbbfa403ea2a7c779186dc20772604442dde72947e7d01069cbe98e3/pycparser-3.0-py3-none-any.whl", hash = "sha256:b727414169a36b7d524c1c3e31839a521725078d7b2ff038656844266160a992", size = 48172, upload-time = "2026-01-21T14:26:50.693Z" }, +] + +[[package]] +name = "pygments" +version = "2.20.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/c3/b2/bc9c9196916376152d655522fdcebac55e66de6603a76a02bca1b6414f6c/pygments-2.20.0.tar.gz", hash = "sha256:6757cd03768053ff99f3039c1a36d6c0aa0b263438fcab17520b30a303a82b5f", size = 4955991, upload-time = "2026-03-29T13:29:33.898Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f4/7e/a72dd26f3b0f4f2bf1dd8923c85f7ceb43172af56d63c7383eb62b332364/pygments-2.20.0-py3-none-any.whl", hash = "sha256:81a9e26dd42fd28a23a2d169d86d7ac03b46e2f8b59ed4698fb4785f946d0176", size = 1231151, upload-time = "2026-03-29T13:29:30.038Z" }, +] + +[[package]] +name = "pywin32-ctypes" +version = "0.2.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/85/9f/01a1a99704853cb63f253eea009390c88e7131c67e66a0a02099a8c917cb/pywin32-ctypes-0.2.3.tar.gz", hash = "sha256:d162dc04946d704503b2edc4d55f3dba5c1d539ead017afa00142c38b9885755", size = 29471, upload-time = "2024-08-14T10:15:34.626Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/de/3d/8161f7711c017e01ac9f008dfddd9410dff3674334c233bde66e7ba65bbf/pywin32_ctypes-0.2.3-py3-none-any.whl", hash = "sha256:8a1513379d709975552d202d942d9837758905c8d01eb82b8bcc30918929e7b8", size = 30756, upload-time = "2024-08-14T10:15:33.187Z" }, +] + +[[package]] +name = "readme-renderer" +version = "45.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "docutils" }, + { name = "nh3" }, + { name = "pygments" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/02/51/d3a6ea424652c60f05600d8c2e01a55c913755e7cdad64afabbd1aa16f44/readme_renderer-45.0.tar.gz", hash = "sha256:030a8fac74904f8fba11ad1bb6964e3f76e896dc7e5e71f16af190c9056696d1", size = 36172, upload-time = "2026-06-09T21:05:17.37Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/97/1b/295bf2fa3e740131778065e5ffa2c481f0e7210182d408e9a2c244ff5b0c/readme_renderer-45.0-py3-none-any.whl", hash = "sha256:3385ed220117104a2bceb4a9dac8c5fdf6d1f96890d7ea2a9c7174fd5c84091f", size = 14134, upload-time = "2026-06-09T21:05:15.85Z" }, +] + +[[package]] +name = "requests" +version = "2.34.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "certifi" }, + { name = "charset-normalizer" }, + { name = "idna" }, + { name = "urllib3" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/ac/c3/e2a2b89f2d3e2179abd6d00ebd70bff6273f37fb3e0cc209f48b39d00cbf/requests-2.34.2.tar.gz", hash = "sha256:f288924cae4e29463698d6d60bc6a4da69c89185ad1e0bcc4104f584e960b9ed", size = 142856, upload-time = "2026-05-14T19:25:27.735Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a0/f4/c67b0b3f1b9245e8d266f0f112c500d50e5b4e83cb6f3b71b6528104182a/requests-2.34.2-py3-none-any.whl", hash = "sha256:2a0d60c172f83ac6ab31e4554906c0f3b3588d37b5cb939b1c061f4907e278e0", size = 73075, upload-time = "2026-05-14T19:25:26.443Z" }, +] + +[[package]] +name = "requests-toolbelt" +version = "1.0.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "requests" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/f3/61/d7545dafb7ac2230c70d38d31cbfe4cc64f7144dc41f6e4e4b78ecd9f5bb/requests-toolbelt-1.0.0.tar.gz", hash = "sha256:7681a0a3d047012b5bdc0ee37d7f8f07ebe76ab08caeccfc3921ce23c88d5bc6", size = 206888, upload-time = "2023-05-01T04:11:33.229Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/3f/51/d4db610ef29373b879047326cbf6fa98b6c1969d6f6dc423279de2b1be2c/requests_toolbelt-1.0.0-py2.py3-none-any.whl", hash = "sha256:cccfdd665f0a24fcf4726e690f65639d272bb0637b9b92dfd91a5568ccf6bd06", size = 54481, upload-time = "2023-05-01T04:11:28.427Z" }, +] + +[[package]] +name = "rfc3986" +version = "2.0.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/85/40/1520d68bfa07ab5a6f065a186815fb6610c86fe957bc065754e47f7b0840/rfc3986-2.0.0.tar.gz", hash = "sha256:97aacf9dbd4bfd829baad6e6309fa6573aaf1be3f6fa735c8ab05e46cecb261c", size = 49026, upload-time = "2022-01-10T00:52:30.832Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ff/9a/9afaade874b2fa6c752c36f1548f718b5b83af81ed9b76628329dab81c1b/rfc3986-2.0.0-py2.py3-none-any.whl", hash = "sha256:50b1502b60e289cb37883f3dfd34532b8873c7de9f49bb546641ce9cbd256ebd", size = 31326, upload-time = "2022-01-10T00:52:29.594Z" }, +] + +[[package]] +name = "rich" +version = "15.0.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "markdown-it-py" }, + { name = "pygments" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/c0/8f/0722ca900cc807c13a6a0c696dacf35430f72e0ec571c4275d2371fca3e9/rich-15.0.0.tar.gz", hash = "sha256:edd07a4824c6b40189fb7ac9bc4c52536e9780fbbfbddf6f1e2502c31b068c36", size = 230680, upload-time = "2026-04-12T08:24:00.75Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/82/3b/64d4899d73f91ba49a8c18a8ff3f0ea8f1c1d75481760df8c68ef5235bf5/rich-15.0.0-py3-none-any.whl", hash = "sha256:33bd4ef74232fb73fe9279a257718407f169c09b78a87ad3d296f548e27de0bb", size = 310654, upload-time = "2026-04-12T08:24:02.83Z" }, +] + +[[package]] +name = "secretstorage" +version = "3.5.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "cryptography" }, + { name = "jeepney" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/1c/03/e834bcd866f2f8a49a85eaff47340affa3bfa391ee9912a952a1faa68c7b/secretstorage-3.5.0.tar.gz", hash = "sha256:f04b8e4689cbce351744d5537bf6b1329c6fc68f91fa666f60a380edddcd11be", size = 19884, upload-time = "2025-11-23T19:02:53.191Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b7/46/f5af3402b579fd5e11573ce652019a67074317e18c1935cc0b4ba9b35552/secretstorage-3.5.0-py3-none-any.whl", hash = "sha256:0ce65888c0725fcb2c5bc0fdb8e5438eece02c523557ea40ce0703c266248137", size = 15554, upload-time = "2025-11-23T19:02:51.545Z" }, +] + +[[package]] +name = "textual" +version = "8.2.8" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "markdown-it-py", extra = ["linkify"] }, + { name = "mdit-py-plugins" }, + { name = "platformdirs" }, + { name = "pygments" }, + { name = "rich" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/00/21/39a76b01bd5eea82a04baaca7580e105d8c59450df03998345bb2cfb307b/textual-8.2.8.tar.gz", hash = "sha256:3f106a9fbc73e39dd266c9712432087de78a6d644084c7c241d6a25c3169115b", size = 1860502, upload-time = "2026-06-30T06:51:24.495Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/fb/be/35261223d9416a0751cdff1c7b4a6f881387218a12d439fe22fefebc8c04/textual-8.2.8-py3-none-any.whl", hash = "sha256:267375fd402dc8d981457212efa71f0e3365fd17bba144ba9bb3ed7563cb374a", size = 731418, upload-time = "2026-06-30T06:51:26.364Z" }, +] + +[package.optional-dependencies] +syntax = [ + { name = "tree-sitter" }, + { name = "tree-sitter-bash" }, + { name = "tree-sitter-css" }, + { name = "tree-sitter-go" }, + { name = "tree-sitter-html" }, + { name = "tree-sitter-java" }, + { name = "tree-sitter-javascript" }, + { name = "tree-sitter-json" }, + { name = "tree-sitter-markdown" }, + { name = "tree-sitter-python" }, + { name = "tree-sitter-regex" }, + { name = "tree-sitter-rust" }, + { name = "tree-sitter-sql" }, + { name = "tree-sitter-toml" }, + { name = "tree-sitter-xml" }, + { name = "tree-sitter-yaml" }, +] + +[[package]] +name = "tree-sitter" +version = "0.26.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/f7/03/5600b84aff2e6c4fe80cfebb4063fe2f50299521befe5f6092ab8c082f4a/tree_sitter-0.26.0.tar.gz", hash = "sha256:b40c219edccc4564530c96f8f1556f6202b37cda964d1cbd7bd2b7e68b40a245", size = 191423, upload-time = "2026-06-30T12:14:27.933Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c5/7a/4d84e6f6ae2c3e757490dd84de251712c31e293dfe31f28da1ec019cefa2/tree_sitter-0.26.0-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:5a3c93a352b7e6f70f73e121bbfa2d0117ba7478bd51114ed35c91b0b78814fa", size = 148901, upload-time = "2026-06-30T12:14:19.452Z" }, + { url = "https://files.pythonhosted.org/packages/b0/d9/efe62ec65dc9d096e834d27b8c058127e2146e42ff3380b822a233f016a6/tree_sitter-0.26.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:5fc2f41bf246ff2f70a9cc3690be35ec7580a4923151873d898c8bcb1a4503d3", size = 140805, upload-time = "2026-06-30T12:14:20.478Z" }, + { url = "https://files.pythonhosted.org/packages/c4/2c/c82326b7b97e3c485c18679883b16f89e5e913c639d3b219d3da70c9e67e/tree_sitter-0.26.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b8ea92a255c91671a7ec4625aba3ab7bb5220c423630ffbf83c45d7312abe084", size = 640586, upload-time = "2026-06-30T12:14:21.527Z" }, + { url = "https://files.pythonhosted.org/packages/e2/7a/f56e7d8282859452611024c7cbc623bfba5b24b8cb9b8f8bc88c5219fe9a/tree_sitter-0.26.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:f665510f0fcf4636fb9696f1f7853bed7a3bd764b7bb0cb8494e619c14ed5a0c", size = 668300, upload-time = "2026-06-30T12:14:22.728Z" }, + { url = "https://files.pythonhosted.org/packages/91/51/240ee81b9d5e9ca0a6cb1528e8605ffa70ab58c89ce126631be96d3e4bae/tree_sitter-0.26.0-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:253df7ab82cc0a9d311cd65f06e9f99fb3eac55996ae9fc94da22f123a861b90", size = 649627, upload-time = "2026-06-30T12:14:23.819Z" }, + { url = "https://files.pythonhosted.org/packages/6a/54/760035cefedf9eb44f0f84c4ac22f1322e73155853e272576ee876336312/tree_sitter-0.26.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:ff80d4833d330a73184a3ac5132abe93c575d2dea31975c6f15c0d21fef238aa", size = 664885, upload-time = "2026-06-30T12:14:25.064Z" }, + { url = "https://files.pythonhosted.org/packages/c9/1b/0b36fe2a984ecedc4ce6aefd5d56447a6626a8e9b595c4e48658510ce8f8/tree_sitter-0.26.0-cp314-cp314-win_amd64.whl", hash = "sha256:a4033fecc8f606c7f2e8b8014d0057b74668a7f0152763606f7bc25c5f9ec64c", size = 132688, upload-time = "2026-06-30T12:14:26.106Z" }, + { url = "https://files.pythonhosted.org/packages/4d/74/ebc041a13fbf40144afdb0d4b447e48e0b4012ca866c63de8b48f801f0c1/tree_sitter-0.26.0-cp314-cp314-win_arm64.whl", hash = "sha256:823251c4b6725a7c03ed497a339135ede7ae4bdde75bb8be7ef5e305aeb4ff52", size = 120287, upload-time = "2026-06-30T12:14:26.991Z" }, +] + +[[package]] +name = "tree-sitter-bash" +version = "0.25.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/8e/0e/f0108be910f1eef6499eabce517e79fe3b12057280ed398da67ce2426cba/tree_sitter_bash-0.25.1.tar.gz", hash = "sha256:bfc0bdaa77bc1e86e3c6652e5a6e140c40c0a16b84185c2b63ad7cd809b88f14", size = 419703, upload-time = "2025-12-02T17:01:08.849Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/30/8e/37e7364d9c9c58da89e05c510671d8c45818afd7b31c6939ab72f8dc6c04/tree_sitter_bash-0.25.1-cp310-abi3-macosx_10_9_x86_64.whl", hash = "sha256:0e6235f59e366d220dde7d830196bed597d01e853e44d8ccd1a82c5dd2500acf", size = 194160, upload-time = "2025-12-02T17:00:59.047Z" }, + { url = "https://files.pythonhosted.org/packages/23/bb/2d2cfbb1f89aaeb1ec892624f069d92d058d06bb66f16b9ec9fb5873ab60/tree_sitter_bash-0.25.1-cp310-abi3-macosx_11_0_arm64.whl", hash = "sha256:f4a34a6504c7c5b2a9b8c5c4065531dea19ca2c35026e706cf2eeeebe2c92512", size = 202659, upload-time = "2025-12-02T17:01:00.275Z" }, + { url = "https://files.pythonhosted.org/packages/25/f0/1bb25519be27460255d3899db677313cfa1e6306988fbf456a3d7e211bbb/tree_sitter_bash-0.25.1-cp310-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e76c4cfb20b076552406782b7f8c2a3946835993df0a44df006de54b7030c7dc", size = 230596, upload-time = "2025-12-02T17:01:01.759Z" }, + { url = "https://files.pythonhosted.org/packages/d7/22/9f70bc3d3b942ab9fc0f89c1dc9e087519a3a94f64ae6b7377aae3a7a0f0/tree_sitter_bash-0.25.1-cp310-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:3f484c4bb8796cde7a87ca351e6116f09653edac0eb3c6d238566359dd28b117", size = 231981, upload-time = "2025-12-02T17:01:02.859Z" }, + { url = "https://files.pythonhosted.org/packages/7a/c3/f1540e42cd41b323c6821e45e52e1aed6ed386209aad52db996f05703963/tree_sitter_bash-0.25.1-cp310-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:5e76af6df46d958c7f5b6d5884c9743218e3902a00ccb493ec92728b1084430b", size = 228364, upload-time = "2025-12-02T17:01:03.997Z" }, + { url = "https://files.pythonhosted.org/packages/f7/a0/c3050a6277dfcac8c480f514dc4fe49f3f65f0eac68b4702cbaca2584e85/tree_sitter_bash-0.25.1-cp310-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:a3332d71c7b7d5f78259b19d02d0ea111fcb82b72712ee4a93aaa5b226d3f0a8", size = 230074, upload-time = "2025-12-02T17:01:05.05Z" }, + { url = "https://files.pythonhosted.org/packages/71/0f/203fe6b27211387f4b9ba8c4a321567ca4ded2624dae6ccdbd2b6e940e17/tree_sitter_bash-0.25.1-cp310-abi3-win_amd64.whl", hash = "sha256:52a6802d9218f86278aa3e8b459c3abdad67eed0fde1f9f13aca5b6c634217a6", size = 195574, upload-time = "2025-12-02T17:01:06.412Z" }, + { url = "https://files.pythonhosted.org/packages/47/75/4ca1a9fabd8fb5aea78cea70f7837ce4dbf2afae115f62051e5fa99cba1c/tree_sitter_bash-0.25.1-cp310-abi3-win_arm64.whl", hash = "sha256:59115057ec2bae319e8082ff29559861045002964c3431ccb0fc92aa4bc9bccb", size = 191196, upload-time = "2025-12-02T17:01:07.486Z" }, +] + +[[package]] +name = "tree-sitter-css" +version = "0.25.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/38/37/7d60171240d4c5ba330f05b725dfb5e5fd5b7cbe0aa98ef9e77f77f868f5/tree_sitter_css-0.25.0.tar.gz", hash = "sha256:2fc996bf05b04e06061e88ee4c60837783dc4e62a695205acbc262ee30454138", size = 43232, upload-time = "2025-09-28T11:37:13.387Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/25/a9/69e556f15ca774638bd79005369213dfbd41995bf032ce81cf3ffe086b8a/tree_sitter_css-0.25.0-cp310-abi3-macosx_10_9_x86_64.whl", hash = "sha256:ddce6f84eeb0bb2877b4587b07bffb0753040c44d811ed9ab2af978c313beda8", size = 29933, upload-time = "2025-09-28T11:37:07.703Z" }, + { url = "https://files.pythonhosted.org/packages/4d/28/ebcbcbba812d3e407f2f393747330eb8843e0c69d159024e33460b622aab/tree_sitter_css-0.25.0-cp310-abi3-macosx_11_0_arm64.whl", hash = "sha256:5a2a9c875037ef5f9da57697fb8075086476d42a49d25a88dcca60dfc09bd092", size = 31097, upload-time = "2025-09-28T11:37:08.46Z" }, + { url = "https://files.pythonhosted.org/packages/86/a2/6f9658c723f3a857367c198bd4f50d854aa9468783b418407492c9634a44/tree_sitter_css-0.25.0-cp310-abi3-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:4f5e1135bfd01bce24e2fc7bca1381f52bdd6c6282ee28f7aa77185340bcd135", size = 41713, upload-time = "2025-09-28T11:37:09.101Z" }, + { url = "https://files.pythonhosted.org/packages/85/bb/f74eea6839cb1ff6b5851c6ed33b18e65309eb347bbbe027c93e70e6c691/tree_sitter_css-0.25.0-cp310-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6b6d0084536828c733a66524a43c9df89f335971d5b1b973e9d1c42ba9dd426b", size = 42312, upload-time = "2025-09-28T11:37:09.757Z" }, + { url = "https://files.pythonhosted.org/packages/ca/fd/031ef1a5938441c98342faf70bb30998683b2130d4b55c282d76b2083f4a/tree_sitter_css-0.25.0-cp310-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:8a83825daf538656cb88f4f7a0dd9963e3f204e83e7f8d92131f17e5bd712a77", size = 41585, upload-time = "2025-09-28T11:37:10.447Z" }, + { url = "https://files.pythonhosted.org/packages/96/74/9f269bb3644a0511c1c263135e32d38a7f2af39cbba24d59a1633a5ebbc1/tree_sitter_css-0.25.0-cp310-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:b486c097d250a598fba5f1f46f62697c7f4428252c8bdaad696a907ee913421d", size = 41490, upload-time = "2025-09-28T11:37:11.134Z" }, + { url = "https://files.pythonhosted.org/packages/04/9f/d4f1d3164b692b97266274dad6437586e0614f75080b7795fc7bfa5bf8ff/tree_sitter_css-0.25.0-cp310-abi3-win_amd64.whl", hash = "sha256:fe319e4ad1b8327afbd9758b3ae22b09226d6c28dc9b022bcadabdaf6ea3716c", size = 32416, upload-time = "2025-09-28T11:37:11.808Z" }, + { url = "https://files.pythonhosted.org/packages/39/5c/fa62d70cb324788bcced741b5e19864ccf4c51ca31766a9f56a6b46a5cf6/tree_sitter_css-0.25.0-cp310-abi3-win_arm64.whl", hash = "sha256:4fc2c82645cd593f1c695b4d6b678d71e633212ca030f26dedee4f92434bfe21", size = 31057, upload-time = "2025-09-28T11:37:12.734Z" }, +] + +[[package]] +name = "tree-sitter-go" +version = "0.25.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/01/05/727308adbbc79bcb1c92fc0ea10556a735f9d0f0a5435a18f59d40f7fd77/tree_sitter_go-0.25.0.tar.gz", hash = "sha256:a7466e9b8d94dda94cae8d91629f26edb2d26166fd454d4831c3bf6dfa2e8d68", size = 93890, upload-time = "2025-08-29T06:20:25.044Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ca/aa/0984707acc2b9bb461fe4a41e7e0fc5b2b1e245c32820f0c83b3c602957c/tree_sitter_go-0.25.0-cp310-abi3-macosx_10_9_x86_64.whl", hash = "sha256:b852993063a3429a443e7bd0aa376dd7dd329d595819fabf56ac4cf9d7257b54", size = 47117, upload-time = "2025-08-29T06:20:14.286Z" }, + { url = "https://files.pythonhosted.org/packages/32/16/dd4cb124b35e99239ab3624225da07d4cb8da4d8564ed81d03fcb3a6ba9f/tree_sitter_go-0.25.0-cp310-abi3-macosx_11_0_arm64.whl", hash = "sha256:503b81a2b4c31e302869a1de3a352ad0912ccab3df9ac9950197b0a9ceeabd8f", size = 48674, upload-time = "2025-08-29T06:20:17.557Z" }, + { url = "https://files.pythonhosted.org/packages/86/fb/b30d63a08044115d8b8bd196c6c2ab4325fb8db5757249a4ef0563966e2e/tree_sitter_go-0.25.0-cp310-abi3-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:04b3b3cb4aff18e74e28d49b716c6f24cb71ddfdd66768987e26e4d0fa812f74", size = 66418, upload-time = "2025-08-29T06:20:18.345Z" }, + { url = "https://files.pythonhosted.org/packages/26/21/d3d88a30ad007419b2c97b3baeeef7431407faf9f686195b6f1cad0aedf9/tree_sitter_go-0.25.0-cp310-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:148255aca2f54b90d48c48a9dbb4c7faad6cad310a980b2c5a5a9822057ed145", size = 72006, upload-time = "2025-08-29T06:20:19.14Z" }, + { url = "https://files.pythonhosted.org/packages/cd/d0/0dd6442353ced8a88bbda9e546f4ea29e381b59b5a40b122e5abb586bb6c/tree_sitter_go-0.25.0-cp310-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:4d338116cdf8a6c6ff990d2441929b41323ef17c710407abe0993c13417d6aad", size = 70603, upload-time = "2025-08-29T06:20:21.544Z" }, + { url = "https://files.pythonhosted.org/packages/01/e2/ee5e09f63504fc286539535d374d2eaa0e7d489b80f8f744bb3962aff22a/tree_sitter_go-0.25.0-cp310-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:5608e089d2a29fa8d2b327abeb2ad1cdb8e223c440a6b0ceab0d3fa80bdeebae", size = 66088, upload-time = "2025-08-29T06:20:22.336Z" }, + { url = "https://files.pythonhosted.org/packages/6e/b6/d9142583374720e79aca9ccb394b3795149a54c012e1dfd80738df2d984e/tree_sitter_go-0.25.0-cp310-abi3-win_amd64.whl", hash = "sha256:30d4ada57a223dfc2c32d942f44d284d40f3d1215ddcf108f96807fd36d53022", size = 48152, upload-time = "2025-08-29T06:20:23.089Z" }, + { url = "https://files.pythonhosted.org/packages/9e/00/9a2638e7339236f5b01622952a4d71c1474dd3783d1982a89555fc1f03b1/tree_sitter_go-0.25.0-cp310-abi3-win_arm64.whl", hash = "sha256:d5d62362059bf79997340773d47cc7e7e002883b527a05cca829c46e40b70ded", size = 46752, upload-time = "2025-08-29T06:20:24.235Z" }, +] + +[[package]] +name = "tree-sitter-html" +version = "0.23.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/04/06/ad1c53c79da15bef85939aa022d72301e12a9773e9bb9a5e6a6f65b7753a/tree_sitter_html-0.23.2.tar.gz", hash = "sha256:bc9922defe23144d9146bc1509fcd00d361bf6b3303f9effee6532c6a0296961", size = 13977, upload-time = "2024-11-11T05:58:07.403Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/fb/27/b846852b567601c4df765bcb4636085a3260e9f03ae21e0ef2e7c7f957fc/tree_sitter_html-0.23.2-cp39-abi3-macosx_10_9_x86_64.whl", hash = "sha256:9e1641d5edf5568a246c6c47b947ed524b5bf944664e6473b21d4ae568e28ee9", size = 14787, upload-time = "2024-11-11T05:57:58.684Z" }, + { url = "https://files.pythonhosted.org/packages/bd/17/827c315deb156bb8cac541da800c4bd62878f50a28b7498fbb722bddd225/tree_sitter_html-0.23.2-cp39-abi3-macosx_11_0_arm64.whl", hash = "sha256:3d0a83dd6cd1c7d4bcf6287b5145c92140f0194f8516f329ae8b9e952fbfa8ff", size = 15232, upload-time = "2024-11-11T05:58:00.139Z" }, + { url = "https://files.pythonhosted.org/packages/91/cb/2028fe446d0e18edf3737d91edcb6430f2c97f2296b8cd760702dfa13d90/tree_sitter_html-0.23.2-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:81b3775732fffc0abd275a419ef018fd4c1ad4044b2a2e422f3378d93c30eded", size = 39109, upload-time = "2024-11-11T05:58:00.986Z" }, + { url = "https://files.pythonhosted.org/packages/19/bc/b24f5e66be51447cf7e9bcce3d9440a6b4f17021da85779a51566646a7c7/tree_sitter_html-0.23.2-cp39-abi3-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4bdaa7ac5030d416aea0c512d4810ef847bbbd62d61e3d213f370b64ce147293", size = 39630, upload-time = "2024-11-11T05:58:02.424Z" }, + { url = "https://files.pythonhosted.org/packages/d2/d5/31b46cb362ad9679af21ff8b75d846fb7522ecf949beea4fddc86e97815d/tree_sitter_html-0.23.2-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:d2e9631b66041a4fd792d7f79a0c4128adb3bfc71f3dcb7e1a3eab5dbee77d67", size = 37440, upload-time = "2024-11-11T05:58:03.819Z" }, + { url = "https://files.pythonhosted.org/packages/28/30/03910b7c037105f33166439f0518dd0aa4f1b7ef8c9d7367c6e9cc6b5681/tree_sitter_html-0.23.2-cp39-abi3-win_amd64.whl", hash = "sha256:85095f49f9e57f0ac9087a3e830783352c8447fdda55b1c1139aa47e5eaa0e21", size = 17765, upload-time = "2024-11-11T05:58:05.163Z" }, + { url = "https://files.pythonhosted.org/packages/20/32/63761055b03c69202a0e67b6e9a5cb3578da23aeefb62ee3e7ec2c1b0ff2/tree_sitter_html-0.23.2-cp39-abi3-win_arm64.whl", hash = "sha256:0f65ed9e877144d0f04ade5644e5b0e88bf98a9e60bce65235c99905623e2f1a", size = 15576, upload-time = "2024-11-11T05:58:06.577Z" }, +] + +[[package]] +name = "tree-sitter-java" +version = "0.23.5" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/fa/dc/eb9c8f96304e5d8ae1663126d89967a622a80937ad2909903569ccb7ec8f/tree_sitter_java-0.23.5.tar.gz", hash = "sha256:f5cd57b8f1270a7f0438878750d02ccc79421d45cca65ff284f1527e9ef02e38", size = 138121, upload-time = "2024-12-21T18:24:26.936Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/67/21/b3399780b440e1567a11d384d0ebb1aea9b642d0d98becf30fa55c0e3a3b/tree_sitter_java-0.23.5-cp39-abi3-macosx_10_9_x86_64.whl", hash = "sha256:355ce0308672d6f7013ec913dee4a0613666f4cda9044a7824240d17f38209df", size = 58926, upload-time = "2024-12-21T18:24:12.53Z" }, + { url = "https://files.pythonhosted.org/packages/57/ef/6406b444e2a93bc72a04e802f4107e9ecf04b8de4a5528830726d210599c/tree_sitter_java-0.23.5-cp39-abi3-macosx_11_0_arm64.whl", hash = "sha256:24acd59c4720dedad80d548fe4237e43ef2b7a4e94c8549b0ca6e4c4d7bf6e69", size = 62288, upload-time = "2024-12-21T18:24:14.634Z" }, + { url = "https://files.pythonhosted.org/packages/4e/6c/74b1c150d4f69c291ab0b78d5dd1b59712559bbe7e7daf6d8466d483463f/tree_sitter_java-0.23.5-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9401e7271f0b333df39fc8a8336a0caf1b891d9a2b89ddee99fae66b794fc5b7", size = 85533, upload-time = "2024-12-21T18:24:16.695Z" }, + { url = "https://files.pythonhosted.org/packages/29/09/e0d08f5c212062fd046db35c1015a2621c2631bc8b4aae5740d7adb276ad/tree_sitter_java-0.23.5-cp39-abi3-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:370b204b9500b847f6d0c5ad584045831cee69e9a3e4d878535d39e4a7e4c4f1", size = 84033, upload-time = "2024-12-21T18:24:18.758Z" }, + { url = "https://files.pythonhosted.org/packages/43/56/7d06b23ddd09bde816a131aa504ee11a1bbe87c6b62ab9b2ed23849a3382/tree_sitter_java-0.23.5-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:aae84449e330363b55b14a2af0585e4e0dae75eb64ea509b7e5b0e1de536846a", size = 82564, upload-time = "2024-12-21T18:24:20.493Z" }, + { url = "https://files.pythonhosted.org/packages/da/d6/0528c7e1e88a18221dbd8ccee3825bf274b1fa300f745fd74eb343878043/tree_sitter_java-0.23.5-cp39-abi3-win_amd64.whl", hash = "sha256:1ee45e790f8d31d416bc84a09dac2e2c6bc343e89b8a2e1d550513498eedfde7", size = 60650, upload-time = "2024-12-21T18:24:22.902Z" }, + { url = "https://files.pythonhosted.org/packages/72/57/5bab54d23179350356515526fff3cc0f3ac23bfbc1a1d518a15978d4880e/tree_sitter_java-0.23.5-cp39-abi3-win_arm64.whl", hash = "sha256:402efe136104c5603b429dc26c7e75ae14faaca54cfd319ecc41c8f2534750f4", size = 59059, upload-time = "2024-12-21T18:24:24.934Z" }, +] + +[[package]] +name = "tree-sitter-javascript" +version = "0.25.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/59/e0/e63103c72a9d3dfd89a31e02e660263ad84b7438e5f44ee82e443e65bbde/tree_sitter_javascript-0.25.0.tar.gz", hash = "sha256:329b5414874f0588a98f1c291f1b28138286617aa907746ffe55adfdcf963f38", size = 132338, upload-time = "2025-09-01T07:13:44.792Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/2c/df/5106ac250cd03661ebc3cc75da6b3d9f6800a3606393a0122eca58038104/tree_sitter_javascript-0.25.0-cp310-abi3-macosx_10_9_x86_64.whl", hash = "sha256:b70f887fb269d6e58c349d683f59fa647140c410cfe2bee44a883b20ec92e3dc", size = 64052, upload-time = "2025-09-01T07:13:36.865Z" }, + { url = "https://files.pythonhosted.org/packages/b1/8f/6b4b2bc90d8ab3955856ce852cc9d1e82c81d7ab9646385f0e75ffd5b5d3/tree_sitter_javascript-0.25.0-cp310-abi3-macosx_11_0_arm64.whl", hash = "sha256:8264a996b8845cfce06965152a013b5d9cbb7d199bc3503e12b5682e62bb1de1", size = 66440, upload-time = "2025-09-01T07:13:37.962Z" }, + { url = "https://files.pythonhosted.org/packages/5f/c4/7da74ecdcd8a398f88bd003a87c65403b5fe0e958cdd43fbd5fd4a398fcf/tree_sitter_javascript-0.25.0-cp310-abi3-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:9dc04ba91fc8583344e57c1f1ed5b2c97ecaaf47480011b92fbeab8dda96db75", size = 99728, upload-time = "2025-09-01T07:13:38.755Z" }, + { url = "https://files.pythonhosted.org/packages/96/c8/97da3af4796495e46421e9344738addb3602fa6426ea695be3fcbadbee37/tree_sitter_javascript-0.25.0-cp310-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:199d09985190852e0912da2b8d26c932159be314bc04952cf917ed0e4c633e6b", size = 106072, upload-time = "2025-09-01T07:13:39.798Z" }, + { url = "https://files.pythonhosted.org/packages/13/be/c964e8130be08cc9bd6627d845f0e4460945b158429d39510953bbcb8fcc/tree_sitter_javascript-0.25.0-cp310-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:dfcf789064c58dc13c0a4edb550acacfc6f0f280577f1e7a00de3e89fc7f8ddc", size = 104388, upload-time = "2025-09-01T07:13:40.866Z" }, + { url = "https://files.pythonhosted.org/packages/ee/89/9b773dee0f8961d1bb8d7baf0a204ab587618df19897c1ef260916f318ec/tree_sitter_javascript-0.25.0-cp310-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:1b852d3aee8a36186dbcc32c798b11b4869f9b5041743b63b65c2ef793db7a54", size = 98377, upload-time = "2025-09-01T07:13:41.838Z" }, + { url = "https://files.pythonhosted.org/packages/3b/dc/d90cb1790f8cec9b4878d278ad9faf7c8f893189ce0f855304fd704fc274/tree_sitter_javascript-0.25.0-cp310-abi3-win_amd64.whl", hash = "sha256:e5ed840f5bd4a3f0272e441d19429b26eedc257abe5574c8546da6b556865e3c", size = 62975, upload-time = "2025-09-01T07:13:42.828Z" }, + { url = "https://files.pythonhosted.org/packages/2e/1f/f9eba1038b7d4394410f3c0a6ec2122b590cd7acb03f196e52fa57ebbe72/tree_sitter_javascript-0.25.0-cp310-abi3-win_arm64.whl", hash = "sha256:622a69d677aa7f6ee2931d8c77c981a33f0ebb6d275aa9d43d3397c879a9bb0b", size = 61668, upload-time = "2025-09-01T07:13:43.803Z" }, +] + +[[package]] +name = "tree-sitter-json" +version = "0.24.8" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/d7/29/e92df6dca3a6b2ab1c179978be398059817e1173fbacd47e832aaff3446b/tree_sitter_json-0.24.8.tar.gz", hash = "sha256:ca8486e52e2d261819311d35cf98656123d59008c3b7dcf91e61d2c0c6f3120e", size = 8155, upload-time = "2024-11-11T06:05:00.667Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/42/41/84866232980fb3cf0cff46f5af2dbb9bfa3324b32614c6a9af3d08926b72/tree_sitter_json-0.24.8-cp39-abi3-macosx_10_9_x86_64.whl", hash = "sha256:59ac06c6db1877d0e2076bce54a5fddcdd2fc38ca778905662e80fa9ffcea2ab", size = 8718, upload-time = "2024-11-11T06:04:49.779Z" }, + { url = "https://files.pythonhosted.org/packages/5c/31/102c15948d97b135611d6a995c97a3933c0e9745f25737723977f58e142c/tree_sitter_json-0.24.8-cp39-abi3-macosx_11_0_arm64.whl", hash = "sha256:62b4c45b561db31436a81a3f037f71ec29049f4fc9bf5269b6ec3ebaaa35a1cd", size = 9163, upload-time = "2024-11-11T06:04:51.275Z" }, + { url = "https://files.pythonhosted.org/packages/28/64/aa44ea2f3d2e76ec086ce83902eb26b2ed0a92d3fd5e2714c9cb007e90d1/tree_sitter_json-0.24.8-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f8627f7d375fda9fc193ebee368c453f374f65c2f25c58b6fea4e6b49a7fccbc", size = 17726, upload-time = "2024-11-11T06:04:52.732Z" }, + { url = "https://files.pythonhosted.org/packages/77/08/10001992526670e0d6f24c571b179f0ece90e5e014a4b98a3ce076884f32/tree_sitter_json-0.24.8-cp39-abi3-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:85cca779872f7278f3a74eb38533d34b9c4de4fd548615e3361fa64fe350ad0a", size = 17236, upload-time = "2024-11-11T06:04:54.189Z" }, + { url = "https://files.pythonhosted.org/packages/92/64/908e9e0bd84fe3c81c564115d3bbe0e49b0e152784bbaf153d749d00bbe6/tree_sitter_json-0.24.8-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:deeb45850dcc52990fbb52c80196492a099e3fa3512d928a390a91cf061068cc", size = 16071, upload-time = "2024-11-11T06:04:55.628Z" }, + { url = "https://files.pythonhosted.org/packages/53/df/31daab1eedb445bef208a04fc35428de3afe2b37075fec84d7737e1c69de/tree_sitter_json-0.24.8-cp39-abi3-win_amd64.whl", hash = "sha256:e4849a03cd7197267b2688a4506a90a13568a8e0e8588080bd0212fcb38974e3", size = 11457, upload-time = "2024-11-11T06:04:57.698Z" }, + { url = "https://files.pythonhosted.org/packages/6c/3d/902d2f3125b6b90cebf404b63ca775bc6d82071ccc76c0d10fabfeb2febe/tree_sitter_json-0.24.8-cp39-abi3-win_arm64.whl", hash = "sha256:591e0096c882d12668b88f30d3ca6f85b9db3406910eaaab6afb6b17d65367dd", size = 10174, upload-time = "2024-11-11T06:04:59.309Z" }, +] + +[[package]] +name = "tree-sitter-markdown" +version = "0.5.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/9a/87/8f705d8f99337c8a691bcc8c22d89ddd323eb2b860a78ae2e894b9f7ade1/tree_sitter_markdown-0.5.1.tar.gz", hash = "sha256:6c69d7270a7e09be8988ced44584c09a6a4f541cea0dc394dd1c1a5ac3b5601d", size = 250138, upload-time = "2025-09-16T17:12:11.732Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/77/73/b5f88217a526f61080ddd71d554cff6a01ea23fffa584ad9de41ee8d1fe5/tree_sitter_markdown-0.5.1-cp39-abi3-macosx_10_9_x86_64.whl", hash = "sha256:f00ce3f48f127377983859fcb93caf0693cbc7970f8c41f1e2bd21e4d56bdfd8", size = 139706, upload-time = "2025-09-16T17:12:03.738Z" }, + { url = "https://files.pythonhosted.org/packages/6d/9b/65eb5e6a8d7791174644854437d35849d9b4e4ed034d54d2c78810eaf1a6/tree_sitter_markdown-0.5.1-cp39-abi3-macosx_11_0_arm64.whl", hash = "sha256:1ec4cc5d7b0d188bad22247501ab13663bb1bf1a60c2c020a22877fabce8daa9", size = 147540, upload-time = "2025-09-16T17:12:04.955Z" }, + { url = "https://files.pythonhosted.org/packages/24/d5/4152d00829c8643243f65b67a5485248661824f15e1868e14e54f03c2069/tree_sitter_markdown-0.5.1-cp39-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:727242a70c46222092eba86c102301646f21ba32aee221f4b1f70e2020755e81", size = 187851, upload-time = "2025-09-16T17:12:05.813Z" }, + { url = "https://files.pythonhosted.org/packages/a8/c1/994001c5a51d09e9da7236e01a855d3d49437a47fa8669f1d5e9ed60e64f/tree_sitter_markdown-0.5.1-cp39-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c0b2fde19e692bb90e300d9788887528c624b659c794de6337f8193396de4399", size = 187563, upload-time = "2025-09-16T17:12:06.929Z" }, + { url = "https://files.pythonhosted.org/packages/cb/d1/1f2ba1ae11568639f133c45c7a697e4e9277d6cc26a66c0caee62c11d1c2/tree_sitter_markdown-0.5.1-cp39-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:13da82db04cec7910b6afd4a67d02da9ef402df8d56fc6ed85e00584af1730ee", size = 185478, upload-time = "2025-09-16T17:12:08.126Z" }, + { url = "https://files.pythonhosted.org/packages/8c/c8/8218482d56b78755cdc20816a28754145cb1767e1e7e0ddde5988547ab86/tree_sitter_markdown-0.5.1-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:b8a8a04a5d942c177cc590ec40074fcf3658f3a7c0a3388a8575990003665d8c", size = 184922, upload-time = "2025-09-16T17:12:08.937Z" }, + { url = "https://files.pythonhosted.org/packages/b6/ca/423600960b91c3aba6f2202ad4c430b5401e652d51a73a59769375c2b4ea/tree_sitter_markdown-0.5.1-cp39-abi3-win_amd64.whl", hash = "sha256:b1b0e4cbcf5a7b85005f1e9266fc2ed9b649b41a6048f3b1abae3612368d97a6", size = 142519, upload-time = "2025-09-16T17:12:10.027Z" }, + { url = "https://files.pythonhosted.org/packages/93/f5/327dd7fa42ae39796a8853685c40a8ac968585260094c581047270cbc851/tree_sitter_markdown-0.5.1-cp39-abi3-win_arm64.whl", hash = "sha256:2296ef53a757d8f5b848616706d0518e04d487bc7748bd05755d4a3a65711542", size = 137166, upload-time = "2025-09-16T17:12:10.858Z" }, +] + +[[package]] +name = "tree-sitter-python" +version = "0.25.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/b8/8b/c992ff0e768cb6768d5c96234579bf8842b3a633db641455d86dd30d5dac/tree_sitter_python-0.25.0.tar.gz", hash = "sha256:b13e090f725f5b9c86aa455a268553c65cadf325471ad5b65cd29cac8a1a68ac", size = 159845, upload-time = "2025-09-11T06:47:58.159Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/cf/64/a4e503c78a4eb3ac46d8e72a29c1b1237fa85238d8e972b063e0751f5a94/tree_sitter_python-0.25.0-cp310-abi3-macosx_10_9_x86_64.whl", hash = "sha256:14a79a47ddef72f987d5a2c122d148a812169d7484ff5c75a3db9609d419f361", size = 73790, upload-time = "2025-09-11T06:47:47.652Z" }, + { url = "https://files.pythonhosted.org/packages/e6/1d/60d8c2a0cc63d6ec4ba4e99ce61b802d2e39ef9db799bdf2a8f932a6cd4b/tree_sitter_python-0.25.0-cp310-abi3-macosx_11_0_arm64.whl", hash = "sha256:480c21dbd995b7fe44813e741d71fed10ba695e7caab627fb034e3828469d762", size = 76691, upload-time = "2025-09-11T06:47:49.038Z" }, + { url = "https://files.pythonhosted.org/packages/aa/cb/d9b0b67d037922d60cbe0359e0c86457c2da721bc714381a63e2c8e35eba/tree_sitter_python-0.25.0-cp310-abi3-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:86f118e5eecad616ecdb81d171a36dde9bef5a0b21ed71ea9c3e390813c3baf5", size = 108133, upload-time = "2025-09-11T06:47:50.499Z" }, + { url = "https://files.pythonhosted.org/packages/40/bd/bf4787f57e6b2860f3f1c8c62f045b39fb32d6bac4b53d7a9e66de968440/tree_sitter_python-0.25.0-cp310-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:be71650ca2b93b6e9649e5d65c6811aad87a7614c8c1003246b303f6b150f61b", size = 110603, upload-time = "2025-09-11T06:47:51.985Z" }, + { url = "https://files.pythonhosted.org/packages/5d/25/feff09f5c2f32484fbce15db8b49455c7572346ce61a699a41972dea7318/tree_sitter_python-0.25.0-cp310-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:e6d5b5799628cc0f24691ab2a172a8e676f668fe90dc60468bee14084a35c16d", size = 108998, upload-time = "2025-09-11T06:47:53.046Z" }, + { url = "https://files.pythonhosted.org/packages/75/69/4946da3d6c0df316ccb938316ce007fb565d08f89d02d854f2d308f0309f/tree_sitter_python-0.25.0-cp310-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:71959832fc5d9642e52c11f2f7d79ae520b461e63334927e93ca46cd61cd9683", size = 107268, upload-time = "2025-09-11T06:47:54.388Z" }, + { url = "https://files.pythonhosted.org/packages/ed/a2/996fc2dfa1076dc460d3e2f3c75974ea4b8f02f6bc925383aaae519920e8/tree_sitter_python-0.25.0-cp310-abi3-win_amd64.whl", hash = "sha256:9bcde33f18792de54ee579b00e1b4fe186b7926825444766f849bf7181793a76", size = 76073, upload-time = "2025-09-11T06:47:55.773Z" }, + { url = "https://files.pythonhosted.org/packages/07/19/4b5569d9b1ebebb5907d11554a96ef3fa09364a30fcfabeff587495b512f/tree_sitter_python-0.25.0-cp310-abi3-win_arm64.whl", hash = "sha256:0fbf6a3774ad7e89ee891851204c2e2c47e12b63a5edbe2e9156997731c128bb", size = 74169, upload-time = "2025-09-11T06:47:56.747Z" }, +] + +[[package]] +name = "tree-sitter-regex" +version = "0.25.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/86/92/1767b833518d731b97c07cf616ea15495dcc0af584aa0381657be4ec446d/tree_sitter_regex-0.25.0.tar.gz", hash = "sha256:5d29111b3f27d4afb31496476d392d1f562fe0bfe954e8968f1d8683424fc331", size = 22156, upload-time = "2025-09-13T05:00:18.699Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/2b/b4/12e9ba02bab4ce13d1875f6585c3f2a5816233104d1507ea118950a4f7eb/tree_sitter_regex-0.25.0-cp310-abi3-macosx_10_9_x86_64.whl", hash = "sha256:3fa11bbd76b29ac8ca2dbf85ad082f9b18ae6352251d805eb2d4191e1706a9d5", size = 13267, upload-time = "2025-09-13T05:00:10.847Z" }, + { url = "https://files.pythonhosted.org/packages/71/06/6b4f995f61952572a94bcfce12d43fc580226551fab9dd0aac4e94465f38/tree_sitter_regex-0.25.0-cp310-abi3-macosx_11_0_arm64.whl", hash = "sha256:df5713649b89c5758649398053c306c41565f22a6f267cb5ec25596504bcf012", size = 13646, upload-time = "2025-09-13T05:00:12.149Z" }, + { url = "https://files.pythonhosted.org/packages/43/61/d94d889ee415805e5d64fc5163e7e2996975bb2c40d13f547efae3e7e37d/tree_sitter_regex-0.25.0-cp310-abi3-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:cdd92400fd9d8229e584c55e12410251561f0d47eea49db17805e2f64a8b2490", size = 24691, upload-time = "2025-09-13T05:00:13.037Z" }, + { url = "https://files.pythonhosted.org/packages/00/a8/09dd698a9ac2b3d3139a936742b41ec1263f0b86d32ad68f4695871c8860/tree_sitter_regex-0.25.0-cp310-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:cceab1c14deeec9c5899babcb2b7942f0607b4355e66eab4083514f644f1bd52", size = 26741, upload-time = "2025-09-13T05:00:14.182Z" }, + { url = "https://files.pythonhosted.org/packages/d7/bf/985e226c9a9f5ae895ff1a2cbc69531589a7d74acac49b2710ec89d53d80/tree_sitter_regex-0.25.0-cp310-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:253436be178150ca4a0603720e0c246e08b5bdd2dc6df313667d97e6c0fce846", size = 25758, upload-time = "2025-09-13T05:00:14.994Z" }, + { url = "https://files.pythonhosted.org/packages/b8/89/c6a6817e94a7deb61770a21e590a46791778ceed053ba4afbfb095488a23/tree_sitter_regex-0.25.0-cp310-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:883eacc46fd7eaffc328efd5865f1fe8825711892d3a89fccc2c414b061e806d", size = 24575, upload-time = "2025-09-13T05:00:16.081Z" }, + { url = "https://files.pythonhosted.org/packages/d0/5e/04e87eb155875f27355703ac7ab703090e30ad9aac6e003ef5c40820ee98/tree_sitter_regex-0.25.0-cp310-abi3-win_amd64.whl", hash = "sha256:f0f2ebf9a6bb5d0d0da2a8ac51d7e5a985b87cdb24d86db5ddc6a58baf115d5d", size = 15684, upload-time = "2025-09-13T05:00:16.865Z" }, + { url = "https://files.pythonhosted.org/packages/fb/d3/1f37c79dc18cc3c7521fdb51b614d29a36628d2afdc2cac2680967e703a6/tree_sitter_regex-0.25.0-cp310-abi3-win_arm64.whl", hash = "sha256:d5a36150daa452f8aec1c2d6d1f2d26255dc05d1490f9618b14c12a6a648cda4", size = 14525, upload-time = "2025-09-13T05:00:17.673Z" }, +] + +[[package]] +name = "tree-sitter-rust" +version = "0.24.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/b7/87/75cbd22b927267d310f76cca1ab3c1d9d41035dfa3eb9cc95f96ee199440/tree_sitter_rust-0.24.2.tar.gz", hash = "sha256:54fb02a5911e345308b405174465112479f56dc39e3f1e7744d7568595f00db9", size = 339341, upload-time = "2026-03-27T21:08:55.629Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d0/24/2b2d33af5e27c84a4fde4e8cd2594bb4ab1e1cf48756a9f40dadc84956cc/tree_sitter_rust-0.24.2-cp39-abi3-macosx_10_9_x86_64.whl", hash = "sha256:3620cfd12340efa43082d45df76349ff511893a9c361da2f8d6d51e307020a59", size = 129507, upload-time = "2026-03-27T21:08:47.585Z" }, + { url = "https://files.pythonhosted.org/packages/78/2a/cf39f881a545360b5a86bb1accba1f4acc713daab01fb9edd35b6e84f473/tree_sitter_rust-0.24.2-cp39-abi3-macosx_11_0_arm64.whl", hash = "sha256:01a46622735498493f29f3e628a90de95c96a07bfbeb88996243eb986b1cee36", size = 136812, upload-time = "2026-03-27T21:08:48.761Z" }, + { url = "https://files.pythonhosted.org/packages/ca/45/a051bbd3045a61182dde25b93ae9a33d2677c935b16952283e12eaf46051/tree_sitter_rust-0.24.2-cp39-abi3-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:e033c5a93b57c88e0a835880de39fc802909ff69f57aaff6000211c196ea5190", size = 164706, upload-time = "2026-03-27T21:08:49.605Z" }, + { url = "https://files.pythonhosted.org/packages/b5/f6/a5a146df5c0a5daea3ffcd5d7245775fe7f084357770d5a313dd6245ae78/tree_sitter_rust-0.24.2-cp39-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9d76d1208c3638b871236090759dfc13d478921320653a6c9da5336e7c58f65a", size = 170310, upload-time = "2026-03-27T21:08:50.424Z" }, + { url = "https://files.pythonhosted.org/packages/95/a8/f85b1ca75e01361ca5f92d226593ca4857cea49551b9f6c8fa6fc08ea917/tree_sitter_rust-0.24.2-cp39-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:87930163a462408c49ab62c667e74029bc26b4cc7123dd1bdc7352215786c64a", size = 168668, upload-time = "2026-03-27T21:08:51.404Z" }, + { url = "https://files.pythonhosted.org/packages/a2/e1/3519f866a4679ca36acd9f5a06a779ecb8a92b18887c5546458d521df557/tree_sitter_rust-0.24.2-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:da2b86099028fd42c6cd32878b7b16b01f8aac0f7b0e98742b7fa6bc3cf09b89", size = 162403, upload-time = "2026-03-27T21:08:52.588Z" }, + { url = "https://files.pythonhosted.org/packages/34/71/7ef609894dbfe5699eb16f7471f9b8af1d958d8ba3e29c238d7607e8cb47/tree_sitter_rust-0.24.2-cp39-abi3-win_amd64.whl", hash = "sha256:4529c125d928882ddfb879fdc6bc0704913261ecc078b6fa7902559e0daf200d", size = 129422, upload-time = "2026-03-27T21:08:54.031Z" }, + { url = "https://files.pythonhosted.org/packages/b9/d8/050a781172745bc345f98abb7c56e72022ea0790f8e793de981c83c2ef15/tree_sitter_rust-0.24.2-cp39-abi3-win_arm64.whl", hash = "sha256:66ba90f61bd54f4c4f5d30434957daf64507c16b0313df76becb37d63f70a227", size = 128245, upload-time = "2026-03-27T21:08:54.803Z" }, +] + +[[package]] +name = "tree-sitter-sql" +version = "0.3.11" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/e8/5c/3d10387f779f36835486167253682f61d5f4fd8336b7001da1ac7d78f31c/tree_sitter_sql-0.3.11.tar.gz", hash = "sha256:700b93be2174c3c83d174ec3e10b682f72a4fb451f0076c7ce5012f1d5a76cbc", size = 834454, upload-time = "2025-10-01T13:44:15.913Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/32/68/bb80073915dfe1b38935451bc0d65528666c126b2d5878e7140ef9bf9f8a/tree_sitter_sql-0.3.11-cp310-abi3-macosx_10_9_x86_64.whl", hash = "sha256:cf1b0c401756940bf47544ad7c4cc97373fc0dac118f821820953e7015a115e3", size = 322035, upload-time = "2025-10-01T13:44:07.497Z" }, + { url = "https://files.pythonhosted.org/packages/05/45/b2bd5f9919ea15c4ae90a156999101ebd4caa4036babe54efaf9d3e77d55/tree_sitter_sql-0.3.11-cp310-abi3-macosx_11_0_arm64.whl", hash = "sha256:a33cd6880ab2debef036f80365c32becb740ec79946805598488732b6c515fff", size = 341635, upload-time = "2025-10-01T13:44:08.961Z" }, + { url = "https://files.pythonhosted.org/packages/8e/96/7cee5661aa897e5d1a67499944ea5cf8a148953c1dc07a3059a50db8cb56/tree_sitter_sql-0.3.11-cp310-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:344e99b59c8c8d72f7154041e9d054400f4a3fccc16c2c96ac106dde0e7f8d0c", size = 381217, upload-time = "2025-10-01T13:44:10.211Z" }, + { url = "https://files.pythonhosted.org/packages/1d/c1/eec7c09a9c94436ea4c56d096feba815e42b209b3d41a17532f99ecf0c67/tree_sitter_sql-0.3.11-cp310-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5128b12f71ac0f5ebcc607f67a62cdc56a187c1a5ba7553feeb9c5f6f9bc3c72", size = 380606, upload-time = "2025-10-01T13:44:11.135Z" }, + { url = "https://files.pythonhosted.org/packages/94/1d/06e9598799bd119e56f6e431d42c2f3a5c6dee858a5b6ad7633cc4d670aa/tree_sitter_sql-0.3.11-cp310-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:03cc164fcf7b1f711e7d939aeb4d1f62c76f4162e081c70b860b4fcd91806a38", size = 380862, upload-time = "2025-10-01T13:44:12.072Z" }, + { url = "https://files.pythonhosted.org/packages/52/e9/a7afd7f68ce165c040ce50e67bb05553784a8e17f37e057405d693fc869d/tree_sitter_sql-0.3.11-cp310-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:0e22ea8de690dd9960d8c0c36c4cd25417b084e1e29c91ac0235fbdb3abb4664", size = 379447, upload-time = "2025-10-01T13:44:13.062Z" }, + { url = "https://files.pythonhosted.org/packages/eb/b3/57ff42dadd33c06fabe6c725de50e1625e1060f1571cc21a9260febadc1f/tree_sitter_sql-0.3.11-cp310-abi3-win_amd64.whl", hash = "sha256:c57b877702d218c0856592d33320c02b2dc8411d8820b3bf7b81be86c54fa0bb", size = 343550, upload-time = "2025-10-01T13:44:13.988Z" }, + { url = "https://files.pythonhosted.org/packages/77/60/f10b8551f435d57a4748820ee30e66df2682820b2972375c2b89d2e5fb10/tree_sitter_sql-0.3.11-cp310-abi3-win_arm64.whl", hash = "sha256:8a1e42f0a2c9b01b23074708ecf5b8d21b9a0440e3dff279d8cf466cdf1a877e", size = 333547, upload-time = "2025-10-01T13:44:14.893Z" }, +] + +[[package]] +name = "tree-sitter-toml" +version = "0.7.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/59/b9/03ee757ac375e77186ea112c14fcf31e0ca70b27b6388d93dcceef61f029/tree_sitter_toml-0.7.0.tar.gz", hash = "sha256:29e257612fa8f0c1fcbc4e7e08ddc561169f1725265302e64d81086354144a70", size = 16803, upload-time = "2024-12-03T05:03:46.711Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ad/4d/1e00a5cd8dba09e340b25aa60a3eaeae584ff5bc5d93b0777169d6741ee5/tree_sitter_toml-0.7.0-cp39-abi3-macosx_10_9_x86_64.whl", hash = "sha256:b9ae5c3e7c5b6bb05299dd73452ceafa7fa0687d5af3012332afa7757653b676", size = 14755, upload-time = "2024-12-03T05:03:39.973Z" }, + { url = "https://files.pythonhosted.org/packages/92/20/ac8a20805339105fe0bbb6beaa99dbbd1159647760ddd786142364e0b7f2/tree_sitter_toml-0.7.0-cp39-abi3-macosx_11_0_arm64.whl", hash = "sha256:18be09538e9775cddc0290392c4e2739de2201260af361473ca60b5c21f7bd22", size = 15201, upload-time = "2024-12-03T05:03:40.871Z" }, + { url = "https://files.pythonhosted.org/packages/36/cf/7bae8e20310e7cc763ae407599e6130b819f91ad5197e210a56f697f15d8/tree_sitter_toml-0.7.0-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a045e0acfcf91b7065066f7e51ea038ed7385c1e35e7e8fae18f252d3f8adb8c", size = 30855, upload-time = "2024-12-03T05:03:41.83Z" }, + { url = "https://files.pythonhosted.org/packages/7d/49/51f2fa25a3ff4d45af1be8cbf7a3d733fb6a390b2763cfa00892fffe90bf/tree_sitter_toml-0.7.0-cp39-abi3-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7a2f8cf9d73f07b6628093b35e5c5fbac039247e32cb075eaa5289a5914e73af", size = 29741, upload-time = "2024-12-03T05:03:42.577Z" }, + { url = "https://files.pythonhosted.org/packages/4d/30/dd94ed1ab0bc3198e16ed2140a6f4d2474c1cd561d8c6847ab269af73654/tree_sitter_toml-0.7.0-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:860ffa4513b2dc3083d8e412bd815a350b0a9490624b37e7c8f6ed5c6f9ce63c", size = 30498, upload-time = "2024-12-03T05:03:43.447Z" }, + { url = "https://files.pythonhosted.org/packages/a2/dd/0681d43aa09dd161565858bcfdd4402c8d10259f142de734448f5ce17418/tree_sitter_toml-0.7.0-cp39-abi3-win_amd64.whl", hash = "sha256:2760a04f06937b01b1562a2135cd7e8207e399e73ef75bbebc77e37b1ad3b15d", size = 16756, upload-time = "2024-12-03T05:03:44.227Z" }, + { url = "https://files.pythonhosted.org/packages/17/e4/cce587001e620f1972e70aeabc1b38893a85681be9ec5a64e4be9ce17410/tree_sitter_toml-0.7.0-cp39-abi3-win_arm64.whl", hash = "sha256:fd00fd8a51c65aa19c40539431cb1773d87c30af5757b4041fa6c229058420b4", size = 15651, upload-time = "2024-12-03T05:03:45.261Z" }, +] + +[[package]] +name = "tree-sitter-xml" +version = "0.7.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/41/ba/77a92dbb4dfb374fb99863a07f938de7509ceeaa74139933ac2bd306eeb1/tree_sitter_xml-0.7.0.tar.gz", hash = "sha256:ab0ff396f20230ad8483d968151ce0c35abe193eb023b20fbd8b8ce4cf9e9f61", size = 54635, upload-time = "2024-11-13T17:27:01.655Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/36/1d/6b8974c493973c0c9df2bbf220a1f0a96fa785da81a5a13461faafd1441c/tree_sitter_xml-0.7.0-cp39-abi3-macosx_10_9_x86_64.whl", hash = "sha256:cc3e516d4c1e0860fb22172c172148debb825ba638971bc48bad15b22e5b0bae", size = 35404, upload-time = "2024-11-13T17:26:51.989Z" }, + { url = "https://files.pythonhosted.org/packages/75/f5/31013d04c4e3b9a55e90168cc222a601c84235ba4953a5a06b5cdf8353c4/tree_sitter_xml-0.7.0-cp39-abi3-macosx_11_0_arm64.whl", hash = "sha256:0674fdf4cc386e4d323cb287d3b072663de0f20a9e9af5d5e09821aae56a9e5c", size = 35488, upload-time = "2024-11-13T17:26:53.526Z" }, + { url = "https://files.pythonhosted.org/packages/c8/e6/e7493217f950a7c5969e3f3f057664142fa948abefd2dba5acea25719d55/tree_sitter_xml-0.7.0-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8c0fe5f2d6cc09974c8375c8ea9b24909f493b5bf04aacdc4c694b5d2ae6b040", size = 74199, upload-time = "2024-11-13T17:26:55.069Z" }, + { url = "https://files.pythonhosted.org/packages/94/27/1dd6815592489de51fa7b5fffc1160cd385ade7fa06f07b998742ac18020/tree_sitter_xml-0.7.0-cp39-abi3-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dd3209516a4d84dff90bc91d2ad2ce246de8504cede4358849687fa8e71536e7", size = 76244, upload-time = "2024-11-13T17:26:56.655Z" }, + { url = "https://files.pythonhosted.org/packages/20/10/2e4e84c50b2175cb53d255ef154aa893cb82cc9d035d7a1a73be9d2d2db4/tree_sitter_xml-0.7.0-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:87578e15fa55f44ecd9f331233b6f8a2cbde3546b354c830ecb862a632379455", size = 75112, upload-time = "2024-11-13T17:26:57.729Z" }, + { url = "https://files.pythonhosted.org/packages/ae/91/77c348568bccb179eca21062c923f6f54026900b09fe0cf1aae89d78a0c8/tree_sitter_xml-0.7.0-cp39-abi3-win_amd64.whl", hash = "sha256:9ba2dafc6ce9feaf4ccc617d3aeea57f8e0ca05edad34953e788001ebff79133", size = 36558, upload-time = "2024-11-13T17:26:58.702Z" }, + { url = "https://files.pythonhosted.org/packages/be/cc/6b4de230770d7be87b2a415583121ac565ce1ff7d9a1ad7fec11f8e613fc/tree_sitter_xml-0.7.0-cp39-abi3-win_arm64.whl", hash = "sha256:fc759f710a8fd7a01c23e2d7cb013679199045bea3dc0e5151650a11322aaf40", size = 34610, upload-time = "2024-11-13T17:27:00.187Z" }, +] + +[[package]] +name = "tree-sitter-yaml" +version = "0.7.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/57/b6/941d356ac70c90b9d2927375259e3a4204f38f7499ec6e7e8a95b9664689/tree_sitter_yaml-0.7.2.tar.gz", hash = "sha256:756db4c09c9d9e97c81699e8f941cb8ce4e51104927f6090eefe638ee567d32c", size = 84882, upload-time = "2025-10-07T14:40:36.071Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/38/29/c0b8dbff302c49ff4284666ffb6f2f21145006843bb4c3a9a85d0ec0b7ae/tree_sitter_yaml-0.7.2-cp310-abi3-macosx_10_9_x86_64.whl", hash = "sha256:7e269ddcfcab8edb14fbb1f1d34eed1e1e26888f78f94eedfe7cc98c60f8bc9f", size = 43898, upload-time = "2025-10-07T14:40:29.486Z" }, + { url = "https://files.pythonhosted.org/packages/18/0d/15a5add06b3932b5e4ce5f5e8e179197097decfe82a0ef000952c8b98216/tree_sitter_yaml-0.7.2-cp310-abi3-macosx_11_0_arm64.whl", hash = "sha256:0807b7966e23ddf7dddc4545216e28b5a58cdadedcecca86b8d8c74271a07870", size = 44691, upload-time = "2025-10-07T14:40:30.369Z" }, + { url = "https://files.pythonhosted.org/packages/72/92/c4b896c90d08deb8308fadbad2210fdcc4c66c44ab4292eac4e80acb4b61/tree_sitter_yaml-0.7.2-cp310-abi3-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:f1a5c60c98b6c4c037aae023569f020d0c489fad8dc26fdfd5510363c9c29a41", size = 91430, upload-time = "2025-10-07T14:40:31.16Z" }, + { url = "https://files.pythonhosted.org/packages/89/59/61f1fed31eb6d46ff080b8c0d53658cf29e10263f41ef5fe34768908037a/tree_sitter_yaml-0.7.2-cp310-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:88636d19d0654fd24f4f242eaaafa90f6f5ebdba8a62e4b32d251ed156c51a2a", size = 92428, upload-time = "2025-10-07T14:40:31.954Z" }, + { url = "https://files.pythonhosted.org/packages/e3/62/a33a04d19b7f9a0ded780b9c9fcc6279e37c5d00b89b00425bb807a22cc2/tree_sitter_yaml-0.7.2-cp310-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:1d2e8f0bb14aa4537320952d0f9607eef3021d5aada8383c34ebeece17db1e06", size = 90580, upload-time = "2025-10-07T14:40:33.037Z" }, + { url = "https://files.pythonhosted.org/packages/6c/e7/9525defa7b30792623f56b1fba9bbba361752348875b165b8975b87398fd/tree_sitter_yaml-0.7.2-cp310-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:74ca712c50fc9d7dbc68cb36b4a7811d6e67a5466b5a789f19bf8dd6084ef752", size = 90455, upload-time = "2025-10-07T14:40:33.778Z" }, + { url = "https://files.pythonhosted.org/packages/4a/d6/8d1e1ace03db3b02e64e91daf21d1347941d1bbecc606a5473a1a605250d/tree_sitter_yaml-0.7.2-cp310-abi3-win_amd64.whl", hash = "sha256:7587b5ca00fc4f9a548eff649697a3b395370b2304b399ceefa2087d8a6c9186", size = 45514, upload-time = "2025-10-07T14:40:34.562Z" }, + { url = "https://files.pythonhosted.org/packages/d8/c7/dcf3ea1c4f5da9b10353b9af4455d756c92d728a8f58f03c480d3ef0ead5/tree_sitter_yaml-0.7.2-cp310-abi3-win_arm64.whl", hash = "sha256:f63c227b18e7ce7587bce124578f0bbf1f890ac63d3e3cd027417574273642c4", size = 44065, upload-time = "2025-10-07T14:40:35.337Z" }, +] + +[[package]] +name = "twine" +version = "6.2.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "id" }, + { name = "keyring", marker = "platform_machine != 'ppc64le' and platform_machine != 's390x'" }, + { name = "packaging" }, + { name = "readme-renderer" }, + { name = "requests" }, + { name = "requests-toolbelt" }, + { name = "rfc3986" }, + { name = "rich" }, + { name = "urllib3" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/e0/a8/949edebe3a82774c1ec34f637f5dd82d1cf22c25e963b7d63771083bbee5/twine-6.2.0.tar.gz", hash = "sha256:e5ed0d2fd70c9959770dce51c8f39c8945c574e18173a7b81802dab51b4b75cf", size = 172262, upload-time = "2025-09-04T15:43:17.255Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/3a/7a/882d99539b19b1490cac5d77c67338d126e4122c8276bf640e411650c830/twine-6.2.0-py3-none-any.whl", hash = "sha256:418ebf08ccda9a8caaebe414433b0ba5e25eb5e4a927667122fbe8f829f985d8", size = 42727, upload-time = "2025-09-04T15:43:15.994Z" }, +] + +[[package]] +name = "typing-extensions" +version = "4.16.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/f6/cc/6253133b5bb138fc3306cebfbda2c520f545d36b5be2c7255cc528bb45d6/typing_extensions-4.16.0.tar.gz", hash = "sha256:dc983d19a509c94dba722ee6abd33940f7c05a89e243c47e907eb4db6f1a43e5", size = 113555, upload-time = "2026-07-02T08:40:05.92Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/49/d3/b8441a820a491ddfc024b0b0cf0393375b75ea13866d9c66727e54c2fc80/typing_extensions-4.16.0-py3-none-any.whl", hash = "sha256:481caa481374e813c1b176ada14e97f1f67a4539ce9cfeb3f350d78d6370c2e8", size = 45571, upload-time = "2026-07-02T08:40:04.659Z" }, +] + +[[package]] +name = "uc-micro-py" +version = "2.0.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/78/67/9a363818028526e2d4579334460df777115bdec1bb77c08f9db88f6389f2/uc_micro_py-2.0.0.tar.gz", hash = "sha256:c53691e495c8db60e16ffc4861a35469b0ba0821fe409a8a7a0a71864d33a811", size = 6611, upload-time = "2026-03-01T06:31:27.526Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/61/73/d21edf5b204d1467e06500080a50f79d49ef2b997c79123a536d4a17d97c/uc_micro_py-2.0.0-py3-none-any.whl", hash = "sha256:3603a3859af53e5a39bc7677713c78ea6589ff188d70f4fee165db88e22b242c", size = 6383, upload-time = "2026-03-01T06:31:26.257Z" }, +] + +[[package]] +name = "urllib3" +version = "2.7.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/53/0c/06f8b233b8fd13b9e5ee11424ef85419ba0d8ba0b3138bf360be2ff56953/urllib3-2.7.0.tar.gz", hash = "sha256:231e0ec3b63ceb14667c67be60f2f2c40a518cb38b03af60abc813da26505f4c", size = 433602, upload-time = "2026-05-07T16:13:18.596Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/7f/3e/5db95bcf282c52709639744ca2a8b149baccf648e39c8cc87553df9eae0c/urllib3-2.7.0-py3-none-any.whl", hash = "sha256:9fb4c81ebbb1ce9531cce37674bbc6f1360472bc18ca9a553ede278ef7276897", size = 131087, upload-time = "2026-05-07T16:13:17.151Z" }, +] + +[[package]] +name = "vimkit" +version = "0.1.0" +source = { editable = "." } +dependencies = [ + { name = "textual", extra = ["syntax"] }, +] + +[package.dev-dependencies] +dev = [ + { name = "twine" }, +] + +[package.metadata] +requires-dist = [{ name = "textual", extras = ["syntax"], specifier = ">=8.2.8" }] + +[package.metadata.requires-dev] +dev = [{ name = "twine", specifier = ">=6.1.0" }]