evo-cli 0.14.4__tar.gz → 0.15.0__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 (108) hide show
  1. {evo_cli-0.14.4 → evo_cli-0.15.0}/PKG-INFO +1 -1
  2. evo_cli-0.15.0/evo_cli/VERSION +1 -0
  3. {evo_cli-0.14.4 → evo_cli-0.15.0}/evo_cli/commands/harness/_model.py +59 -1
  4. {evo_cli-0.14.4 → evo_cli-0.15.0}/evo_cli/commands/harness/_mutate.py +19 -0
  5. {evo_cli-0.14.4 → evo_cli-0.15.0}/evo_cli/commands/harness/_server.py +37 -1
  6. {evo_cli-0.14.4 → evo_cli-0.15.0}/evo_cli/commands/harness/serve.py +3 -3
  7. evo_cli-0.15.0/evo_cli/commands/harness/web/assets/index-Bo-8pFen.js +257 -0
  8. evo_cli-0.14.4/evo_cli/commands/harness/web/assets/index-CiUsumT3.css → evo_cli-0.15.0/evo_cli/commands/harness/web/assets/index-ByHqvOcG.css +1 -1
  9. {evo_cli-0.14.4 → evo_cli-0.15.0}/evo_cli/commands/harness/web/index.html +2 -2
  10. {evo_cli-0.14.4 → evo_cli-0.15.0}/evo_cli.egg-info/PKG-INFO +1 -1
  11. {evo_cli-0.14.4 → evo_cli-0.15.0}/evo_cli.egg-info/SOURCES.txt +2 -2
  12. {evo_cli-0.14.4 → evo_cli-0.15.0}/tests/test_harness.py +35 -0
  13. evo_cli-0.14.4/evo_cli/VERSION +0 -1
  14. evo_cli-0.14.4/evo_cli/commands/harness/web/assets/index-BpeLRwRd.js +0 -242
  15. {evo_cli-0.14.4 → evo_cli-0.15.0}/Containerfile +0 -0
  16. {evo_cli-0.14.4 → evo_cli-0.15.0}/HISTORY.md +0 -0
  17. {evo_cli-0.14.4 → evo_cli-0.15.0}/LICENSE +0 -0
  18. {evo_cli-0.14.4 → evo_cli-0.15.0}/MANIFEST.in +0 -0
  19. {evo_cli-0.14.4 → evo_cli-0.15.0}/README.md +0 -0
  20. {evo_cli-0.14.4 → evo_cli-0.15.0}/evo_cli/__init__.py +0 -0
  21. {evo_cli-0.14.4 → evo_cli-0.15.0}/evo_cli/__main__.py +0 -0
  22. {evo_cli-0.14.4 → evo_cli-0.15.0}/evo_cli/base.py +0 -0
  23. {evo_cli-0.14.4 → evo_cli-0.15.0}/evo_cli/cli.py +0 -0
  24. {evo_cli-0.14.4 → evo_cli-0.15.0}/evo_cli/commands/__init__.py +0 -0
  25. {evo_cli-0.14.4 → evo_cli-0.15.0}/evo_cli/commands/claude_code.py +0 -0
  26. {evo_cli-0.14.4 → evo_cli-0.15.0}/evo_cli/commands/cloudflare.py +0 -0
  27. {evo_cli-0.14.4 → evo_cli-0.15.0}/evo_cli/commands/cred.py +0 -0
  28. {evo_cli-0.14.4 → evo_cli-0.15.0}/evo_cli/commands/download.py +0 -0
  29. {evo_cli-0.14.4 → evo_cli-0.15.0}/evo_cli/commands/fix_claude.py +0 -0
  30. {evo_cli-0.14.4 → evo_cli-0.15.0}/evo_cli/commands/gdrive.py +0 -0
  31. {evo_cli-0.14.4 → evo_cli-0.15.0}/evo_cli/commands/gh.py +0 -0
  32. {evo_cli-0.14.4 → evo_cli-0.15.0}/evo_cli/commands/harness/__init__.py +0 -0
  33. {evo_cli-0.14.4 → evo_cli-0.15.0}/evo_cli/commands/harness/_dag.py +0 -0
  34. {evo_cli-0.14.4 → evo_cli-0.15.0}/evo_cli/commands/harness/_git.py +0 -0
  35. {evo_cli-0.14.4 → evo_cli-0.15.0}/evo_cli/commands/harness/_paths.py +0 -0
  36. {evo_cli-0.14.4 → evo_cli-0.15.0}/evo_cli/commands/harness/_render.py +0 -0
  37. {evo_cli-0.14.4 → evo_cli-0.15.0}/evo_cli/commands/harness/check.py +0 -0
  38. {evo_cli-0.14.4 → evo_cli-0.15.0}/evo_cli/commands/harness/edit.py +0 -0
  39. {evo_cli-0.14.4 → evo_cli-0.15.0}/evo_cli/commands/harness/pull.py +0 -0
  40. {evo_cli-0.14.4 → evo_cli-0.15.0}/evo_cli/commands/harness/show.py +0 -0
  41. {evo_cli-0.14.4 → evo_cli-0.15.0}/evo_cli/commands/harness/views.py +0 -0
  42. {evo_cli-0.14.4 → evo_cli-0.15.0}/evo_cli/commands/harness/web/assets/inter-cyrillic-ext-wght-normal-BOeWTOD4.woff2 +0 -0
  43. {evo_cli-0.14.4 → evo_cli-0.15.0}/evo_cli/commands/harness/web/assets/inter-cyrillic-wght-normal-DqGufNeO.woff2 +0 -0
  44. {evo_cli-0.14.4 → evo_cli-0.15.0}/evo_cli/commands/harness/web/assets/inter-greek-ext-wght-normal-DlzME5K_.woff2 +0 -0
  45. {evo_cli-0.14.4 → evo_cli-0.15.0}/evo_cli/commands/harness/web/assets/inter-greek-wght-normal-CkhJZR-_.woff2 +0 -0
  46. {evo_cli-0.14.4 → evo_cli-0.15.0}/evo_cli/commands/harness/web/assets/inter-latin-ext-wght-normal-DO1Apj_S.woff2 +0 -0
  47. {evo_cli-0.14.4 → evo_cli-0.15.0}/evo_cli/commands/harness/web/assets/inter-latin-wght-normal-Dx4kXJAl.woff2 +0 -0
  48. {evo_cli-0.14.4 → evo_cli-0.15.0}/evo_cli/commands/harness/web/assets/inter-vietnamese-wght-normal-CBcvBZtf.woff2 +0 -0
  49. {evo_cli-0.14.4 → evo_cli-0.15.0}/evo_cli/commands/harness/web/assets/jetbrains-mono-cyrillic-wght-normal-D73BlboJ.woff2 +0 -0
  50. {evo_cli-0.14.4 → evo_cli-0.15.0}/evo_cli/commands/harness/web/assets/jetbrains-mono-greek-wght-normal-Bw9x6K1M.woff2 +0 -0
  51. {evo_cli-0.14.4 → evo_cli-0.15.0}/evo_cli/commands/harness/web/assets/jetbrains-mono-latin-ext-wght-normal-DBQx-q_a.woff2 +0 -0
  52. {evo_cli-0.14.4 → evo_cli-0.15.0}/evo_cli/commands/harness/web/assets/jetbrains-mono-latin-wght-normal-B9CIFXIH.woff2 +0 -0
  53. {evo_cli-0.14.4 → evo_cli-0.15.0}/evo_cli/commands/harness/web/assets/jetbrains-mono-vietnamese-wght-normal-Bt-aOZkq.woff2 +0 -0
  54. {evo_cli-0.14.4 → evo_cli-0.15.0}/evo_cli/commands/hwid.py +0 -0
  55. {evo_cli-0.14.4 → evo_cli-0.15.0}/evo_cli/commands/hwid_reset.py +0 -0
  56. {evo_cli-0.14.4 → evo_cli-0.15.0}/evo_cli/commands/localproxy.py +0 -0
  57. {evo_cli-0.14.4 → evo_cli-0.15.0}/evo_cli/commands/mcp.py +0 -0
  58. {evo_cli-0.14.4 → evo_cli-0.15.0}/evo_cli/commands/miniconda.py +0 -0
  59. {evo_cli-0.14.4 → evo_cli-0.15.0}/evo_cli/commands/netcheck.py +0 -0
  60. {evo_cli-0.14.4 → evo_cli-0.15.0}/evo_cli/commands/opencode.py +0 -0
  61. {evo_cli-0.14.4 → evo_cli-0.15.0}/evo_cli/commands/plantuml.py +0 -0
  62. {evo_cli-0.14.4 → evo_cli-0.15.0}/evo_cli/commands/site2s.py +0 -0
  63. {evo_cli-0.14.4 → evo_cli-0.15.0}/evo_cli/commands/ssh.py +0 -0
  64. {evo_cli-0.14.4 → evo_cli-0.15.0}/evo_cli/commands/sysmon.py +0 -0
  65. {evo_cli-0.14.4 → evo_cli-0.15.0}/evo_cli/commands/tts.py +0 -0
  66. {evo_cli-0.14.4 → evo_cli-0.15.0}/evo_cli/commands/update.py +0 -0
  67. {evo_cli-0.14.4 → evo_cli-0.15.0}/evo_cli/commands/wifi.py +0 -0
  68. {evo_cli-0.14.4 → evo_cli-0.15.0}/evo_cli/console.py +0 -0
  69. {evo_cli-0.14.4 → evo_cli-0.15.0}/evo_cli/credentials/__init__.py +0 -0
  70. {evo_cli-0.14.4 → evo_cli-0.15.0}/evo_cli/credentials/doctor.py +0 -0
  71. {evo_cli-0.14.4 → evo_cli-0.15.0}/evo_cli/credentials/google_oauth.py +0 -0
  72. {evo_cli-0.14.4 → evo_cli-0.15.0}/evo_cli/credentials/migrate.py +0 -0
  73. {evo_cli-0.14.4 → evo_cli-0.15.0}/evo_cli/credentials/oauth_flow.py +0 -0
  74. {evo_cli-0.14.4 → evo_cli-0.15.0}/evo_cli/credentials/registry.py +0 -0
  75. {evo_cli-0.14.4 → evo_cli-0.15.0}/evo_cli/credentials/store.py +0 -0
  76. {evo_cli-0.14.4 → evo_cli-0.15.0}/evo_cli/credentials/sync.py +0 -0
  77. {evo_cli-0.14.4 → evo_cli-0.15.0}/evo_cli/mcp_registry.py +0 -0
  78. {evo_cli-0.14.4 → evo_cli-0.15.0}/evo_cli/tts/__init__.py +0 -0
  79. {evo_cli-0.14.4 → evo_cli-0.15.0}/evo_cli/tts/chunking.py +0 -0
  80. {evo_cli-0.14.4 → evo_cli-0.15.0}/evo_cli/tts/core.py +0 -0
  81. {evo_cli-0.14.4 → evo_cli-0.15.0}/evo_cli/tts/creds.py +0 -0
  82. {evo_cli-0.14.4 → evo_cli-0.15.0}/evo_cli/tts/errors.py +0 -0
  83. {evo_cli-0.14.4 → evo_cli-0.15.0}/evo_cli/tts/http.py +0 -0
  84. {evo_cli-0.14.4 → evo_cli-0.15.0}/evo_cli/tts/openai.py +0 -0
  85. {evo_cli-0.14.4 → evo_cli-0.15.0}/evo_cli/tts/player.py +0 -0
  86. {evo_cli-0.14.4 → evo_cli-0.15.0}/evo_cli/tts/vbee.py +0 -0
  87. {evo_cli-0.14.4 → evo_cli-0.15.0}/evo_cli.egg-info/dependency_links.txt +0 -0
  88. {evo_cli-0.14.4 → evo_cli-0.15.0}/evo_cli.egg-info/entry_points.txt +0 -0
  89. {evo_cli-0.14.4 → evo_cli-0.15.0}/evo_cli.egg-info/requires.txt +0 -0
  90. {evo_cli-0.14.4 → evo_cli-0.15.0}/evo_cli.egg-info/top_level.txt +0 -0
  91. {evo_cli-0.14.4 → evo_cli-0.15.0}/pyproject.toml +0 -0
  92. {evo_cli-0.14.4 → evo_cli-0.15.0}/setup.cfg +0 -0
  93. {evo_cli-0.14.4 → evo_cli-0.15.0}/tests/__init__.py +0 -0
  94. {evo_cli-0.14.4 → evo_cli-0.15.0}/tests/test_claude_code.py +0 -0
  95. {evo_cli-0.14.4 → evo_cli-0.15.0}/tests/test_cli.py +0 -0
  96. {evo_cli-0.14.4 → evo_cli-0.15.0}/tests/test_console.py +0 -0
  97. {evo_cli-0.14.4 → evo_cli-0.15.0}/tests/test_cred.py +0 -0
  98. {evo_cli-0.14.4 → evo_cli-0.15.0}/tests/test_download.py +0 -0
  99. {evo_cli-0.14.4 → evo_cli-0.15.0}/tests/test_fix_claude.py +0 -0
  100. {evo_cli-0.14.4 → evo_cli-0.15.0}/tests/test_gh.py +0 -0
  101. {evo_cli-0.14.4 → evo_cli-0.15.0}/tests/test_harness_dag.py +0 -0
  102. {evo_cli-0.14.4 → evo_cli-0.15.0}/tests/test_mcp.py +0 -0
  103. {evo_cli-0.14.4 → evo_cli-0.15.0}/tests/test_opencode.py +0 -0
  104. {evo_cli-0.14.4 → evo_cli-0.15.0}/tests/test_plantuml.py +0 -0
  105. {evo_cli-0.14.4 → evo_cli-0.15.0}/tests/test_sysmon.py +0 -0
  106. {evo_cli-0.14.4 → evo_cli-0.15.0}/tests/test_tts.py +0 -0
  107. {evo_cli-0.14.4 → evo_cli-0.15.0}/tests/test_update.py +0 -0
  108. {evo_cli-0.14.4 → evo_cli-0.15.0}/tests/test_wifi.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: evo_cli
3
- Version: 0.14.4
3
+ Version: 0.15.0
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.15.0
@@ -32,6 +32,10 @@ def contracts_file(manifest_path: Path) -> Path:
32
32
  return harness_root(manifest_path) / "contracts.yaml"
33
33
 
34
34
 
35
+ def deployments_file(manifest_path: Path) -> Path:
36
+ return harness_root(manifest_path) / "deployments.yaml"
37
+
38
+
35
39
  def tone_of(section: str, status: str | None) -> str:
36
40
  value = (status or "").strip().lower()
37
41
  if not value:
@@ -218,6 +222,60 @@ def load_seams(manifest_path: Path) -> list[dict]:
218
222
  return out
219
223
 
220
224
 
225
+ def load_deployments(manifest_path: Path) -> dict:
226
+ path = deployments_file(manifest_path)
227
+ empty = {"version": 0, "configVersion": "", "environments": [], "tenants": [], "deployments": []}
228
+ if not path.is_file():
229
+ return empty
230
+ raw = read_yaml(path, required=False)
231
+ if not isinstance(raw, dict):
232
+ return empty
233
+
234
+ environments = [str(e) for e in (raw.get("environments") or []) if e is not None]
235
+
236
+ tenants = []
237
+ for tenant in raw.get("tenants") or []:
238
+ if not isinstance(tenant, dict):
239
+ continue
240
+ tenants.append(
241
+ {
242
+ "id": str(tenant.get("id") or ""),
243
+ "code": str(tenant.get("code") or ""),
244
+ "name": str(tenant.get("name") or ""),
245
+ "aliases": [str(a) for a in (tenant.get("aliases") or [])],
246
+ }
247
+ )
248
+
249
+ deployments = []
250
+ for index, dep in enumerate(raw.get("deployments") or []):
251
+ if not isinstance(dep, dict):
252
+ continue
253
+ deployments.append(
254
+ {
255
+ "index": index,
256
+ "deploymentId": str(dep.get("deployment_id") or f"deployment-{index}"),
257
+ "product": str(dep.get("product") or ""),
258
+ "tenantId": str(dep.get("tenant_id")) if dep.get("tenant_id") else "",
259
+ "environment": str(dep.get("environment") or ""),
260
+ "kind": str(dep.get("deployment_kind") or ""),
261
+ "webUrl": str(dep.get("web_url")) if dep.get("web_url") else "",
262
+ "apiUrl": str(dep.get("api_url")) if dep.get("api_url") else "",
263
+ "authUrl": str(dep.get("auth_url")) if dep.get("auth_url") else "",
264
+ "capabilities": [str(c) for c in (dep.get("capabilities") or [])],
265
+ "status": str(dep.get("status") or ""),
266
+ "aliases": [str(a) for a in (dep.get("aliases") or [])],
267
+ }
268
+ )
269
+
270
+ return {
271
+ "version": raw.get("version") or 0,
272
+ "configVersion": str(raw.get("config_version") or ""),
273
+ "environments": environments,
274
+ "tenants": tenants,
275
+ "deployments": deployments,
276
+ }
277
+
278
+
221
279
  def _read_text(path: Path) -> str:
222
280
  try:
223
281
  return path.read_text(encoding="utf-8")
@@ -262,7 +320,7 @@ def cluster(manifest_path: Path) -> dict:
262
320
  def digest(manifest_path: Path) -> str:
263
321
  root = harness_root(manifest_path)
264
322
  parts = []
265
- targets = [manifest_path, contracts_file(manifest_path)]
323
+ targets = [manifest_path, contracts_file(manifest_path), deployments_file(manifest_path)]
266
324
  targets.extend(sorted(plans_dir(manifest_path).rglob("*.yaml")) if plans_dir(manifest_path).is_dir() else [])
267
325
  targets.extend(sorted((root / "principles").glob("*.md")) if (root / "principles").is_dir() else [])
268
326
  for path in targets:
@@ -7,6 +7,8 @@ from pathlib import Path
7
7
  import rich_click as click
8
8
  import yaml
9
9
 
10
+ from evo_cli.commands.harness._model import Plan, load_plan_file
11
+
10
12
  PLAIN_SAFE = re.compile(r"^[A-Za-z][\w./#@-]*$")
11
13
 
12
14
 
@@ -93,3 +95,20 @@ def update_item(path: Path, section: str, index: int, updates: dict) -> dict:
93
95
 
94
96
  path.write_text(updated, encoding="utf-8")
95
97
  return {"old": old, "new": updates}
98
+
99
+
100
+ def complete_plan(plan: Plan) -> Plan:
101
+ """Move an active plan into the completed area without rewriting its YAML."""
102
+ if plan.area == "completed":
103
+ return plan
104
+ if plan.area != "active":
105
+ raise click.ClickException(f"Plan {plan.id!r} is in an unsupported area: {plan.area!r}.")
106
+
107
+ destination_dir = plan.path.parent.parent / "completed"
108
+ destination = destination_dir / plan.path.name
109
+ if destination.exists():
110
+ raise click.ClickException(f"Cannot complete {plan.id!r}: {destination} already exists.")
111
+
112
+ destination_dir.mkdir(parents=True, exist_ok=True)
113
+ plan.path.rename(destination)
114
+ return load_plan_file(destination)
@@ -11,7 +11,15 @@ import rich_click as click
11
11
 
12
12
  from evo_cli.commands.harness._dag import plan_graphs, seam_graph
13
13
  from evo_cli.commands.harness._git import overlay
14
- from evo_cli.commands.harness._model import cluster, digest, find_plan, load_plans, load_seams
14
+ from evo_cli.commands.harness._model import (
15
+ cluster,
16
+ digest,
17
+ find_plan,
18
+ load_deployments,
19
+ load_plans,
20
+ load_seams,
21
+ )
22
+ from evo_cli.commands.harness._mutate import complete_plan
15
23
 
16
24
  WEB_DIR = Path(__file__).resolve().parent / "web"
17
25
  INDEX = WEB_DIR / "index.html"
@@ -68,6 +76,20 @@ class Handler(BaseHTTPRequestHandler):
68
76
  except Exception as exc: # a dashboard must not die because one plan has broken YAML
69
77
  self._json({"error": str(exc)}, status=500)
70
78
 
79
+ def do_POST(self):
80
+ route = unquote(urlparse(self.path).path)
81
+ try:
82
+ if self.headers.get("X-Evo-Harness-Write") != "1":
83
+ self._json({"error": "missing harness write header"}, status=403)
84
+ elif route.startswith("/api/"):
85
+ self._write_api(route)
86
+ else:
87
+ self._json({"error": "not found"}, status=404)
88
+ except (BrokenPipeError, ConnectionResetError, ConnectionAbortedError):
89
+ pass
90
+ except Exception as exc:
91
+ self._json({"error": str(exc)}, status=500)
92
+
71
93
  def _send(self, body: bytes, content_type: str, status: int = 200, headers=None):
72
94
  self.send_response(status)
73
95
  self.send_header("Content-Type", content_type)
@@ -106,6 +128,7 @@ class Handler(BaseHTTPRequestHandler):
106
128
  "cluster": cluster(manifest),
107
129
  "seams": load_seams(manifest),
108
130
  "seamGraph": seam_graph(manifest),
131
+ "deployments": load_deployments(manifest),
109
132
  "plans": [p.summary() for p in load_plans(manifest)],
110
133
  }
111
134
  )
@@ -127,6 +150,19 @@ class Handler(BaseHTTPRequestHandler):
127
150
  return
128
151
  self._json({"error": f"no route {route}"}, status=404)
129
152
 
153
+ def _write_api(self, route: str):
154
+ parts = route.strip("/").split("/")
155
+ if len(parts) == 4 and parts[:2] == ["api", "plans"] and parts[3] == "complete":
156
+ try:
157
+ plan = find_plan(self.manifest_path, parts[2])
158
+ completed = complete_plan(plan)
159
+ except click.ClickException as exc:
160
+ self._json({"error": exc.format_message()}, status=409)
161
+ return
162
+ self._json({"plan": completed.detail(), "graphs": plan_graphs(completed)})
163
+ return
164
+ self._json({"error": f"no route {route}"}, status=404)
165
+
130
166
  def _stream(self):
131
167
  self.send_response(200)
132
168
  self.send_header("Content-Type", "text/event-stream")
@@ -17,8 +17,8 @@ from evo_cli.console import console
17
17
  @click.option("--host", default="127.0.0.1", show_default=True)
18
18
  @click.option("--open/--no-open", "open_browser", default=True, help="Open a browser once the server is up.")
19
19
  def serve(harness_path, port, host, open_browser):
20
- """Read-only: the UI never writes YAML. Edit with `evo harness step|debt|question|repo`
21
- and the dashboard follows the file within a second or two."""
20
+ """Inspect the harness and complete plans from the UI. Edit item statuses with
21
+ `evo harness step|debt|question|repo`; the dashboard follows changes within seconds."""
22
22
  manifest_path = find_manifest(harness_path)
23
23
  if not bundle_ready():
24
24
  raise click.ClickException(MISSING_BUNDLE)
@@ -32,7 +32,7 @@ def serve(harness_path, port, host, open_browser):
32
32
  console.print(f"[accent]{info['name']}[/] harness -> [bold]{url}[/]")
33
33
  console.print(
34
34
  f"[dim]{len(info['repos'])} repos, {len(seams)} seams, {len(plans)} plans from {info['root']}"
35
- f" | read-only, edit with `evo harness step ...`[/]"
35
+ f" | plan completion enabled; edit items with `evo harness step ...`[/]"
36
36
  )
37
37
  console.print("[dim]Ctrl+C to stop.[/]")
38
38