Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
40 changes: 22 additions & 18 deletions src/council/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
import importlib.resources
import shutil
import subprocess
import sys
from pathlib import Path
from typing import Annotated

Expand Down Expand Up @@ -709,19 +710,32 @@ def doctor(
raise typer.Exit(1)


def _run_tool(args: list[str], timeout: int = 5) -> subprocess.CompletedProcess[str]:
"""Run a tool command, using shell=True on Windows for .cmd wrappers.

On Windows many CLI tools (e.g. ``codex``) are installed as ``.cmd``
batch wrappers via npm. ``subprocess.run(["codex", ...])`` cannot
launch ``.cmd`` files directly — ``shell=True`` is required so that
``cmd.exe`` resolves the wrapper.
"""
use_shell = sys.platform == "win32"
return subprocess.run(
args,
capture_output=True,
text=True,
timeout=timeout,
shell=use_shell,
)


def _probe_tool_version(cmd: str) -> str | None:
"""Try to get a version string from a tool (--version, then --help).

Returns version text or None. Never calls the tool with a prompt.
"""
for flag in ("--version", "--help"):
try:
result = subprocess.run(
[cmd, flag],
capture_output=True,
text=True,
timeout=5,
)
result = _run_tool([cmd, flag])
output = (result.stdout or result.stderr or "").strip()
if output and result.returncode == 0:
# Return first line, truncated.
Expand All @@ -741,12 +755,7 @@ def _check_subcommand(full_cmd: list[str]) -> bool:
that contains typical help keywords.
"""
try:
result = subprocess.run(
[*full_cmd, "--help"],
capture_output=True,
text=True,
timeout=5,
)
result = _run_tool([*full_cmd, "--help"])
if result.returncode == 0:
return True
# Fallback: treat as valid if stdout/stderr contains help-like text.
Expand All @@ -763,12 +772,7 @@ def _check_codex_auth() -> bool | None:
Returns True if logged in (exit 0), False if not (non-zero), None on error.
"""
try:
result = subprocess.run(
["codex", "login", "status"],
capture_output=True,
text=True,
timeout=5,
)
result = _run_tool(["codex", "login", "status"])
return result.returncode == 0
except (FileNotFoundError, subprocess.TimeoutExpired, OSError):
return None
Expand Down