diff --git a/CHANGELOG.md b/CHANGELOG.md index 3d87f8a..9d204a3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -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: diff --git a/README.md b/README.md index fa1c7c6..b0886c1 100644 --- a/README.md +++ b/README.md @@ -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 diff --git a/docs/assets/workspace-first-run.gif b/docs/assets/workspace-first-run.gif new file mode 100644 index 0000000..595a0e6 Binary files /dev/null and b/docs/assets/workspace-first-run.gif differ diff --git a/docs/assets/workspace-first-run.tape b/docs/assets/workspace-first-run.tape new file mode 100644 index 0000000..f7fa7da --- /dev/null +++ b/docs/assets/workspace-first-run.tape @@ -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 diff --git a/docs/first-run.md b/docs/first-run.md index b6b20b4..cdd5be3 100644 --- a/docs/first-run.md +++ b/docs/first-run.md @@ -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/`, 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 diff --git a/docs/install.md b/docs/install.md index 10c9742..9e1e1d5 100644 --- a/docs/install.md +++ b/docs/install.md @@ -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 diff --git a/docs/integrations.md b/docs/integrations.md index f263c61..ecfd18f 100644 --- a/docs/integrations.md +++ b/docs/integrations.md @@ -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 diff --git a/docs/public-contract.md b/docs/public-contract.md index 1319cb1..3d70ab4 100644 --- a/docs/public-contract.md +++ b/docs/public-contract.md @@ -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: diff --git a/docs/roadmap/task-workspace-ga.md b/docs/roadmap/task-workspace-ga.md index 081b34f..d824f4a 100644 --- a/docs/roadmap/task-workspace-ga.md +++ b/docs/roadmap/task-workspace-ga.md @@ -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 diff --git a/docs/roadmap/task-workspace-ga/3.0.0-stable-workspace-product.md b/docs/roadmap/task-workspace-ga/3.0.0-stable-workspace-product.md index 44a9de7..5eb1c93 100644 --- a/docs/roadmap/task-workspace-ga/3.0.0-stable-workspace-product.md +++ b/docs/roadmap/task-workspace-ga/3.0.0-stable-workspace-product.md @@ -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 diff --git a/pyproject.toml b/pyproject.toml index 84f27dc..9987a30 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -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 = [ diff --git a/src/pyro_mcp/cli.py b/src/pyro_mcp/cli.py index 9e6e365..6984aec 100644 --- a/src/pyro_mcp/cli.py +++ b/src/pyro_mcp/cli.py @@ -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: diff --git a/src/pyro_mcp/vm_environments.py b/src/pyro_mcp/vm_environments.py index 0a248c3..1cedeeb 100644 --- a/src/pyro_mcp/vm_environments.py +++ b/src/pyro_mcp/vm_environments.py @@ -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) diff --git a/tests/test_cli.py b/tests/test_cli.py index 56791ac..609abb2 100644 --- a/tests/test_cli.py +++ b/tests/test_cli.py @@ -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 diff --git a/uv.lock b/uv.lock index 7583706..bf70717 100644 --- a/uv.lock +++ b/uv.lock @@ -706,7 +706,7 @@ crypto = [ [[package]] name = "pyro-mcp" -version = "2.10.0" +version = "3.0.0" source = { editable = "." } dependencies = [ { name = "mcp" },