evo-cli 0.8.0__tar.gz → 0.8.1__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {evo_cli-0.8.0 → evo_cli-0.8.1}/PKG-INFO +1 -1
- evo_cli-0.8.1/evo_cli/VERSION +1 -0
- {evo_cli-0.8.0 → evo_cli-0.8.1}/evo_cli/commands/opencode.py +19 -3
- {evo_cli-0.8.0 → evo_cli-0.8.1}/evo_cli/console.py +23 -9
- {evo_cli-0.8.0 → evo_cli-0.8.1}/evo_cli.egg-info/PKG-INFO +1 -1
- {evo_cli-0.8.0 → evo_cli-0.8.1}/evo_cli.egg-info/SOURCES.txt +1 -0
- evo_cli-0.8.1/tests/test_console.py +32 -0
- {evo_cli-0.8.0 → evo_cli-0.8.1}/tests/test_opencode.py +20 -0
- evo_cli-0.8.0/evo_cli/VERSION +0 -1
- {evo_cli-0.8.0 → evo_cli-0.8.1}/Containerfile +0 -0
- {evo_cli-0.8.0 → evo_cli-0.8.1}/HISTORY.md +0 -0
- {evo_cli-0.8.0 → evo_cli-0.8.1}/LICENSE +0 -0
- {evo_cli-0.8.0 → evo_cli-0.8.1}/MANIFEST.in +0 -0
- {evo_cli-0.8.0 → evo_cli-0.8.1}/README.md +0 -0
- {evo_cli-0.8.0 → evo_cli-0.8.1}/evo_cli/__init__.py +0 -0
- {evo_cli-0.8.0 → evo_cli-0.8.1}/evo_cli/__main__.py +0 -0
- {evo_cli-0.8.0 → evo_cli-0.8.1}/evo_cli/base.py +0 -0
- {evo_cli-0.8.0 → evo_cli-0.8.1}/evo_cli/cli.py +0 -0
- {evo_cli-0.8.0 → evo_cli-0.8.1}/evo_cli/commands/__init__.py +0 -0
- {evo_cli-0.8.0 → evo_cli-0.8.1}/evo_cli/commands/cloudflare.py +0 -0
- {evo_cli-0.8.0 → evo_cli-0.8.1}/evo_cli/commands/fix_claude.py +0 -0
- {evo_cli-0.8.0 → evo_cli-0.8.1}/evo_cli/commands/gdrive.py +0 -0
- {evo_cli-0.8.0 → evo_cli-0.8.1}/evo_cli/commands/localproxy.py +0 -0
- {evo_cli-0.8.0 → evo_cli-0.8.1}/evo_cli/commands/miniconda.py +0 -0
- {evo_cli-0.8.0 → evo_cli-0.8.1}/evo_cli/commands/netcheck.py +0 -0
- {evo_cli-0.8.0 → evo_cli-0.8.1}/evo_cli/commands/plantuml.py +0 -0
- {evo_cli-0.8.0 → evo_cli-0.8.1}/evo_cli/commands/site2s.py +0 -0
- {evo_cli-0.8.0 → evo_cli-0.8.1}/evo_cli/commands/ssh.py +0 -0
- {evo_cli-0.8.0 → evo_cli-0.8.1}/evo_cli/commands/sysmon.py +0 -0
- {evo_cli-0.8.0 → evo_cli-0.8.1}/evo_cli/commands/wifi.py +0 -0
- {evo_cli-0.8.0 → evo_cli-0.8.1}/evo_cli.egg-info/dependency_links.txt +0 -0
- {evo_cli-0.8.0 → evo_cli-0.8.1}/evo_cli.egg-info/entry_points.txt +0 -0
- {evo_cli-0.8.0 → evo_cli-0.8.1}/evo_cli.egg-info/requires.txt +0 -0
- {evo_cli-0.8.0 → evo_cli-0.8.1}/evo_cli.egg-info/top_level.txt +0 -0
- {evo_cli-0.8.0 → evo_cli-0.8.1}/pyproject.toml +0 -0
- {evo_cli-0.8.0 → evo_cli-0.8.1}/setup.cfg +0 -0
- {evo_cli-0.8.0 → evo_cli-0.8.1}/tests/__init__.py +0 -0
- {evo_cli-0.8.0 → evo_cli-0.8.1}/tests/test_cli.py +0 -0
- {evo_cli-0.8.0 → evo_cli-0.8.1}/tests/test_fix_claude.py +0 -0
- {evo_cli-0.8.0 → evo_cli-0.8.1}/tests/test_plantuml.py +0 -0
- {evo_cli-0.8.0 → evo_cli-0.8.1}/tests/test_sysmon.py +0 -0
- {evo_cli-0.8.0 → evo_cli-0.8.1}/tests/test_wifi.py +0 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
0.8.1
|
|
@@ -112,15 +112,29 @@ def ensure_node_installed():
|
|
|
112
112
|
|
|
113
113
|
|
|
114
114
|
def install_mcp_servers():
|
|
115
|
-
"""
|
|
115
|
+
"""Pre-fetch MCP server npm packages into the npx cache.
|
|
116
|
+
|
|
117
|
+
MCP servers speak JSON-RPC over stdio, so most do not implement a real
|
|
118
|
+
``--version`` flag - they simply start up and block reading stdin. Run with
|
|
119
|
+
stdin detached (DEVNULL) so a server that ignores ``--version`` sees EOF and
|
|
120
|
+
exits right away instead of hanging on the terminal, and cap each call with a
|
|
121
|
+
timeout as a safety net. ``check=False`` keeps a non-zero exit from aborting
|
|
122
|
+
the whole setup; the verify step confirms the servers actually work.
|
|
123
|
+
"""
|
|
116
124
|
step("Installing MCP servers")
|
|
117
125
|
servers = [
|
|
118
126
|
"@mcp-server/google-search-mcp@latest",
|
|
119
127
|
"@playwright/mcp@latest",
|
|
120
128
|
]
|
|
121
129
|
for server in servers:
|
|
122
|
-
info(f"
|
|
123
|
-
run_command(
|
|
130
|
+
info(f"Fetching {server}")
|
|
131
|
+
run_command(
|
|
132
|
+
["npx", "-y", server, "--version"],
|
|
133
|
+
status=f"Fetching {server}",
|
|
134
|
+
stdin=subprocess.DEVNULL,
|
|
135
|
+
timeout=180,
|
|
136
|
+
check=False,
|
|
137
|
+
)
|
|
124
138
|
success("MCP server packages ready")
|
|
125
139
|
|
|
126
140
|
|
|
@@ -131,6 +145,8 @@ def install_playwright_browsers():
|
|
|
131
145
|
run_command(
|
|
132
146
|
["npx", "playwright", "install", "chromium"],
|
|
133
147
|
status="Downloading Chromium for Playwright",
|
|
148
|
+
stdin=subprocess.DEVNULL,
|
|
149
|
+
timeout=600,
|
|
134
150
|
)
|
|
135
151
|
success("Playwright Chromium installed")
|
|
136
152
|
except Exception as exc:
|
|
@@ -52,17 +52,31 @@ def step(title):
|
|
|
52
52
|
console.rule(f"[step]{title}[/step]", style="step", align="left")
|
|
53
53
|
|
|
54
54
|
|
|
55
|
-
def run_command(cmd, capture=False, check=True, input_text=None, status=None):
|
|
55
|
+
def run_command(cmd, capture=False, check=True, input_text=None, status=None, timeout=None, stdin=None):
|
|
56
56
|
cmd = [str(part) for part in cmd]
|
|
57
57
|
console.print(f"[cmd]$ {' '.join(cmd)}[/cmd]")
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
58
|
+
|
|
59
|
+
run_kwargs = {"text": True, "input": input_text, "timeout": timeout}
|
|
60
|
+
# subprocess.run rejects passing both `input` and `stdin`. Only attach an
|
|
61
|
+
# explicit stdin (e.g. DEVNULL) when we are not feeding input, so callers can
|
|
62
|
+
# detach a child from the terminal and avoid it blocking on an inherited TTY.
|
|
63
|
+
if input_text is None and stdin is not None:
|
|
64
|
+
run_kwargs["stdin"] = stdin
|
|
65
|
+
|
|
66
|
+
try:
|
|
67
|
+
if status:
|
|
68
|
+
with console.status(f"[info]{status}[/info]", spinner="dots"):
|
|
69
|
+
result = subprocess.run(cmd, capture_output=True, **run_kwargs)
|
|
70
|
+
output = (result.stdout or "").strip()
|
|
71
|
+
if output:
|
|
72
|
+
console.print(output)
|
|
73
|
+
else:
|
|
74
|
+
result = subprocess.run(cmd, capture_output=capture, **run_kwargs)
|
|
75
|
+
except subprocess.TimeoutExpired as exc:
|
|
76
|
+
console.print(f"[error]command timed out after {timeout}s: {' '.join(cmd)}[/error]")
|
|
77
|
+
if check:
|
|
78
|
+
raise CommandError(f"command timed out after {timeout}s: {' '.join(cmd)}") from exc
|
|
79
|
+
return exc
|
|
66
80
|
|
|
67
81
|
if result.returncode != 0:
|
|
68
82
|
message = (result.stderr or "").strip()
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import subprocess
|
|
2
|
+
import sys
|
|
3
|
+
|
|
4
|
+
import pytest
|
|
5
|
+
|
|
6
|
+
from evo_cli.console import CommandError, run_command
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
def test_run_command_raises_on_timeout():
|
|
10
|
+
# A command that outlives the timeout must not hang; it should raise.
|
|
11
|
+
with pytest.raises(CommandError, match="timed out"):
|
|
12
|
+
run_command([sys.executable, "-c", "import time; time.sleep(5)"], timeout=1)
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def test_run_command_timeout_without_check_returns_exception():
|
|
16
|
+
result = run_command(
|
|
17
|
+
[sys.executable, "-c", "import time; time.sleep(5)"],
|
|
18
|
+
timeout=1,
|
|
19
|
+
check=False,
|
|
20
|
+
)
|
|
21
|
+
assert isinstance(result, subprocess.TimeoutExpired)
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def test_run_command_detaches_stdin_when_requested():
|
|
25
|
+
# With stdin=DEVNULL a child that reads stdin sees EOF immediately instead
|
|
26
|
+
# of blocking on an inherited terminal.
|
|
27
|
+
result = run_command(
|
|
28
|
+
[sys.executable, "-c", "import sys; sys.exit(0 if sys.stdin.read() == '' else 1)"],
|
|
29
|
+
stdin=subprocess.DEVNULL,
|
|
30
|
+
timeout=10,
|
|
31
|
+
)
|
|
32
|
+
assert result.returncode == 0
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import json
|
|
2
|
+
import subprocess
|
|
2
3
|
|
|
3
4
|
import pytest
|
|
4
5
|
from click.testing import CliRunner
|
|
@@ -8,6 +9,7 @@ from evo_cli.commands.opencode import (
|
|
|
8
9
|
DEFAULT_MCP_SERVERS,
|
|
9
10
|
configure_opencode_global,
|
|
10
11
|
configure_opencode_project,
|
|
12
|
+
install_mcp_servers,
|
|
11
13
|
load_jsonc,
|
|
12
14
|
merge_mcp_config,
|
|
13
15
|
save_jsonc,
|
|
@@ -73,6 +75,24 @@ def test_configure_opencode_project(tmp_path):
|
|
|
73
75
|
assert set(data["mcp"].keys()) == {"google-search", "playwright"}
|
|
74
76
|
|
|
75
77
|
|
|
78
|
+
def test_install_mcp_servers_detaches_stdin_and_sets_timeout(monkeypatch):
|
|
79
|
+
"""Regression: MCP servers read stdin, so the fetch must not block on a TTY."""
|
|
80
|
+
calls = []
|
|
81
|
+
|
|
82
|
+
def fake_run_command(cmd, **kwargs):
|
|
83
|
+
calls.append((cmd, kwargs))
|
|
84
|
+
|
|
85
|
+
monkeypatch.setattr("evo_cli.commands.opencode.run_command", fake_run_command)
|
|
86
|
+
install_mcp_servers()
|
|
87
|
+
|
|
88
|
+
assert calls, "expected at least one MCP server fetch"
|
|
89
|
+
for cmd, kwargs in calls:
|
|
90
|
+
assert kwargs.get("stdin") is subprocess.DEVNULL
|
|
91
|
+
assert kwargs.get("timeout")
|
|
92
|
+
# A server that ignores --version must not abort the whole setup.
|
|
93
|
+
assert kwargs.get("check") is False
|
|
94
|
+
|
|
95
|
+
|
|
76
96
|
def test_setup_opencode_writes_global_and_project(runner, tmp_path, monkeypatch):
|
|
77
97
|
monkeypatch.setattr(
|
|
78
98
|
"evo_cli.commands.opencode.get_global_config_path",
|
evo_cli-0.8.0/evo_cli/VERSION
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
0.8.0
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|