mirror of
https://github.com/hanzoai/python-sdk.git
synced 2026-10-23 20:23:00 +00:00
feat: add hanzo-node cross-platform binary installer
Python package that downloads and installs the Rust hanzo-node binary for the current platform (macOS/Linux/Windows, x64/arm64). - Detects platform and downloads appropriate binary from GitHub releases - Installs to ~/.local/bin (or %LOCALAPPDATA%\hanzo\bin on Windows) - CLI commands: install, uninstall, upgrade, status, run - Passes through to actual binary when installed
This commit is contained in:
@@ -0,0 +1,57 @@
|
||||
# hanzo-node
|
||||
|
||||
Cross-platform installer for the Hanzo AI node (Rust binary).
|
||||
|
||||
## Installation
|
||||
|
||||
```bash
|
||||
# Install via uv
|
||||
uv tool install hanzo-node
|
||||
|
||||
# Or via pip
|
||||
pip install hanzo-node
|
||||
```
|
||||
|
||||
## Usage
|
||||
|
||||
```bash
|
||||
# Install the node binary
|
||||
hanzo-node install
|
||||
|
||||
# Check status
|
||||
hanzo-node status
|
||||
|
||||
# Upgrade to latest
|
||||
hanzo-node upgrade
|
||||
|
||||
# Run the node (passes args to binary)
|
||||
hanzo-node run --help
|
||||
|
||||
# Uninstall
|
||||
hanzo-node uninstall
|
||||
```
|
||||
|
||||
## How It Works
|
||||
|
||||
This Python package is a thin wrapper that:
|
||||
|
||||
1. Detects your platform (macOS/Linux/Windows, x64/arm64)
|
||||
2. Downloads the appropriate Rust binary from GitHub releases
|
||||
3. Installs it to `~/.local/bin` (or `%LOCALAPPDATA%\hanzo\bin` on Windows)
|
||||
4. Provides a CLI to manage the installation
|
||||
|
||||
The actual `hanzo-node` is written in Rust for performance. This package just handles cross-platform distribution.
|
||||
|
||||
## Supported Platforms
|
||||
|
||||
- macOS (Apple Silicon / Intel)
|
||||
- Linux (x64 / arm64)
|
||||
- Windows (x64)
|
||||
|
||||
## Environment Variables
|
||||
|
||||
- `HANZO_INSTALL_DIR` - Override the installation directory (default: `~/.local/bin`)
|
||||
|
||||
## License
|
||||
|
||||
Apache 2.0
|
||||
@@ -0,0 +1,43 @@
|
||||
[project]
|
||||
name = "hanzo-node"
|
||||
version = "0.1.0"
|
||||
description = "Hanzo Node - Cross-platform installer for the Hanzo AI node (Rust binary)"
|
||||
authors = [
|
||||
{name = "Hanzo AI", email = "[email protected]"},
|
||||
]
|
||||
dependencies = [
|
||||
"httpx>=0.23.0",
|
||||
"rich>=13.0.0",
|
||||
"click>=8.1.0",
|
||||
]
|
||||
readme = "README.md"
|
||||
requires-python = ">= 3.10"
|
||||
keywords = ["hanzo", "node", "ai", "blockchain", "p2p"]
|
||||
classifiers = [
|
||||
"Development Status :: 3 - Alpha",
|
||||
"Environment :: Console",
|
||||
"Intended Audience :: Developers",
|
||||
"License :: OSI Approved :: Apache Software License",
|
||||
"Operating System :: OS Independent",
|
||||
"Programming Language :: Python :: 3",
|
||||
"Programming Language :: Python :: 3.10",
|
||||
"Programming Language :: Python :: 3.11",
|
||||
"Programming Language :: Python :: 3.12",
|
||||
"Programming Language :: Python :: 3.13",
|
||||
"Topic :: Software Development",
|
||||
]
|
||||
|
||||
[project.scripts]
|
||||
hanzo-node = "hanzo_node.cli:main"
|
||||
|
||||
[project.urls]
|
||||
Homepage = "https://hanzo.ai"
|
||||
Repository = "https://github.com/hanzoai/python-sdk"
|
||||
Documentation = "https://docs.hanzo.ai/node"
|
||||
|
||||
[build-system]
|
||||
requires = ["hatchling"]
|
||||
build-backend = "hatchling.build"
|
||||
|
||||
[tool.hatch.build.targets.wheel]
|
||||
packages = ["src/hanzo_node"]
|
||||
@@ -0,0 +1,7 @@
|
||||
"""Hanzo Node - Cross-platform installer for the Hanzo AI node."""
|
||||
|
||||
__version__ = "0.1.0"
|
||||
|
||||
from .installer import install, uninstall, get_binary_path, is_installed
|
||||
|
||||
__all__ = ["install", "uninstall", "get_binary_path", "is_installed", "__version__"]
|
||||
@@ -0,0 +1,123 @@
|
||||
"""CLI for hanzo-node installer and runner."""
|
||||
|
||||
import os
|
||||
import subprocess
|
||||
import sys
|
||||
|
||||
import click
|
||||
|
||||
from . import __version__
|
||||
from .installer import (
|
||||
get_binary_path,
|
||||
get_installed_version,
|
||||
install,
|
||||
is_installed,
|
||||
uninstall,
|
||||
)
|
||||
|
||||
|
||||
@click.group(invoke_without_command=True)
|
||||
@click.option("--version", "-v", is_flag=True, help="Show version")
|
||||
@click.pass_context
|
||||
def main(ctx, version):
|
||||
"""
|
||||
Hanzo Node - AI infrastructure node.
|
||||
|
||||
If hanzo-node binary is installed, runs it directly.
|
||||
Otherwise, use 'hanzo-node install' to install it first.
|
||||
"""
|
||||
if version:
|
||||
print(f"hanzo-node {__version__} (installer)")
|
||||
if is_installed():
|
||||
node_ver = get_installed_version()
|
||||
print(f"hanzo-node {node_ver or '?'} (binary) at {get_binary_path()}")
|
||||
return
|
||||
|
||||
# If no subcommand and binary is installed, run it
|
||||
if ctx.invoked_subcommand is None:
|
||||
if is_installed():
|
||||
# Pass through to the actual binary
|
||||
binary = get_binary_path()
|
||||
sys.exit(subprocess.call([str(binary)] + sys.argv[1:]))
|
||||
else:
|
||||
click.echo("hanzo-node is not installed. run: hanzo-node install")
|
||||
ctx.invoke(status)
|
||||
|
||||
|
||||
@main.command()
|
||||
@click.option("--force", "-f", is_flag=True, help="Force reinstall")
|
||||
@click.option("--version", "-V", "ver", help="Specific version to install")
|
||||
def install_cmd(force, ver):
|
||||
"""Install the hanzo-node binary."""
|
||||
click.echo()
|
||||
try:
|
||||
install(force=force, version=ver)
|
||||
except Exception as e:
|
||||
click.echo(f" ✗ {e}", err=True)
|
||||
sys.exit(1)
|
||||
click.echo()
|
||||
|
||||
|
||||
# Alias 'install' command
|
||||
main.add_command(install_cmd, name="install")
|
||||
|
||||
|
||||
@main.command()
|
||||
def uninstall_cmd():
|
||||
"""Uninstall the hanzo-node binary."""
|
||||
click.echo()
|
||||
uninstall()
|
||||
click.echo()
|
||||
|
||||
|
||||
main.add_command(uninstall_cmd, name="uninstall")
|
||||
|
||||
|
||||
@main.command()
|
||||
@click.option("--force", "-f", is_flag=True, help="Force upgrade")
|
||||
def upgrade(force):
|
||||
"""Upgrade to the latest version."""
|
||||
click.echo()
|
||||
try:
|
||||
install(force=True)
|
||||
except Exception as e:
|
||||
click.echo(f" ✗ {e}", err=True)
|
||||
sys.exit(1)
|
||||
click.echo()
|
||||
|
||||
|
||||
@main.command()
|
||||
def status():
|
||||
"""Show installation status."""
|
||||
click.echo()
|
||||
if is_installed():
|
||||
binary = get_binary_path()
|
||||
ver = get_installed_version()
|
||||
click.echo(f" ✓ hanzo-node {ver or '?'}")
|
||||
click.echo(f" path: {binary}")
|
||||
else:
|
||||
click.echo(" ○ hanzo-node not installed")
|
||||
click.echo(" run: hanzo-node install")
|
||||
click.echo()
|
||||
|
||||
|
||||
@main.command()
|
||||
@click.argument("args", nargs=-1)
|
||||
def run(args):
|
||||
"""Run hanzo-node with arguments."""
|
||||
if not is_installed():
|
||||
click.echo("hanzo-node is not installed. run: hanzo-node install", err=True)
|
||||
sys.exit(1)
|
||||
|
||||
binary = get_binary_path()
|
||||
sys.exit(subprocess.call([str(binary)] + list(args)))
|
||||
|
||||
|
||||
@main.command()
|
||||
def path():
|
||||
"""Print the binary path."""
|
||||
print(get_binary_path())
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -0,0 +1,283 @@
|
||||
"""Cross-platform binary installer for hanzo-node."""
|
||||
|
||||
import os
|
||||
import platform
|
||||
import shutil
|
||||
import stat
|
||||
import sys
|
||||
import tarfile
|
||||
import tempfile
|
||||
import zipfile
|
||||
from pathlib import Path
|
||||
|
||||
import httpx
|
||||
|
||||
# GitHub release info
|
||||
GITHUB_REPO = "hanzoai/node"
|
||||
BINARY_NAME = "hanzo-node"
|
||||
|
||||
# Platform detection
|
||||
PLATFORM_MAP = {
|
||||
("Darwin", "arm64"): "darwin-arm64",
|
||||
("Darwin", "x86_64"): "darwin-x64",
|
||||
("Linux", "x86_64"): "linux-x64",
|
||||
("Linux", "aarch64"): "linux-arm64",
|
||||
("Windows", "AMD64"): "windows-x64",
|
||||
("Windows", "x86_64"): "windows-x64",
|
||||
}
|
||||
|
||||
# Asset name patterns for each platform
|
||||
ASSET_PATTERNS = {
|
||||
"darwin-arm64": ["darwin", "arm64", "macos", "apple"],
|
||||
"darwin-x64": ["darwin", "x64", "amd64", "macos"],
|
||||
"linux-x64": ["linux", "x64", "amd64"],
|
||||
"linux-arm64": ["linux", "arm64", "aarch64"],
|
||||
"windows-x64": ["windows", "x64", "amd64", "win"],
|
||||
}
|
||||
|
||||
|
||||
def get_install_dir() -> Path:
|
||||
"""Get the installation directory for the binary."""
|
||||
if os.name == "nt": # Windows
|
||||
base = Path(os.environ.get("LOCALAPPDATA", Path.home() / "AppData" / "Local"))
|
||||
return base / "hanzo" / "bin"
|
||||
else: # Unix-like
|
||||
return Path(os.environ.get("HANZO_INSTALL_DIR", Path.home() / ".local" / "bin"))
|
||||
|
||||
|
||||
def get_binary_path() -> Path:
|
||||
"""Get the full path to the hanzo-node binary."""
|
||||
binary = BINARY_NAME
|
||||
if os.name == "nt":
|
||||
binary += ".exe"
|
||||
return get_install_dir() / binary
|
||||
|
||||
|
||||
def is_installed() -> bool:
|
||||
"""Check if hanzo-node is installed."""
|
||||
return get_binary_path().exists()
|
||||
|
||||
|
||||
def get_installed_version() -> str | None:
|
||||
"""Get the version of the installed binary."""
|
||||
import subprocess
|
||||
|
||||
binary = get_binary_path()
|
||||
if not binary.exists():
|
||||
return None
|
||||
|
||||
try:
|
||||
result = subprocess.run(
|
||||
[str(binary), "--version"],
|
||||
capture_output=True,
|
||||
text=True,
|
||||
timeout=10,
|
||||
)
|
||||
if result.returncode == 0:
|
||||
# Parse version from output like "hanzo-node 0.1.0"
|
||||
parts = result.stdout.strip().split()
|
||||
if len(parts) >= 2:
|
||||
return parts[1]
|
||||
return None
|
||||
except Exception:
|
||||
return None
|
||||
|
||||
|
||||
def detect_platform() -> str:
|
||||
"""Detect the current platform."""
|
||||
system = platform.system()
|
||||
machine = platform.machine()
|
||||
|
||||
key = (system, machine)
|
||||
if key not in PLATFORM_MAP:
|
||||
raise RuntimeError(f"Unsupported platform: {system}-{machine}")
|
||||
|
||||
return PLATFORM_MAP[key]
|
||||
|
||||
|
||||
def get_latest_release() -> dict:
|
||||
"""Get the latest release info from GitHub."""
|
||||
url = f"https://api.github.com/repos/{GITHUB_REPO}/releases/latest"
|
||||
|
||||
with httpx.Client(follow_redirects=True, timeout=30) as client:
|
||||
resp = client.get(url)
|
||||
resp.raise_for_status()
|
||||
return resp.json()
|
||||
|
||||
|
||||
def find_asset_url(release: dict, plat: str) -> str | None:
|
||||
"""Find the download URL for the current platform."""
|
||||
patterns = ASSET_PATTERNS.get(plat, [])
|
||||
|
||||
for asset in release.get("assets", []):
|
||||
name = asset.get("name", "").lower()
|
||||
|
||||
# Check if asset matches platform patterns
|
||||
matches = sum(1 for p in patterns if p in name)
|
||||
if matches >= 2: # Need at least 2 pattern matches
|
||||
return asset.get("browser_download_url")
|
||||
|
||||
return None
|
||||
|
||||
|
||||
def download_and_extract(url: str, dest: Path) -> None:
|
||||
"""Download and extract the binary."""
|
||||
dest.parent.mkdir(parents=True, exist_ok=True)
|
||||
|
||||
with httpx.Client(follow_redirects=True, timeout=120) as client:
|
||||
with tempfile.NamedTemporaryFile(delete=False) as tmp:
|
||||
tmp_path = Path(tmp.name)
|
||||
|
||||
# Download with progress
|
||||
with client.stream("GET", url) as resp:
|
||||
resp.raise_for_status()
|
||||
total = int(resp.headers.get("content-length", 0))
|
||||
downloaded = 0
|
||||
|
||||
for chunk in resp.iter_bytes(chunk_size=8192):
|
||||
tmp.write(chunk)
|
||||
downloaded += len(chunk)
|
||||
if total > 0:
|
||||
pct = (downloaded / total) * 100
|
||||
print(f"\r downloading... {pct:.0f}%", end="", flush=True)
|
||||
|
||||
print() # newline after progress
|
||||
|
||||
# Extract based on file type
|
||||
try:
|
||||
if url.endswith(".tar.gz") or url.endswith(".tgz"):
|
||||
_extract_tarball(tmp_path, dest)
|
||||
elif url.endswith(".zip"):
|
||||
_extract_zip(tmp_path, dest)
|
||||
else:
|
||||
# Assume raw binary
|
||||
shutil.copy2(tmp_path, dest)
|
||||
finally:
|
||||
tmp_path.unlink(missing_ok=True)
|
||||
|
||||
# Make executable
|
||||
if os.name != "nt":
|
||||
dest.chmod(dest.stat().st_mode | stat.S_IXUSR | stat.S_IXGRP | stat.S_IXOTH)
|
||||
|
||||
|
||||
def _extract_tarball(archive: Path, dest: Path) -> None:
|
||||
"""Extract binary from tarball."""
|
||||
binary_name = BINARY_NAME
|
||||
|
||||
with tarfile.open(archive, "r:gz") as tar:
|
||||
# Find the binary in the archive
|
||||
for member in tar.getmembers():
|
||||
if member.name.endswith(binary_name) or member.name == binary_name:
|
||||
# Extract to temp location then move
|
||||
with tempfile.TemporaryDirectory() as tmpdir:
|
||||
tar.extract(member, tmpdir)
|
||||
extracted = Path(tmpdir) / member.name
|
||||
shutil.copy2(extracted, dest)
|
||||
return
|
||||
|
||||
# If not found by name, try extracting all and finding it
|
||||
with tempfile.TemporaryDirectory() as tmpdir:
|
||||
tar.extractall(tmpdir)
|
||||
for f in Path(tmpdir).rglob(binary_name):
|
||||
if f.is_file():
|
||||
shutil.copy2(f, dest)
|
||||
return
|
||||
|
||||
raise RuntimeError(f"Could not find {binary_name} in archive")
|
||||
|
||||
|
||||
def _extract_zip(archive: Path, dest: Path) -> None:
|
||||
"""Extract binary from zip."""
|
||||
binary_name = BINARY_NAME
|
||||
if os.name == "nt":
|
||||
binary_name += ".exe"
|
||||
|
||||
with zipfile.ZipFile(archive, "r") as zf:
|
||||
# Find the binary in the archive
|
||||
for name in zf.namelist():
|
||||
if name.endswith(binary_name) or os.path.basename(name) == binary_name:
|
||||
with tempfile.TemporaryDirectory() as tmpdir:
|
||||
zf.extract(name, tmpdir)
|
||||
extracted = Path(tmpdir) / name
|
||||
shutil.copy2(extracted, dest)
|
||||
return
|
||||
|
||||
# If not found, extract all and search
|
||||
with tempfile.TemporaryDirectory() as tmpdir:
|
||||
zf.extractall(tmpdir)
|
||||
for f in Path(tmpdir).rglob(binary_name):
|
||||
if f.is_file():
|
||||
shutil.copy2(f, dest)
|
||||
return
|
||||
|
||||
raise RuntimeError(f"Could not find {binary_name} in archive")
|
||||
|
||||
|
||||
def install(force: bool = False, version: str | None = None) -> Path:
|
||||
"""
|
||||
Install hanzo-node binary.
|
||||
|
||||
Args:
|
||||
force: Force reinstall even if already installed
|
||||
version: Specific version to install (default: latest)
|
||||
|
||||
Returns:
|
||||
Path to installed binary
|
||||
"""
|
||||
binary_path = get_binary_path()
|
||||
|
||||
if binary_path.exists() and not force:
|
||||
installed_ver = get_installed_version()
|
||||
print(f" hanzo-node {installed_ver or '?'} already installed at {binary_path}")
|
||||
return binary_path
|
||||
|
||||
plat = detect_platform()
|
||||
print(f" platform: {plat}")
|
||||
|
||||
# Get release info
|
||||
print(" fetching release info...")
|
||||
if version:
|
||||
# TODO: fetch specific version
|
||||
release = get_latest_release()
|
||||
else:
|
||||
release = get_latest_release()
|
||||
|
||||
release_version = release.get("tag_name", "unknown")
|
||||
print(f" version: {release_version}")
|
||||
|
||||
# Find asset URL
|
||||
asset_url = find_asset_url(release, plat)
|
||||
if not asset_url:
|
||||
raise RuntimeError(f"No binary available for {plat}")
|
||||
|
||||
print(f" url: {asset_url}")
|
||||
|
||||
# Download and install
|
||||
download_and_extract(asset_url, binary_path)
|
||||
|
||||
print(f" ✓ installed to {binary_path}")
|
||||
|
||||
# Check if in PATH
|
||||
install_dir = str(get_install_dir())
|
||||
if install_dir not in os.environ.get("PATH", ""):
|
||||
print(f"\n add to PATH: export PATH=\"{install_dir}:$PATH\"")
|
||||
|
||||
return binary_path
|
||||
|
||||
|
||||
def uninstall() -> bool:
|
||||
"""
|
||||
Uninstall hanzo-node binary.
|
||||
|
||||
Returns:
|
||||
True if uninstalled, False if wasn't installed
|
||||
"""
|
||||
binary_path = get_binary_path()
|
||||
|
||||
if not binary_path.exists():
|
||||
print(" hanzo-node is not installed")
|
||||
return False
|
||||
|
||||
binary_path.unlink()
|
||||
print(f" ✓ removed {binary_path}")
|
||||
return True
|
||||
Generated
+174
@@ -0,0 +1,174 @@
|
||||
version = 1
|
||||
revision = 3
|
||||
requires-python = ">=3.10"
|
||||
|
||||
[[package]]
|
||||
name = "anyio"
|
||||
version = "4.12.1"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "exceptiongroup", marker = "python_full_version < '3.11'" },
|
||||
{ name = "idna" },
|
||||
{ name = "typing-extensions", marker = "python_full_version < '3.13'" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/96/f0/5eb65b2bb0d09ac6776f2eb54adee6abe8228ea05b20a5ad0e4945de8aac/anyio-4.12.1.tar.gz", hash = "sha256:41cfcc3a4c85d3f05c932da7c26d0201ac36f72abd4435ba90d0464a3ffed703", size = 228685, upload-time = "2026-01-06T11:45:21.246Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/38/0e/27be9fdef66e72d64c0cdc3cc2823101b80585f8119b5c112c2e8f5f7dab/anyio-4.12.1-py3-none-any.whl", hash = "sha256:d405828884fc140aa80a3c667b8beed277f1dfedec42ba031bd6ac3db606ab6c", size = 113592, upload-time = "2026-01-06T11:45:19.497Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "certifi"
|
||||
version = "2026.1.4"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/e0/2d/a891ca51311197f6ad14a7ef42e2399f36cf2f9bd44752b3dc4eab60fdc5/certifi-2026.1.4.tar.gz", hash = "sha256:ac726dd470482006e014ad384921ed6438c457018f4b3d204aea4281258b2120", size = 154268, upload-time = "2026-01-04T02:42:41.825Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/e6/ad/3cc14f097111b4de0040c83a525973216457bbeeb63739ef1ed275c1c021/certifi-2026.1.4-py3-none-any.whl", hash = "sha256:9943707519e4add1115f44c2bc244f782c0249876bf51b6599fee1ffbedd685c", size = 152900, upload-time = "2026-01-04T02:42:40.15Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "click"
|
||||
version = "8.3.1"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "colorama", marker = "sys_platform == 'win32'" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/3d/fa/656b739db8587d7b5dfa22e22ed02566950fbfbcdc20311993483657a5c0/click-8.3.1.tar.gz", hash = "sha256:12ff4785d337a1bb490bb7e9c2b1ee5da3112e94a8622f26a6c77f5d2fc6842a", size = 295065, upload-time = "2025-11-15T20:45:42.706Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/98/78/01c019cdb5d6498122777c1a43056ebb3ebfeef2076d9d026bfe15583b2b/click-8.3.1-py3-none-any.whl", hash = "sha256:981153a64e25f12d547d3426c367a4857371575ee7ad18df2a6183ab0545b2a6", size = 108274, upload-time = "2025-11-15T20:45:41.139Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "colorama"
|
||||
version = "0.4.6"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/d8/53/6f443c9a4a8358a93a6792e2acffb9d9d5cb0a5cfd8802644b7b1c9a02e4/colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44", size = 27697, upload-time = "2022-10-25T02:36:22.414Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6", size = 25335, upload-time = "2022-10-25T02:36:20.889Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "exceptiongroup"
|
||||
version = "1.3.1"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "typing-extensions", marker = "python_full_version < '3.13'" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/50/79/66800aadf48771f6b62f7eb014e352e5d06856655206165d775e675a02c9/exceptiongroup-1.3.1.tar.gz", hash = "sha256:8b412432c6055b0b7d14c310000ae93352ed6754f70fa8f7c34141f91c4e3219", size = 30371, upload-time = "2025-11-21T23:01:54.787Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/8a/0e/97c33bf5009bdbac74fd2beace167cab3f978feb69cc36f1ef79360d6c4e/exceptiongroup-1.3.1-py3-none-any.whl", hash = "sha256:a7a39a3bd276781e98394987d3a5701d0c4edffb633bb7a5144577f82c773598", size = 16740, upload-time = "2025-11-21T23:01:53.443Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "h11"
|
||||
version = "0.16.0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/01/ee/02a2c011bdab74c6fb3c75474d40b3052059d95df7e73351460c8588d963/h11-0.16.0.tar.gz", hash = "sha256:4e35b956cf45792e4caa5885e69fba00bdbc6ffafbfa020300e549b208ee5ff1", size = 101250, upload-time = "2025-04-24T03:35:25.427Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/04/4b/29cac41a4d98d144bf5f6d33995617b185d14b22401f75ca86f384e87ff1/h11-0.16.0-py3-none-any.whl", hash = "sha256:63cf8bbe7522de3bf65932fda1d9c2772064ffb3dae62d55932da54b31cb6c86", size = 37515, upload-time = "2025-04-24T03:35:24.344Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "hanzo-node"
|
||||
version = "0.1.0"
|
||||
source = { editable = "." }
|
||||
dependencies = [
|
||||
{ name = "click" },
|
||||
{ name = "httpx" },
|
||||
{ name = "rich" },
|
||||
]
|
||||
|
||||
[package.metadata]
|
||||
requires-dist = [
|
||||
{ name = "click", specifier = ">=8.1.0" },
|
||||
{ name = "httpx", specifier = ">=0.23.0" },
|
||||
{ name = "rich", specifier = ">=13.0.0" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "httpcore"
|
||||
version = "1.0.9"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "certifi" },
|
||||
{ name = "h11" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/06/94/82699a10bca87a5556c9c59b5963f2d039dbd239f25bc2a63907a05a14cb/httpcore-1.0.9.tar.gz", hash = "sha256:6e34463af53fd2ab5d807f399a9b45ea31c3dfa2276f15a2c3f00afff6e176e8", size = 85484, upload-time = "2025-04-24T22:06:22.219Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/7e/f5/f66802a942d491edb555dd61e3a9961140fd64c90bce1eafd741609d334d/httpcore-1.0.9-py3-none-any.whl", hash = "sha256:2d400746a40668fc9dec9810239072b40b4484b640a8c38fd654a024c7a1bf55", size = 78784, upload-time = "2025-04-24T22:06:20.566Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "httpx"
|
||||
version = "0.28.1"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "anyio" },
|
||||
{ name = "certifi" },
|
||||
{ name = "httpcore" },
|
||||
{ name = "idna" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/b1/df/48c586a5fe32a0f01324ee087459e112ebb7224f646c0b5023f5e79e9956/httpx-0.28.1.tar.gz", hash = "sha256:75e98c5f16b0f35b567856f597f06ff2270a374470a5c2392242528e3e3e42fc", size = 141406, upload-time = "2024-12-06T15:37:23.222Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/2a/39/e50c7c3a983047577ee07d2a9e53faf5a69493943ec3f6a384bdc792deb2/httpx-0.28.1-py3-none-any.whl", hash = "sha256:d909fcccc110f8c7faf814ca82a9a4d816bc5a6dbfea25d6591d6985b8ba59ad", size = 73517, upload-time = "2024-12-06T15:37:21.509Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "idna"
|
||||
version = "3.11"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/6f/6d/0703ccc57f3a7233505399edb88de3cbd678da106337b9fcde432b65ed60/idna-3.11.tar.gz", hash = "sha256:795dafcc9c04ed0c1fb032c2aa73654d8e8c5023a7df64a53f39190ada629902", size = 194582, upload-time = "2025-10-12T14:55:20.501Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/0e/61/66938bbb5fc52dbdf84594873d5b51fb1f7c7794e9c0f5bd885f30bc507b/idna-3.11-py3-none-any.whl", hash = "sha256:771a87f49d9defaf64091e6e6fe9c18d4833f140bd19464795bc32d966ca37ea", size = 71008, upload-time = "2025-10-12T14:55:18.883Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "markdown-it-py"
|
||||
version = "4.0.0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "mdurl" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/5b/f5/4ec618ed16cc4f8fb3b701563655a69816155e79e24a17b651541804721d/markdown_it_py-4.0.0.tar.gz", hash = "sha256:cb0a2b4aa34f932c007117b194e945bd74e0ec24133ceb5bac59009cda1cb9f3", size = 73070, upload-time = "2025-08-11T12:57:52.854Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/94/54/e7d793b573f298e1c9013b8c4dade17d481164aa517d1d7148619c2cedbf/markdown_it_py-4.0.0-py3-none-any.whl", hash = "sha256:87327c59b172c5011896038353a81343b6754500a08cd7a4973bb48c6d578147", size = 87321, upload-time = "2025-08-11T12:57:51.923Z" },
|
||||
]
|
||||
|
||||
[[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 = "pygments"
|
||||
version = "2.19.2"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/b0/77/a5b8c569bf593b0140bde72ea885a803b82086995367bf2037de0159d924/pygments-2.19.2.tar.gz", hash = "sha256:636cb2477cec7f8952536970bc533bc43743542f70392ae026374600add5b887", size = 4968631, upload-time = "2025-06-21T13:39:12.283Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/c7/21/705964c7812476f378728bdf590ca4b771ec72385c533964653c68e86bdc/pygments-2.19.2-py3-none-any.whl", hash = "sha256:86540386c03d588bb81d44bc3928634ff26449851e99741617ecb9037ee5ec0b", size = 1225217, upload-time = "2025-06-21T13:39:07.939Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rich"
|
||||
version = "14.2.0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "markdown-it-py" },
|
||||
{ name = "pygments" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/fb/d2/8920e102050a0de7bfabeb4c4614a49248cf8d5d7a8d01885fbb24dc767a/rich-14.2.0.tar.gz", hash = "sha256:73ff50c7c0c1c77c8243079283f4edb376f0f6442433aecb8ce7e6d0b92d1fe4", size = 219990, upload-time = "2025-10-09T14:16:53.064Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/25/7a/b0178788f8dc6cafce37a212c99565fa1fe7872c70c6c9c1e1a372d9d88f/rich-14.2.0-py3-none-any.whl", hash = "sha256:76bc51fe2e57d2b1be1f96c524b890b816e334ab4c1e45888799bfaab0021edd", size = 243393, upload-time = "2025-10-09T14:16:51.245Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "typing-extensions"
|
||||
version = "4.15.0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/72/94/1a15dd82efb362ac84269196e94cf00f187f7ed21c242792a923cdb1c61f/typing_extensions-4.15.0.tar.gz", hash = "sha256:0cea48d173cc12fa28ecabc3b837ea3cf6f38c6d1136f85cbaaf598984861466", size = 109391, upload-time = "2025-08-25T13:49:26.313Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/18/67/36e9267722cc04a6b9f15c7f3441c2363321a3ea07da7ae0c0707beb2a9c/typing_extensions-4.15.0-py3-none-any.whl", hash = "sha256:f0fa19c6845758ab08074a0cfa8b7aecb71c999ca73d62883bc25cc018c4e548", size = 44614, upload-time = "2025-08-25T13:49:24.86Z" },
|
||||
]
|
||||
Reference in New Issue
Block a user