Promote stable workspace product for 3.0.0

Freeze the current workspace-first surface as the stable 3.0 contract and reposition the
landing docs, CLI help, and public contract around the stable workspace path after the
one-shot proof.

Bump the package and catalog compatibility to 3.0.0, add a dedicated workspace walkthrough
tape/GIF, and mark the 3.0.0 roadmap milestone done while keeping runtime capability
unchanged in this release.

Validation: uv lock; UV_CACHE_DIR=.uv-cache make check; UV_CACHE_DIR=.uv-cache make dist-check;
UV_CACHE_DIR=.uv-cache uv build; UV_CACHE_DIR=.uv-cache uvx --from twine twine check dist/*;
built-wheel CLI smoke for pyro --help and pyro workspace --help; vhs validate plus rendered
workspace-first-run.gif outside the sandbox because vhs crashes when sandboxed.
This commit is contained in:
Thales Maciel 2026-03-12 18:59:09 -03:00
parent c82f4629b2
commit f2d20ef30a
15 changed files with 255 additions and 42 deletions

View file

@ -2,6 +2,16 @@
All notable user-visible changes to `pyro-mcp` are documented here.
## 3.0.0
- Promoted the workspace-first product surface to stable across the CLI, Python SDK, and MCP
server, with `pyro run` retained as the stable one-shot entrypoint.
- Repositioned the main docs, help text, examples, and walkthrough assets around the stable
workspace path: create, sync, exec or shell, services, snapshots/reset, diff/export, and
delete.
- Froze the `3.x` public contract around the current workspace surface without introducing new
runtime capability in this release.
## 2.10.0
- Replaced the workspace-level boolean network toggle with explicit workspace network policies:

View file

@ -1,10 +1,10 @@
# pyro-mcp
`pyro-mcp` runs one-shot commands and repeated workspaces inside ephemeral Firecracker microVMs using curated Linux environments such as `debian:12`.
`pyro-mcp` is a stable agent workspace product for one-shot commands and persistent work inside ephemeral Firecracker microVMs using curated Linux environments such as `debian:12`.
[![PyPI version](https://img.shields.io/pypi/v/pyro-mcp.svg)](https://pypi.org/project/pyro-mcp/)
This is for coding agents, MCP clients, and developers who want isolated command execution in ephemeral microVMs.
This is for coding agents, MCP clients, and developers who want isolated command execution and stable disposable workspaces in ephemeral microVMs.
It exposes the same runtime in three public forms:
@ -18,9 +18,10 @@ It exposes the same runtime in three public forms:
- Vision: [docs/vision.md](docs/vision.md)
- Workspace roadmap: [docs/roadmap/task-workspace-ga.md](docs/roadmap/task-workspace-ga.md)
- First run transcript: [docs/first-run.md](docs/first-run.md)
- Stable workspace walkthrough GIF: [docs/assets/workspace-first-run.gif](docs/assets/workspace-first-run.gif)
- Terminal walkthrough GIF: [docs/assets/first-run.gif](docs/assets/first-run.gif)
- PyPI package: [pypi.org/project/pyro-mcp](https://pypi.org/project/pyro-mcp/)
- What's new in 2.10.0: [CHANGELOG.md#2100](CHANGELOG.md#2100)
- What's new in 3.0.0: [CHANGELOG.md#300](CHANGELOG.md#300)
- Host requirements: [docs/host-requirements.md](docs/host-requirements.md)
- Integration targets: [docs/integrations.md](docs/integrations.md)
- Public contract: [docs/public-contract.md](docs/public-contract.md)
@ -57,7 +58,7 @@ What success looks like:
```bash
Platform: linux-x86_64
Runtime: PASS
Catalog version: 2.10.0
Catalog version: 3.0.0
...
[pull] phase=install environment=debian:12
[pull] phase=ready environment=debian:12
@ -73,6 +74,40 @@ git version ...
The first pull downloads an OCI environment from public Docker Hub, requires outbound HTTPS
access to `registry-1.docker.io`, and needs local cache space for the guest image.
## Stable Workspace Path
`pyro run` is the stable one-shot entrypoint. `pyro workspace ...` is the stable path when an
agent needs one sandbox to stay alive across repeated commands, shells, services, checkpoints,
diffs, exports, and reset.
The commands below use plain `pyro ...`. Run the same flow with `uvx --from pyro-mcp pyro ...`
for the published package, or `uv run pyro ...` from a source checkout.
```bash
uv tool install pyro-mcp
WORKSPACE_ID="$(pyro workspace create debian:12 --seed-path ./repo --json | python -c 'import json,sys; print(json.load(sys.stdin)["workspace_id"])')"
pyro workspace sync push "$WORKSPACE_ID" ./changes
pyro workspace exec "$WORKSPACE_ID" -- cat note.txt
pyro workspace snapshot create "$WORKSPACE_ID" checkpoint
pyro workspace service start "$WORKSPACE_ID" web --ready-file .web-ready -- sh -lc 'touch .web-ready && while true; do sleep 60; done'
pyro workspace reset "$WORKSPACE_ID" --snapshot checkpoint
pyro workspace export "$WORKSPACE_ID" note.txt --output ./note.txt
pyro workspace delete "$WORKSPACE_ID"
```
![Stable workspace walkthrough](docs/assets/workspace-first-run.gif)
That stable workspace path gives you:
- initial host-in seeding with `--seed-path`
- later host-in updates with `workspace sync push`
- one-shot commands with `workspace exec` and persistent PTYs with `workspace shell *`
- long-running processes with `workspace service *`
- explicit checkpoints with `workspace snapshot *`
- full-sandbox recovery with `workspace reset`
- baseline comparison with `workspace diff`
- explicit host-out export with `workspace export`
After the quickstart works:
- prove the full one-shot lifecycle with `uvx --from pyro-mcp pyro demo`
@ -141,7 +176,7 @@ uvx --from pyro-mcp pyro env list
Expected output:
```bash
Catalog version: 2.10.0
Catalog version: 3.0.0
debian:12 [installed|not installed] Debian 12 environment with Git preinstalled for common agent workflows.
debian:12-base [installed|not installed] Minimal Debian 12 environment for shell and core Unix tooling.
debian:12-build [installed|not installed] Debian 12 environment with Git and common build tools preinstalled.
@ -205,7 +240,7 @@ When you are done evaluating and want to remove stale cached environments, run `
If you prefer a fuller copy-pasteable transcript, see [docs/first-run.md](docs/first-run.md).
The walkthrough GIF above was rendered from [docs/assets/first-run.tape](docs/assets/first-run.tape) using [scripts/render_tape.sh](scripts/render_tape.sh).
## Persistent Workspaces
## Stable Workspaces
Use `pyro run` for one-shot commands. Use `pyro workspace ...` when you need repeated commands in one
workspace without recreating the sandbox every time.
@ -248,7 +283,7 @@ Persistent workspaces start in `/workspace` and keep command history until you d
machine consumption, add `--json` and read the returned `workspace_id`. Use `--seed-path` when
you want the workspace to start from a host directory or a local `.tar` / `.tar.gz` / `.tgz`
archive instead of an empty workspace. Use `pyro workspace sync push` when you want to import
later host-side changes into a started workspace. Sync is non-atomic in `2.10.0`; if it fails
later host-side changes into a started workspace. Sync is non-atomic in `3.0.0`; if it fails
partway through, prefer `pyro workspace reset` to recover from `baseline` or one named snapshot.
Use `pyro workspace diff` to compare the live `/workspace` tree to its immutable create-time
baseline, and `pyro workspace export` to copy one changed file or directory back to the host. Use

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 MiB

View file

@ -0,0 +1,98 @@
Output docs/assets/workspace-first-run.gif
Require uv
Require python3
Set Shell "zsh"
Set FontSize 18
Set Width 1400
Set Height 860
Set Theme "Dracula"
Set TypingSpeed 35ms
Set Padding 24
Set WindowBar Colorful
Hide
Type "cd /home/thales/projects/personal/pyro"
Enter
Type "setopt interactivecomments"
Enter
Type "export UV_CACHE_DIR=.uv-cache"
Enter
Type "export PYRO_ENVIRONMENT_CACHE_DIR=$(mktemp -d)"
Enter
Type "alias pyro='uv run pyro'"
Enter
Type "SEED_DIR=$(mktemp -d)"
Enter
Type "SYNC_DIR=$(mktemp -d)"
Enter
Type "EXPORT_DIR=$(mktemp -d)"
Enter
Type 'printf "%s\n" "hello from seed" > "$SEED_DIR/note.txt"'
Enter
Type 'printf "%s\n" "hello from sync" > "$SYNC_DIR/note.txt"'
Enter
Type "pyro env pull debian:12 >/dev/null"
Enter
Show
Type "# Create a stable workspace from host content and capture its id"
Enter
Sleep 700ms
Type 'pyro workspace create debian:12 --seed-path "$SEED_DIR" --json | tee /tmp/pyro-workspace.json'
Enter
Sleep 2200ms
Hide
Type 'export WORKSPACE_ID=$(python3 -c "import json; print(json.load(open(\"/tmp/pyro-workspace.json\", encoding=\"utf-8\"))[\"workspace_id\"])")'
Enter
Show
Type "# Push a later host-side change into the same workspace"
Enter
Sleep 700ms
Type 'pyro workspace sync push "$WORKSPACE_ID" "$SYNC_DIR"'
Enter
Sleep 1800ms
Type "# Run inside the live workspace"
Enter
Sleep 700ms
Type 'pyro workspace exec "$WORKSPACE_ID" -- cat note.txt'
Enter
Sleep 2000ms
Type "# Capture a checkpoint, then start one long-running service"
Enter
Sleep 700ms
Type 'pyro workspace snapshot create "$WORKSPACE_ID" checkpoint'
Enter
Sleep 1800ms
Type 'pyro workspace service start "$WORKSPACE_ID" web --ready-file .web-ready -- sh -lc "touch .web-ready && while true; do sleep 60; done"'
Enter
Sleep 2200ms
Type "# Reset the full sandbox back to that checkpoint"
Enter
Sleep 700ms
Type 'pyro workspace reset "$WORKSPACE_ID" --snapshot checkpoint'
Enter
Sleep 2200ms
Type "# Export one file back to the host and inspect it locally"
Enter
Sleep 700ms
Type 'pyro workspace export "$WORKSPACE_ID" note.txt --output "$EXPORT_DIR/note.txt"'
Enter
Sleep 1800ms
Type 'cat "$EXPORT_DIR/note.txt"'
Enter
Sleep 1800ms
Type "# Remove the workspace when the loop is done"
Enter
Sleep 700ms
Type 'pyro workspace delete "$WORKSPACE_ID"'
Enter
Sleep 2000ms

View file

@ -22,7 +22,7 @@ Networking: tun=yes ip_forward=yes
```bash
$ uvx --from pyro-mcp pyro env list
Catalog version: 2.10.0
Catalog version: 3.0.0
debian:12 [installed|not installed] Debian 12 environment with Git preinstalled for common agent workflows.
debian:12-base [installed|not installed] Minimal Debian 12 environment for shell and core Unix tooling.
debian:12-build [installed|not installed] Debian 12 environment with Git and common build tools preinstalled.
@ -66,7 +66,25 @@ The guest command output and the `[run] ...` summary are written to different st
may appear in either order in terminals or capture tools. Use `--json` if you need a
deterministic structured result.
## 5. Optional next steps
## 5. Continue into the stable workspace path
The commands below use the published-package form. The same stable workspace path works with an
installed `pyro` binary by dropping the `uvx --from pyro-mcp` prefix, or with `uv run pyro` from
a source checkout.
```bash
$ uvx --from pyro-mcp pyro workspace create debian:12 --seed-path ./repo --json | tee /tmp/pyro-workspace.json
$ export WORKSPACE_ID="$(python -c 'import json,sys; print(json.load(sys.stdin)["workspace_id"])' < /tmp/pyro-workspace.json)"
$ uvx --from pyro-mcp pyro workspace sync push "$WORKSPACE_ID" ./changes
$ uvx --from pyro-mcp pyro workspace exec "$WORKSPACE_ID" -- cat note.txt
$ uvx --from pyro-mcp pyro workspace snapshot create "$WORKSPACE_ID" checkpoint
$ uvx --from pyro-mcp pyro workspace service start "$WORKSPACE_ID" web --ready-file .web-ready -- sh -lc 'touch .web-ready && while true; do sleep 60; done'
$ uvx --from pyro-mcp pyro workspace reset "$WORKSPACE_ID" --snapshot checkpoint
$ uvx --from pyro-mcp pyro workspace export "$WORKSPACE_ID" note.txt --output ./note.txt
$ uvx --from pyro-mcp pyro workspace delete "$WORKSPACE_ID"
```
## 6. Optional one-shot demo and expanded workspace flow
```bash
$ uvx --from pyro-mcp pyro demo
@ -187,7 +205,7 @@ $ uvx --from pyro-mcp pyro workspace service stop WORKSPACE_ID worker
Use `--seed-path` when the workspace should start from a host directory or a local
`.tar` / `.tar.gz` / `.tgz` archive instead of an empty `/workspace`. Use
`pyro workspace sync push` when you need to import later host-side changes into a started
workspace. Sync is non-atomic in `2.10.0`; if it fails partway through, prefer `pyro workspace reset`
workspace. Sync is non-atomic in `3.0.0`; if it fails partway through, prefer `pyro workspace reset`
to recover from `baseline` or one named snapshot. Use `pyro workspace diff` to compare the current
`/workspace` tree to its immutable create-time baseline, `pyro workspace snapshot *` to create
named checkpoints, and `pyro workspace export` to copy one changed file or directory back to the
@ -203,6 +221,10 @@ materialized at `/run/pyro-secrets/<name>`, and `--secret-env SECRET_NAME[=ENV_V
secret into one exec, shell, or service call without storing that environment mapping on the
workspace itself.
The stable workspace walkthrough GIF in the README is rendered from
[docs/assets/workspace-first-run.tape](assets/workspace-first-run.tape) with
[scripts/render_tape.sh](../scripts/render_tape.sh).
Example output:
```json

View file

@ -55,6 +55,8 @@ pyro run debian:12 -- git --version
If you are running from a repo checkout instead, replace `pyro` with `uv run pyro`.
After that one-shot proof works, continue into the stable workspace path with `pyro workspace ...`.
### 1. Check the host first
```bash
@ -83,7 +85,7 @@ uvx --from pyro-mcp pyro env list
Expected output:
```bash
Catalog version: 2.10.0
Catalog version: 3.0.0
debian:12 [installed|not installed] Debian 12 environment with Git preinstalled for common agent workflows.
debian:12-base [installed|not installed] Minimal Debian 12 environment for shell and core Unix tooling.
debian:12-build [installed|not installed] Debian 12 environment with Git and common build tools preinstalled.
@ -131,7 +133,34 @@ deterministic structured result.
If guest execution is unavailable, the command fails unless you explicitly pass
`--allow-host-compat`.
## 5. Optional demo proof point
## 5. Continue into the stable workspace path
The commands below use plain `pyro ...`. Run the same flow with `uvx --from pyro-mcp pyro ...`
for the published package, or `uv run pyro ...` from a source checkout.
```bash
uv tool install pyro-mcp
WORKSPACE_ID="$(pyro workspace create debian:12 --seed-path ./repo --json | python -c 'import json,sys; print(json.load(sys.stdin)["workspace_id"])')"
pyro workspace sync push "$WORKSPACE_ID" ./changes
pyro workspace exec "$WORKSPACE_ID" -- cat note.txt
pyro workspace snapshot create "$WORKSPACE_ID" checkpoint
pyro workspace service start "$WORKSPACE_ID" web --ready-file .web-ready -- sh -lc 'touch .web-ready && while true; do sleep 60; done'
pyro workspace reset "$WORKSPACE_ID" --snapshot checkpoint
pyro workspace export "$WORKSPACE_ID" note.txt --output ./note.txt
pyro workspace delete "$WORKSPACE_ID"
```
This is the stable persistent-workspace contract:
- `workspace create` seeds `/workspace`
- `workspace sync push` imports later host-side changes
- `workspace exec` and `workspace shell *` keep work inside one sandbox
- `workspace service *` manages long-running processes with typed readiness
- `workspace snapshot *` and `workspace reset` make reset-over-repair explicit
- `workspace diff` compares against the immutable create-time baseline
- `workspace export` copies results back to the host
## 6. Optional demo proof point
```bash
uvx --from pyro-mcp pyro demo
@ -188,7 +217,7 @@ After the CLI path works, you can move on to:
- Python SDK: `from pyro_mcp import Pyro`
- Demos: `pyro demo` or `pyro demo --network`
## Persistent Workspace
## Stable Workspace
Use `pyro workspace ...` when you need repeated commands in one sandbox instead of one-shot `pyro run`.
@ -225,7 +254,7 @@ Workspace commands default to the persistent `/workspace` directory inside the g
the identifier programmatically, use `--json` and read the `workspace_id` field. Use `--seed-path`
when the workspace should start from a host directory or a local `.tar` / `.tar.gz` / `.tgz`
archive. Use `pyro workspace sync push` for later host-side changes to a started workspace. Sync
is non-atomic in `2.10.0`; if it fails partway through, prefer `pyro workspace reset` to recover
is non-atomic in `3.0.0`; if it fails partway through, prefer `pyro workspace reset` to recover
from `baseline` or one named snapshot. Use `pyro workspace diff` to compare the current workspace
tree to its immutable create-time baseline, `pyro workspace snapshot *` to capture named
checkpoints, and `pyro workspace export` to copy one changed file or directory back to the host. Use

View file

@ -7,7 +7,8 @@ CLI path in [install.md](install.md) or [first-run.md](first-run.md).
## Recommended Default
Use `vm_run` first for one-shot commands.
Use `vm_run` first for one-shot commands, then move to the stable workspace surface when the
agent needs to inhabit one sandbox across multiple calls.
That keeps the model-facing contract small:
@ -16,8 +17,8 @@ That keeps the model-facing contract small:
- one ephemeral VM
- automatic cleanup
Move to `workspace_*` only when the agent truly needs repeated commands in one workspace across
multiple calls.
Move to `workspace_*` when the agent needs repeated commands, shells, services, snapshots, reset,
diff, or export in one stable workspace across multiple calls.
## OpenAI Responses API

View file

@ -1,6 +1,6 @@
# Public Contract
This document defines the supported public interface for `pyro-mcp` `2.x`.
This document defines the stable public interface for `pyro-mcp` `3.x`.
## Package Identity
@ -9,6 +9,11 @@ This document defines the supported public interface for `pyro-mcp` `2.x`.
- Public Python import: `from pyro_mcp import Pyro`
- Public package-level factory: `from pyro_mcp import create_server`
Stable product framing:
- `pyro run` is the stable one-shot entrypoint.
- `pyro workspace ...` is the stable persistent workspace contract.
## CLI Contract
Top-level commands:

View file

@ -2,7 +2,7 @@
This roadmap turns the agent-workspace vision into release-sized milestones.
Current baseline is `2.10.0`:
Current baseline is `3.0.0`:
- workspace persistence exists and the public surface is now workspace-first
- host crossing currently covers create-time seeding, later sync push, and explicit export
@ -37,16 +37,13 @@ also expected to update:
5. [`2.8.0` Named Snapshots And Reset](task-workspace-ga/2.8.0-named-snapshots-and-reset.md) - Done
6. [`2.9.0` Secrets](task-workspace-ga/2.9.0-secrets.md) - Done
7. [`2.10.0` Network Policy And Host Port Publication](task-workspace-ga/2.10.0-network-policy-and-host-port-publication.md) - Done
8. [`3.0.0` Stable Workspace Product](task-workspace-ga/3.0.0-stable-workspace-product.md)
8. [`3.0.0` Stable Workspace Product](task-workspace-ga/3.0.0-stable-workspace-product.md) - Done
9. [`3.1.0` Secondary Disk Tools](task-workspace-ga/3.1.0-secondary-disk-tools.md)
## Definition Of Done For The Roadmap
## Remaining Follow-Up
The workspace product is ready to leave beta when:
The core workspace product is now stable. The remaining planned follow-up is intentionally
secondary:
- the public contract is workspace-first rather than task-first
- an agent can inhabit a sandbox through shell, exec, service, diff, export,
snapshot, reset, and explicit host-crossing operations
- the main docs lead with the workspace product, not one-shot VM execution
- the remaining deliberate deferrals are secondary disk tools rather than core
workspace features
- `3.1.0` secondary disk tools for offline inspection and disk-level workflows
- no further roadmap milestone changes the stable workspace-first core contract

View file

@ -1,5 +1,7 @@
# `3.0.0` Stable Workspace Product
Status: Done
## Goal
Freeze the workspace-first public contract and promote the product from a

View file

@ -1,7 +1,7 @@
[project]
name = "pyro-mcp"
version = "2.10.0"
description = "Ephemeral Firecracker sandboxes with curated environments, persistent workspaces, and MCP tools."
version = "3.0.0"
description = "Stable Firecracker workspaces, one-shot sandboxes, and MCP tools for coding agents."
readme = "README.md"
license = { file = "LICENSE" }
authors = [

View file

@ -458,7 +458,7 @@ class _HelpFormatter(
def _build_parser() -> argparse.ArgumentParser:
parser = argparse.ArgumentParser(
description=(
"Run ephemeral Firecracker microVM workflows from the CLI on supported "
"Run stable one-shot and persistent workspace workflows on supported "
"Linux x86_64 KVM hosts."
),
epilog=dedent(
@ -469,14 +469,17 @@ def _build_parser() -> argparse.ArgumentParser:
pyro env pull debian:12
pyro run debian:12 -- git --version
Need repeated commands in one workspace after that?
Continue into the stable workspace path after that:
pyro workspace create debian:12 --seed-path ./repo
pyro workspace sync push WORKSPACE_ID ./changes
pyro workspace exec WORKSPACE_ID -- cat note.txt
pyro workspace diff WORKSPACE_ID
pyro workspace export WORKSPACE_ID note.txt --output ./note.txt
pyro workspace snapshot create WORKSPACE_ID checkpoint
pyro workspace reset WORKSPACE_ID --snapshot checkpoint
pyro workspace shell open WORKSPACE_ID
pyro workspace service start WORKSPACE_ID app --ready-file .ready -- \
sh -lc 'touch .ready && while true; do sleep 60; done'
pyro workspace export WORKSPACE_ID note.txt --output ./note.txt
Use `pyro mcp serve` only after the CLI validation path works.
"""
@ -675,8 +678,8 @@ def _build_parser() -> argparse.ArgumentParser:
"workspace",
help="Manage persistent workspaces.",
description=(
"Create a persistent workspace when you need repeated commands in one "
"sandbox instead of one-shot `pyro run`."
"Use the stable workspace contract when you need one sandbox to stay alive "
"across repeated exec, shell, service, diff, export, snapshot, and reset calls."
),
epilog=dedent(
"""
@ -692,6 +695,9 @@ def _build_parser() -> argparse.ArgumentParser:
pyro workspace service start WORKSPACE_ID app --ready-file .ready -- \
sh -lc 'touch .ready && while true; do sleep 60; done'
pyro workspace logs WORKSPACE_ID
`pyro run` remains the fastest one-shot proof. `pyro workspace ...` is the
stable path when an agent needs to inhabit one sandbox over time.
"""
),
formatter_class=_HelpFormatter,
@ -704,7 +710,10 @@ def _build_parser() -> argparse.ArgumentParser:
workspace_create_parser = workspace_subparsers.add_parser(
"create",
help="Create and start a persistent workspace.",
description="Create a persistent workspace that stays alive across repeated exec calls.",
description=(
"Create and start a stable persistent workspace that stays alive across repeated "
"exec, shell, service, diff, export, snapshot, and reset calls."
),
epilog=dedent(
"""
Examples:

View file

@ -19,7 +19,7 @@ from typing import Any
from pyro_mcp.runtime import DEFAULT_PLATFORM, RuntimePaths
DEFAULT_ENVIRONMENT_VERSION = "1.0.0"
DEFAULT_CATALOG_VERSION = "2.10.0"
DEFAULT_CATALOG_VERSION = "3.0.0"
OCI_MANIFEST_ACCEPT = ", ".join(
(
"application/vnd.oci.image.index.v1+json",
@ -48,7 +48,7 @@ class VmEnvironment:
oci_repository: str | None = None
oci_reference: str | None = None
source_digest: str | None = None
compatibility: str = ">=2.0.0,<3.0.0"
compatibility: str = ">=3.0.0,<4.0.0"
@dataclass(frozen=True)

View file

@ -31,6 +31,10 @@ def test_cli_help_guides_first_run() -> None:
assert "pyro env list" in help_text
assert "pyro env pull debian:12" in help_text
assert "pyro run debian:12 -- git --version" in help_text
assert "Continue into the stable workspace path after that:" in help_text
assert "pyro workspace exec WORKSPACE_ID -- cat note.txt" in help_text
assert "pyro workspace snapshot create WORKSPACE_ID checkpoint" in help_text
assert "pyro workspace reset WORKSPACE_ID --snapshot checkpoint" in help_text
assert "pyro workspace sync push WORKSPACE_ID ./changes" in help_text
assert "Use `pyro mcp serve` only after the CLI validation path works." in help_text
@ -62,6 +66,7 @@ def test_cli_subcommand_help_includes_examples_and_guidance() -> None:
assert "Use this from an MCP client config after the CLI evaluation path works." in mcp_help
workspace_help = _subparser_choice(parser, "workspace").format_help()
assert "stable workspace contract" in workspace_help
assert "pyro workspace create debian:12 --seed-path ./repo" in workspace_help
assert "pyro workspace sync push WORKSPACE_ID ./repo --dest src" in workspace_help
assert "pyro workspace exec WORKSPACE_ID" in workspace_help
@ -2569,7 +2574,7 @@ def test_cli_workspace_exec_json_error_exits_nonzero(
def test_print_env_helpers_render_human_output(capsys: pytest.CaptureFixture[str]) -> None:
cli._print_env_list_human(
{
"catalog_version": "2.0.0",
"catalog_version": "3.0.0",
"environments": [
{"name": "debian:12", "installed": True, "description": "Git environment"},
"ignored",
@ -2615,7 +2620,7 @@ def test_print_env_helpers_render_human_output(capsys: pytest.CaptureFixture[str
}
)
captured = capsys.readouterr().out
assert "Catalog version: 2.0.0" in captured
assert "Catalog version: 3.0.0" in captured
assert "debian:12 [installed] Git environment" in captured
assert "Install manifest: /cache/linux-x86_64/debian_12-1.0.0/environment.json" in captured
assert "Deleted 2 cached environment entries." in captured
@ -2624,7 +2629,7 @@ def test_print_env_helpers_render_human_output(capsys: pytest.CaptureFixture[str
def test_print_env_list_human_handles_empty(capsys: pytest.CaptureFixture[str]) -> None:
cli._print_env_list_human({"catalog_version": "2.0.0", "environments": []})
cli._print_env_list_human({"catalog_version": "3.0.0", "environments": []})
output = capsys.readouterr().out
assert "No environments found." in output

2
uv.lock generated
View file

@ -706,7 +706,7 @@ crypto = [
[[package]]
name = "pyro-mcp"
version = "2.10.0"
version = "3.0.0"
source = { editable = "." }
dependencies = [
{ name = "mcp" },