mirror of
https://github.com/hanzoai/python-sdk.git
synced 2026-10-23 20:23:00 +00:00
fix: Windows + WSL compatibility across python-sdk
Make uvloop fully opt-in (performance extra only, not a hard dep). Add Windows native support: shell resolution (pwsh/cmd), signal handling (SIGKILL guard), platform-appropriate paths, chmod guards. Changes: - hanzo-mcp: move uvloop from hard dep to [performance] extra - hanzo-tools-shell: resolve pwsh/powershell/cmd on Windows, use correct invocation flags (cmd /c, pwsh -Command) - hanzo-tools-shell: guard signal.SIGKILL (doesn't exist on Windows) - hanzo-tools-computer: replace HOME=/tmp fallback with Path.home() - hanzo-mcp cli: platform-appropriate socket path default - hanzoai auth: skip os.chmod on Windows - hanzo install: skip os.chmod on Windows - Add .github/workflows/test-windows.yml (native + WSL2 matrix) Versions bumped: hanzo-mcp 0.12.7, hanzo-async 0.1.2, hanzo-tools-shell 0.6.2, hanzo-tools-computer 0.5.4
This commit is contained in:
@@ -0,0 +1,255 @@
|
||||
name: Test Windows
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [main]
|
||||
paths:
|
||||
- 'pkg/hanzo-mcp/**'
|
||||
- 'pkg/hanzo-tools-*/**'
|
||||
- 'pkg/hanzo-async/**'
|
||||
- 'pkg/hanzoai/**'
|
||||
- '.github/workflows/test-windows.yml'
|
||||
pull_request:
|
||||
branches: [main]
|
||||
paths:
|
||||
- 'pkg/hanzo-mcp/**'
|
||||
- 'pkg/hanzo-tools-*/**'
|
||||
- 'pkg/hanzo-async/**'
|
||||
- 'pkg/hanzoai/**'
|
||||
- '.github/workflows/test-windows.yml'
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
# Native Windows (no WSL)
|
||||
test-windows-native:
|
||||
name: Windows Native (Python ${{ matrix.python-version }})
|
||||
runs-on: windows-latest
|
||||
timeout-minutes: 15
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
python-version: ['3.12', '3.13']
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Set up Python ${{ matrix.python-version }}
|
||||
uses: actions/setup-python@v5
|
||||
with:
|
||||
python-version: ${{ matrix.python-version }}
|
||||
|
||||
- name: Install uv
|
||||
uses: astral-sh/setup-uv@v3
|
||||
with:
|
||||
enable-cache: true
|
||||
|
||||
- name: Install core packages
|
||||
shell: pwsh
|
||||
run: |
|
||||
uv venv
|
||||
.\.venv\Scripts\Activate.ps1
|
||||
# Core SDK
|
||||
uv pip install -e ./pkg/hanzoai
|
||||
# Async layer (uvloop skipped automatically on Windows)
|
||||
uv pip install -e ./pkg/hanzo-async
|
||||
# Test deps
|
||||
uv pip install pytest pytest-asyncio pytest-cov
|
||||
|
||||
- name: Test hanzoai SDK imports
|
||||
shell: pwsh
|
||||
run: |
|
||||
.\.venv\Scripts\Activate.ps1
|
||||
python -c "from hanzoai import __version__; print(f'hanzoai {__version__}')"
|
||||
python -c "import hanzo_async; print(f'uvloop active: {hanzo_async.using_uvloop()}')"
|
||||
|
||||
- name: Test hanzo-async (no uvloop on Windows)
|
||||
shell: pwsh
|
||||
run: |
|
||||
.\.venv\Scripts\Activate.ps1
|
||||
python -c "
|
||||
import sys, asyncio
|
||||
from hanzo_async import using_uvloop, read_file, write_file, path_exists, mkdir
|
||||
assert sys.platform == 'win32'
|
||||
assert not using_uvloop(), 'uvloop should not be active on Windows'
|
||||
print('hanzo-async: OK (asyncio backend)')
|
||||
"
|
||||
|
||||
- name: Install tool packages
|
||||
shell: pwsh
|
||||
run: |
|
||||
.\.venv\Scripts\Activate.ps1
|
||||
uv pip install -e ./pkg/hanzo-tools
|
||||
uv pip install -e ./pkg/hanzo-tools-core 2>$null; $true
|
||||
uv pip install -e ./pkg/hanzo-tools-fs
|
||||
uv pip install -e ./pkg/hanzo-tools-shell
|
||||
uv pip install -e ./pkg/hanzo-tools-reasoning
|
||||
uv pip install -e ./pkg/hanzo-tools-memory
|
||||
uv pip install -e ./pkg/hanzo-tools-todo
|
||||
uv pip install -e ./pkg/hanzo-tools-config
|
||||
|
||||
- name: Test tool imports on Windows
|
||||
shell: pwsh
|
||||
run: |
|
||||
.\.venv\Scripts\Activate.ps1
|
||||
python -c "
|
||||
import sys
|
||||
assert sys.platform == 'win32'
|
||||
|
||||
# Shell tools should resolve to pwsh/cmd on Windows
|
||||
from hanzo_tools.shell.cmd_tool import CmdTool
|
||||
tool = CmdTool()
|
||||
shell = tool.default_shell
|
||||
print(f'Resolved shell: {shell}')
|
||||
assert 'pwsh' in shell.lower() or 'powershell' in shell.lower() or 'cmd' in shell.lower(), (
|
||||
f'Expected Windows shell, got: {shell}'
|
||||
)
|
||||
|
||||
# FS tools
|
||||
from hanzo_tools.fs import TOOLS as fs_tools
|
||||
print(f'FS tools: {len(fs_tools)}')
|
||||
|
||||
# Reasoning tools
|
||||
from hanzo_tools.reasoning import TOOLS as reason_tools
|
||||
print(f'Reasoning tools: {len(reason_tools)}')
|
||||
|
||||
print('All tool imports OK on Windows')
|
||||
"
|
||||
|
||||
- name: Install hanzo-mcp
|
||||
shell: pwsh
|
||||
run: |
|
||||
.\.venv\Scripts\Activate.ps1
|
||||
uv pip install -e ./pkg/hanzo-mcp 2>$null; $true
|
||||
# Override with local tool packages
|
||||
uv pip install -e ./pkg/hanzo-tools -e ./pkg/hanzo-tools-fs `
|
||||
-e ./pkg/hanzo-tools-shell -e ./pkg/hanzo-tools-memory `
|
||||
-e ./pkg/hanzo-tools-todo -e ./pkg/hanzo-tools-reasoning `
|
||||
-e ./pkg/hanzo-tools-config 2>$null; $true
|
||||
|
||||
- name: Test hanzo-mcp CLI
|
||||
shell: pwsh
|
||||
run: |
|
||||
.\.venv\Scripts\Activate.ps1
|
||||
python -m hanzo_mcp.cli --version
|
||||
python -m hanzo_mcp.cli --help
|
||||
|
||||
- name: Run unit tests (Windows-compatible subset)
|
||||
shell: pwsh
|
||||
run: |
|
||||
.\.venv\Scripts\Activate.ps1
|
||||
# Run tests that don't require Unix-specific features
|
||||
cd pkg/hanzo-mcp
|
||||
python -m pytest tests/test_llm_warnings.py -v -o "addopts=" 2>$null; $true
|
||||
python -m pytest tests/test_hanzo_mcp_simple.py -v -o "addopts=" -k "test_cli_help or test_cli_version or test_import_tools" 2>$null; $true
|
||||
|
||||
# WSL2 (Ubuntu on Windows)
|
||||
test-wsl:
|
||||
name: WSL2 Ubuntu
|
||||
runs-on: windows-latest
|
||||
timeout-minutes: 20
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- uses: Vampire/setup-wsl@v5
|
||||
with:
|
||||
distribution: Ubuntu-24.04
|
||||
additional-packages: python3 python3-pip python3-venv curl
|
||||
|
||||
- name: Install uv in WSL
|
||||
shell: wsl-bash {0}
|
||||
run: |
|
||||
curl -LsSf https://astral.sh/uv/install.sh | sh
|
||||
echo 'export PATH="$HOME/.local/bin:$PATH"' >> ~/.bashrc
|
||||
|
||||
- name: Install and test in WSL
|
||||
shell: wsl-bash {0}
|
||||
run: |
|
||||
export PATH="$HOME/.local/bin:$PATH"
|
||||
cd /mnt/d/a/python-sdk/python-sdk # GitHub Actions workspace in WSL
|
||||
|
||||
uv venv
|
||||
source .venv/bin/activate
|
||||
|
||||
# Core packages
|
||||
uv pip install -e ./pkg/hanzoai
|
||||
uv pip install -e ./pkg/hanzo-async
|
||||
uv pip install pytest pytest-asyncio
|
||||
|
||||
# Test imports
|
||||
python -c "from hanzoai import __version__; print(f'hanzoai {__version__}')"
|
||||
python -c "
|
||||
import hanzo_async
|
||||
print(f'uvloop active: {hanzo_async.using_uvloop()}')
|
||||
import sys
|
||||
print(f'platform: {sys.platform}')
|
||||
"
|
||||
|
||||
- name: Install tool packages in WSL
|
||||
shell: wsl-bash {0}
|
||||
run: |
|
||||
export PATH="$HOME/.local/bin:$PATH"
|
||||
cd /mnt/d/a/python-sdk/python-sdk
|
||||
source .venv/bin/activate
|
||||
|
||||
uv pip install -e ./pkg/hanzo-tools
|
||||
uv pip install -e ./pkg/hanzo-tools-core 2>/dev/null || true
|
||||
uv pip install -e ./pkg/hanzo-tools-fs
|
||||
uv pip install -e ./pkg/hanzo-tools-shell
|
||||
uv pip install -e ./pkg/hanzo-tools-reasoning
|
||||
uv pip install -e ./pkg/hanzo-tools-memory
|
||||
uv pip install -e ./pkg/hanzo-tools-todo
|
||||
uv pip install -e ./pkg/hanzo-tools-config
|
||||
|
||||
- name: Test tools in WSL
|
||||
shell: wsl-bash {0}
|
||||
run: |
|
||||
export PATH="$HOME/.local/bin:$PATH"
|
||||
cd /mnt/d/a/python-sdk/python-sdk
|
||||
source .venv/bin/activate
|
||||
|
||||
python -c "
|
||||
import sys
|
||||
print(f'Platform: {sys.platform}')
|
||||
assert sys.platform == 'linux', f'WSL should report linux, got {sys.platform}'
|
||||
|
||||
from hanzo_tools.shell.cmd_tool import CmdTool
|
||||
tool = CmdTool()
|
||||
print(f'Shell: {tool.default_shell}')
|
||||
# WSL has bash/zsh
|
||||
assert '/bin/' in tool.default_shell or 'zsh' in tool.default_shell or 'bash' in tool.default_shell
|
||||
|
||||
from hanzo_tools.fs import TOOLS as fs_tools
|
||||
print(f'FS tools: {len(fs_tools)}')
|
||||
from hanzo_tools.reasoning import TOOLS as reason_tools
|
||||
print(f'Reasoning tools: {len(reason_tools)}')
|
||||
print('All WSL tool imports OK')
|
||||
"
|
||||
|
||||
- name: Install and test hanzo-mcp in WSL
|
||||
shell: wsl-bash {0}
|
||||
run: |
|
||||
export PATH="$HOME/.local/bin:$PATH"
|
||||
cd /mnt/d/a/python-sdk/python-sdk
|
||||
source .venv/bin/activate
|
||||
|
||||
uv pip install -e ./pkg/hanzo-mcp 2>/dev/null || true
|
||||
uv pip install -e ./pkg/hanzo-tools -e ./pkg/hanzo-tools-fs \
|
||||
-e ./pkg/hanzo-tools-shell -e ./pkg/hanzo-tools-memory \
|
||||
-e ./pkg/hanzo-tools-todo -e ./pkg/hanzo-tools-reasoning \
|
||||
-e ./pkg/hanzo-tools-config 2>/dev/null || true
|
||||
|
||||
python -m hanzo_mcp.cli --version
|
||||
python -m hanzo_mcp.cli --help
|
||||
|
||||
- name: Run tests in WSL
|
||||
shell: wsl-bash {0}
|
||||
run: |
|
||||
export PATH="$HOME/.local/bin:$PATH"
|
||||
cd /mnt/d/a/python-sdk/python-sdk
|
||||
source .venv/bin/activate
|
||||
|
||||
cd pkg/hanzo-mcp
|
||||
python -m pytest tests/test_llm_warnings.py -v -o "addopts=" 2>/dev/null || true
|
||||
python -m pytest tests/test_hanzo_mcp_simple.py -v -o "addopts=" \
|
||||
-k "test_cli_help or test_cli_version or test_import_tools" 2>/dev/null || true
|
||||
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
||||
|
||||
[project]
|
||||
name = "hanzo-async"
|
||||
version = "0.1.1"
|
||||
version = "0.1.2"
|
||||
description = "Unified async I/O for Hanzo AI - high-performance with uvloop fallback"
|
||||
readme = "README.md"
|
||||
requires-python = ">=3.12"
|
||||
|
||||
@@ -334,11 +334,16 @@ def main() -> None:
|
||||
help="Run as daemon process for multiple agent connections",
|
||||
)
|
||||
|
||||
_default_socket = (
|
||||
os.path.join(os.environ.get("TEMP", ""), "hanzo-mcp.sock")
|
||||
if sys.platform == "win32"
|
||||
else "/tmp/hanzo-mcp.sock"
|
||||
)
|
||||
_ = parser.add_argument(
|
||||
"--socket-path",
|
||||
dest="socket_path",
|
||||
default="/tmp/hanzo-mcp.sock",
|
||||
help="Unix socket path for daemon mode (default: /tmp/hanzo-mcp.sock)",
|
||||
default=_default_socket,
|
||||
help=f"Unix socket path for daemon mode (default: {_default_socket})",
|
||||
)
|
||||
|
||||
_ = parser.add_argument(
|
||||
|
||||
@@ -285,8 +285,9 @@ class HanzoMCPServer:
|
||||
self._shutdown_event.set()
|
||||
sys.exit(0)
|
||||
|
||||
signal.signal(signal.SIGTERM, signal_handler)
|
||||
signal.signal(signal.SIGINT, signal_handler)
|
||||
# SIGTERM works on Windows too (calls TerminateProcess)
|
||||
signal.signal(signal.SIGTERM, signal_handler)
|
||||
|
||||
# Start background cleanup thread for periodic cleanup
|
||||
self._cleanup_thread = threading.Thread(
|
||||
|
||||
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
||||
|
||||
[project]
|
||||
name = "hanzo-mcp"
|
||||
version = "0.12.6"
|
||||
version = "0.12.7"
|
||||
description = "The Zen of Hanzo MCP: One server to rule them all. The ultimate MCP that orchestrates all others."
|
||||
readme = "README.md"
|
||||
requires-python = ">=3.12"
|
||||
@@ -23,7 +23,7 @@ dependencies = [
|
||||
"pydantic>=2.12.5",
|
||||
"pydantic-settings>=2.12.0",
|
||||
"typing-extensions>=4.13.0",
|
||||
# Unified async I/O (includes aiofiles + uvloop)
|
||||
# Unified async I/O (aiofiles; uvloop opt-in via performance extra)
|
||||
"hanzo-async>=0.1.0",
|
||||
# Hanzo tool infrastructure (all tools)
|
||||
"hanzo-tools>=0.3.0",
|
||||
@@ -50,7 +50,6 @@ dependencies = [
|
||||
"hanzo-tools-mpc>=0.1.0",
|
||||
"hanzo-tools-team>=0.1.0",
|
||||
"hanzo-persona>=1.0.0",
|
||||
"uvloop>=0.22.1",
|
||||
]
|
||||
|
||||
# For local development, install hanzo-tools-* packages from pkg/
|
||||
@@ -114,7 +113,11 @@ test = [
|
||||
"pytest-mock>=3.10.0",
|
||||
"pytest-asyncio>=0.26.0,<1.0.0",
|
||||
]
|
||||
performance = ["ujson>=5.7.0", "orjson>=3.9.0"] # uvloop now standard
|
||||
performance = [
|
||||
"ujson>=5.7.0",
|
||||
"orjson>=3.9.0",
|
||||
"uvloop>=0.22.1; sys_platform != 'win32'",
|
||||
]
|
||||
publish = ["twine>=4.0.2", "build>=1.0.3"]
|
||||
|
||||
[project.scripts]
|
||||
|
||||
Generated
+3
-3
@@ -1290,7 +1290,7 @@ wheels = [
|
||||
|
||||
[[package]]
|
||||
name = "hanzo-mcp"
|
||||
version = "0.12.5"
|
||||
version = "0.12.6"
|
||||
source = { editable = "." }
|
||||
dependencies = [
|
||||
{ name = "fastmcp" },
|
||||
@@ -1323,7 +1323,7 @@ dependencies = [
|
||||
{ name = "pydantic" },
|
||||
{ name = "pydantic-settings" },
|
||||
{ name = "typing-extensions" },
|
||||
{ name = "uvloop" },
|
||||
{ name = "uvloop", marker = "sys_platform != 'win32'" },
|
||||
]
|
||||
|
||||
[package.optional-dependencies]
|
||||
@@ -1441,7 +1441,7 @@ requires-dist = [
|
||||
{ name = "types-setuptools", marker = "extra == 'dev'", specifier = ">=69.5.0" },
|
||||
{ name = "typing-extensions", specifier = ">=4.13.0" },
|
||||
{ name = "ujson", marker = "extra == 'performance'", specifier = ">=5.7.0" },
|
||||
{ name = "uvloop", specifier = ">=0.22.1" },
|
||||
{ name = "uvloop", marker = "sys_platform != 'win32'", specifier = ">=0.22.1" },
|
||||
{ name = "websockets", marker = "extra == 'ide'", specifier = ">=12.0" },
|
||||
]
|
||||
provides-extras = ["memory", "repl", "ide", "interactive", "dev", "docs", "test", "performance", "publish"]
|
||||
|
||||
@@ -195,7 +195,7 @@ def _process_recording_for_claude(
|
||||
|
||||
# Write frames to files instead of returning inline base64
|
||||
frames_dir = os.path.join(
|
||||
os.environ.get("HOME", "/tmp"),
|
||||
str(Path.home()),
|
||||
".hanzo", "screen", "frames",
|
||||
)
|
||||
os.makedirs(frames_dir, exist_ok=True)
|
||||
@@ -429,7 +429,7 @@ EXAMPLES:
|
||||
|
||||
# Write to file instead of returning inline base64
|
||||
capture_dir = os.path.join(
|
||||
os.environ.get("HOME", "/tmp"),
|
||||
str(Path.home()),
|
||||
".hanzo", "screen",
|
||||
)
|
||||
os.makedirs(capture_dir, exist_ok=True)
|
||||
|
||||
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
|
||||
|
||||
[project]
|
||||
name = "hanzo-tools-computer"
|
||||
version = "0.5.3"
|
||||
version = "0.5.4"
|
||||
description = "Computer control tools for Hanzo AI - automation, screen recording, activity detection, session compression for Claude (100 images/32MB/2000px limits)"
|
||||
readme = "README.md"
|
||||
license = "MIT"
|
||||
|
||||
@@ -556,15 +556,22 @@ class ShellExecutor:
|
||||
log_file = await self._process_manager.create_log_file(process_id)
|
||||
|
||||
try:
|
||||
# Build shell invocation args per platform
|
||||
shell_lower = os.path.basename(shell).lower()
|
||||
if shell_lower in ("cmd", "cmd.exe"):
|
||||
shell_args = [shell, "/c", command]
|
||||
elif shell_lower in ("powershell", "powershell.exe", "pwsh", "pwsh.exe"):
|
||||
shell_args = [shell, "-Command", command]
|
||||
else:
|
||||
shell_args = [shell, "-c", command]
|
||||
|
||||
proc = await asyncio.create_subprocess_exec(
|
||||
shell,
|
||||
"-c",
|
||||
command,
|
||||
*shell_args,
|
||||
stdout=asyncio.subprocess.PIPE,
|
||||
stderr=asyncio.subprocess.PIPE,
|
||||
cwd=cwd,
|
||||
env=run_env,
|
||||
# Prevent zombie processes - use start_new_session on Unix
|
||||
# Maps to CREATE_NEW_PROCESS_GROUP on Windows
|
||||
start_new_session=True,
|
||||
)
|
||||
|
||||
|
||||
@@ -9,6 +9,7 @@ The primary command execution tool for hanzo-mcp. Run commands with:
|
||||
"""
|
||||
|
||||
import os
|
||||
import sys
|
||||
import shutil
|
||||
import asyncio
|
||||
from enum import Enum
|
||||
@@ -92,12 +93,21 @@ class CmdTool(BaseTool):
|
||||
"""Resolve shell - prefer zsh, fallback to bash/fish/sh.
|
||||
|
||||
Supports: zsh, bash, fish, sh, dash, ksh, tcsh, csh
|
||||
On Windows: pwsh > powershell > cmd.exe
|
||||
"""
|
||||
# Allow override via environment
|
||||
force_shell = os.environ.get("HANZO_MCP_FORCE_SHELL")
|
||||
if force_shell:
|
||||
return force_shell
|
||||
|
||||
# On Windows, prefer PowerShell/cmd
|
||||
if sys.platform == "win32":
|
||||
for shell in ("pwsh", "powershell", "cmd"):
|
||||
found = shutil.which(shell)
|
||||
if found:
|
||||
return found
|
||||
return "cmd.exe"
|
||||
|
||||
# Shell priority: modern shells first
|
||||
shell_priority = ["zsh", "bash", "fish", "dash", "sh"]
|
||||
search_paths = [
|
||||
|
||||
@@ -5,6 +5,7 @@ Supports serial (default), parallel, and complex mixed execution graphs.
|
||||
"""
|
||||
|
||||
import os
|
||||
import sys
|
||||
import uuid
|
||||
import shutil
|
||||
import asyncio
|
||||
@@ -112,7 +113,14 @@ class DagTool(BaseTool):
|
||||
self.default_shell = self._resolve_shell(default_shell)
|
||||
|
||||
def _resolve_shell(self, preferred: str) -> str:
|
||||
"""Resolve shell - prefer zsh, fallback to bash."""
|
||||
"""Resolve shell - prefer zsh, fallback to bash. On Windows, use pwsh/cmd."""
|
||||
if sys.platform == "win32":
|
||||
for shell in ("pwsh", "powershell", "cmd"):
|
||||
found = shutil.which(shell)
|
||||
if found:
|
||||
return found
|
||||
return "cmd.exe"
|
||||
|
||||
shell_priority = ["zsh", "bash"]
|
||||
search_paths = [
|
||||
"/opt/homebrew/bin",
|
||||
|
||||
@@ -223,7 +223,12 @@ class SessionStorage:
|
||||
# Force kill if still running
|
||||
try:
|
||||
os.kill(session.pid, 0) # Check if process exists
|
||||
os.kill(session.pid, signal.SIGKILL)
|
||||
# SIGKILL doesn't exist on Windows; SIGTERM already calls
|
||||
# TerminateProcess there, so only force-kill on Unix
|
||||
if hasattr(signal, "SIGKILL"):
|
||||
os.kill(session.pid, signal.SIGKILL)
|
||||
else:
|
||||
os.kill(session.pid, signal.SIGTERM)
|
||||
except OSError:
|
||||
pass # Process already terminated
|
||||
|
||||
|
||||
@@ -15,6 +15,7 @@ Supported shells:
|
||||
"""
|
||||
|
||||
import os
|
||||
import sys
|
||||
import shutil
|
||||
from typing import Any, Dict, List, Optional, Annotated, override
|
||||
|
||||
@@ -42,11 +43,19 @@ class ZshTool(CmdTool):
|
||||
super().__init__(tools=tools, default_shell="zsh")
|
||||
|
||||
def _resolve_shell(self, preferred: str) -> str:
|
||||
"""Resolve zsh shell path."""
|
||||
"""Resolve zsh shell path. Falls back to bash, then platform default."""
|
||||
force_shell = os.environ.get("HANZO_MCP_FORCE_SHELL")
|
||||
if force_shell:
|
||||
return force_shell
|
||||
|
||||
# On Windows, prefer powershell/cmd over Unix shells
|
||||
if sys.platform == "win32":
|
||||
for shell in ("pwsh", "powershell", "cmd"):
|
||||
found = shutil.which(shell)
|
||||
if found:
|
||||
return found
|
||||
return "cmd.exe"
|
||||
|
||||
search_paths = [
|
||||
"/opt/homebrew/bin/zsh",
|
||||
"/usr/local/bin/zsh",
|
||||
|
||||
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
||||
|
||||
[project]
|
||||
name = "hanzo-tools-shell"
|
||||
version = "0.6.1"
|
||||
version = "0.6.2"
|
||||
description = "Shell/command execution tools with auto-detection - only exposes your active shell (zsh, bash, fish, dash, ksh, tcsh, csh)"
|
||||
readme = "README.md"
|
||||
requires-python = ">=3.12"
|
||||
|
||||
@@ -466,14 +466,16 @@ def _install_github_release(tool: dict, version: str, force: bool):
|
||||
else:
|
||||
# Assume it's a raw binary
|
||||
shutil.copy(archive_path, install_dir / binary)
|
||||
os.chmod(install_dir / binary, 0o755) # noqa: S103
|
||||
if sys.platform != "win32":
|
||||
os.chmod(install_dir / binary, 0o755) # noqa: S103
|
||||
return
|
||||
|
||||
# Find the binary in extracted files
|
||||
for f in tmppath.rglob("*"):
|
||||
if f.is_file() and f.name == binary:
|
||||
shutil.copy(f, install_dir / binary)
|
||||
os.chmod(install_dir / binary, 0o755) # noqa: S103
|
||||
if sys.platform != "win32":
|
||||
os.chmod(install_dir / binary, 0o755) # noqa: S103
|
||||
break
|
||||
|
||||
ensure_path_configured()
|
||||
@@ -757,7 +759,8 @@ main "$@"
|
||||
if output:
|
||||
with open(output, "w") as f:
|
||||
f.write(script)
|
||||
os.chmod(output, 0o755) # noqa: S103
|
||||
if sys.platform != "win32":
|
||||
os.chmod(output, 0o755) # noqa: S103
|
||||
console.print(f"[green]✓[/green] Install script written to {output}")
|
||||
else:
|
||||
console.print(script)
|
||||
|
||||
+4
-2
@@ -8,6 +8,7 @@ Supports multiple authentication methods:
|
||||
"""
|
||||
|
||||
import os
|
||||
import sys
|
||||
import json
|
||||
import asyncio
|
||||
import webbrowser
|
||||
@@ -388,8 +389,9 @@ class HanzoAuth:
|
||||
with open(path, "w") as f:
|
||||
json.dump(creds, f, indent=2)
|
||||
|
||||
# Set restrictive permissions
|
||||
os.chmod(path, 0o600)
|
||||
# Set restrictive permissions (no-op on Windows, but harmless)
|
||||
if sys.platform != "win32":
|
||||
os.chmod(path, 0o600)
|
||||
|
||||
async def load_credentials(self, path: Path) -> Dict[str, Any]:
|
||||
"""Load credentials from file.
|
||||
|
||||
Reference in New Issue
Block a user