Skip to main content

Evolution CLI (Develop by Dev And for Dev)

codecov CI

Awesome evo_cli created by maycuatroi

Install it from PyPI

pip install evo-cli

Available Commands

evo is built with click and rich. Run evo --help or evo <command> -h for colorized help, option tables, and examples.

SSH Setup

Set up SSH with key-based authentication:

evo setupssh

Options:

  • -H, --host - SSH server hostname or IP address
  • -u, --user - SSH username
  • -p, --password - SSH password (prefer the interactive prompt)
  • -P, --port - SSH port (default: 22)
  • -i, --identity - Existing private key to install instead of generating one

Miniconda Installation

Install Miniconda with cross-platform support:

evo miniconda

Options:

  • -p, --prefix - Installation directory (default: ~/miniconda3 or %USERPROFILE%\miniconda3)
  • -f, --force - Force reinstallation even if Miniconda is already installed

Cloudflare SSH Tunnel

Expose this Ubuntu machine's SSH server through a Cloudflare named tunnel, so you can reach it from anywhere without opening a public inbound port:

evo cfssh -H dev.example.com

It installs cloudflared, logs in to Cloudflare, creates a named tunnel, writes /etc/cloudflared/config.yml with an ssh:// ingress rule, routes a proxied DNS record, and installs the cloudflared systemd service. Requires a Cloudflare account with a domain managed in Cloudflare.

Options:

  • -H, --hostname - Public hostname for SSH, e.g. dev.example.com
  • -n, --name - Tunnel name (default: first label of the hostname)
  • -P, --ssh-port - Local SSH port to forward (default: 22)
  • --no-service - Configure only, do not install the systemd service

To connect from a client machine, install cloudflared and add to ~/.ssh/config:

Host dev.example.com
  User <your-user>
  ProxyCommand cloudflared access ssh --hostname %h

Fix Claude Code

Detect and fix the Claude Code 2.1.154-2.1.158 tool-result delivery bug (commands run but their output is returned to the model empty, duplicated, or out of order):

evo f-claude

It checks the installed version against the affected range, disables the auto-updater in ~/.claude/settings.json (backing it up first), downgrades to a known-good build, respawns background sessions, and verifies the result.

Options:

  • -c, --check - Diagnose only; make no changes
  • --pin-version - Known-good version to install when downgrading (default: 2.1.153)
  • --no-downgrade - Only disable the auto-updater; skip the reinstall
  • -y, --yes - Skip the confirmation prompt
  • -f, --force - Apply the fix even if the version is not in the affected range
  • --unpin - Undo the fix: re-enable the auto-updater and install the latest build

OpenVPN

Manage separate VPN profiles on macOS or Linux. Install the OpenVPN binary first (brew install openvpn on macOS); QR enrollment also needs brew install zbar.

evo openvpn list --app                         # inventory OpenVPN Connect, without changing it
evo openvpn import work ~/Downloads/work.ovpn  # also accepts .ovpn.txt
evo openvpn credentials work --username alice --qr ~/Downloads/otp.png
evo openvpn list                               # numbered table: state, tunnel IP, remote
evo openvpn connect work                       # hidden sudo prompt, then a live status view
evo openvpn connect 2                          # by # from list; omit to pick (last one is default)
evo openvpn status -w                          # watch again later; alerts on drops
evo openvpn log -n 80                          # recent OpenVPN log lines: why it dropped
evo openvpn disconnect                         # picks the active tunnel
evo cred sync push

connect, status, disconnect, otp and credentials take a profile name, its # from list, or nothing: a single (or single active) profile is used directly, otherwise a numbered prompt appears. Without a terminal, pass the name so scripts never block on a prompt.

After connecting in a terminal, connect keeps a live view of state, uptime, traffic and reconnects (--no-watch returns immediately). Ctrl-C stops watching and offers to disconnect; the tunnel otherwise keeps running. Soft drops (ping-restart, connection-reset) reconnect automatically with a fresh OTP; the view logs each one and raises a desktop notification, and exits 1 with the reason if the tunnel goes down. evo openvpn log prints the worker's last 200 OpenVPN log lines, kept in memory only, to show why a drop happened. The client sends a keepalive ping every 10 seconds so idle TCP sessions survive NAT and firewalls; a keepalive pushed by the server takes precedence.

Import each profile under a different name. Imports refuse to overwrite an existing name unless --replace is supplied; replacement preserves that profile's credentials. Configurations (including embedded private keys), passwords and TOTP enrollments live under openvpn.profiles.NAME in evo cred. They sync through the existing private GitHub credential repository. That store is plaintext, protected by local file permissions and private-repository access, not an encrypted vault. Do not sync it to a public repository or treat storing the password and OTP together as independent factors.

Use credentials NAME --otp for hidden manual enrollment input instead of a QR image. evo openvpn otp NAME explicitly prints the current code; list and status never print secrets. Sudo passwords are not stored. Automation can pass the sudo password on stdin with connect NAME --sudo-password-stdin, not in an argument or environment variable.

Connections run in the background with a private Unix management socket and an OTP-aware worker that remains available for reauthentication. Static challenges use OpenVPN's SCRV1 protocol. Dynamic challenges and encrypted private-key prompts are not supported. Only trusted, self-contained TUN client profiles are accepted; scripts, plugins and external certificate/key files are refused. Runtime data uses ~/.evo/openvpn (override with EVO_OPENVPN_HOME).

Several evo-managed tunnels can run at once, as long as at most one of them may take the default route. A profile is split-tunnel when it has route-nopull or pull-filter ignore "redirect-gateway" (add route lines for the networks it should carry); any other profile might receive a pushed redirect-gateway, so connect refuses to stack two of those. While a split-tunnel profile runs, its server is pinned to the physical gateway with a host route, so a full-tunnel VPN connected before or after it never carries its handshake. Disconnect any GUI VPN before connecting through evo; GUI profiles are never removed or modified. Server routes are applied, but this backend does not change system DNS. Internal IP access works through the tunnel; private DNS names may need your organization's DNS configuration. CONNECTED confirms the tunnel, not the availability of a particular internal application.

Credentials

evo owns the omelet credential store. The source of truth is a folder of one JSON file per service (~/.omelet.d/credentials/), which compiles into a flat ~/.omelet.json that older consumers read directly.

evo cred doctor                       # health + expiry of every credential, exit 1 if any expired
evo cred get openai_api_key           # print one value by dotted path, nothing else on stdout
evo cred add openai_api_key           # prompt with no echo, write the folder file, recompile
evo cred auth --service google-drive  # first-time OAuth consent, stores the refresh token
evo cred refresh --all                # refresh Google OAuth access tokens
evo cred compile                      # rebuild ~/.omelet.json from the folder
evo cred sync push                    # push the folder to a private GitHub repo via gh

Read a value into the environment without it reaching your shell history:

eval "$(evo cred get --export OPENAI_API_KEY openai_api_key)"

Configuration is env-driven, with no personal defaults baked in:

  • OMELET_DIR - store root (default ~/.omelet.d; the folder is $OMELET_DIR/credentials)
  • OMELET_CONFIG - compiled flat file (default ~/.omelet.json)
  • OMELET_SYNC_REPO - required for sync; a private GitHub repo as owner/repo
  • OMELET_SYNC_DIR - folder name inside the sync repo (default credentials)
  • RCLONE_DRIVE_CLIENT_ID / RCLONE_DRIVE_CLIENT_SECRET - OAuth client for the rclone refresh

evo cred sync push refuses to push to a repo whose visibility is not PRIVATE. Never echo a value into a shared terminal or a log; doctor and list only ever print a masked preview.

evo cred auth performs the initial OAuth consent that refresh cannot: it starts a loopback server, opens the consent screen, and writes the resulting refresh token into the store. Create the OAuth client in the Cloud Console first (APIs & Services -> Credentials -> Create OAuth client ID -> Desktop app) and pass the downloaded JSON with --client-secrets; there is no gcloud equivalent for creating an OAuth client.

evo gdrive reads its token from the google_drive entry through this store rather than parsing ~/.omelet.json itself, so a refresh writes to the folder and recompiles instead of racing whatever else has the flat file open.

Harness Repositories

Clone every repository a harness manifest declares but this machine does not have yet:

evo harness clone
evo harness clone --dry-run                     # show what lands where, change nothing
evo harness clone --repo backend --depth 1
evo harness clone --all                         # include repos marked present: false

Each repo lands on its declared path, falling back to <workspace>/<name>, so a fresh machine ends up with the layout the manifest, the contract seams and the exec-plans already assume. A repo already on disk is left alone; if its origin is not the remote the manifest names, the summary says so instead of touching it. When the manifest names a branch, the clone moves onto it, and a branch nobody has pushed yet is reported rather than treated as a failure.

Fast-forward every available repository declared in a harness manifest:

evo harness pull

Run the command from a harness directory or any registered member repository. Use --harness PATH when a repository belongs to multiple harnesses or the harness is not registered.

evo harness pull --harness ~/github/my-project-harness
evo harness pull --repo backend --repo frontend
evo harness pull --dry-run

The command reads harness.yaml and its optional harness.local.yaml overlay. Repositories marked present: false are skipped. Repositories with uncommitted changes are not modified, and every pull uses git pull --ff-only so the command never creates merge commits.

Text to Speech

Synthesise speech through Gemini gemini-3.1-flash-tts-preview, Vbee (Vietnamese), or OpenAI gpt-4o-mini-tts, and play it right away:

evo tts speak "Xin chào, bản build đã xong"
evo tts speak -f notes.md -o notes.wav
evo tts speak "hôm nay trời đẹp" -V Sulafat --instructions "kể chuyện, ấm áp"
evo tts speak "[whispers] đừng nói với ai nhé"
evo tts speak "hello there" -p openai -V nova --instructions "calm and encouraging"
git log -1 --format=%s | evo tts speak

speak is the realtime path. Text longer than the provider's per-request limit (Gemini 2000 characters, Vbee 300, OpenAI 4000) is split on sentence boundaries and the audio is joined back into one file, so the first words start playing while the rest is still being synthesised.

Gemini is the most expressive of the three: 30 prebuilt voices, automatic language detection across 70+ languages, free-form delivery notes through --instructions, and inline audio tags such as [whispers], [excited], [sighs], or [very slow] anywhere in the text. It answers with raw 24 kHz PCM, so --format wav is the default there; --format mp3 re-encodes through ffmpeg and needs it on PATH. The format is also inferred from the --output suffix, so -o notes.mp3 still produces an mp3.

For bulk work use the batch path, which goes through Vbee's async API and polls /v1/tts/requests/{id} until each audio link appears:

evo tts batch chapters/ -o audio/          # one file per .txt/.md input
evo tts batch a.txt b.txt -c 8             # 8 items in flight
evo tts batch --manifest jobs.jsonl        # {"id":.., "text":.., "voice":..} per line

Gemini and OpenAI have no batch speech endpoint, so there the items are parallelised locally instead.

Voice codes come from evo tts voices (-p gemini, -p openai, -l en-US, --gender male, --json).

Credentials live in the omelet store, never in flags or source:

evo cred add gemini_api_key --from-stdin   # key from https://aistudio.google.com/apikey
evo cred add vbee.app_id --from-stdin      # UUID from https://studio.vbee.vn/apps
evo cred add vbee.token --from-stdin       # JWT from the same app page
evo cred add openai_api_key --from-stdin

GEMINI_API_KEY (or GOOGLE_API_KEY), VBEE_APP_ID, VBEE_TOKEN, and OPENAI_API_KEY override the store when set.

--provider auto (the default) resolves to EVO_TTS_PROVIDER when that is set, and otherwise to the first of Gemini, Vbee, OpenAI that has credentials. Pick a machine default once:

export EVO_TTS_PROVIDER=gemini        # what `auto` means here
export EVO_TTS_VOICE_GEMINI=Sulafat   # default voice for that provider only

Prefer the provider-scoped EVO_TTS_VOICE_GEMINI / EVO_TTS_VOICE_OPENAI / EVO_TTS_VOICE_VBEE over a bare EVO_TTS_VOICE: a shared value breaks as soon as you pass --provider vbee, because a Gemini voice name is not a Vbee voice code.

Playback uses whichever of ffplay, mpv, cvlc, afplay, or paplay/aplay is on PATH, and falls back to PowerShell's MediaPlayer on Windows. Without any of them the audio is still written to disk and the command warns.

The same engine backs the evo-tts MCP server in agent-skills, which gives an agent a speak tool.

Google Search (SerpApi)

Search Google from the terminal through SerpApi. One-time setup installs the official serpapi CLI and stores the key in the omelet credential store:

evo setup serp                        # install the CLI + prompt for the key (no echo)
evo setup serp --from-stdin < key.txt # non-interactive
evo setup serp --method none          # key only; searches use the HTTPS API

--method auto (the default) tries Homebrew first, then falls back to the GitHub release binary dropped into ~/.evo/bin. Homebrew 5 refuses to build from an untrusted third-party tap, so the brew path needs evo setup serp --trust-tap (which runs brew trust --formula serpapi/tap/serpapi-cli); without it the fallback fetches the same official binary.

evo serp search "evo cli python"                     # top 10 Google results
evo serp search coffee -l "Austin, Texas" -n 5       # localized
evo serp search openai -t news --hl vi --gl vn       # news vertical, Vietnamese
evo serp search "site:github.com serpapi" --links    # only URLs, pipe-friendly
evo serp search "rust async" -p 3 --json > out.json  # 3 merged pages of raw JSON
evo serp search x -P tbs=qdr:d                       # any extra SerpApi parameter

-t picks the vertical (web, news, images, videos, shopping, scholar). Google treats num as a hint and often returns more, so the rendered output is capped at what -n asked for while --json / -o keep the full payload.

Supporting commands:

evo serp account            # plan, searches used and left this month
evo serp locations Hanoi    # canonical names to pass to --location
evo serp doctor             # binary, key source (masked), remaining quota
evo serp raw search --jq '.organic_results[0]' engine=google q=coffee

search runs through the serpapi binary when it is installed and falls back to the SerpApi HTTPS endpoint otherwise, so a machine that only has the key still works (--transport cli|http|auto forces the choice). The key is resolved from SERPAPI_KEY, then evo cred get serpapi_api_key, then ~/.config/serpapi/config.toml, and is passed to the binary through the environment - never on the command line, where any process listing would show it. evo setup serp --write-config writes ~/.config/serpapi/config.toml (mode 600) if you also want the bare serpapi binary to authenticate on its own.

Storage

See what fills the disk and clear developer caches (macOS and Linux):

evo storage audit                  # free space, swap, size of every known cache
evo storage audit --deep           # also rank the biggest folders in ~ (and ~/Library)
evo storage clean --all -n         # dry run: what a full clean would remove
evo storage clean uv npm gradle    # clear only these caches (asks first)
evo storage clean --all -y         # clear everything without asking

Targets: uv, npm (+ npx), pip, conda, pnpm, gradle (caches + wrapper dists), go, docker (build cache + unused images), brew, xcode (DerivedData + unavailable simulators) and updaters (*.ShipIt, *-updater, editor VSIX caches). Only data the tools rebuild on demand is touched - never projects, VMs or app data.

clean reports the space it actually freed, measured on the filesystem: on APFS uv clones cache files into venvs, so a 32 GB uv cache can give back only a few GB. Caches in use are left alone - uv/npx envs that a running process was started from are kept, and Gradle is skipped while a Gradle daemon runs. Files owned by another user (for example from a past sudo uvx) are reported, not removed with sudo.

evo harness - read a repo cluster

A harness is a repo that describes a cluster of repos: harness.yaml lists them, contracts.yaml declares the seams between them (who owns what, who consumes it, what verifies it), and plans/ holds exec-plans for changes that span several repos at once.

evo harness finds that repo by walking up from the current directory to a harness.yaml, falling back to ~/.claude/harness/registry.json, so every command works with no arguments from anywhere inside the cluster. Pass --harness PATH to override.

evo harness serve      # dashboard on http://127.0.0.1:8788
evo harness repos      # repos in the manifest, with branch and dirty state
evo harness seams      # contract seams; --graph for the owner -> consumer DAG
evo harness plans      # progress across every exec-plan
evo harness show <plan>                 # every section, open questions with status and blocking
evo harness show <plan> --step 3        # one step in full: what, verify, note, acceptance, evidence
evo harness show <plan> --section acceptance  # each criterion in full: id, criterion, verify, steps naming it
evo harness show <plan> --section non_goals   # each non-goal in full
evo harness step <plan> --no-input      # the step board: repo, status, depends_on, title
evo harness check <plan> [--fetch]      # what the plan claims vs what git says
evo harness graph <plan>:steps          # the same DAG as an adjacency list
evo harness clone      # clone every declared repo into its declared path
evo harness pull       # fast-forward every repo

The dashboard

serve draws every dependency in the cluster as a DAG: seam ownership between repos, repo merge order inside a plan, and step order built from depends_on / depends_on_step / blocked_by / blocks. It detects cycles (Tarjan) and reports them, because a cycle means no merge order satisfies every seam - which is exactly the thing a plan gets wrong silently.

Every graph has a table view beside it. A node-link diagram conveys nothing to a screen reader and cannot be pasted into a document, so the adjacency list is a first-class view rather than a fallback, and the terminal gets the same data from evo harness graph.

The dashboard is read-only. Plan YAML is folded block scalars and hand-written prose; re-emitting it with a YAML dumper would destroy the formatting. Writes go through the CLI, which splices the one line it needs and refuses to save if the reparsed file is not exactly the intended change:

evo harness step <plan> 3 done --evidence "repo@sha: tests pass" --note "..."   # keyed by the step's `id` (or `order`)
evo harness debt <plan> 0 fixed
evo harness question <plan> 1 answered
evo harness repo <plan> 2 merged

--note and --evidence append their text on a line of its own; an existing note or evidence is kept, and the same text is not added twice.

Plans on the evo-agents hub

When harness.yaml has hub: {project: P}, the plans live on the evo-agents hub and every plans/<area>/<plan>.yaml is a read-only copy the hub wrote. step, debt, question, repo and the dashboard's complete button then never edit those files: they run evo-agents hub plan patch (or complete) with --if-revision, and evo-agents hub plan export writes the copies back. When someone else changed the plan in between, the write is retried on the new revision: appended text lands after theirs, and a status they changed to something else stops the command instead of being overwritten. A copy edited by hand stops every write before the hub hears of it, and evo harness check reports it (digest mismatch), with the same fix evo-agents harness validate prints. These commands need evo-agents 0.2.0 or later, signed in:

uv tool install 'evo-ak[graphify]>=0.2.0'
evo-agents hub login --url https://agents.omelet.tech

Without it, or when the hub cannot be reached, the command fails with that advice and no file changes.

The server is stdlib http.server, so serve needs no dependency beyond what pip install evo_cli already brings.

Building the dashboard bundle

Release wheels ship the built bundle, so users never need Node. Only changing the UI does:

cd web && npm install && npm run build

That writes evo_cli/commands/harness/web/, which is committed. npm run dev serves the UI on :5178 and proxies /api to a running evo harness serve for a live-reload loop.

Metadata

Release files for evo-cli 0.31.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for evo-cli 0.31.0
File Size Uploaded Provenance
evo_cli-0.31.0.tar.gz 809.6 kB Without provenance Details

Built distribution (wheel)

Table of built distributions (wheels) for evo-cli 0.31.0
File Interpreter ABI Platform
evo_cli-0.31.0-py3-none-any.whl Python 3 none any Details

Total release size: 1.6 MB

Release files / evo_cli-0.31.0.tar.gz

Download URL evo_cli-0.31.0.tar.gz
Size 809.6 kB
Tags Source
SHA-256 checksum
How to use checksums
8139b490b0ae4ed0423a400b5a782e421368fa33fb46ec1a3e716ce1b8dc7120
BLAKE2b-256 checksum
How to use checksums
b2868192aa2a9bea2140f9dbbfd2f58a964b27ae482344546fd1b7050c46158a
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/7.0.0 CPython/3.12.15

Release files / evo_cli-0.31.0-py3-none-any.whl

Download URL evo_cli-0.31.0-py3-none-any.whl
Size 765.3 kB
Tags Python 3
SHA-256 checksum
How to use checksums
08574a5d725bb0e18916f84ff84cabdd2dc4ca93f7e8b2ef73e3f81f3f9b2c29
BLAKE2b-256 checksum
How to use checksums
c321fd158f3b4dece04a713eb8f183582d99ba590a425dab52fa801f5ac4d96e
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/7.0.0 CPython/3.12.15

Security for evo-cli 0.31.0

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

No provenance found

None of the files in this release were uploaded with provenance metadata.

A note on package security

PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.

Release history Release notifications | RSS feed

This release

0.31.0 This release

2 release files No provenance

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page