From e846f029ef159de5966ecbd72e924bf02b23ee69 Mon Sep 17 00:00:00 2001 From: James Brechtel Date: Sat, 9 May 2026 15:08:43 -0400 Subject: [PATCH] Initial project skeleton - basic process management --- .gitignore | 1 + .python-version | 1 + README.md | 13 +++ pyproject.toml | 19 ++++ src/honker/__init__.py | 0 .../__pycache__/__init__.cpython-312.pyc | Bin 0 -> 141 bytes src/honker/__pycache__/config.cpython-312.pyc | Bin 0 -> 1018 bytes src/honker/__pycache__/main.cpython-312.pyc | Bin 0 -> 3339 bytes .../__pycache__/processes.cpython-312.pyc | Bin 0 -> 6031 bytes src/honker/config.py | 18 ++++ src/honker/main.py | 70 +++++++++++++ src/honker/processes.py | 95 ++++++++++++++++++ src/honker/py.typed | 0 uv.lock | 60 +++++++++++ 14 files changed, 277 insertions(+) create mode 100644 .gitignore create mode 100644 .python-version create mode 100644 README.md create mode 100644 pyproject.toml create mode 100644 src/honker/__init__.py create mode 100644 src/honker/__pycache__/__init__.cpython-312.pyc create mode 100644 src/honker/__pycache__/config.cpython-312.pyc create mode 100644 src/honker/__pycache__/main.cpython-312.pyc create mode 100644 src/honker/__pycache__/processes.cpython-312.pyc create mode 100644 src/honker/config.py create mode 100644 src/honker/main.py create mode 100644 src/honker/processes.py create mode 100644 src/honker/py.typed create mode 100644 uv.lock diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..98d8a5a --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +logs diff --git a/.python-version b/.python-version new file mode 100644 index 0000000..e4fba21 --- /dev/null +++ b/.python-version @@ -0,0 +1 @@ +3.12 diff --git a/README.md b/README.md new file mode 100644 index 0000000..8d50505 --- /dev/null +++ b/README.md @@ -0,0 +1,13 @@ +# Honker + +Honker acts as an ACP (https://agentclientprotocol.com/protocol/overview) bridge to let a user use Signal Messenger to drive the Goose https://goose-docs.ai/ coding agent + +## Technical details + +Honker is written in Python and uses SQLite as its backing store for minimal state tracking. + +Configuration is done via ~/.config/honker/config.yaml + +Honker expects both Goose and signal-cli to already be configured on the machine where it runs. + +Honker manages the lifecycle of both goose and signal-cli rather than expecting them to be ran as a separate service. diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000..6d48926 --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,19 @@ +[project] +name = "honker" +version = "0.1.0" +description = "ACP bridge between Signal Messenger and the Goose coding agent" +readme = "README.md" +authors = [ + { name = "James Brechtel", email = "git@jamesbrechtel.com" } +] +requires-python = ">=3.12" +dependencies = [ + "pyyaml>=6.0", +] + +[project.scripts] +honker = "honker.main:main" + +[build-system] +requires = ["uv_build>=0.11.11,<0.12.0"] +build-backend = "uv_build" diff --git a/src/honker/__init__.py b/src/honker/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/src/honker/__pycache__/__init__.cpython-312.pyc b/src/honker/__pycache__/__init__.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..468013e64f538e8cbba583f2b5b596febfc724ae GIT binary patch literal 141 zcmX@j%ge<81iUT(GePuY5P=Rpvj9b=GgLBYGWxA#C}INgK7-W!($O!^FUr<0NG&SP z&r8hF&&bcqPA$?eE=q|kYwc^VF3c-(P2?_!+b$Tkq%WjBXo125ZqP2*$?_C4~w?@PXa`Mvkf?{5J_ zHXi-ben0?@sN;&KX`H+u#yU`d0(Fp~JEOxQlVPCqj?J(PP?=euoA_tnZkh^P6#-DV zhW1dM@%%T3Z$x{~W%?SKywa)&Dx<)vm(*-gy*W-1A9^yr3XJ-{(vGPpw7Ch%%vZf} z*(l|-8Jo`=rGkp8qT|OaQcQ%vq9*y>7(>|4>3yy%U>R8+7T1KVCYwXQutbZXQ} zl+ERhqKX7+hFmI>BSWd=Sax!}e{>-2z~K~4HXF0P1ScdIpbIp`cdKY9bGmvPbrF}I zJXVQ1W+8+hf$$l09s%(%*uAQ(Yirum!sdt-jP3ZWVDIZbD>%FhIzEJZ>gnfG8&l8j wZ}nQ?>pL@6xbKb63J>p&PFvyWT@d=l`{0#TrJi0Z90Ou*rN7e+b&^iM0lOIZ{{R30 literal 0 HcmV?d00001 diff --git a/src/honker/__pycache__/main.cpython-312.pyc b/src/honker/__pycache__/main.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..611b8dace61a0f440f06168abace5796998a8e5d GIT binary patch literal 3339 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 b{rhbz2&%X1KjS{#LaXUFh98kGde#2|eq*av literal 0 HcmV?d00001 diff --git a/src/honker/config.py b/src/honker/config.py new file mode 100644 index 0000000..b28c85a --- /dev/null +++ b/src/honker/config.py @@ -0,0 +1,18 @@ +import os +from pathlib import Path + +import yaml + +DEFAULT_CONFIG_PATH = Path.home() / ".config" / "honker" / "config.yaml" +DEFAULT_SIGNAL_SOCKET_PATH = Path.home() / ".local" / "share" / "honker" / "signal-cli.sock" + + +def load_config(path: Path | None = None) -> dict: + """Load configuration from ~/.config/honker/config.yaml""" + config_path = path or DEFAULT_CONFIG_PATH + + if not config_path.exists(): + return {} + + with open(config_path) as f: + return yaml.safe_load(f) or {} diff --git a/src/honker/main.py b/src/honker/main.py new file mode 100644 index 0000000..34237d3 --- /dev/null +++ b/src/honker/main.py @@ -0,0 +1,70 @@ +import argparse +import logging +import signal +import sys +import time +from pathlib import Path + +from honker.config import load_config +from honker.processes import start_goose, start_signal_cli + +log = logging.getLogger(__name__) + + +def parse_args() -> argparse.Namespace: + parser = argparse.ArgumentParser(description="Honker – Signal ↔ Goose ACP bridge") + parser.add_argument( + "--debug", + metavar="DIR", + type=Path, + default=None, + help="Enable debug logging; write per-process stdout/stderr logs to DIR", + ) + return parser.parse_args() + + +def main() -> None: + args = parse_args() + + logging.basicConfig( + level=logging.DEBUG if args.debug else logging.INFO, + format="%(asctime)s [%(name)s] %(levelname)s: %(message)s", + ) + + config = load_config() + log.info("Honker starting up") + + if args.debug: + log.debug("Debug mode enabled — process logs will be written to %s", args.debug) + + goose = start_goose(debug_dir=args.debug) + signal_cli = start_signal_cli(debug_dir=args.debug) + + processes = [goose, signal_cli] + + def shutdown(signum, frame): + log.info("Received signal %s, shutting down…", signum) + for proc in reversed(processes): + proc.stop() + sys.exit(0) + + signal.signal(signal.SIGINT, shutdown) + signal.signal(signal.SIGTERM, shutdown) + + # Keep running and watch for unexpected exits + try: + while True: + for proc in processes: + if not proc.is_running: + log.error("%s exited unexpectedly (rc=%s)", proc.name, proc.process.returncode) + # Shut everything down if a child dies + for p in reversed(processes): + p.stop() + sys.exit(1) + time.sleep(1) + except KeyboardInterrupt: + shutdown(signal.SIGINT, None) + + +if __name__ == "__main__": + main() diff --git a/src/honker/processes.py b/src/honker/processes.py new file mode 100644 index 0000000..578a2e7 --- /dev/null +++ b/src/honker/processes.py @@ -0,0 +1,95 @@ +import logging +import subprocess +import signal +import sys +import threading +from pathlib import Path + +log = logging.getLogger(__name__) + + +def _stream_to_file(stream, path: Path) -> threading.Thread: + """Read from a stream and write each line to a file in a background thread.""" + def _run(): + with open(path, "a") as f: + for line in stream: + f.write(line if isinstance(line, str) else line.decode("utf-8", errors="replace")) + f.flush() + t = threading.Thread(target=_run, daemon=True) + t.start() + return t + + +class ManagedProcess: + """Wraps a subprocess with lifecycle management.""" + + def __init__(self, name: str, args: list[str], debug_dir: Path | None = None): + self.name = name + self.args = args + self.debug_dir = debug_dir + self.process: subprocess.Popen | None = None + self._log_threads: list[threading.Thread] = [] + + def start(self) -> None: + log.info("Starting %s: %s", self.name, " ".join(self.args)) + + if self.debug_dir is not None: + self.debug_dir.mkdir(parents=True, exist_ok=True) + stdout_path = self.debug_dir / f"{self.name}.stdout.log" + stderr_path = self.debug_dir / f"{self.name}.stderr.log" + log.info("%s debug logs: stdout=%s stderr=%s", self.name, stdout_path, stderr_path) + + self.process = subprocess.Popen( + self.args, + stdout=subprocess.PIPE, + stderr=subprocess.PIPE, + ) + self._log_threads.append(_stream_to_file(self.process.stdout, stdout_path)) + self._log_threads.append(_stream_to_file(self.process.stderr, stderr_path)) + else: + self.process = subprocess.Popen( + self.args, + stdout=subprocess.PIPE, + stderr=subprocess.STDOUT, + ) + + log.info("%s started (pid=%d)", self.name, self.process.pid) + + def stop(self) -> None: + if self.process is None or self.process.poll() is not None: + return + log.info("Stopping %s (pid=%d)", self.name, self.process.pid) + self.process.terminate() + try: + self.process.wait(timeout=10) + except subprocess.TimeoutExpired: + log.warning("%s did not exit in time, killing", self.name) + self.process.kill() + self.process.wait() + # Wait for log threads to drain + for t in self._log_threads: + t.join(timeout=5) + log.info("%s stopped", self.name) + + @property + def is_running(self) -> bool: + return self.process is not None and self.process.poll() is None + + +def start_goose(debug_dir: Path | None = None) -> ManagedProcess: + proc = ManagedProcess("goose", ["goose", "serve"], debug_dir=debug_dir) + proc.start() + return proc + + +def start_signal_cli(socket_path: Path | None = None, debug_dir: Path | None = None) -> ManagedProcess: + from honker.config import DEFAULT_SIGNAL_SOCKET_PATH + sock = socket_path or DEFAULT_SIGNAL_SOCKET_PATH + sock.parent.mkdir(parents=True, exist_ok=True) + proc = ManagedProcess( + "signal-cli", + ["signal-cli", "daemon", "--socket", str(sock)], + debug_dir=debug_dir, + ) + proc.start() + return proc diff --git a/src/honker/py.typed b/src/honker/py.typed new file mode 100644 index 0000000..e69de29 diff --git a/uv.lock b/uv.lock new file mode 100644 index 0000000..dedb3aa --- /dev/null +++ b/uv.lock @@ -0,0 +1,60 @@ +version = 1 +revision = 3 +requires-python = ">=3.12" + +[[package]] +name = "honker" +version = "0.1.0" +source = { editable = "." } +dependencies = [ + { name = "pyyaml" }, +] + +[package.metadata] +requires-dist = [{ name = "pyyaml", specifier = ">=6.0" }] + +[[package]] +name = "pyyaml" +version = "6.0.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/05/8e/961c0007c59b8dd7729d542c61a4d537767a59645b82a0b521206e1e25c2/pyyaml-6.0.3.tar.gz", hash = "sha256:d76623373421df22fb4cf8817020cbb7ef15c725b9d5e45f17e189bfc384190f", size = 130960, upload-time = "2025-09-25T21:33:16.546Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d1/33/422b98d2195232ca1826284a76852ad5a86fe23e31b009c9886b2d0fb8b2/pyyaml-6.0.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:7f047e29dcae44602496db43be01ad42fc6f1cc0d8cd6c83d342306c32270196", size = 182063, upload-time = "2025-09-25T21:32:11.445Z" }, + { url = "https://files.pythonhosted.org/packages/89/a0/6cf41a19a1f2f3feab0e9c0b74134aa2ce6849093d5517a0c550fe37a648/pyyaml-6.0.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:fc09d0aa354569bc501d4e787133afc08552722d3ab34836a80547331bb5d4a0", size = 173973, upload-time = "2025-09-25T21:32:12.492Z" }, + { url = "https://files.pythonhosted.org/packages/ed/23/7a778b6bd0b9a8039df8b1b1d80e2e2ad78aa04171592c8a5c43a56a6af4/pyyaml-6.0.3-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9149cad251584d5fb4981be1ecde53a1ca46c891a79788c0df828d2f166bda28", size = 775116, upload-time = "2025-09-25T21:32:13.652Z" }, + { url = "https://files.pythonhosted.org/packages/65/30/d7353c338e12baef4ecc1b09e877c1970bd3382789c159b4f89d6a70dc09/pyyaml-6.0.3-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:5fdec68f91a0c6739b380c83b951e2c72ac0197ace422360e6d5a959d8d97b2c", size = 844011, upload-time = "2025-09-25T21:32:15.21Z" }, + { url = "https://files.pythonhosted.org/packages/8b/9d/b3589d3877982d4f2329302ef98a8026e7f4443c765c46cfecc8858c6b4b/pyyaml-6.0.3-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ba1cc08a7ccde2d2ec775841541641e4548226580ab850948cbfda66a1befcdc", size = 807870, upload-time = "2025-09-25T21:32:16.431Z" }, + { url = "https://files.pythonhosted.org/packages/05/c0/b3be26a015601b822b97d9149ff8cb5ead58c66f981e04fedf4e762f4bd4/pyyaml-6.0.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:8dc52c23056b9ddd46818a57b78404882310fb473d63f17b07d5c40421e47f8e", size = 761089, upload-time = "2025-09-25T21:32:17.56Z" }, + { url = "https://files.pythonhosted.org/packages/be/8e/98435a21d1d4b46590d5459a22d88128103f8da4c2d4cb8f14f2a96504e1/pyyaml-6.0.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:41715c910c881bc081f1e8872880d3c650acf13dfa8214bad49ed4cede7c34ea", size = 790181, upload-time = "2025-09-25T21:32:18.834Z" }, + { url = "https://files.pythonhosted.org/packages/74/93/7baea19427dcfbe1e5a372d81473250b379f04b1bd3c4c5ff825e2327202/pyyaml-6.0.3-cp312-cp312-win32.whl", hash = "sha256:96b533f0e99f6579b3d4d4995707cf36df9100d67e0c8303a0c55b27b5f99bc5", size = 137658, upload-time = "2025-09-25T21:32:20.209Z" }, + { url = "https://files.pythonhosted.org/packages/86/bf/899e81e4cce32febab4fb42bb97dcdf66bc135272882d1987881a4b519e9/pyyaml-6.0.3-cp312-cp312-win_amd64.whl", hash = "sha256:5fcd34e47f6e0b794d17de1b4ff496c00986e1c83f7ab2fb8fcfe9616ff7477b", size = 154003, upload-time = "2025-09-25T21:32:21.167Z" }, + { url = "https://files.pythonhosted.org/packages/1a/08/67bd04656199bbb51dbed1439b7f27601dfb576fb864099c7ef0c3e55531/pyyaml-6.0.3-cp312-cp312-win_arm64.whl", hash = "sha256:64386e5e707d03a7e172c0701abfb7e10f0fb753ee1d773128192742712a98fd", size = 140344, upload-time = "2025-09-25T21:32:22.617Z" }, + { url = "https://files.pythonhosted.org/packages/d1/11/0fd08f8192109f7169db964b5707a2f1e8b745d4e239b784a5a1dd80d1db/pyyaml-6.0.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:8da9669d359f02c0b91ccc01cac4a67f16afec0dac22c2ad09f46bee0697eba8", size = 181669, upload-time = "2025-09-25T21:32:23.673Z" }, + { url = "https://files.pythonhosted.org/packages/b1/16/95309993f1d3748cd644e02e38b75d50cbc0d9561d21f390a76242ce073f/pyyaml-6.0.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:2283a07e2c21a2aa78d9c4442724ec1eb15f5e42a723b99cb3d822d48f5f7ad1", size = 173252, upload-time = "2025-09-25T21:32:25.149Z" }, + { url = "https://files.pythonhosted.org/packages/50/31/b20f376d3f810b9b2371e72ef5adb33879b25edb7a6d072cb7ca0c486398/pyyaml-6.0.3-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ee2922902c45ae8ccada2c5b501ab86c36525b883eff4255313a253a3160861c", size = 767081, upload-time = "2025-09-25T21:32:26.575Z" }, + { url = "https://files.pythonhosted.org/packages/49/1e/a55ca81e949270d5d4432fbbd19dfea5321eda7c41a849d443dc92fd1ff7/pyyaml-6.0.3-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:a33284e20b78bd4a18c8c2282d549d10bc8408a2a7ff57653c0cf0b9be0afce5", size = 841159, upload-time = "2025-09-25T21:32:27.727Z" }, + { url = "https://files.pythonhosted.org/packages/74/27/e5b8f34d02d9995b80abcef563ea1f8b56d20134d8f4e5e81733b1feceb2/pyyaml-6.0.3-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0f29edc409a6392443abf94b9cf89ce99889a1dd5376d94316ae5145dfedd5d6", size = 801626, upload-time = "2025-09-25T21:32:28.878Z" }, + { url = "https://files.pythonhosted.org/packages/f9/11/ba845c23988798f40e52ba45f34849aa8a1f2d4af4b798588010792ebad6/pyyaml-6.0.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:f7057c9a337546edc7973c0d3ba84ddcdf0daa14533c2065749c9075001090e6", size = 753613, upload-time = "2025-09-25T21:32:30.178Z" }, + { url = "https://files.pythonhosted.org/packages/3d/e0/7966e1a7bfc0a45bf0a7fb6b98ea03fc9b8d84fa7f2229e9659680b69ee3/pyyaml-6.0.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:eda16858a3cab07b80edaf74336ece1f986ba330fdb8ee0d6c0d68fe82bc96be", size = 794115, upload-time = "2025-09-25T21:32:31.353Z" }, + { url = "https://files.pythonhosted.org/packages/de/94/980b50a6531b3019e45ddeada0626d45fa85cbe22300844a7983285bed3b/pyyaml-6.0.3-cp313-cp313-win32.whl", hash = "sha256:d0eae10f8159e8fdad514efdc92d74fd8d682c933a6dd088030f3834bc8e6b26", size = 137427, upload-time = "2025-09-25T21:32:32.58Z" }, + { url = "https://files.pythonhosted.org/packages/97/c9/39d5b874e8b28845e4ec2202b5da735d0199dbe5b8fb85f91398814a9a46/pyyaml-6.0.3-cp313-cp313-win_amd64.whl", hash = "sha256:79005a0d97d5ddabfeeea4cf676af11e647e41d81c9a7722a193022accdb6b7c", size = 154090, upload-time = "2025-09-25T21:32:33.659Z" }, + { url = "https://files.pythonhosted.org/packages/73/e8/2bdf3ca2090f68bb3d75b44da7bbc71843b19c9f2b9cb9b0f4ab7a5a4329/pyyaml-6.0.3-cp313-cp313-win_arm64.whl", hash = "sha256:5498cd1645aa724a7c71c8f378eb29ebe23da2fc0d7a08071d89469bf1d2defb", size = 140246, upload-time = "2025-09-25T21:32:34.663Z" }, + { url = "https://files.pythonhosted.org/packages/9d/8c/f4bd7f6465179953d3ac9bc44ac1a8a3e6122cf8ada906b4f96c60172d43/pyyaml-6.0.3-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:8d1fab6bb153a416f9aeb4b8763bc0f22a5586065f86f7664fc23339fc1c1fac", size = 181814, upload-time = "2025-09-25T21:32:35.712Z" }, + { url = "https://files.pythonhosted.org/packages/bd/9c/4d95bb87eb2063d20db7b60faa3840c1b18025517ae857371c4dd55a6b3a/pyyaml-6.0.3-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:34d5fcd24b8445fadc33f9cf348c1047101756fd760b4dacb5c3e99755703310", size = 173809, upload-time = "2025-09-25T21:32:36.789Z" }, + { url = "https://files.pythonhosted.org/packages/92/b5/47e807c2623074914e29dabd16cbbdd4bf5e9b2db9f8090fa64411fc5382/pyyaml-6.0.3-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:501a031947e3a9025ed4405a168e6ef5ae3126c59f90ce0cd6f2bfc477be31b7", size = 766454, upload-time = "2025-09-25T21:32:37.966Z" }, + { url = "https://files.pythonhosted.org/packages/02/9e/e5e9b168be58564121efb3de6859c452fccde0ab093d8438905899a3a483/pyyaml-6.0.3-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:b3bc83488de33889877a0f2543ade9f70c67d66d9ebb4ac959502e12de895788", size = 836355, upload-time = "2025-09-25T21:32:39.178Z" }, + { url = "https://files.pythonhosted.org/packages/88/f9/16491d7ed2a919954993e48aa941b200f38040928474c9e85ea9e64222c3/pyyaml-6.0.3-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c458b6d084f9b935061bc36216e8a69a7e293a2f1e68bf956dcd9e6cbcd143f5", size = 794175, upload-time = "2025-09-25T21:32:40.865Z" }, + { url = "https://files.pythonhosted.org/packages/dd/3f/5989debef34dc6397317802b527dbbafb2b4760878a53d4166579111411e/pyyaml-6.0.3-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:7c6610def4f163542a622a73fb39f534f8c101d690126992300bf3207eab9764", size = 755228, upload-time = "2025-09-25T21:32:42.084Z" }, + { url = "https://files.pythonhosted.org/packages/d7/ce/af88a49043cd2e265be63d083fc75b27b6ed062f5f9fd6cdc223ad62f03e/pyyaml-6.0.3-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:5190d403f121660ce8d1d2c1bb2ef1bd05b5f68533fc5c2ea899bd15f4399b35", size = 789194, upload-time = "2025-09-25T21:32:43.362Z" }, + { url = "https://files.pythonhosted.org/packages/23/20/bb6982b26a40bb43951265ba29d4c246ef0ff59c9fdcdf0ed04e0687de4d/pyyaml-6.0.3-cp314-cp314-win_amd64.whl", hash = "sha256:4a2e8cebe2ff6ab7d1050ecd59c25d4c8bd7e6f400f5f82b96557ac0abafd0ac", size = 156429, upload-time = "2025-09-25T21:32:57.844Z" }, + { url = "https://files.pythonhosted.org/packages/f4/f4/a4541072bb9422c8a883ab55255f918fa378ecf083f5b85e87fc2b4eda1b/pyyaml-6.0.3-cp314-cp314-win_arm64.whl", hash = "sha256:93dda82c9c22deb0a405ea4dc5f2d0cda384168e466364dec6255b293923b2f3", size = 143912, upload-time = "2025-09-25T21:32:59.247Z" }, + { url = "https://files.pythonhosted.org/packages/7c/f9/07dd09ae774e4616edf6cda684ee78f97777bdd15847253637a6f052a62f/pyyaml-6.0.3-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:02893d100e99e03eda1c8fd5c441d8c60103fd175728e23e431db1b589cf5ab3", size = 189108, upload-time = "2025-09-25T21:32:44.377Z" }, + { url = "https://files.pythonhosted.org/packages/4e/78/8d08c9fb7ce09ad8c38ad533c1191cf27f7ae1effe5bb9400a46d9437fcf/pyyaml-6.0.3-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:c1ff362665ae507275af2853520967820d9124984e0f7466736aea23d8611fba", size = 183641, upload-time = "2025-09-25T21:32:45.407Z" }, + { url = "https://files.pythonhosted.org/packages/7b/5b/3babb19104a46945cf816d047db2788bcaf8c94527a805610b0289a01c6b/pyyaml-6.0.3-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6adc77889b628398debc7b65c073bcb99c4a0237b248cacaf3fe8a557563ef6c", size = 831901, upload-time = "2025-09-25T21:32:48.83Z" }, + { url = "https://files.pythonhosted.org/packages/8b/cc/dff0684d8dc44da4d22a13f35f073d558c268780ce3c6ba1b87055bb0b87/pyyaml-6.0.3-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:a80cb027f6b349846a3bf6d73b5e95e782175e52f22108cfa17876aaeff93702", size = 861132, upload-time = "2025-09-25T21:32:50.149Z" }, + { url = "https://files.pythonhosted.org/packages/b1/5e/f77dc6b9036943e285ba76b49e118d9ea929885becb0a29ba8a7c75e29fe/pyyaml-6.0.3-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:00c4bdeba853cc34e7dd471f16b4114f4162dc03e6b7afcc2128711f0eca823c", size = 839261, upload-time = "2025-09-25T21:32:51.808Z" }, + { url = "https://files.pythonhosted.org/packages/ce/88/a9db1376aa2a228197c58b37302f284b5617f56a5d959fd1763fb1675ce6/pyyaml-6.0.3-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:66e1674c3ef6f541c35191caae2d429b967b99e02040f5ba928632d9a7f0f065", size = 805272, upload-time = "2025-09-25T21:32:52.941Z" }, + { url = "https://files.pythonhosted.org/packages/da/92/1446574745d74df0c92e6aa4a7b0b3130706a4142b2d1a5869f2eaa423c6/pyyaml-6.0.3-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:16249ee61e95f858e83976573de0f5b2893b3677ba71c9dd36b9cf8be9ac6d65", size = 829923, upload-time = "2025-09-25T21:32:54.537Z" }, + { url = "https://files.pythonhosted.org/packages/f0/7a/1c7270340330e575b92f397352af856a8c06f230aa3e76f86b39d01b416a/pyyaml-6.0.3-cp314-cp314t-win_amd64.whl", hash = "sha256:4ad1906908f2f5ae4e5a8ddfce73c320c2a1429ec52eafd27138b7f1cbe341c9", size = 174062, upload-time = "2025-09-25T21:32:55.767Z" }, + { url = "https://files.pythonhosted.org/packages/f1/12/de94a39c2ef588c7e6455cfbe7343d3b2dc9d6b6b2f40c4c6565744c873d/pyyaml-6.0.3-cp314-cp314t-win_arm64.whl", hash = "sha256:ebc55a14a21cb14062aa4162f906cd962b28e2e9ea38f9b4391244cd8de4ae0b", size = 149341, upload-time = "2025-09-25T21:32:56.828Z" }, +]