evo-cli 0.15.2__tar.gz → 0.15.4__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.15.2 → evo_cli-0.15.4}/PKG-INFO +1 -1
- evo_cli-0.15.4/evo_cli/VERSION +1 -0
- {evo_cli-0.15.2 → evo_cli-0.15.4}/evo_cli/cli.py +2 -0
- evo_cli-0.15.4/evo_cli/commands/agent_toy.py +336 -0
- {evo_cli-0.15.2 → evo_cli-0.15.4}/evo_cli/commands/gdrive.py +2 -2
- {evo_cli-0.15.2 → evo_cli-0.15.4}/evo_cli/credentials/registry.py +6 -3
- {evo_cli-0.15.2 → evo_cli-0.15.4}/evo_cli.egg-info/PKG-INFO +1 -1
- {evo_cli-0.15.2 → evo_cli-0.15.4}/evo_cli.egg-info/SOURCES.txt +2 -0
- evo_cli-0.15.4/tests/test_agent_toy.py +164 -0
- evo_cli-0.15.2/evo_cli/VERSION +0 -1
- {evo_cli-0.15.2 → evo_cli-0.15.4}/Containerfile +0 -0
- {evo_cli-0.15.2 → evo_cli-0.15.4}/HISTORY.md +0 -0
- {evo_cli-0.15.2 → evo_cli-0.15.4}/LICENSE +0 -0
- {evo_cli-0.15.2 → evo_cli-0.15.4}/MANIFEST.in +0 -0
- {evo_cli-0.15.2 → evo_cli-0.15.4}/README.md +0 -0
- {evo_cli-0.15.2 → evo_cli-0.15.4}/evo_cli/__init__.py +0 -0
- {evo_cli-0.15.2 → evo_cli-0.15.4}/evo_cli/__main__.py +0 -0
- {evo_cli-0.15.2 → evo_cli-0.15.4}/evo_cli/base.py +0 -0
- {evo_cli-0.15.2 → evo_cli-0.15.4}/evo_cli/commands/__init__.py +0 -0
- {evo_cli-0.15.2 → evo_cli-0.15.4}/evo_cli/commands/claude_code.py +0 -0
- {evo_cli-0.15.2 → evo_cli-0.15.4}/evo_cli/commands/cloudflare.py +0 -0
- {evo_cli-0.15.2 → evo_cli-0.15.4}/evo_cli/commands/cred.py +0 -0
- {evo_cli-0.15.2 → evo_cli-0.15.4}/evo_cli/commands/download.py +0 -0
- {evo_cli-0.15.2 → evo_cli-0.15.4}/evo_cli/commands/fix_claude.py +0 -0
- {evo_cli-0.15.2 → evo_cli-0.15.4}/evo_cli/commands/gh.py +0 -0
- {evo_cli-0.15.2 → evo_cli-0.15.4}/evo_cli/commands/harness/__init__.py +0 -0
- {evo_cli-0.15.2 → evo_cli-0.15.4}/evo_cli/commands/harness/_dag.py +0 -0
- {evo_cli-0.15.2 → evo_cli-0.15.4}/evo_cli/commands/harness/_git.py +0 -0
- {evo_cli-0.15.2 → evo_cli-0.15.4}/evo_cli/commands/harness/_model.py +0 -0
- {evo_cli-0.15.2 → evo_cli-0.15.4}/evo_cli/commands/harness/_mutate.py +0 -0
- {evo_cli-0.15.2 → evo_cli-0.15.4}/evo_cli/commands/harness/_paths.py +0 -0
- {evo_cli-0.15.2 → evo_cli-0.15.4}/evo_cli/commands/harness/_render.py +0 -0
- {evo_cli-0.15.2 → evo_cli-0.15.4}/evo_cli/commands/harness/_server.py +0 -0
- {evo_cli-0.15.2 → evo_cli-0.15.4}/evo_cli/commands/harness/check.py +0 -0
- {evo_cli-0.15.2 → evo_cli-0.15.4}/evo_cli/commands/harness/edit.py +0 -0
- {evo_cli-0.15.2 → evo_cli-0.15.4}/evo_cli/commands/harness/pull.py +0 -0
- {evo_cli-0.15.2 → evo_cli-0.15.4}/evo_cli/commands/harness/serve.py +0 -0
- {evo_cli-0.15.2 → evo_cli-0.15.4}/evo_cli/commands/harness/show.py +0 -0
- {evo_cli-0.15.2 → evo_cli-0.15.4}/evo_cli/commands/harness/views.py +0 -0
- {evo_cli-0.15.2 → evo_cli-0.15.4}/evo_cli/commands/harness/web/assets/index-Bo-8pFen.js +0 -0
- {evo_cli-0.15.2 → evo_cli-0.15.4}/evo_cli/commands/harness/web/assets/index-ByHqvOcG.css +0 -0
- {evo_cli-0.15.2 → evo_cli-0.15.4}/evo_cli/commands/harness/web/assets/inter-cyrillic-ext-wght-normal-BOeWTOD4.woff2 +0 -0
- {evo_cli-0.15.2 → evo_cli-0.15.4}/evo_cli/commands/harness/web/assets/inter-cyrillic-wght-normal-DqGufNeO.woff2 +0 -0
- {evo_cli-0.15.2 → evo_cli-0.15.4}/evo_cli/commands/harness/web/assets/inter-greek-ext-wght-normal-DlzME5K_.woff2 +0 -0
- {evo_cli-0.15.2 → evo_cli-0.15.4}/evo_cli/commands/harness/web/assets/inter-greek-wght-normal-CkhJZR-_.woff2 +0 -0
- {evo_cli-0.15.2 → evo_cli-0.15.4}/evo_cli/commands/harness/web/assets/inter-latin-ext-wght-normal-DO1Apj_S.woff2 +0 -0
- {evo_cli-0.15.2 → evo_cli-0.15.4}/evo_cli/commands/harness/web/assets/inter-latin-wght-normal-Dx4kXJAl.woff2 +0 -0
- {evo_cli-0.15.2 → evo_cli-0.15.4}/evo_cli/commands/harness/web/assets/inter-vietnamese-wght-normal-CBcvBZtf.woff2 +0 -0
- {evo_cli-0.15.2 → evo_cli-0.15.4}/evo_cli/commands/harness/web/assets/jetbrains-mono-cyrillic-wght-normal-D73BlboJ.woff2 +0 -0
- {evo_cli-0.15.2 → evo_cli-0.15.4}/evo_cli/commands/harness/web/assets/jetbrains-mono-greek-wght-normal-Bw9x6K1M.woff2 +0 -0
- {evo_cli-0.15.2 → evo_cli-0.15.4}/evo_cli/commands/harness/web/assets/jetbrains-mono-latin-ext-wght-normal-DBQx-q_a.woff2 +0 -0
- {evo_cli-0.15.2 → evo_cli-0.15.4}/evo_cli/commands/harness/web/assets/jetbrains-mono-latin-wght-normal-B9CIFXIH.woff2 +0 -0
- {evo_cli-0.15.2 → evo_cli-0.15.4}/evo_cli/commands/harness/web/assets/jetbrains-mono-vietnamese-wght-normal-Bt-aOZkq.woff2 +0 -0
- {evo_cli-0.15.2 → evo_cli-0.15.4}/evo_cli/commands/harness/web/index.html +0 -0
- {evo_cli-0.15.2 → evo_cli-0.15.4}/evo_cli/commands/hwid.py +0 -0
- {evo_cli-0.15.2 → evo_cli-0.15.4}/evo_cli/commands/hwid_reset.py +0 -0
- {evo_cli-0.15.2 → evo_cli-0.15.4}/evo_cli/commands/localproxy.py +0 -0
- {evo_cli-0.15.2 → evo_cli-0.15.4}/evo_cli/commands/mcp.py +0 -0
- {evo_cli-0.15.2 → evo_cli-0.15.4}/evo_cli/commands/miniconda.py +0 -0
- {evo_cli-0.15.2 → evo_cli-0.15.4}/evo_cli/commands/netcheck.py +0 -0
- {evo_cli-0.15.2 → evo_cli-0.15.4}/evo_cli/commands/opencode.py +0 -0
- {evo_cli-0.15.2 → evo_cli-0.15.4}/evo_cli/commands/plantuml.py +0 -0
- {evo_cli-0.15.2 → evo_cli-0.15.4}/evo_cli/commands/site2s.py +0 -0
- {evo_cli-0.15.2 → evo_cli-0.15.4}/evo_cli/commands/ssh.py +0 -0
- {evo_cli-0.15.2 → evo_cli-0.15.4}/evo_cli/commands/sysmon.py +0 -0
- {evo_cli-0.15.2 → evo_cli-0.15.4}/evo_cli/commands/tts.py +0 -0
- {evo_cli-0.15.2 → evo_cli-0.15.4}/evo_cli/commands/update.py +0 -0
- {evo_cli-0.15.2 → evo_cli-0.15.4}/evo_cli/commands/wifi.py +0 -0
- {evo_cli-0.15.2 → evo_cli-0.15.4}/evo_cli/console.py +0 -0
- {evo_cli-0.15.2 → evo_cli-0.15.4}/evo_cli/credentials/__init__.py +0 -0
- {evo_cli-0.15.2 → evo_cli-0.15.4}/evo_cli/credentials/doctor.py +0 -0
- {evo_cli-0.15.2 → evo_cli-0.15.4}/evo_cli/credentials/google_oauth.py +0 -0
- {evo_cli-0.15.2 → evo_cli-0.15.4}/evo_cli/credentials/migrate.py +0 -0
- {evo_cli-0.15.2 → evo_cli-0.15.4}/evo_cli/credentials/oauth_flow.py +0 -0
- {evo_cli-0.15.2 → evo_cli-0.15.4}/evo_cli/credentials/store.py +0 -0
- {evo_cli-0.15.2 → evo_cli-0.15.4}/evo_cli/credentials/sync.py +0 -0
- {evo_cli-0.15.2 → evo_cli-0.15.4}/evo_cli/mcp_registry.py +0 -0
- {evo_cli-0.15.2 → evo_cli-0.15.4}/evo_cli/tts/__init__.py +0 -0
- {evo_cli-0.15.2 → evo_cli-0.15.4}/evo_cli/tts/chunking.py +0 -0
- {evo_cli-0.15.2 → evo_cli-0.15.4}/evo_cli/tts/core.py +0 -0
- {evo_cli-0.15.2 → evo_cli-0.15.4}/evo_cli/tts/creds.py +0 -0
- {evo_cli-0.15.2 → evo_cli-0.15.4}/evo_cli/tts/errors.py +0 -0
- {evo_cli-0.15.2 → evo_cli-0.15.4}/evo_cli/tts/http.py +0 -0
- {evo_cli-0.15.2 → evo_cli-0.15.4}/evo_cli/tts/openai.py +0 -0
- {evo_cli-0.15.2 → evo_cli-0.15.4}/evo_cli/tts/player.py +0 -0
- {evo_cli-0.15.2 → evo_cli-0.15.4}/evo_cli/tts/vbee.py +0 -0
- {evo_cli-0.15.2 → evo_cli-0.15.4}/evo_cli.egg-info/dependency_links.txt +0 -0
- {evo_cli-0.15.2 → evo_cli-0.15.4}/evo_cli.egg-info/entry_points.txt +0 -0
- {evo_cli-0.15.2 → evo_cli-0.15.4}/evo_cli.egg-info/requires.txt +0 -0
- {evo_cli-0.15.2 → evo_cli-0.15.4}/evo_cli.egg-info/top_level.txt +0 -0
- {evo_cli-0.15.2 → evo_cli-0.15.4}/pyproject.toml +0 -0
- {evo_cli-0.15.2 → evo_cli-0.15.4}/setup.cfg +0 -0
- {evo_cli-0.15.2 → evo_cli-0.15.4}/tests/__init__.py +0 -0
- {evo_cli-0.15.2 → evo_cli-0.15.4}/tests/test_claude_code.py +0 -0
- {evo_cli-0.15.2 → evo_cli-0.15.4}/tests/test_cli.py +0 -0
- {evo_cli-0.15.2 → evo_cli-0.15.4}/tests/test_console.py +0 -0
- {evo_cli-0.15.2 → evo_cli-0.15.4}/tests/test_cred.py +0 -0
- {evo_cli-0.15.2 → evo_cli-0.15.4}/tests/test_download.py +0 -0
- {evo_cli-0.15.2 → evo_cli-0.15.4}/tests/test_fix_claude.py +0 -0
- {evo_cli-0.15.2 → evo_cli-0.15.4}/tests/test_gh.py +0 -0
- {evo_cli-0.15.2 → evo_cli-0.15.4}/tests/test_harness.py +0 -0
- {evo_cli-0.15.2 → evo_cli-0.15.4}/tests/test_harness_dag.py +0 -0
- {evo_cli-0.15.2 → evo_cli-0.15.4}/tests/test_mcp.py +0 -0
- {evo_cli-0.15.2 → evo_cli-0.15.4}/tests/test_opencode.py +0 -0
- {evo_cli-0.15.2 → evo_cli-0.15.4}/tests/test_plantuml.py +0 -0
- {evo_cli-0.15.2 → evo_cli-0.15.4}/tests/test_sysmon.py +0 -0
- {evo_cli-0.15.2 → evo_cli-0.15.4}/tests/test_tts.py +0 -0
- {evo_cli-0.15.2 → evo_cli-0.15.4}/tests/test_update.py +0 -0
- {evo_cli-0.15.2 → evo_cli-0.15.4}/tests/test_wifi.py +0 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
0.15.4
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import rich_click as click
|
|
2
2
|
|
|
3
3
|
from evo_cli import __version__
|
|
4
|
+
from evo_cli.commands.agent_toy import agent_toy
|
|
4
5
|
from evo_cli.commands.claude_code import setup_claude
|
|
5
6
|
from evo_cli.commands.cloudflare import cfssh
|
|
6
7
|
from evo_cli.commands.cred import cred_group
|
|
@@ -51,6 +52,7 @@ def cli():
|
|
|
51
52
|
"""
|
|
52
53
|
|
|
53
54
|
|
|
55
|
+
setup_group.add_command(agent_toy)
|
|
54
56
|
setup_group.add_command(setup_claude)
|
|
55
57
|
setup_group.add_command(setup_gh)
|
|
56
58
|
setup_group.add_command(setup_opencode)
|
|
@@ -0,0 +1,336 @@
|
|
|
1
|
+
import os
|
|
2
|
+
import re
|
|
3
|
+
import shutil
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
|
|
6
|
+
import rich_click as click
|
|
7
|
+
from rich.panel import Panel
|
|
8
|
+
from rich.table import Table
|
|
9
|
+
from rich.text import Text
|
|
10
|
+
|
|
11
|
+
from evo_cli.commands.claude_code import ensure_claude_on_path, is_windows
|
|
12
|
+
from evo_cli.console import CommandError, console, error, info, run_command, step, success, warning
|
|
13
|
+
|
|
14
|
+
EPILOG = Text.from_markup(
|
|
15
|
+
"[bold]Examples[/bold]\n\n"
|
|
16
|
+
" [cyan]evo setup agent-toy[/cyan] install every toy\n"
|
|
17
|
+
" [cyan]evo setup agent-toy --list[/cyan] show the catalogue\n"
|
|
18
|
+
" [cyan]evo setup agent-toy plannotator[/cyan] install a single toy\n"
|
|
19
|
+
" [cyan]evo setup agent-toy context-mode agent-skills[/cyan]\n"
|
|
20
|
+
" [cyan]evo setup agent-toy plannotator --minimal[/cyan] binary only, no agent wiring\n"
|
|
21
|
+
" [cyan]evo setup agent-toy --scope project[/cyan] install into the current project"
|
|
22
|
+
)
|
|
23
|
+
|
|
24
|
+
PLANNOTATOR_INSTALL_SH = "https://plannotator.ai/install.sh"
|
|
25
|
+
PLANNOTATOR_INSTALL_PS1 = "https://plannotator.ai/install.ps1"
|
|
26
|
+
PLANNOTATOR_MARKETPLACE = "backnotprop/plannotator"
|
|
27
|
+
|
|
28
|
+
CONTEXT_MODE_MARKETPLACE = "mksglu/context-mode"
|
|
29
|
+
|
|
30
|
+
SKILLFISH_PACKAGE = "skillfish@latest"
|
|
31
|
+
AGENT_SKILLS_REPO = "maycuatroi1/agent-skills"
|
|
32
|
+
|
|
33
|
+
ENTRY_RE = re.compile(r"^[^\w]*([\w][\w.@/-]*)\s*$")
|
|
34
|
+
|
|
35
|
+
INSTALLED = "installed"
|
|
36
|
+
PARTIAL = "partial"
|
|
37
|
+
FAILED = "failed"
|
|
38
|
+
|
|
39
|
+
RESULT_STYLES = {
|
|
40
|
+
INSTALLED: "[success]installed[/success]",
|
|
41
|
+
PARTIAL: "[warning]partial[/warning]",
|
|
42
|
+
FAILED: "[error]failed[/error]",
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
def parse_entries(output):
|
|
47
|
+
names = []
|
|
48
|
+
for line in (output or "").splitlines():
|
|
49
|
+
if ":" in line:
|
|
50
|
+
continue
|
|
51
|
+
match = ENTRY_RE.match(line)
|
|
52
|
+
if match:
|
|
53
|
+
names.append(match.group(1))
|
|
54
|
+
return names
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
def claude_entries(*args):
|
|
58
|
+
result = run_command(["claude", "plugin", *args], capture=True, check=False)
|
|
59
|
+
return parse_entries(getattr(result, "stdout", ""))
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
def ensure_marketplace(name, source, scope):
|
|
63
|
+
if name in claude_entries("marketplace", "list"):
|
|
64
|
+
info(f"Marketplace [accent]{name}[/accent] already configured; refreshing it")
|
|
65
|
+
run_command(
|
|
66
|
+
["claude", "plugin", "marketplace", "update", name],
|
|
67
|
+
status=f"Updating marketplace {name}",
|
|
68
|
+
timeout=300,
|
|
69
|
+
check=False,
|
|
70
|
+
)
|
|
71
|
+
return True
|
|
72
|
+
|
|
73
|
+
try:
|
|
74
|
+
run_command(
|
|
75
|
+
["claude", "plugin", "marketplace", "add", source, "--scope", scope],
|
|
76
|
+
status=f"Adding marketplace {source}",
|
|
77
|
+
timeout=300,
|
|
78
|
+
)
|
|
79
|
+
except CommandError as exc:
|
|
80
|
+
error(f"Could not add marketplace {source}: {exc}")
|
|
81
|
+
return False
|
|
82
|
+
return True
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
def ensure_plugin(plugin, marketplace, scope):
|
|
86
|
+
target = f"{plugin}@{marketplace}"
|
|
87
|
+
|
|
88
|
+
if target in claude_entries("list"):
|
|
89
|
+
info(f"Plugin [accent]{target}[/accent] already installed; updating it")
|
|
90
|
+
try:
|
|
91
|
+
run_command(
|
|
92
|
+
["claude", "plugin", "update", target, "--scope", scope],
|
|
93
|
+
status=f"Updating {target}",
|
|
94
|
+
timeout=600,
|
|
95
|
+
)
|
|
96
|
+
except CommandError as exc:
|
|
97
|
+
warning(f"Could not update {target}: {exc}")
|
|
98
|
+
return True
|
|
99
|
+
|
|
100
|
+
try:
|
|
101
|
+
run_command(
|
|
102
|
+
["claude", "plugin", "install", target, "--scope", scope],
|
|
103
|
+
status=f"Installing {target}",
|
|
104
|
+
timeout=600,
|
|
105
|
+
)
|
|
106
|
+
except CommandError as exc:
|
|
107
|
+
error(f"Could not install {target}: {exc}")
|
|
108
|
+
return False
|
|
109
|
+
|
|
110
|
+
success(f"Plugin [accent]{target}[/accent] installed")
|
|
111
|
+
return True
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
def plannotator_install_command(minimal):
|
|
115
|
+
if is_windows():
|
|
116
|
+
if not shutil.which("powershell"):
|
|
117
|
+
return None
|
|
118
|
+
script = f"irm {PLANNOTATOR_INSTALL_PS1} | iex"
|
|
119
|
+
if minimal:
|
|
120
|
+
script = f"$env:PLANNOTATOR_MINIMAL=1; {script}"
|
|
121
|
+
return ["powershell", "-NoProfile", "-Command", script]
|
|
122
|
+
|
|
123
|
+
if not (shutil.which("curl") and shutil.which("bash")):
|
|
124
|
+
return None
|
|
125
|
+
script = f"curl -fsSL {PLANNOTATOR_INSTALL_SH} | bash"
|
|
126
|
+
if minimal:
|
|
127
|
+
script += " -s -- --minimal"
|
|
128
|
+
return ["bash", "-c", script]
|
|
129
|
+
|
|
130
|
+
|
|
131
|
+
def plannotator_binary():
|
|
132
|
+
found = shutil.which("plannotator")
|
|
133
|
+
if found:
|
|
134
|
+
return found
|
|
135
|
+
|
|
136
|
+
candidates = []
|
|
137
|
+
if is_windows():
|
|
138
|
+
local_app = os.environ.get("LOCALAPPDATA")
|
|
139
|
+
if local_app:
|
|
140
|
+
candidates.append(Path(local_app) / "plannotator" / "plannotator.exe")
|
|
141
|
+
candidates.append(Path.home() / ".local" / "bin" / "plannotator.exe")
|
|
142
|
+
else:
|
|
143
|
+
candidates.append(Path.home() / ".local" / "bin" / "plannotator")
|
|
144
|
+
|
|
145
|
+
for candidate in candidates:
|
|
146
|
+
if candidate.exists():
|
|
147
|
+
return str(candidate)
|
|
148
|
+
return None
|
|
149
|
+
|
|
150
|
+
|
|
151
|
+
def run_plannotator_installer(minimal, attempts=2):
|
|
152
|
+
command = plannotator_install_command(minimal)
|
|
153
|
+
if command is None:
|
|
154
|
+
error("No usable installer here: PowerShell (Windows) or curl + bash (POSIX) is required.")
|
|
155
|
+
return False
|
|
156
|
+
|
|
157
|
+
for attempt in range(1, attempts + 1):
|
|
158
|
+
try:
|
|
159
|
+
run_command(command, status="Running the plannotator installer", timeout=900)
|
|
160
|
+
return True
|
|
161
|
+
except CommandError as exc:
|
|
162
|
+
if attempt < attempts:
|
|
163
|
+
warning(f"The plannotator installer failed ({exc}); retrying")
|
|
164
|
+
else:
|
|
165
|
+
error(f"The plannotator installer failed: {exc}")
|
|
166
|
+
return False
|
|
167
|
+
|
|
168
|
+
|
|
169
|
+
def install_plannotator(options):
|
|
170
|
+
plugin_ok = True
|
|
171
|
+
if options["minimal"]:
|
|
172
|
+
info("Minimal install: no plugin, skills, hooks or slash commands")
|
|
173
|
+
elif not options["claude"]:
|
|
174
|
+
warning("Claude Code is not on PATH; skipping its plugin. Run `evo setup claude` first.")
|
|
175
|
+
plugin_ok = False
|
|
176
|
+
else:
|
|
177
|
+
plugin_ok = ensure_marketplace("plannotator", PLANNOTATOR_MARKETPLACE, options["scope"]) and ensure_plugin(
|
|
178
|
+
"plannotator", "plannotator", options["scope"]
|
|
179
|
+
)
|
|
180
|
+
|
|
181
|
+
installed = run_plannotator_installer(options["minimal"])
|
|
182
|
+
if not installed:
|
|
183
|
+
binary = plannotator_binary()
|
|
184
|
+
if not binary:
|
|
185
|
+
return FAILED
|
|
186
|
+
warning(f"The installer errored but the binary is in place ({binary})")
|
|
187
|
+
warning("Skills and hooks for the other agents may be missing; re-run to finish them.")
|
|
188
|
+
|
|
189
|
+
return INSTALLED if (plugin_ok and installed) else PARTIAL
|
|
190
|
+
|
|
191
|
+
|
|
192
|
+
def install_context_mode(options):
|
|
193
|
+
if not options["claude"]:
|
|
194
|
+
error("Claude Code is not on PATH; run `evo setup claude` first.")
|
|
195
|
+
return FAILED
|
|
196
|
+
|
|
197
|
+
if not ensure_marketplace("context-mode", CONTEXT_MODE_MARKETPLACE, options["scope"]):
|
|
198
|
+
return FAILED
|
|
199
|
+
return INSTALLED if ensure_plugin("context-mode", "context-mode", options["scope"]) else FAILED
|
|
200
|
+
|
|
201
|
+
|
|
202
|
+
def install_agent_skills(options):
|
|
203
|
+
if not shutil.which("npx"):
|
|
204
|
+
error("npx was not found; install Node.js 18+ and retry.")
|
|
205
|
+
return FAILED
|
|
206
|
+
|
|
207
|
+
command = [
|
|
208
|
+
"npx",
|
|
209
|
+
"-y",
|
|
210
|
+
SKILLFISH_PACKAGE,
|
|
211
|
+
"add",
|
|
212
|
+
AGENT_SKILLS_REPO,
|
|
213
|
+
"--all",
|
|
214
|
+
"--yes",
|
|
215
|
+
"--force",
|
|
216
|
+
"--project" if options["scope"] == "project" else "--global",
|
|
217
|
+
]
|
|
218
|
+
try:
|
|
219
|
+
run_command(command, status="Installing agent-skills via skillfish", timeout=900)
|
|
220
|
+
except CommandError as exc:
|
|
221
|
+
error(f"skillfish failed: {exc}")
|
|
222
|
+
return FAILED
|
|
223
|
+
|
|
224
|
+
success("agent-skills installed")
|
|
225
|
+
return INSTALLED
|
|
226
|
+
|
|
227
|
+
|
|
228
|
+
TOYS = {
|
|
229
|
+
"plannotator": {
|
|
230
|
+
"summary": "Browser review surface for plans, diffs and HTML your agent produces",
|
|
231
|
+
"url": "https://github.com/backnotprop/plannotator",
|
|
232
|
+
"install": install_plannotator,
|
|
233
|
+
},
|
|
234
|
+
"context-mode": {
|
|
235
|
+
"summary": "Sandboxed command execution and an indexed knowledge base that keep raw output out of context",
|
|
236
|
+
"url": "https://github.com/mksglu/context-mode",
|
|
237
|
+
"install": install_context_mode,
|
|
238
|
+
},
|
|
239
|
+
"agent-skills": {
|
|
240
|
+
"summary": "The personal skill library (credentials, slides, gitnexus, life CLI, TTS, ...)",
|
|
241
|
+
"url": "https://github.com/maycuatroi1/agent-skills",
|
|
242
|
+
"install": install_agent_skills,
|
|
243
|
+
},
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
|
|
247
|
+
def print_catalogue():
|
|
248
|
+
table = Table(box=None, pad_edge=False, show_edge=False)
|
|
249
|
+
table.add_column("Toy", style="accent", no_wrap=True)
|
|
250
|
+
table.add_column("What it does")
|
|
251
|
+
table.add_column("Source", style="dim")
|
|
252
|
+
for name, toy in TOYS.items():
|
|
253
|
+
table.add_row(name, toy["summary"], toy["url"])
|
|
254
|
+
console.print()
|
|
255
|
+
console.print(table)
|
|
256
|
+
console.print()
|
|
257
|
+
console.print("Install everything with [accent]evo setup agent-toy[/accent], or name the ones you want.")
|
|
258
|
+
|
|
259
|
+
|
|
260
|
+
def print_summary(results, scope):
|
|
261
|
+
table = Table(box=None, pad_edge=False, show_edge=False)
|
|
262
|
+
table.add_column("Toy", style="accent", no_wrap=True)
|
|
263
|
+
table.add_column("Result", no_wrap=True)
|
|
264
|
+
for name, status in results:
|
|
265
|
+
table.add_row(name, RESULT_STYLES[status])
|
|
266
|
+
|
|
267
|
+
console.print()
|
|
268
|
+
console.print(table)
|
|
269
|
+
|
|
270
|
+
usable = [name for name, status in results if status != FAILED]
|
|
271
|
+
lines = []
|
|
272
|
+
if "plannotator" in usable:
|
|
273
|
+
lines.append("Plannotator opens plans in your browser as soon as your agent proposes one.")
|
|
274
|
+
if "context-mode" in usable:
|
|
275
|
+
lines.append("Run [accent]ctx doctor[/accent] inside Claude Code to verify the hooks.")
|
|
276
|
+
if "agent-skills" in usable:
|
|
277
|
+
target = "./.claude/skills" if scope == "project" else "~/.claude/skills"
|
|
278
|
+
lines.append(f"Skills landed in [accent]{target}[/accent].")
|
|
279
|
+
if lines:
|
|
280
|
+
lines.append("")
|
|
281
|
+
lines.append("Restart Claude Code so the new plugins, hooks and skills load.")
|
|
282
|
+
|
|
283
|
+
complete = all(status == INSTALLED for _, status in results)
|
|
284
|
+
console.print(
|
|
285
|
+
Panel(
|
|
286
|
+
Text.from_markup("\n".join(lines) if lines else "Nothing was installed."),
|
|
287
|
+
title="setup agent-toy complete" if complete else "setup agent-toy incomplete",
|
|
288
|
+
border_style="success" if complete else "warning",
|
|
289
|
+
expand=False,
|
|
290
|
+
)
|
|
291
|
+
)
|
|
292
|
+
|
|
293
|
+
|
|
294
|
+
@click.command("agent-toy", epilog=EPILOG)
|
|
295
|
+
@click.argument("names", nargs=-1, metavar="[TOY]...")
|
|
296
|
+
@click.option("--list", "list_only", is_flag=True, help="Show the catalogue and exit.")
|
|
297
|
+
@click.option(
|
|
298
|
+
"--scope",
|
|
299
|
+
type=click.Choice(["user", "project", "local"]),
|
|
300
|
+
default="user",
|
|
301
|
+
show_default=True,
|
|
302
|
+
help="Where to install: `user` is machine-wide, `project` scopes to the current repo.",
|
|
303
|
+
)
|
|
304
|
+
@click.option("--minimal", is_flag=True, help="plannotator: install only the binary, no hooks or skills.")
|
|
305
|
+
def agent_toy(names, list_only, scope, minimal):
|
|
306
|
+
"""Install the agent toolbox: plannotator, context-mode and agent-skills.
|
|
307
|
+
|
|
308
|
+
Each toy is pulled at its latest version and installed idempotently: a
|
|
309
|
+
marketplace that is already configured gets refreshed, a plugin that is
|
|
310
|
+
already installed gets updated, and skills are overwritten in place. Name
|
|
311
|
+
one or more toys to install a subset, or pass no argument to install all of
|
|
312
|
+
them. Run with --list to see what is on offer.
|
|
313
|
+
|
|
314
|
+
plannotator needs PowerShell (Windows) or curl + bash (POSIX); context-mode
|
|
315
|
+
and the plannotator plugin need the Claude Code CLI (`evo setup claude`);
|
|
316
|
+
agent-skills needs Node.js 18+ for npx.
|
|
317
|
+
"""
|
|
318
|
+
if list_only:
|
|
319
|
+
print_catalogue()
|
|
320
|
+
return
|
|
321
|
+
|
|
322
|
+
unknown = [name for name in names if name not in TOYS]
|
|
323
|
+
if unknown:
|
|
324
|
+
error(f"Unknown toy(s): {', '.join(unknown)}. Run `evo setup agent-toy --list` to see the catalogue.")
|
|
325
|
+
return
|
|
326
|
+
|
|
327
|
+
selected = list(names) or list(TOYS)
|
|
328
|
+
options = {"scope": scope, "minimal": minimal, "claude": bool(ensure_claude_on_path())}
|
|
329
|
+
|
|
330
|
+
results = []
|
|
331
|
+
for name in selected:
|
|
332
|
+
step(f"evo setup agent-toy: {name}")
|
|
333
|
+
info(TOYS[name]["url"])
|
|
334
|
+
results.append((name, TOYS[name]["install"](options)))
|
|
335
|
+
|
|
336
|
+
print_summary(results, scope)
|
|
@@ -466,8 +466,8 @@ def doc_read(target, out_dir, no_images, raw, via_docs_api):
|
|
|
466
466
|
"""Read a Google Doc and write **doc.md** + downloaded **images/**.
|
|
467
467
|
|
|
468
468
|
`TARGET` is either a Google Docs URL (`https://docs.google.com/document/d/<id>/...`)
|
|
469
|
-
or a raw document ID.
|
|
470
|
-
`~/.omelet.json` under `
|
|
469
|
+
or a raw document ID. Auth uses the Drive OAuth token saved in
|
|
470
|
+
`~/.omelet.json` under `google_drive.token`, refreshing it automatically when expired.
|
|
471
471
|
|
|
472
472
|
Default path: Drive API export to `text/markdown`, then any inline image URLs
|
|
473
473
|
in the markdown are downloaded into `images/` and their references rewritten
|
|
@@ -167,9 +167,12 @@ SPECS = [
|
|
|
167
167
|
"type": "oauth_token",
|
|
168
168
|
"lifetime": "access ~1h, refresh until revoked",
|
|
169
169
|
"description": (
|
|
170
|
-
"rclone Google Drive remote: OAuth token + root folder. Being replaced by google_drive
|
|
171
|
-
"
|
|
172
|
-
"
|
|
170
|
+
"rclone Google Drive remote: OAuth token + root folder. Being replaced by google_drive. "
|
|
171
|
+
"The in-cluster consumer (red-life/packages/core/src/teaching/drive.ts) now reads "
|
|
172
|
+
"google_drive; rclone stays active because two sibling repos outside the harness still "
|
|
173
|
+
"read and refresh it: evo-lms-harness/cli/modules/gdrive.py and "
|
|
174
|
+
"fu-teching/tools/schedule/drive.py. Do not mark deprecated until both migrate - compile "
|
|
175
|
+
"would drop the key and break them."
|
|
173
176
|
),
|
|
174
177
|
"rotate": "evo cred refresh --service rclone (or rclone config reconnect)",
|
|
175
178
|
"oauth": {
|
|
@@ -18,6 +18,7 @@ evo_cli.egg-info/entry_points.txt
|
|
|
18
18
|
evo_cli.egg-info/requires.txt
|
|
19
19
|
evo_cli.egg-info/top_level.txt
|
|
20
20
|
evo_cli/commands/__init__.py
|
|
21
|
+
evo_cli/commands/agent_toy.py
|
|
21
22
|
evo_cli/commands/claude_code.py
|
|
22
23
|
evo_cli/commands/cloudflare.py
|
|
23
24
|
evo_cli/commands/cred.py
|
|
@@ -86,6 +87,7 @@ evo_cli/tts/openai.py
|
|
|
86
87
|
evo_cli/tts/player.py
|
|
87
88
|
evo_cli/tts/vbee.py
|
|
88
89
|
tests/__init__.py
|
|
90
|
+
tests/test_agent_toy.py
|
|
89
91
|
tests/test_claude_code.py
|
|
90
92
|
tests/test_cli.py
|
|
91
93
|
tests/test_console.py
|
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
import pytest
|
|
2
|
+
from click.testing import CliRunner
|
|
3
|
+
|
|
4
|
+
from evo_cli.cli import cli
|
|
5
|
+
from evo_cli.commands import agent_toy as toy
|
|
6
|
+
from evo_cli.commands.agent_toy import (
|
|
7
|
+
FAILED,
|
|
8
|
+
INSTALLED,
|
|
9
|
+
PARTIAL,
|
|
10
|
+
TOYS,
|
|
11
|
+
install_agent_skills,
|
|
12
|
+
install_context_mode,
|
|
13
|
+
install_plannotator,
|
|
14
|
+
parse_entries,
|
|
15
|
+
plannotator_install_command,
|
|
16
|
+
)
|
|
17
|
+
|
|
18
|
+
PLUGIN_LIST = """Installed plugins:
|
|
19
|
+
|
|
20
|
+
❯ context-mode@context-mode
|
|
21
|
+
Version: 1.0.169
|
|
22
|
+
Scope: user
|
|
23
|
+
Status: ✔ enabled
|
|
24
|
+
|
|
25
|
+
❯ warp@claude-code-warp
|
|
26
|
+
Version: 2.2.0
|
|
27
|
+
Scope: user
|
|
28
|
+
Status: ✔ enabled
|
|
29
|
+
"""
|
|
30
|
+
|
|
31
|
+
MARKETPLACE_LIST = """Configured marketplaces:
|
|
32
|
+
|
|
33
|
+
❯ context-mode
|
|
34
|
+
Source: Git (https://github.com/mksglu/context-mode.git)
|
|
35
|
+
|
|
36
|
+
❯ plannotator
|
|
37
|
+
Source: GitHub (backnotprop/plannotator)
|
|
38
|
+
"""
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
@pytest.fixture
|
|
42
|
+
def runner():
|
|
43
|
+
return CliRunner()
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
@pytest.fixture
|
|
47
|
+
def options():
|
|
48
|
+
return {"scope": "user", "minimal": False, "claude": True}
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
def test_agent_toy_is_registered():
|
|
52
|
+
assert "agent-toy" in cli.commands["setup"].commands
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
def test_agent_toy_help_runs(runner):
|
|
56
|
+
result = runner.invoke(cli, ["setup", "agent-toy", "--help"])
|
|
57
|
+
assert result.exit_code == 0
|
|
58
|
+
assert "plannotator" in result.output
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
def test_list_shows_every_toy(runner):
|
|
62
|
+
result = runner.invoke(cli, ["setup", "agent-toy", "--list"])
|
|
63
|
+
assert result.exit_code == 0
|
|
64
|
+
for name in TOYS:
|
|
65
|
+
assert name in result.output
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
def test_unknown_toy_is_rejected(runner, monkeypatch):
|
|
69
|
+
monkeypatch.setattr(toy, "ensure_claude_on_path", lambda: "/usr/bin/claude")
|
|
70
|
+
result = runner.invoke(cli, ["setup", "agent-toy", "nope"])
|
|
71
|
+
assert "Unknown toy" in result.output
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
def test_parse_entries_reads_plugin_names():
|
|
75
|
+
assert parse_entries(PLUGIN_LIST) == ["context-mode@context-mode", "warp@claude-code-warp"]
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
def test_parse_entries_reads_marketplace_names():
|
|
79
|
+
assert parse_entries(MARKETPLACE_LIST) == ["context-mode", "plannotator"]
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
def test_plannotator_install_command_posix(monkeypatch):
|
|
83
|
+
monkeypatch.setattr(toy, "is_windows", lambda: False)
|
|
84
|
+
monkeypatch.setattr(toy.shutil, "which", lambda name: f"/usr/bin/{name}")
|
|
85
|
+
assert plannotator_install_command(False) == [
|
|
86
|
+
"bash",
|
|
87
|
+
"-c",
|
|
88
|
+
"curl -fsSL https://plannotator.ai/install.sh | bash",
|
|
89
|
+
]
|
|
90
|
+
assert plannotator_install_command(True)[-1].endswith("| bash -s -- --minimal")
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
def test_plannotator_install_command_windows(monkeypatch):
|
|
94
|
+
monkeypatch.setattr(toy, "is_windows", lambda: True)
|
|
95
|
+
monkeypatch.setattr(toy.shutil, "which", lambda name: f"C:\\{name}.exe")
|
|
96
|
+
command = plannotator_install_command(True)
|
|
97
|
+
assert command[0] == "powershell"
|
|
98
|
+
assert "PLANNOTATOR_MINIMAL=1" in command[-1]
|
|
99
|
+
assert "install.ps1" in command[-1]
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
def test_plannotator_install_command_without_a_shell(monkeypatch):
|
|
103
|
+
monkeypatch.setattr(toy, "is_windows", lambda: False)
|
|
104
|
+
monkeypatch.setattr(toy.shutil, "which", lambda name: None)
|
|
105
|
+
assert plannotator_install_command(False) is None
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
def test_plannotator_is_partial_when_the_installer_fails(monkeypatch, options):
|
|
109
|
+
monkeypatch.setattr(toy, "ensure_marketplace", lambda *args: True)
|
|
110
|
+
monkeypatch.setattr(toy, "ensure_plugin", lambda *args: True)
|
|
111
|
+
monkeypatch.setattr(toy, "run_plannotator_installer", lambda minimal: False)
|
|
112
|
+
monkeypatch.setattr(toy, "plannotator_binary", lambda: "/usr/local/bin/plannotator")
|
|
113
|
+
assert install_plannotator(options) == PARTIAL
|
|
114
|
+
|
|
115
|
+
|
|
116
|
+
def test_plannotator_fails_when_no_binary_landed(monkeypatch, options):
|
|
117
|
+
monkeypatch.setattr(toy, "ensure_marketplace", lambda *args: True)
|
|
118
|
+
monkeypatch.setattr(toy, "ensure_plugin", lambda *args: True)
|
|
119
|
+
monkeypatch.setattr(toy, "run_plannotator_installer", lambda minimal: False)
|
|
120
|
+
monkeypatch.setattr(toy, "plannotator_binary", lambda: None)
|
|
121
|
+
assert install_plannotator(options) == FAILED
|
|
122
|
+
|
|
123
|
+
|
|
124
|
+
def test_plannotator_minimal_skips_the_plugin(monkeypatch, options):
|
|
125
|
+
calls = []
|
|
126
|
+
monkeypatch.setattr(toy, "ensure_marketplace", lambda *args: calls.append(args) or True)
|
|
127
|
+
monkeypatch.setattr(toy, "run_plannotator_installer", lambda minimal: True)
|
|
128
|
+
options["minimal"] = True
|
|
129
|
+
assert install_plannotator(options) == INSTALLED
|
|
130
|
+
assert calls == []
|
|
131
|
+
|
|
132
|
+
|
|
133
|
+
def test_context_mode_needs_claude(options):
|
|
134
|
+
options["claude"] = False
|
|
135
|
+
assert install_context_mode(options) == FAILED
|
|
136
|
+
|
|
137
|
+
|
|
138
|
+
def test_context_mode_installs_the_plugin(monkeypatch, options):
|
|
139
|
+
monkeypatch.setattr(toy, "ensure_marketplace", lambda *args: True)
|
|
140
|
+
monkeypatch.setattr(toy, "ensure_plugin", lambda *args: True)
|
|
141
|
+
assert install_context_mode(options) == INSTALLED
|
|
142
|
+
|
|
143
|
+
|
|
144
|
+
def test_agent_skills_installs_globally(monkeypatch, options):
|
|
145
|
+
captured = {}
|
|
146
|
+
monkeypatch.setattr(toy.shutil, "which", lambda name: f"/usr/bin/{name}")
|
|
147
|
+
monkeypatch.setattr(toy, "run_command", lambda command, **kwargs: captured.setdefault("command", command))
|
|
148
|
+
assert install_agent_skills(options) == INSTALLED
|
|
149
|
+
assert captured["command"][-1] == "--global"
|
|
150
|
+
assert "maycuatroi1/agent-skills" in captured["command"]
|
|
151
|
+
|
|
152
|
+
|
|
153
|
+
def test_agent_skills_scopes_to_the_project(monkeypatch, options):
|
|
154
|
+
captured = {}
|
|
155
|
+
monkeypatch.setattr(toy.shutil, "which", lambda name: f"/usr/bin/{name}")
|
|
156
|
+
monkeypatch.setattr(toy, "run_command", lambda command, **kwargs: captured.setdefault("command", command))
|
|
157
|
+
options["scope"] = "project"
|
|
158
|
+
install_agent_skills(options)
|
|
159
|
+
assert captured["command"][-1] == "--project"
|
|
160
|
+
|
|
161
|
+
|
|
162
|
+
def test_agent_skills_needs_npx(monkeypatch, options):
|
|
163
|
+
monkeypatch.setattr(toy.shutil, "which", lambda name: None)
|
|
164
|
+
assert install_agent_skills(options) == FAILED
|
evo_cli-0.15.2/evo_cli/VERSION
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
0.15.2
|
|
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
|
|
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
|
|
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
|