evo-cli 0.10.3__tar.gz → 0.11.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.
Files changed (49) hide show
  1. {evo_cli-0.10.3 → evo_cli-0.11.1}/PKG-INFO +1 -1
  2. evo_cli-0.11.1/evo_cli/VERSION +1 -0
  3. {evo_cli-0.10.3 → evo_cli-0.11.1}/evo_cli/cli.py +5 -0
  4. evo_cli-0.11.1/evo_cli/commands/claude_code.py +309 -0
  5. evo_cli-0.11.1/evo_cli/commands/gh.py +420 -0
  6. {evo_cli-0.10.3 → evo_cli-0.11.1}/evo_cli/console.py +5 -1
  7. {evo_cli-0.10.3 → evo_cli-0.11.1}/evo_cli.egg-info/PKG-INFO +1 -1
  8. {evo_cli-0.10.3 → evo_cli-0.11.1}/evo_cli.egg-info/SOURCES.txt +4 -0
  9. evo_cli-0.11.1/tests/test_claude_code.py +228 -0
  10. {evo_cli-0.10.3 → evo_cli-0.11.1}/tests/test_console.py +13 -1
  11. evo_cli-0.11.1/tests/test_gh.py +285 -0
  12. evo_cli-0.10.3/evo_cli/VERSION +0 -1
  13. {evo_cli-0.10.3 → evo_cli-0.11.1}/Containerfile +0 -0
  14. {evo_cli-0.10.3 → evo_cli-0.11.1}/HISTORY.md +0 -0
  15. {evo_cli-0.10.3 → evo_cli-0.11.1}/LICENSE +0 -0
  16. {evo_cli-0.10.3 → evo_cli-0.11.1}/MANIFEST.in +0 -0
  17. {evo_cli-0.10.3 → evo_cli-0.11.1}/README.md +0 -0
  18. {evo_cli-0.10.3 → evo_cli-0.11.1}/evo_cli/__init__.py +0 -0
  19. {evo_cli-0.10.3 → evo_cli-0.11.1}/evo_cli/__main__.py +0 -0
  20. {evo_cli-0.10.3 → evo_cli-0.11.1}/evo_cli/base.py +0 -0
  21. {evo_cli-0.10.3 → evo_cli-0.11.1}/evo_cli/commands/__init__.py +0 -0
  22. {evo_cli-0.10.3 → evo_cli-0.11.1}/evo_cli/commands/cloudflare.py +0 -0
  23. {evo_cli-0.10.3 → evo_cli-0.11.1}/evo_cli/commands/fix_claude.py +0 -0
  24. {evo_cli-0.10.3 → evo_cli-0.11.1}/evo_cli/commands/gdrive.py +0 -0
  25. {evo_cli-0.10.3 → evo_cli-0.11.1}/evo_cli/commands/localproxy.py +0 -0
  26. {evo_cli-0.10.3 → evo_cli-0.11.1}/evo_cli/commands/mcp.py +0 -0
  27. {evo_cli-0.10.3 → evo_cli-0.11.1}/evo_cli/commands/miniconda.py +0 -0
  28. {evo_cli-0.10.3 → evo_cli-0.11.1}/evo_cli/commands/netcheck.py +0 -0
  29. {evo_cli-0.10.3 → evo_cli-0.11.1}/evo_cli/commands/opencode.py +0 -0
  30. {evo_cli-0.10.3 → evo_cli-0.11.1}/evo_cli/commands/plantuml.py +0 -0
  31. {evo_cli-0.10.3 → evo_cli-0.11.1}/evo_cli/commands/site2s.py +0 -0
  32. {evo_cli-0.10.3 → evo_cli-0.11.1}/evo_cli/commands/ssh.py +0 -0
  33. {evo_cli-0.10.3 → evo_cli-0.11.1}/evo_cli/commands/sysmon.py +0 -0
  34. {evo_cli-0.10.3 → evo_cli-0.11.1}/evo_cli/commands/wifi.py +0 -0
  35. {evo_cli-0.10.3 → evo_cli-0.11.1}/evo_cli/mcp_registry.py +0 -0
  36. {evo_cli-0.10.3 → evo_cli-0.11.1}/evo_cli.egg-info/dependency_links.txt +0 -0
  37. {evo_cli-0.10.3 → evo_cli-0.11.1}/evo_cli.egg-info/entry_points.txt +0 -0
  38. {evo_cli-0.10.3 → evo_cli-0.11.1}/evo_cli.egg-info/requires.txt +0 -0
  39. {evo_cli-0.10.3 → evo_cli-0.11.1}/evo_cli.egg-info/top_level.txt +0 -0
  40. {evo_cli-0.10.3 → evo_cli-0.11.1}/pyproject.toml +0 -0
  41. {evo_cli-0.10.3 → evo_cli-0.11.1}/setup.cfg +0 -0
  42. {evo_cli-0.10.3 → evo_cli-0.11.1}/tests/__init__.py +0 -0
  43. {evo_cli-0.10.3 → evo_cli-0.11.1}/tests/test_cli.py +0 -0
  44. {evo_cli-0.10.3 → evo_cli-0.11.1}/tests/test_fix_claude.py +0 -0
  45. {evo_cli-0.10.3 → evo_cli-0.11.1}/tests/test_mcp.py +0 -0
  46. {evo_cli-0.10.3 → evo_cli-0.11.1}/tests/test_opencode.py +0 -0
  47. {evo_cli-0.10.3 → evo_cli-0.11.1}/tests/test_plantuml.py +0 -0
  48. {evo_cli-0.10.3 → evo_cli-0.11.1}/tests/test_sysmon.py +0 -0
  49. {evo_cli-0.10.3 → evo_cli-0.11.1}/tests/test_wifi.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: evo_cli
3
- Version: 0.10.3
3
+ Version: 0.11.1
4
4
  Summary: Evolution CLI - a developer toolbox for setting up dev machines
5
5
  Author: maycuatroi
6
6
  Project-URL: Homepage, https://github.com/maycuatroi/evo-cli
@@ -0,0 +1 @@
1
+ 0.11.1
@@ -1,9 +1,11 @@
1
1
  import rich_click as click
2
2
 
3
3
  from evo_cli import __version__
4
+ from evo_cli.commands.claude_code import setup_claude
4
5
  from evo_cli.commands.cloudflare import cfssh
5
6
  from evo_cli.commands.fix_claude import f_claude
6
7
  from evo_cli.commands.gdrive import gdrive
8
+ from evo_cli.commands.gh import setup_gh
7
9
  from evo_cli.commands.localproxy import localproxy
8
10
  from evo_cli.commands.mcp import mcp_group
9
11
  from evo_cli.commands.miniconda import miniconda
@@ -20,6 +22,7 @@ from evo_cli.commands.wifi import wifi
20
22
  def setup_group():
21
23
  """Commands for bootstrapping tools on a fresh machine."""
22
24
 
25
+
23
26
  click.rich_click.USE_MARKDOWN = True
24
27
  click.rich_click.SHOW_ARGUMENTS = True
25
28
  click.rich_click.STYLE_OPTIONS_TABLE_BOX = "SIMPLE"
@@ -41,6 +44,8 @@ def cli():
41
44
  """
42
45
 
43
46
 
47
+ setup_group.add_command(setup_claude)
48
+ setup_group.add_command(setup_gh)
44
49
  setup_group.add_command(setup_opencode)
45
50
  setup_group.add_command(miniconda)
46
51
  setup_group.add_command(setupssh)
@@ -0,0 +1,309 @@
1
+ """Install Claude Code and register its default MCP servers."""
2
+
3
+ import os
4
+ import platform
5
+ import shutil
6
+ import subprocess
7
+ from pathlib import Path
8
+
9
+ import rich_click as click
10
+ from rich.panel import Panel
11
+ from rich.text import Text
12
+
13
+ from evo_cli.commands.fix_claude import is_affected, probe_version, version_str
14
+ from evo_cli.commands.gh import check_gh_auth, install_gh
15
+ from evo_cli.commands.mcp import add_to_claude
16
+ from evo_cli.console import CommandError, console, error, info, run_command, step, success, warning
17
+ from evo_cli.mcp_registry import MCP_REGISTRY
18
+
19
+ EPILOG = Text.from_markup(
20
+ "[bold]Examples[/bold]\n\n"
21
+ " [cyan]evo setup claude[/cyan] install Claude Code + gh + the default MCP servers\n"
22
+ " [cyan]evo setup claude --skip-install[/cyan] only register MCP servers\n"
23
+ " [cyan]evo setup claude --no-mcp --no-gh[/cyan] only install the CLI\n"
24
+ " [cyan]evo setup claude --mcp playwright --mcp github[/cyan]\n"
25
+ " [cyan]evo setup claude --method npm[/cyan] install via npm instead of the native installer\n"
26
+ " [cyan]evo setup claude --install-version 2.1.153[/cyan]\n"
27
+ " [cyan]evo setup claude --reinstall[/cyan] reinstall even if already present"
28
+ )
29
+
30
+ INSTALL_SH = "https://claude.ai/install.sh"
31
+ INSTALL_PS1 = "https://claude.ai/install.ps1"
32
+ NPM_PACKAGE = "@anthropic-ai/claude-code"
33
+
34
+ # Claude Code already ships web search, file editing and shell tools, so the
35
+ # defaults only cover what it cannot do on its own: drive a real browser
36
+ # (playwright) and pull current library docs (context7). Add any other server
37
+ # from the library later with `evo mcp add <name>`.
38
+ DEFAULT_MCP_SERVERS = ("playwright", "context7")
39
+
40
+ # The native installer drops the binary in one of these; neither is on PATH until
41
+ # a new shell is started.
42
+ LOCAL_BIN_DIRS = (
43
+ Path.home() / ".local" / "bin",
44
+ Path.home() / ".claude" / "local",
45
+ )
46
+
47
+
48
+ def is_windows():
49
+ return platform.system() == "Windows"
50
+
51
+
52
+ def ensure_claude_on_path():
53
+ """Return the `claude` binary, making a fresh install visible to this process.
54
+
55
+ The native installer writes to ~/.local/bin, which the shell that launched
56
+ evo has not picked up yet, so `claude` stays invisible until the user opens a
57
+ new terminal. Prepend the install dir to PATH so the MCP and doctor steps can
58
+ still run in this same invocation.
59
+ """
60
+ found = shutil.which("claude")
61
+ if found:
62
+ return found
63
+
64
+ binary = "claude.exe" if is_windows() else "claude"
65
+ for directory in LOCAL_BIN_DIRS:
66
+ candidate = directory / binary
67
+ if candidate.exists():
68
+ os.environ["PATH"] = f"{directory}{os.pathsep}{os.environ.get('PATH', '')}"
69
+ return str(candidate)
70
+ return None
71
+
72
+
73
+ def native_install_command(version=None):
74
+ """Build the official installer command for this platform."""
75
+ if is_windows():
76
+ return ["powershell", "-NoProfile", "-Command", f"irm {INSTALL_PS1} | iex"]
77
+ script = f"curl -fsSL {INSTALL_SH} | bash"
78
+ if version:
79
+ script += f" -s {version}"
80
+ return ["bash", "-c", script]
81
+
82
+
83
+ def npm_install_command(version=None):
84
+ package = f"{NPM_PACKAGE}@{version}" if version else NPM_PACKAGE
85
+ return ["npm", "install", "-g", package]
86
+
87
+
88
+ def native_install_available(version=None):
89
+ """Whether the native installer can run here.
90
+
91
+ install.ps1 takes no version argument, so a pinned version on Windows has to
92
+ go through npm instead.
93
+ """
94
+ if is_windows():
95
+ return bool(shutil.which("powershell")) and not version
96
+ return bool(shutil.which("curl") and shutil.which("bash"))
97
+
98
+
99
+ def install_attempts(method, version):
100
+ """Ordered (label, command) install attempts for the requested method."""
101
+ attempts = []
102
+ if method in ("auto", "native") and native_install_available(version):
103
+ attempts.append(("native installer", native_install_command(version)))
104
+ if method in ("auto", "npm") and shutil.which("npm"):
105
+ attempts.append(("npm", npm_install_command(version)))
106
+ return attempts
107
+
108
+
109
+ def install_claude(method="auto", version=None, reinstall=False):
110
+ """Install the Claude Code CLI. Returns True when `claude` ends up runnable."""
111
+ step("Installing Claude Code")
112
+
113
+ if ensure_claude_on_path() and not reinstall:
114
+ info(f"Claude Code already installed ({version_str(probe_version())}); skipping install")
115
+ info("Pass --reinstall to install it again, or run `claude update` to update.")
116
+ return True
117
+
118
+ attempts = install_attempts(method, version)
119
+ if not attempts:
120
+ error(f"No usable install method for --method {method} on {platform.system()}.")
121
+ if is_windows():
122
+ info(f"Install manually: [accent]irm {INSTALL_PS1} | iex[/accent] (PowerShell)")
123
+ else:
124
+ info(f"Install manually: [accent]curl -fsSL {INSTALL_SH} | bash[/accent]")
125
+ return False
126
+
127
+ for label, command in attempts:
128
+ info(f"Installing via {label}")
129
+ try:
130
+ run_command(command, status=f"Installing Claude Code via {label}", timeout=600)
131
+ except CommandError as exc:
132
+ warning(f"Install via {label} failed: {exc}")
133
+ continue
134
+
135
+ if ensure_claude_on_path():
136
+ success(f"Claude Code installed ({version_str(probe_version())})")
137
+ return True
138
+ warning(f"Install via {label} finished but `claude` is not on PATH")
139
+
140
+ error("Could not install Claude Code.")
141
+ info("Open a new shell and retry, or add the install directory (~/.local/bin) to PATH.")
142
+ return False
143
+
144
+
145
+ def configure_mcp_servers(names, scope):
146
+ """Register the named library MCP servers with Claude Code."""
147
+ added = []
148
+ for name in names:
149
+ if add_to_claude(name, MCP_REGISTRY[name], scope):
150
+ added.append(name)
151
+ return added
152
+
153
+
154
+ def run_doctor():
155
+ """Run Claude Code's own diagnostic. Never fatal: it is a report, not a gate."""
156
+ step("Verifying installation")
157
+ result = run_command(
158
+ ["claude", "doctor"],
159
+ status="Running claude doctor",
160
+ stdin=subprocess.DEVNULL,
161
+ timeout=120,
162
+ check=False,
163
+ )
164
+ if getattr(result, "returncode", None) == 0:
165
+ success("`claude doctor` reported no problems")
166
+ else:
167
+ warning("`claude doctor` reported problems; see its output above")
168
+
169
+
170
+ def setup_github_cli():
171
+ """Install gh and report its auth state. Returns one of: ready, unauthenticated, missing.
172
+
173
+ Claude Code shells out to `gh` for PRs, issues and the GitHub API, so a fresh
174
+ machine wants it too. A failure here is not fatal: Claude Code still works,
175
+ it just cannot touch GitHub until gh is installed and signed in.
176
+ """
177
+ if not install_gh():
178
+ return "missing"
179
+ return "ready" if check_gh_auth() else "unauthenticated"
180
+
181
+
182
+ def print_summary(installed, added, scope, gh_state=None):
183
+ console.print()
184
+ if not installed:
185
+ console.print(
186
+ Panel(
187
+ "Claude Code is not installed, so nothing was configured.\n\n"
188
+ f"Install it with [accent]curl -fsSL {INSTALL_SH} | bash[/accent] "
189
+ "(or [accent]evo setup claude --method npm[/accent]), then re-run this command.",
190
+ title="setup claude incomplete",
191
+ border_style="warning",
192
+ expand=False,
193
+ )
194
+ )
195
+ return
196
+
197
+ version = probe_version()
198
+ lines = [f"Claude Code [accent]{version_str(version)}[/accent] is ready."]
199
+ if added:
200
+ lines.append(f"MCP servers registered ({scope} scope): [accent]{', '.join(added)}[/accent]")
201
+ lines.append("Run [accent]/mcp[/accent] inside Claude Code to authenticate any server that needs OAuth.")
202
+ lines.append("")
203
+ lines.append("Run [accent]claude[/accent] to start; the first run opens a browser to sign in.")
204
+ lines.append(
205
+ "Add more MCP servers anytime with [accent]evo mcp add <name>[/accent] ([accent]evo mcp list[/accent])."
206
+ )
207
+ if gh_state == "unauthenticated":
208
+ lines.append("Sign in to GitHub with [accent]gh auth login[/accent] so Claude Code can open PRs.")
209
+ elif gh_state == "missing":
210
+ lines.append("[warning]GitHub CLI is missing; retry with [accent]evo setup gh[/accent].[/warning]")
211
+ if is_affected(version):
212
+ lines.append("")
213
+ lines.append(
214
+ "[warning]This build has the tool-result bug; run [accent]evo f-claude[/accent] to fix it.[/warning]"
215
+ )
216
+
217
+ console.print(
218
+ Panel(
219
+ Text.from_markup("\n".join(lines)),
220
+ title="setup claude complete",
221
+ border_style="success",
222
+ expand=False,
223
+ )
224
+ )
225
+
226
+
227
+ def run_setup_claude(skip_install, reinstall, method, install_version, mcp_names, no_mcp, scope, no_gh):
228
+ step("evo setup claude")
229
+
230
+ if skip_install:
231
+ info("Skipping install as requested")
232
+ installed = bool(ensure_claude_on_path())
233
+ if not installed:
234
+ error("`claude` was not found on PATH; nothing to configure.")
235
+ else:
236
+ installed = install_claude(method, install_version, reinstall)
237
+
238
+ gh_state = None
239
+ if no_gh:
240
+ info("Skipping the GitHub CLI as requested")
241
+ else:
242
+ gh_state = setup_github_cli()
243
+
244
+ added = []
245
+ if not installed:
246
+ pass
247
+ elif no_mcp:
248
+ info("Skipping MCP registration as requested")
249
+ else:
250
+ added = configure_mcp_servers(mcp_names or DEFAULT_MCP_SERVERS, scope)
251
+
252
+ if installed:
253
+ run_doctor()
254
+
255
+ print_summary(installed, added, scope, gh_state)
256
+
257
+
258
+ @click.command("claude", epilog=EPILOG)
259
+ @click.option("--skip-install", is_flag=True, help="Skip installing the CLI; only register MCP servers.")
260
+ @click.option("--reinstall", is_flag=True, help="Install even if Claude Code is already present.")
261
+ @click.option(
262
+ "--method",
263
+ type=click.Choice(["auto", "native", "npm"]),
264
+ default="auto",
265
+ show_default=True,
266
+ help="Install method. `auto` tries the native installer, then falls back to npm.",
267
+ )
268
+ @click.option(
269
+ "--install-version",
270
+ metavar="VERSION",
271
+ help="Install a specific version instead of the latest, e.g. 2.1.153.",
272
+ )
273
+ @click.option(
274
+ "--mcp",
275
+ "mcp_names",
276
+ multiple=True,
277
+ metavar="NAME",
278
+ help=f"MCP server from the library to register (repeatable). Default: {', '.join(DEFAULT_MCP_SERVERS)}.",
279
+ )
280
+ @click.option("--no-mcp", is_flag=True, help="Skip MCP server registration.")
281
+ @click.option("--no-gh", is_flag=True, help="Skip installing the GitHub CLI (gh).")
282
+ @click.option(
283
+ "--scope",
284
+ type=click.Choice(["local", "user", "project"]),
285
+ default="user",
286
+ show_default=True,
287
+ help="Claude Code config scope for the MCP servers.",
288
+ )
289
+ def setup_claude(skip_install, reinstall, method, install_version, mcp_names, no_mcp, no_gh, scope):
290
+ """Install Claude Code, the GitHub CLI, and the default MCP servers.
291
+
292
+ Installs the CLI with Anthropic's native installer (falling back to npm),
293
+ installs the GitHub CLI that Claude Code shells out to for PRs and issues,
294
+ registers the MCP servers Claude Code lacks natively - playwright for browser
295
+ automation and context7 for up-to-date library docs - and runs `claude doctor`
296
+ to verify the result. Everything is idempotent: existing installs are left
297
+ alone unless --reinstall is passed, and servers already registered are skipped.
298
+
299
+ Pick your own servers with --mcp (repeatable, from `evo mcp list`), or skip
300
+ them with --no-mcp; skip the GitHub CLI with --no-gh (`evo setup gh` installs
301
+ it on its own). No credentials are written: `gh auth login` and OAuth MCP
302
+ servers (/mcp inside Claude Code) stay in your hands.
303
+ """
304
+ unknown = [name for name in mcp_names if name not in MCP_REGISTRY]
305
+ if unknown:
306
+ error(f"Unknown MCP server(s): {', '.join(unknown)}. Run `evo mcp list` to see the library.")
307
+ return
308
+
309
+ run_setup_claude(skip_install, reinstall, method, install_version, mcp_names, no_mcp, scope, no_gh)