From 372a6272b7de3245ecbf3a8efd37065ff4d8cef1 Mon Sep 17 00:00:00 2001 From: Xi Zhang <106144707+X-iZhang@users.noreply.github.com> Date: Fri, 20 Mar 2026 11:42:56 +0100 Subject: [PATCH] fix(onboard): Windows npx detection & rename /install-skills to /evoskills (#79) * fix: improve npx detection on Windows using shutil.which * fix: rename /install-skills command to /evoskills for consistency --- EvoScientist/cli/interactive.py | 6 +++--- EvoScientist/cli/skills_cmd.py | 2 +- EvoScientist/cli/widgets/skill_browser.py | 2 +- .../commands/implementation/skills.py | 4 ++-- EvoScientist/config/onboard.py | 20 +++++++++++++++++-- 5 files changed, 25 insertions(+), 9 deletions(-) diff --git a/EvoScientist/cli/interactive.py b/EvoScientist/cli/interactive.py index 1397b93..49ae6d9 100644 --- a/EvoScientist/cli/interactive.py +++ b/EvoScientist/cli/interactive.py @@ -127,7 +127,7 @@ _SLASH_COMMANDS = [ ("/skills", "List installed skills"), ("/install-skill", "Add a skill from path or GitHub"), ("/uninstall-skill", "Remove an installed skill"), - ("/install-skills", "Browse and install skills (optional: /install-skills )"), + ("/evoskills", "Browse and install EvoSkills (optional: /evoskills )"), ("/mcp", "Manage MCP servers"), ("/channel", "Configure messaging channels"), ("/compact", "Compact conversation to free context"), @@ -771,8 +771,8 @@ def cmd_interactive( _cmd_uninstall_skill(name) continue - if user_input.lower().startswith("/install-skills"): - browse_args = user_input[len("/install-skills") :].strip() + if user_input.lower().startswith("/evoskills"): + browse_args = user_input[len("/evoskills") :].strip() _cmd_install_skills(browse_args) continue diff --git a/EvoScientist/cli/skills_cmd.py b/EvoScientist/cli/skills_cmd.py index e977fec..f435174 100644 --- a/EvoScientist/cli/skills_cmd.py +++ b/EvoScientist/cli/skills_cmd.py @@ -1,4 +1,4 @@ -"""Slash commands for skill management: /skills, /install-skill, /uninstall-skill, /install-skills.""" +"""Slash commands for skill management: /skills, /install-skill, /uninstall-skill, /evoskills.""" from pathlib import Path diff --git a/EvoScientist/cli/widgets/skill_browser.py b/EvoScientist/cli/widgets/skill_browser.py index 44bc7af..e6de017 100644 --- a/EvoScientist/cli/widgets/skill_browser.py +++ b/EvoScientist/cli/widgets/skill_browser.py @@ -1,4 +1,4 @@ -"""Inline skill browser widget for /install-skills in TUI. +"""Inline skill browser widget for /evoskills in TUI. Two-phase keyboard-driven widget: Phase 1 — tag picker (arrow keys + Enter to select, or Esc for all) diff --git a/EvoScientist/commands/implementation/skills.py b/EvoScientist/commands/implementation/skills.py index a29caac..75e1fa4 100644 --- a/EvoScientist/commands/implementation/skills.py +++ b/EvoScientist/commands/implementation/skills.py @@ -111,9 +111,9 @@ class InstallSkill(Command): class InstallSkills(Command): """Browse and install skills.""" - name: ClassVar[str] = "/install-skills" + name: ClassVar[str] = "/evoskills" description: ClassVar[str] = ( - "Browse and install skills (optional: /install-skills )" + "Browse and install EvoSkills (optional: /evoskills )" ) arguments: ClassVar[list[Argument]] = [ Argument( diff --git a/EvoScientist/config/onboard.py b/EvoScientist/config/onboard.py index 241ed5c..2d677d1 100644 --- a/EvoScientist/config/onboard.py +++ b/EvoScientist/config/onboard.py @@ -7,6 +7,7 @@ workspace settings, and agent parameters. Uses flow-style arrow-key selection UI from __future__ import annotations import os +import shutil import subprocess import sys from pathlib import Path @@ -1421,12 +1422,18 @@ _RECOMMENDED_SKILLS = [ def _check_npx() -> bool: """Check if npx is available on the system. + Uses shutil.which() to resolve the executable path, which correctly + finds .cmd/.bat wrappers on Windows (e.g., npx.cmd). + Returns: True if npx is found and working. """ + npx = shutil.which("npx") + if not npx: + return False try: result = subprocess.run( - ["npx", "--version"], + [npx, "--version"], capture_output=True, text=True, timeout=10, @@ -1460,6 +1467,13 @@ def _detect_node_install_method() -> tuple[str, str]: except (FileNotFoundError, subprocess.TimeoutExpired): pass + # Windows: winget (built-in on Win 10+) or chocolatey + if sys.platform == "win32": + if shutil.which("winget"): + return "winget", "winget install OpenJS.NodeJS.LTS" + if shutil.which("choco"): + return "choco", "choco install nodejs-lts -y" + return "manual", "https://nodejs.org" @@ -1472,9 +1486,11 @@ def _install_node(method: str, command: str) -> bool: if method == "manual": return False + parts = command.split() + exe = shutil.which(parts[0]) or parts[0] try: proc = subprocess.run( - command.split(), + [exe, *parts[1:]], capture_output=True, text=True, timeout=120,