mirror of
https://github.com/hanzoai/python-sdk.git
synced 2026-10-23 20:23:00 +00:00
fix: Windows/WSL compatibility for shell tools and CLI helpers
- exec_tool: add Windows shell detection (pwsh/cmd), use taskkill for process kill - shell_detect: skip Unix paths on Windows, add pwsh/cmd fallback chain - dev.py: replace all `which` subprocess calls with shutil.which() - test_hanzo_dev: use shutil.which() instead of subprocess which
This commit is contained in:
Generated
+1
-1
@@ -22,7 +22,7 @@ wheels = [
|
||||
|
||||
[[package]]
|
||||
name = "hanzo-async"
|
||||
version = "0.1.0"
|
||||
version = "0.1.2"
|
||||
source = { editable = "." }
|
||||
dependencies = [
|
||||
{ name = "aiofiles" },
|
||||
|
||||
Generated
+3
-3
@@ -1290,7 +1290,7 @@ wheels = [
|
||||
|
||||
[[package]]
|
||||
name = "hanzo-mcp"
|
||||
version = "0.12.6"
|
||||
version = "0.12.7"
|
||||
source = { editable = "." }
|
||||
dependencies = [
|
||||
{ name = "fastmcp" },
|
||||
@@ -1323,7 +1323,6 @@ dependencies = [
|
||||
{ name = "pydantic" },
|
||||
{ name = "pydantic-settings" },
|
||||
{ name = "typing-extensions" },
|
||||
{ name = "uvloop", marker = "sys_platform != 'win32'" },
|
||||
]
|
||||
|
||||
[package.optional-dependencies]
|
||||
@@ -1363,6 +1362,7 @@ memory = [
|
||||
performance = [
|
||||
{ name = "orjson" },
|
||||
{ name = "ujson" },
|
||||
{ name = "uvloop", marker = "sys_platform != 'win32'" },
|
||||
]
|
||||
publish = [
|
||||
{ name = "build" },
|
||||
@@ -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", marker = "sys_platform != 'win32'", specifier = ">=0.22.1" },
|
||||
{ name = "uvloop", marker = "sys_platform != 'win32' and extra == 'performance'", specifier = ">=0.22.1" },
|
||||
{ name = "websockets", marker = "extra == 'ide'", specifier = ">=12.0" },
|
||||
]
|
||||
provides-extras = ["memory", "repl", "ide", "interactive", "dev", "docs", "test", "performance", "publish"]
|
||||
|
||||
@@ -58,13 +58,22 @@ class ExecTool(BaseTool):
|
||||
self._register_proc_actions()
|
||||
|
||||
def _resolve_shell(self) -> str:
|
||||
"""Resolve shell - prefer zsh, fallback to bash/sh."""
|
||||
"""Resolve shell - prefer zsh, fallback to bash/sh. On Windows: pwsh/cmd."""
|
||||
import sys
|
||||
import shutil
|
||||
|
||||
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"
|
||||
|
||||
for shell in ["zsh", "bash", "fish", "dash", "sh"]:
|
||||
for prefix in ["/opt/homebrew/bin", "/usr/local/bin", "/bin", "/usr/bin"]:
|
||||
full_path = f"{prefix}/{shell}"
|
||||
@@ -372,6 +381,8 @@ Auto-backgrounds commands after {AUTO_BACKGROUND_TIMEOUT}s.
|
||||
)
|
||||
|
||||
# Resolve signal
|
||||
import sys as _sys
|
||||
|
||||
if isinstance(signal, str):
|
||||
signal_map = {
|
||||
"TERM": 15,
|
||||
@@ -385,7 +396,17 @@ Auto-backgrounds commands after {AUTO_BACKGROUND_TIMEOUT}s.
|
||||
sig_num = signal
|
||||
|
||||
try:
|
||||
os.kill(pid, sig_num)
|
||||
if _sys.platform == "win32":
|
||||
# On Windows, os.kill() only supports SIGTERM (terminate)
|
||||
# and signal 0 (existence check). Use terminate semantics.
|
||||
import subprocess
|
||||
subprocess.call(
|
||||
["taskkill", "/F", "/PID", str(pid)],
|
||||
stdout=subprocess.DEVNULL,
|
||||
stderr=subprocess.DEVNULL,
|
||||
)
|
||||
else:
|
||||
os.kill(pid, sig_num)
|
||||
return {
|
||||
"proc_id": proc_id,
|
||||
"pid": pid,
|
||||
|
||||
@@ -280,6 +280,10 @@ def resolve_shell_path(shell_name: str) -> Optional[str]:
|
||||
if os.path.isfile(force_shell) and os.access(force_shell, os.X_OK):
|
||||
return force_shell
|
||||
|
||||
# On Windows, skip Unix-specific paths and use shutil.which directly
|
||||
if os.name == "nt":
|
||||
return shutil.which(shell_name)
|
||||
|
||||
# Priority paths - Homebrew first on macOS
|
||||
search_paths = [
|
||||
f"/opt/homebrew/bin/{shell_name}", # Apple Silicon Homebrew
|
||||
@@ -337,7 +341,14 @@ def get_active_shell() -> Tuple[str, str]:
|
||||
best_path = resolve_shell_path(chosen_name)
|
||||
return chosen_name, best_path or chosen_path
|
||||
|
||||
# Fallback: if detected shell isn't supported, default to zsh
|
||||
# Fallback: if detected shell isn't supported, default to zsh (or pwsh on Windows)
|
||||
if os.name == "nt":
|
||||
for fallback in ["pwsh", "powershell", "cmd"]:
|
||||
path = shutil.which(fallback)
|
||||
if path:
|
||||
return fallback, path
|
||||
return "cmd", "cmd.exe"
|
||||
|
||||
for fallback in ["zsh", "bash", "fish", "dash"]:
|
||||
path = resolve_shell_path(fallback)
|
||||
if path:
|
||||
|
||||
Generated
+1
-1
@@ -439,7 +439,7 @@ wheels = [
|
||||
|
||||
[[package]]
|
||||
name = "hanzo-tools-shell"
|
||||
version = "0.6.1"
|
||||
version = "0.6.2"
|
||||
source = { editable = "." }
|
||||
dependencies = [
|
||||
{ name = "hanzo-async" },
|
||||
|
||||
+10
-10
@@ -12,6 +12,7 @@ import os
|
||||
import sys
|
||||
import json
|
||||
import time
|
||||
import shutil
|
||||
import signal
|
||||
import asyncio
|
||||
import logging
|
||||
@@ -244,6 +245,8 @@ class HanzoDevOrchestrator:
|
||||
|
||||
def _find_claude_code(self) -> str:
|
||||
"""Find Claude Code executable."""
|
||||
import shutil
|
||||
|
||||
# Check common locations
|
||||
possible_paths = [
|
||||
"/usr/local/bin/claude",
|
||||
@@ -255,7 +258,7 @@ class HanzoDevOrchestrator:
|
||||
for path in possible_paths:
|
||||
expanded = Path(path).expanduser()
|
||||
if expanded.exists() or (
|
||||
path == "claude" and os.system(f"which {path} >/dev/null 2>&1") == 0
|
||||
path == "claude" and shutil.which(path) is not None
|
||||
):
|
||||
return str(expanded) if expanded.exists() else path
|
||||
|
||||
@@ -1472,13 +1475,13 @@ Examples:
|
||||
"""Use OpenAI CLI (Codex) - the official OpenAI CLI tool."""
|
||||
try:
|
||||
import json
|
||||
import shutil
|
||||
import subprocess
|
||||
|
||||
console.print("[dim]Using OpenAI CLI (Codex)...[/dim]")
|
||||
|
||||
# Check if openai CLI is installed
|
||||
result = subprocess.run(["which", "openai"], capture_output=True, text=True)
|
||||
if result.returncode != 0:
|
||||
if not shutil.which("openai"):
|
||||
console.print("[red]OpenAI CLI not installed![/red]")
|
||||
console.print("[yellow]To install:[/yellow]")
|
||||
console.print(" • pip install openai-cli")
|
||||
@@ -1517,6 +1520,7 @@ Examples:
|
||||
"""Use Claude Desktop/Code CLI."""
|
||||
try:
|
||||
import os
|
||||
import shutil
|
||||
import subprocess
|
||||
|
||||
console.print("[dim]Using Claude Desktop...[/dim]")
|
||||
@@ -1531,11 +1535,7 @@ Examples:
|
||||
|
||||
claude_path = None
|
||||
for path in claude_paths:
|
||||
if (
|
||||
os.path.exists(path)
|
||||
or subprocess.run(["which", path], capture_output=True).returncode
|
||||
== 0
|
||||
):
|
||||
if os.path.exists(path) or shutil.which(path) is not None:
|
||||
claude_path = path
|
||||
break
|
||||
|
||||
@@ -1587,13 +1587,13 @@ Examples:
|
||||
async def _use_gemini_cli(self, message: str):
|
||||
"""Use Gemini CLI."""
|
||||
try:
|
||||
import shutil
|
||||
import subprocess
|
||||
|
||||
console.print("[dim]Using Gemini CLI...[/dim]")
|
||||
|
||||
# Check if gemini CLI is installed
|
||||
result = subprocess.run(["which", "gemini"], capture_output=True, text=True)
|
||||
if result.returncode != 0:
|
||||
if not shutil.which("gemini"):
|
||||
console.print("[red]Gemini CLI not installed![/red]")
|
||||
console.print("[yellow]To install:[/yellow]")
|
||||
console.print(" • pip install google-generativeai-cli")
|
||||
|
||||
@@ -7,6 +7,7 @@ Tests all orchestrator modes and ensures everything works.
|
||||
import os
|
||||
import sys
|
||||
import time
|
||||
import shutil
|
||||
import asyncio
|
||||
import subprocess
|
||||
from pathlib import Path
|
||||
@@ -24,11 +25,7 @@ def print_test(name):
|
||||
|
||||
def check_command_exists(cmd):
|
||||
"""Check if a command exists."""
|
||||
try:
|
||||
result = subprocess.run(["which", cmd], capture_output=True, text=True)
|
||||
return result.returncode == 0
|
||||
except Exception:
|
||||
return False
|
||||
return shutil.which(cmd) is not None
|
||||
|
||||
|
||||
async def test_imports():
|
||||
|
||||
Reference in New Issue
Block a user