evo-cli 0.26.2__tar.gz → 0.28.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 (147) hide show
  1. {evo_cli-0.26.2 → evo_cli-0.28.0}/PKG-INFO +32 -3
  2. {evo_cli-0.26.2 → evo_cli-0.28.0}/README.md +31 -2
  3. evo_cli-0.28.0/evo_cli/VERSION +1 -0
  4. {evo_cli-0.26.2 → evo_cli-0.28.0}/evo_cli/cli.py +2 -0
  5. {evo_cli-0.26.2 → evo_cli-0.28.0}/evo_cli/commands/openvpn.py +12 -5
  6. evo_cli-0.28.0/evo_cli/commands/storage.py +763 -0
  7. {evo_cli-0.26.2 → evo_cli-0.28.0}/evo_cli/openvpn.py +177 -23
  8. {evo_cli-0.26.2 → evo_cli-0.28.0}/evo_cli.egg-info/PKG-INFO +32 -3
  9. {evo_cli-0.26.2 → evo_cli-0.28.0}/evo_cli.egg-info/SOURCES.txt +2 -0
  10. {evo_cli-0.26.2 → evo_cli-0.28.0}/tests/test_openvpn.py +127 -0
  11. evo_cli-0.28.0/tests/test_storage.py +251 -0
  12. evo_cli-0.26.2/evo_cli/VERSION +0 -1
  13. {evo_cli-0.26.2 → evo_cli-0.28.0}/Containerfile +0 -0
  14. {evo_cli-0.26.2 → evo_cli-0.28.0}/HISTORY.md +0 -0
  15. {evo_cli-0.26.2 → evo_cli-0.28.0}/LICENSE +0 -0
  16. {evo_cli-0.26.2 → evo_cli-0.28.0}/MANIFEST.in +0 -0
  17. {evo_cli-0.26.2 → evo_cli-0.28.0}/evo_cli/__init__.py +0 -0
  18. {evo_cli-0.26.2 → evo_cli-0.28.0}/evo_cli/__main__.py +0 -0
  19. {evo_cli-0.26.2 → evo_cli-0.28.0}/evo_cli/base.py +0 -0
  20. {evo_cli-0.26.2 → evo_cli-0.28.0}/evo_cli/commands/__init__.py +0 -0
  21. {evo_cli-0.26.2 → evo_cli-0.28.0}/evo_cli/commands/agent_toy.py +0 -0
  22. {evo_cli-0.26.2 → evo_cli-0.28.0}/evo_cli/commands/claude_code.py +0 -0
  23. {evo_cli-0.26.2 → evo_cli-0.28.0}/evo_cli/commands/cloudflare.py +0 -0
  24. {evo_cli-0.26.2 → evo_cli-0.28.0}/evo_cli/commands/cred.py +0 -0
  25. {evo_cli-0.26.2 → evo_cli-0.28.0}/evo_cli/commands/download.py +0 -0
  26. {evo_cli-0.26.2 → evo_cli-0.28.0}/evo_cli/commands/fix_claude.py +0 -0
  27. {evo_cli-0.26.2 → evo_cli-0.28.0}/evo_cli/commands/gdrive.py +0 -0
  28. {evo_cli-0.26.2 → evo_cli-0.28.0}/evo_cli/commands/gh.py +0 -0
  29. {evo_cli-0.26.2 → evo_cli-0.28.0}/evo_cli/commands/harness/__init__.py +0 -0
  30. {evo_cli-0.26.2 → evo_cli-0.28.0}/evo_cli/commands/harness/_dag.py +0 -0
  31. {evo_cli-0.26.2 → evo_cli-0.28.0}/evo_cli/commands/harness/_git.py +0 -0
  32. {evo_cli-0.26.2 → evo_cli-0.28.0}/evo_cli/commands/harness/_model.py +0 -0
  33. {evo_cli-0.26.2 → evo_cli-0.28.0}/evo_cli/commands/harness/_mutate.py +0 -0
  34. {evo_cli-0.26.2 → evo_cli-0.28.0}/evo_cli/commands/harness/_paths.py +0 -0
  35. {evo_cli-0.26.2 → evo_cli-0.28.0}/evo_cli/commands/harness/_render.py +0 -0
  36. {evo_cli-0.26.2 → evo_cli-0.28.0}/evo_cli/commands/harness/_server.py +0 -0
  37. {evo_cli-0.26.2 → evo_cli-0.28.0}/evo_cli/commands/harness/check.py +0 -0
  38. {evo_cli-0.26.2 → evo_cli-0.28.0}/evo_cli/commands/harness/clone.py +0 -0
  39. {evo_cli-0.26.2 → evo_cli-0.28.0}/evo_cli/commands/harness/edit.py +0 -0
  40. {evo_cli-0.26.2 → evo_cli-0.28.0}/evo_cli/commands/harness/pull.py +0 -0
  41. {evo_cli-0.26.2 → evo_cli-0.28.0}/evo_cli/commands/harness/serve.py +0 -0
  42. {evo_cli-0.26.2 → evo_cli-0.28.0}/evo_cli/commands/harness/show.py +0 -0
  43. {evo_cli-0.26.2 → evo_cli-0.28.0}/evo_cli/commands/harness/views.py +0 -0
  44. {evo_cli-0.26.2 → evo_cli-0.28.0}/evo_cli/commands/harness/web/assets/index-Cs7hXRPJ.css +0 -0
  45. {evo_cli-0.26.2 → evo_cli-0.28.0}/evo_cli/commands/harness/web/assets/index-CuNOlaH7.js +0 -0
  46. {evo_cli-0.26.2 → evo_cli-0.28.0}/evo_cli/commands/harness/web/assets/inter-cyrillic-ext-wght-normal-BOeWTOD4.woff2 +0 -0
  47. {evo_cli-0.26.2 → evo_cli-0.28.0}/evo_cli/commands/harness/web/assets/inter-cyrillic-wght-normal-DqGufNeO.woff2 +0 -0
  48. {evo_cli-0.26.2 → evo_cli-0.28.0}/evo_cli/commands/harness/web/assets/inter-greek-ext-wght-normal-DlzME5K_.woff2 +0 -0
  49. {evo_cli-0.26.2 → evo_cli-0.28.0}/evo_cli/commands/harness/web/assets/inter-greek-wght-normal-CkhJZR-_.woff2 +0 -0
  50. {evo_cli-0.26.2 → evo_cli-0.28.0}/evo_cli/commands/harness/web/assets/inter-latin-ext-wght-normal-DO1Apj_S.woff2 +0 -0
  51. {evo_cli-0.26.2 → evo_cli-0.28.0}/evo_cli/commands/harness/web/assets/inter-latin-wght-normal-Dx4kXJAl.woff2 +0 -0
  52. {evo_cli-0.26.2 → evo_cli-0.28.0}/evo_cli/commands/harness/web/assets/inter-vietnamese-wght-normal-CBcvBZtf.woff2 +0 -0
  53. {evo_cli-0.26.2 → evo_cli-0.28.0}/evo_cli/commands/harness/web/assets/jetbrains-mono-cyrillic-wght-normal-D73BlboJ.woff2 +0 -0
  54. {evo_cli-0.26.2 → evo_cli-0.28.0}/evo_cli/commands/harness/web/assets/jetbrains-mono-greek-wght-normal-Bw9x6K1M.woff2 +0 -0
  55. {evo_cli-0.26.2 → evo_cli-0.28.0}/evo_cli/commands/harness/web/assets/jetbrains-mono-latin-ext-wght-normal-DBQx-q_a.woff2 +0 -0
  56. {evo_cli-0.26.2 → evo_cli-0.28.0}/evo_cli/commands/harness/web/assets/jetbrains-mono-latin-wght-normal-B9CIFXIH.woff2 +0 -0
  57. {evo_cli-0.26.2 → evo_cli-0.28.0}/evo_cli/commands/harness/web/assets/jetbrains-mono-vietnamese-wght-normal-Bt-aOZkq.woff2 +0 -0
  58. {evo_cli-0.26.2 → evo_cli-0.28.0}/evo_cli/commands/harness/web/index.html +0 -0
  59. {evo_cli-0.26.2 → evo_cli-0.28.0}/evo_cli/commands/hwid.py +0 -0
  60. {evo_cli-0.26.2 → evo_cli-0.28.0}/evo_cli/commands/hwid_reset.py +0 -0
  61. {evo_cli-0.26.2 → evo_cli-0.28.0}/evo_cli/commands/image.py +0 -0
  62. {evo_cli-0.26.2 → evo_cli-0.28.0}/evo_cli/commands/localproxy.py +0 -0
  63. {evo_cli-0.26.2 → evo_cli-0.28.0}/evo_cli/commands/mcp.py +0 -0
  64. {evo_cli-0.26.2 → evo_cli-0.28.0}/evo_cli/commands/miniconda.py +0 -0
  65. {evo_cli-0.26.2 → evo_cli-0.28.0}/evo_cli/commands/netcheck.py +0 -0
  66. {evo_cli-0.26.2 → evo_cli-0.28.0}/evo_cli/commands/ngrok.py +0 -0
  67. {evo_cli-0.26.2 → evo_cli-0.28.0}/evo_cli/commands/opencode.py +0 -0
  68. {evo_cli-0.26.2 → evo_cli-0.28.0}/evo_cli/commands/plantuml.py +0 -0
  69. {evo_cli-0.26.2 → evo_cli-0.28.0}/evo_cli/commands/route/__init__.py +0 -0
  70. {evo_cli-0.26.2 → evo_cli-0.28.0}/evo_cli/commands/route/_dns.py +0 -0
  71. {evo_cli-0.26.2 → evo_cli-0.28.0}/evo_cli/commands/route/_dpi.py +0 -0
  72. {evo_cli-0.26.2 → evo_cli-0.28.0}/evo_cli/commands/route/_hosts.py +0 -0
  73. {evo_cli-0.26.2 → evo_cli-0.28.0}/evo_cli/commands/route/_probe.py +0 -0
  74. {evo_cli-0.26.2 → evo_cli-0.28.0}/evo_cli/commands/route/check.py +0 -0
  75. {evo_cli-0.26.2 → evo_cli-0.28.0}/evo_cli/commands/route/fix.py +0 -0
  76. {evo_cli-0.26.2 → evo_cli-0.28.0}/evo_cli/commands/route/probe.py +0 -0
  77. {evo_cli-0.26.2 → evo_cli-0.28.0}/evo_cli/commands/route/status.py +0 -0
  78. {evo_cli-0.26.2 → evo_cli-0.28.0}/evo_cli/commands/serp.py +0 -0
  79. {evo_cli-0.26.2 → evo_cli-0.28.0}/evo_cli/commands/site2s.py +0 -0
  80. {evo_cli-0.26.2 → evo_cli-0.28.0}/evo_cli/commands/ssh.py +0 -0
  81. {evo_cli-0.26.2 → evo_cli-0.28.0}/evo_cli/commands/sysmon.py +0 -0
  82. {evo_cli-0.26.2 → evo_cli-0.28.0}/evo_cli/commands/tts.py +0 -0
  83. {evo_cli-0.26.2 → evo_cli-0.28.0}/evo_cli/commands/update.py +0 -0
  84. {evo_cli-0.26.2 → evo_cli-0.28.0}/evo_cli/commands/wifi.py +0 -0
  85. {evo_cli-0.26.2 → evo_cli-0.28.0}/evo_cli/console.py +0 -0
  86. {evo_cli-0.26.2 → evo_cli-0.28.0}/evo_cli/credentials/__init__.py +0 -0
  87. {evo_cli-0.26.2 → evo_cli-0.28.0}/evo_cli/credentials/doctor.py +0 -0
  88. {evo_cli-0.26.2 → evo_cli-0.28.0}/evo_cli/credentials/google_oauth.py +0 -0
  89. {evo_cli-0.26.2 → evo_cli-0.28.0}/evo_cli/credentials/migrate.py +0 -0
  90. {evo_cli-0.26.2 → evo_cli-0.28.0}/evo_cli/credentials/oauth_flow.py +0 -0
  91. {evo_cli-0.26.2 → evo_cli-0.28.0}/evo_cli/credentials/registry.py +0 -0
  92. {evo_cli-0.26.2 → evo_cli-0.28.0}/evo_cli/credentials/store.py +0 -0
  93. {evo_cli-0.26.2 → evo_cli-0.28.0}/evo_cli/credentials/sync.py +0 -0
  94. {evo_cli-0.26.2 → evo_cli-0.28.0}/evo_cli/imaging/__init__.py +0 -0
  95. {evo_cli-0.26.2 → evo_cli-0.28.0}/evo_cli/imaging/align.py +0 -0
  96. {evo_cli-0.26.2 → evo_cli-0.28.0}/evo_cli/imaging/core.py +0 -0
  97. {evo_cli-0.26.2 → evo_cli-0.28.0}/evo_cli/imaging/creds.py +0 -0
  98. {evo_cli-0.26.2 → evo_cli-0.28.0}/evo_cli/imaging/errors.py +0 -0
  99. {evo_cli-0.26.2 → evo_cli-0.28.0}/evo_cli/imaging/gemini.py +0 -0
  100. {evo_cli-0.26.2 → evo_cli-0.28.0}/evo_cli/imaging/ncnn.py +0 -0
  101. {evo_cli-0.26.2 → evo_cli-0.28.0}/evo_cli/mcp_registry.py +0 -0
  102. {evo_cli-0.26.2 → evo_cli-0.28.0}/evo_cli/serp/__init__.py +0 -0
  103. {evo_cli-0.26.2 → evo_cli-0.28.0}/evo_cli/serp/client.py +0 -0
  104. {evo_cli-0.26.2 → evo_cli-0.28.0}/evo_cli/serp/creds.py +0 -0
  105. {evo_cli-0.26.2 → evo_cli-0.28.0}/evo_cli/serp/errors.py +0 -0
  106. {evo_cli-0.26.2 → evo_cli-0.28.0}/evo_cli/serp/install.py +0 -0
  107. {evo_cli-0.26.2 → evo_cli-0.28.0}/evo_cli/serp/render.py +0 -0
  108. {evo_cli-0.26.2 → evo_cli-0.28.0}/evo_cli/tts/__init__.py +0 -0
  109. {evo_cli-0.26.2 → evo_cli-0.28.0}/evo_cli/tts/chunking.py +0 -0
  110. {evo_cli-0.26.2 → evo_cli-0.28.0}/evo_cli/tts/core.py +0 -0
  111. {evo_cli-0.26.2 → evo_cli-0.28.0}/evo_cli/tts/creds.py +0 -0
  112. {evo_cli-0.26.2 → evo_cli-0.28.0}/evo_cli/tts/errors.py +0 -0
  113. {evo_cli-0.26.2 → evo_cli-0.28.0}/evo_cli/tts/gemini.py +0 -0
  114. {evo_cli-0.26.2 → evo_cli-0.28.0}/evo_cli/tts/http.py +0 -0
  115. {evo_cli-0.26.2 → evo_cli-0.28.0}/evo_cli/tts/openai.py +0 -0
  116. {evo_cli-0.26.2 → evo_cli-0.28.0}/evo_cli/tts/player.py +0 -0
  117. {evo_cli-0.26.2 → evo_cli-0.28.0}/evo_cli/tts/vbee.py +0 -0
  118. {evo_cli-0.26.2 → evo_cli-0.28.0}/evo_cli.egg-info/dependency_links.txt +0 -0
  119. {evo_cli-0.26.2 → evo_cli-0.28.0}/evo_cli.egg-info/entry_points.txt +0 -0
  120. {evo_cli-0.26.2 → evo_cli-0.28.0}/evo_cli.egg-info/requires.txt +0 -0
  121. {evo_cli-0.26.2 → evo_cli-0.28.0}/evo_cli.egg-info/top_level.txt +0 -0
  122. {evo_cli-0.26.2 → evo_cli-0.28.0}/pyproject.toml +0 -0
  123. {evo_cli-0.26.2 → evo_cli-0.28.0}/setup.cfg +0 -0
  124. {evo_cli-0.26.2 → evo_cli-0.28.0}/tests/__init__.py +0 -0
  125. {evo_cli-0.26.2 → evo_cli-0.28.0}/tests/test_agent_toy.py +0 -0
  126. {evo_cli-0.26.2 → evo_cli-0.28.0}/tests/test_claude_code.py +0 -0
  127. {evo_cli-0.26.2 → evo_cli-0.28.0}/tests/test_cli.py +0 -0
  128. {evo_cli-0.26.2 → evo_cli-0.28.0}/tests/test_console.py +0 -0
  129. {evo_cli-0.26.2 → evo_cli-0.28.0}/tests/test_cred.py +0 -0
  130. {evo_cli-0.26.2 → evo_cli-0.28.0}/tests/test_download.py +0 -0
  131. {evo_cli-0.26.2 → evo_cli-0.28.0}/tests/test_fix_claude.py +0 -0
  132. {evo_cli-0.26.2 → evo_cli-0.28.0}/tests/test_gdrive.py +0 -0
  133. {evo_cli-0.26.2 → evo_cli-0.28.0}/tests/test_gh.py +0 -0
  134. {evo_cli-0.26.2 → evo_cli-0.28.0}/tests/test_harness.py +0 -0
  135. {evo_cli-0.26.2 → evo_cli-0.28.0}/tests/test_harness_clone.py +0 -0
  136. {evo_cli-0.26.2 → evo_cli-0.28.0}/tests/test_harness_dag.py +0 -0
  137. {evo_cli-0.26.2 → evo_cli-0.28.0}/tests/test_image.py +0 -0
  138. {evo_cli-0.26.2 → evo_cli-0.28.0}/tests/test_mcp.py +0 -0
  139. {evo_cli-0.26.2 → evo_cli-0.28.0}/tests/test_ngrok.py +0 -0
  140. {evo_cli-0.26.2 → evo_cli-0.28.0}/tests/test_opencode.py +0 -0
  141. {evo_cli-0.26.2 → evo_cli-0.28.0}/tests/test_plantuml.py +0 -0
  142. {evo_cli-0.26.2 → evo_cli-0.28.0}/tests/test_route.py +0 -0
  143. {evo_cli-0.26.2 → evo_cli-0.28.0}/tests/test_serp.py +0 -0
  144. {evo_cli-0.26.2 → evo_cli-0.28.0}/tests/test_sysmon.py +0 -0
  145. {evo_cli-0.26.2 → evo_cli-0.28.0}/tests/test_tts.py +0 -0
  146. {evo_cli-0.26.2 → evo_cli-0.28.0}/tests/test_update.py +0 -0
  147. {evo_cli-0.26.2 → evo_cli-0.28.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.26.2
3
+ Version: 0.28.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
@@ -170,8 +170,14 @@ supported. Only trusted, self-contained TUN client profiles are accepted; script
170
170
  plugins and external certificate/key files are refused. Runtime data uses
171
171
  `~/.evo/openvpn` (override with `EVO_OPENVPN_HOME`).
172
172
 
173
- Only one evo-managed tunnel runs at a time. Disconnect any GUI VPN before connecting
174
- through evo; GUI profiles are never removed or modified. Server routes are applied,
173
+ Several evo-managed tunnels can run at once, as long as at most one of them may take the
174
+ default route. A profile is split-tunnel when it has `route-nopull` or
175
+ `pull-filter ignore "redirect-gateway"` (add `route` lines for the networks it should
176
+ carry); any other profile might receive a pushed `redirect-gateway`, so `connect` refuses
177
+ to stack two of those. While a split-tunnel profile runs, its server is pinned to the
178
+ physical gateway with a host route, so a full-tunnel VPN connected before or after it
179
+ never carries its handshake. Disconnect any GUI VPN before connecting through evo; GUI
180
+ profiles are never removed or modified. Server routes are applied,
175
181
  but this backend does not change system DNS. Internal IP access works through the
176
182
  tunnel; private DNS names may need your organization's DNS configuration. `CONNECTED`
177
183
  confirms the tunnel, not the availability of a particular internal application.
@@ -371,6 +377,29 @@ the command line, where any process listing would show it. `evo setup serp --wri
371
377
  `~/.config/serpapi/config.toml` (mode 600) if you also want the bare `serpapi` binary to
372
378
  authenticate on its own.
373
379
 
380
+ #### Storage
381
+
382
+ See what fills the disk and clear developer caches (macOS and Linux):
383
+
384
+ ```bash
385
+ evo storage audit # free space, swap, size of every known cache
386
+ evo storage audit --deep # also rank the biggest folders in ~ (and ~/Library)
387
+ evo storage clean --all -n # dry run: what a full clean would remove
388
+ evo storage clean uv npm gradle # clear only these caches (asks first)
389
+ evo storage clean --all -y # clear everything without asking
390
+ ```
391
+
392
+ Targets: `uv`, `npm` (+ npx), `pip`, `conda`, `pnpm`, `gradle` (caches + wrapper dists), `go`,
393
+ `docker` (build cache + unused images), `brew`, `xcode` (DerivedData + unavailable simulators) and
394
+ `updaters` (`*.ShipIt`, `*-updater`, editor VSIX caches). Only data the tools rebuild on demand is
395
+ touched - never projects, VMs or app data.
396
+
397
+ `clean` reports the space it actually freed, measured on the filesystem: on APFS uv clones cache
398
+ files into venvs, so a 32 GB uv cache can give back only a few GB. Caches in use are left alone -
399
+ uv/npx envs that a running process was started from are kept, and Gradle is skipped while a Gradle
400
+ daemon runs. Files owned by another user (for example from a past `sudo uvx`) are reported, not
401
+ removed with sudo.
402
+
374
403
  ## `evo harness` - read a repo cluster
375
404
 
376
405
  A harness is a repo that describes a *cluster* of repos: `harness.yaml` lists them, `contracts.yaml`
@@ -137,8 +137,14 @@ supported. Only trusted, self-contained TUN client profiles are accepted; script
137
137
  plugins and external certificate/key files are refused. Runtime data uses
138
138
  `~/.evo/openvpn` (override with `EVO_OPENVPN_HOME`).
139
139
 
140
- Only one evo-managed tunnel runs at a time. Disconnect any GUI VPN before connecting
141
- through evo; GUI profiles are never removed or modified. Server routes are applied,
140
+ Several evo-managed tunnels can run at once, as long as at most one of them may take the
141
+ default route. A profile is split-tunnel when it has `route-nopull` or
142
+ `pull-filter ignore "redirect-gateway"` (add `route` lines for the networks it should
143
+ carry); any other profile might receive a pushed `redirect-gateway`, so `connect` refuses
144
+ to stack two of those. While a split-tunnel profile runs, its server is pinned to the
145
+ physical gateway with a host route, so a full-tunnel VPN connected before or after it
146
+ never carries its handshake. Disconnect any GUI VPN before connecting through evo; GUI
147
+ profiles are never removed or modified. Server routes are applied,
142
148
  but this backend does not change system DNS. Internal IP access works through the
143
149
  tunnel; private DNS names may need your organization's DNS configuration. `CONNECTED`
144
150
  confirms the tunnel, not the availability of a particular internal application.
@@ -338,6 +344,29 @@ the command line, where any process listing would show it. `evo setup serp --wri
338
344
  `~/.config/serpapi/config.toml` (mode 600) if you also want the bare `serpapi` binary to
339
345
  authenticate on its own.
340
346
 
347
+ #### Storage
348
+
349
+ See what fills the disk and clear developer caches (macOS and Linux):
350
+
351
+ ```bash
352
+ evo storage audit # free space, swap, size of every known cache
353
+ evo storage audit --deep # also rank the biggest folders in ~ (and ~/Library)
354
+ evo storage clean --all -n # dry run: what a full clean would remove
355
+ evo storage clean uv npm gradle # clear only these caches (asks first)
356
+ evo storage clean --all -y # clear everything without asking
357
+ ```
358
+
359
+ Targets: `uv`, `npm` (+ npx), `pip`, `conda`, `pnpm`, `gradle` (caches + wrapper dists), `go`,
360
+ `docker` (build cache + unused images), `brew`, `xcode` (DerivedData + unavailable simulators) and
361
+ `updaters` (`*.ShipIt`, `*-updater`, editor VSIX caches). Only data the tools rebuild on demand is
362
+ touched - never projects, VMs or app data.
363
+
364
+ `clean` reports the space it actually freed, measured on the filesystem: on APFS uv clones cache
365
+ files into venvs, so a 32 GB uv cache can give back only a few GB. Caches in use are left alone -
366
+ uv/npx envs that a running process was started from are kept, and Gradle is skipped while a Gradle
367
+ daemon runs. Files owned by another user (for example from a past `sudo uvx`) are reported, not
368
+ removed with sudo.
369
+
341
370
  ## `evo harness` - read a repo cluster
342
371
 
343
372
  A harness is a repo that describes a *cluster* of repos: `harness.yaml` lists them, `contracts.yaml`
@@ -0,0 +1 @@
1
+ 0.28.0
@@ -25,6 +25,7 @@ from evo_cli.commands.route import route_group
25
25
  from evo_cli.commands.serp import serp_group, setup_serp
26
26
  from evo_cli.commands.site2s import site2s
27
27
  from evo_cli.commands.ssh import setupssh
28
+ from evo_cli.commands.storage import storage
28
29
  from evo_cli.commands.sysmon import sysmon
29
30
  from evo_cli.commands.tts import tts_group
30
31
  from evo_cli.commands.update import update
@@ -84,6 +85,7 @@ cli.add_command(openvpn_group)
84
85
  cli.add_command(plantuml)
85
86
  cli.add_command(route_group)
86
87
  cli.add_command(serp_group)
88
+ cli.add_command(storage)
87
89
  cli.add_command(sysmon)
88
90
  cli.add_command(tts_group)
89
91
  cli.add_command(update)
@@ -324,15 +324,22 @@ def connect(name, timeout, sudo_password_stdin, watch):
324
324
  raise click.ClickException("Missing credentials; run evo openvpn credentials PROFILE.")
325
325
  if _guard(vpn.request, name)["state"] != "STOPPED":
326
326
  raise click.ClickException("This profile already has a worker; use status or disconnect first.")
327
- # Avoid unintentionally stacking two route sets. The external GUI is never controlled here.
328
- for other in _guard(vpn.profiles):
329
- if other != name and _guard(vpn.request, other)["state"] != "STOPPED":
330
- raise click.ClickException(f"Disconnect '{other}' first to avoid conflicting VPN routes.")
327
+ # Two tunnels that may both take the default route would fight over it; a split-tunnel profile stacks
328
+ # beside any other. The external GUI is never controlled here.
329
+ full = _guard(vpn.takes_default_route, profile["config"])
330
+ for other, data in _guard(vpn.profiles).items():
331
+ if other == name or _guard(vpn.request, other)["state"] == "STOPPED":
332
+ continue
333
+ if full and _guard(vpn.takes_default_route, data.get("config", "")):
334
+ raise click.ClickException(
335
+ f"Disconnect '{other}' first: both may take the default route. To run them together, add "
336
+ 'pull-filter ignore "redirect-gateway" (plus the routes you need) to one of them.'
337
+ )
331
338
  password = ""
332
339
  if os.geteuid() != 0:
333
340
  if sudo_password_stdin:
334
341
  password = sys.stdin.readline().rstrip("\n")
335
- elif subprocess.run(["sudo", "-n", "true"], capture_output=True).returncode:
342
+ elif subprocess.run(["sudo", "-n", "true"], capture_output=True, check=False).returncode:
336
343
  password = getpass.getpass("sudo password (not stored): ")
337
344
  state_path = Path(str(_guard(vpn.session_path, name)) + ".json")
338
345
  state_path.unlink(missing_ok=True)