Polish onboarding and CLI help
This commit is contained in:
parent
38b6aeba68
commit
b2ea56db4c
7 changed files with 561 additions and 58 deletions
22
CHANGELOG.md
Normal file
22
CHANGELOG.md
Normal file
|
|
@ -0,0 +1,22 @@
|
|||
# Changelog
|
||||
|
||||
All notable user-visible changes to `pyro-mcp` are documented here.
|
||||
|
||||
## 2.0.0
|
||||
|
||||
- Made guest execution fail closed by default; host compatibility execution now requires
|
||||
explicit opt-in with `--allow-host-compat` or `allow_host_compat=True`.
|
||||
- Switched the main CLI commands to human-readable output by default and kept `--json`
|
||||
for structured output.
|
||||
- Added default sizing of `1 vCPU / 1024 MiB` across the CLI, Python SDK, and MCP tools.
|
||||
- Unified environment cache resolution across `pyro`, `Pyro`, and `pyro doctor`.
|
||||
- Kept the stable environment-first contract centered on `vm_run`, `pyro run`, and
|
||||
curated OCI-published environments.
|
||||
|
||||
## 1.0.0
|
||||
|
||||
- Shipped the first stable public `pyro` CLI, `Pyro` SDK, and MCP server contract.
|
||||
- Replaced the old bundled-profile model with curated named environments.
|
||||
- Switched distribution to a thin Python package plus official OCI environment artifacts.
|
||||
- Published the initial official environment catalog on public Docker Hub.
|
||||
- Added first-party environment pull, inspect, prune, and one-shot run flows.
|
||||
102
README.md
102
README.md
|
|
@ -11,26 +11,110 @@ It exposes the same runtime in three public forms:
|
|||
## Start Here
|
||||
|
||||
- Install: [docs/install.md](docs/install.md)
|
||||
- First run transcript: [docs/first-run.md](docs/first-run.md)
|
||||
- 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)
|
||||
- Troubleshooting: [docs/troubleshooting.md](docs/troubleshooting.md)
|
||||
- Changelog: [CHANGELOG.md](CHANGELOG.md)
|
||||
|
||||
## Public UX
|
||||
## Supported Hosts
|
||||
|
||||
Primary install/run path:
|
||||
Supported today:
|
||||
|
||||
- Linux x86_64
|
||||
- Python 3.12+
|
||||
- `uv`
|
||||
- `/dev/kvm`
|
||||
|
||||
Optional for outbound guest networking:
|
||||
|
||||
- `ip`
|
||||
- `nft` or `iptables`
|
||||
- privilege to create TAP devices and configure NAT
|
||||
|
||||
Not supported today:
|
||||
|
||||
- macOS
|
||||
- Windows
|
||||
- Linux hosts without working KVM at `/dev/kvm`
|
||||
|
||||
If you do not already have `uv`, install it first:
|
||||
|
||||
```bash
|
||||
uvx --from pyro-mcp pyro mcp serve
|
||||
python -m pip install uv
|
||||
```
|
||||
|
||||
Installed package path:
|
||||
## 5-Minute Evaluation
|
||||
|
||||
Use the package directly without a manual install:
|
||||
|
||||
### 1. Check the host
|
||||
|
||||
```bash
|
||||
pyro mcp serve
|
||||
uvx --from pyro-mcp pyro doctor
|
||||
```
|
||||
|
||||
The public user-facing interface is `pyro` and `Pyro`.
|
||||
Expected success signals:
|
||||
|
||||
```bash
|
||||
Platform: linux-x86_64
|
||||
Runtime: PASS
|
||||
KVM: exists=yes readable=yes writable=yes
|
||||
Environment cache: /home/you/.cache/pyro-mcp/environments
|
||||
Capabilities: vm_boot=yes guest_exec=yes guest_network=yes
|
||||
Networking: tun=yes ip_forward=yes
|
||||
```
|
||||
|
||||
### 2. Inspect the catalog and pull the default environment
|
||||
|
||||
```bash
|
||||
uvx --from pyro-mcp pyro env list
|
||||
```
|
||||
|
||||
Expected output:
|
||||
|
||||
```bash
|
||||
Catalog version: 2.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.
|
||||
```
|
||||
|
||||
```bash
|
||||
uvx --from pyro-mcp pyro env pull debian:12
|
||||
```
|
||||
|
||||
### 3. Run one command in a guest
|
||||
|
||||
```bash
|
||||
uvx --from pyro-mcp pyro run debian:12 -- git --version
|
||||
```
|
||||
|
||||
Expected success signals:
|
||||
|
||||
```bash
|
||||
git version ...
|
||||
[run] environment=debian:12 execution_mode=guest_vsock exit_code=0 duration_ms=...
|
||||
```
|
||||
|
||||
### 4. Optional demos
|
||||
|
||||
```bash
|
||||
uvx --from pyro-mcp pyro demo
|
||||
uvx --from pyro-mcp pyro demo --network
|
||||
```
|
||||
|
||||
If you prefer a fuller copy-pasteable transcript, see [docs/first-run.md](docs/first-run.md).
|
||||
|
||||
## Public Interfaces
|
||||
|
||||
The public user-facing interface is `pyro` and `Pyro`. After the CLI validation path works, you can choose one of three surfaces:
|
||||
|
||||
- `pyro` for direct CLI usage
|
||||
- `from pyro_mcp import Pyro` for Python orchestration
|
||||
- `pyro mcp serve` for MCP clients
|
||||
|
||||
`Makefile` targets are contributor conveniences for this repository and are not the primary product UX.
|
||||
|
||||
## Official Environments
|
||||
|
|
@ -86,6 +170,12 @@ pyro doctor --json
|
|||
It fails closed when guest boot or guest exec is unavailable.
|
||||
Use `--allow-host-compat` only if you explicitly want host execution.
|
||||
|
||||
Run the MCP server after the CLI path above works:
|
||||
|
||||
```bash
|
||||
pyro mcp serve
|
||||
```
|
||||
|
||||
Run the deterministic demo:
|
||||
|
||||
```bash
|
||||
|
|
|
|||
61
docs/first-run.md
Normal file
61
docs/first-run.md
Normal file
|
|
@ -0,0 +1,61 @@
|
|||
# First Run Transcript
|
||||
|
||||
This is the intended evaluator path for a first successful run on a supported host.
|
||||
Copy the commands as-is. Paths and timing values will differ on your machine.
|
||||
|
||||
## 1. Verify the host
|
||||
|
||||
```bash
|
||||
$ uvx --from pyro-mcp pyro doctor
|
||||
Platform: linux-x86_64
|
||||
Runtime: PASS
|
||||
KVM: exists=yes readable=yes writable=yes
|
||||
Environment cache: /home/you/.cache/pyro-mcp/environments
|
||||
Capabilities: vm_boot=yes guest_exec=yes guest_network=yes
|
||||
Networking: tun=yes ip_forward=yes
|
||||
```
|
||||
|
||||
## 2. Inspect the catalog
|
||||
|
||||
```bash
|
||||
$ uvx --from pyro-mcp pyro env list
|
||||
Catalog version: 2.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.
|
||||
```
|
||||
|
||||
## 3. Pull the default environment
|
||||
|
||||
```bash
|
||||
$ uvx --from pyro-mcp pyro env pull debian:12
|
||||
Pulled: debian:12
|
||||
Version: 1.0.0
|
||||
Distribution: debian 12
|
||||
Installed: yes
|
||||
Cache dir: /home/you/.cache/pyro-mcp/environments
|
||||
Default packages: bash, coreutils, git
|
||||
Install dir: /home/you/.cache/pyro-mcp/environments/linux-x86_64/debian_12-1.0.0
|
||||
Install manifest: /home/you/.cache/pyro-mcp/environments/linux-x86_64/debian_12-1.0.0/environment.json
|
||||
Kernel image: /home/you/.cache/pyro-mcp/environments/linux-x86_64/debian_12-1.0.0/vmlinux
|
||||
Rootfs image: /home/you/.cache/pyro-mcp/environments/linux-x86_64/debian_12-1.0.0/rootfs.ext4
|
||||
OCI source: registry-1.docker.io/thalesmaciel/pyro-environment-debian-12:1.0.0
|
||||
```
|
||||
|
||||
## 4. Run one command in a guest
|
||||
|
||||
```bash
|
||||
$ uvx --from pyro-mcp pyro run debian:12 -- git --version
|
||||
git version ...
|
||||
[run] environment=debian:12 execution_mode=guest_vsock exit_code=0 duration_ms=...
|
||||
```
|
||||
|
||||
## 5. Optional next steps
|
||||
|
||||
```bash
|
||||
$ uvx --from pyro-mcp pyro demo
|
||||
$ uvx --from pyro-mcp pyro mcp serve
|
||||
```
|
||||
|
||||
If `pyro doctor` reports `Runtime: FAIL`, or if the `pyro run` summary does not show
|
||||
`execution_mode=guest_vsock`, stop and use [troubleshooting.md](troubleshooting.md).
|
||||
|
|
@ -1,59 +1,110 @@
|
|||
# Install
|
||||
|
||||
## Requirements
|
||||
## Support Matrix
|
||||
|
||||
- Linux x86_64 host
|
||||
Supported today:
|
||||
|
||||
- Linux x86_64
|
||||
- Python 3.12+
|
||||
- `uv`
|
||||
- `/dev/kvm`
|
||||
|
||||
If you want outbound guest networking:
|
||||
Optional for outbound guest networking:
|
||||
|
||||
- `ip`
|
||||
- `nft` or `iptables`
|
||||
- privilege to create TAP devices and configure NAT
|
||||
|
||||
## Fastest Start
|
||||
Not supported today:
|
||||
|
||||
Run the MCP server directly from the package without a manual install:
|
||||
- macOS
|
||||
- Windows
|
||||
- Linux hosts without working KVM at `/dev/kvm`
|
||||
|
||||
If you do not already have `uv`, install it first:
|
||||
|
||||
```bash
|
||||
uvx --from pyro-mcp pyro mcp serve
|
||||
python -m pip install uv
|
||||
```
|
||||
|
||||
Prefetch the default official environment:
|
||||
## Fastest Evaluation Path
|
||||
|
||||
Use the package directly without a manual install:
|
||||
|
||||
### 1. Check the host first
|
||||
|
||||
```bash
|
||||
uvx --from pyro-mcp pyro doctor
|
||||
```
|
||||
|
||||
Expected success signals:
|
||||
|
||||
```bash
|
||||
Platform: linux-x86_64
|
||||
Runtime: PASS
|
||||
KVM: exists=yes readable=yes writable=yes
|
||||
Environment cache: /home/you/.cache/pyro-mcp/environments
|
||||
Capabilities: vm_boot=yes guest_exec=yes guest_network=yes
|
||||
Networking: tun=yes ip_forward=yes
|
||||
```
|
||||
|
||||
If `Runtime: FAIL`, stop here and use [troubleshooting.md](troubleshooting.md).
|
||||
|
||||
### 2. Inspect the catalog
|
||||
|
||||
```bash
|
||||
uvx --from pyro-mcp pyro env list
|
||||
```
|
||||
|
||||
Expected output:
|
||||
|
||||
```bash
|
||||
Catalog version: 2.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.
|
||||
```
|
||||
|
||||
### 3. Pull the default environment
|
||||
|
||||
```bash
|
||||
uvx --from pyro-mcp pyro env pull debian:12
|
||||
```
|
||||
|
||||
Run one command in a curated environment:
|
||||
### 4. Run one command in a guest
|
||||
|
||||
```bash
|
||||
uvx --from pyro-mcp pyro run debian:12 -- git --version
|
||||
```
|
||||
|
||||
Inspect the official environment catalog:
|
||||
Expected success signals:
|
||||
|
||||
```bash
|
||||
uvx --from pyro-mcp pyro env list
|
||||
git version ...
|
||||
[run] environment=debian:12 execution_mode=guest_vsock exit_code=0 duration_ms=...
|
||||
```
|
||||
|
||||
If guest execution is unavailable, the command fails unless you explicitly pass
|
||||
`--allow-host-compat`.
|
||||
|
||||
For a fuller copy-pasteable transcript, see [first-run.md](first-run.md).
|
||||
|
||||
## Installed CLI
|
||||
|
||||
```bash
|
||||
uv tool install pyro-mcp
|
||||
pyro --version
|
||||
pyro doctor
|
||||
pyro env list
|
||||
pyro env pull debian:12
|
||||
pyro env inspect debian:12
|
||||
pyro doctor
|
||||
pyro run debian:12 -- git --version
|
||||
```
|
||||
|
||||
`pyro run` defaults to `1 vCPU / 1024 MiB`.
|
||||
If guest execution is unavailable, the command fails unless you explicitly pass
|
||||
`--allow-host-compat`.
|
||||
After the CLI path works, you can move on to:
|
||||
|
||||
- MCP: `pyro mcp serve`
|
||||
- Python SDK: `from pyro_mcp import Pyro`
|
||||
- Demos: `pyro demo` or `pyro demo --network`
|
||||
|
||||
## Contributor Clone
|
||||
|
||||
|
|
|
|||
|
|
@ -2,6 +2,9 @@
|
|||
|
||||
These are the main ways to integrate `pyro-mcp` into an LLM application.
|
||||
|
||||
Use this page after you have already validated the host and guest execution through the
|
||||
CLI path in [install.md](install.md) or [first-run.md](first-run.md).
|
||||
|
||||
## Recommended Default
|
||||
|
||||
Use `vm_run` first.
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ from __future__ import annotations
|
|||
import argparse
|
||||
import json
|
||||
import sys
|
||||
from textwrap import dedent
|
||||
from typing import Any
|
||||
|
||||
from pyro_mcp import __version__
|
||||
|
|
@ -142,55 +143,284 @@ def _print_doctor_human(payload: dict[str, Any]) -> None:
|
|||
print(f"- {issue}")
|
||||
|
||||
|
||||
class _HelpFormatter(
|
||||
argparse.RawDescriptionHelpFormatter,
|
||||
argparse.ArgumentDefaultsHelpFormatter,
|
||||
):
|
||||
"""Help formatter with examples and default values."""
|
||||
|
||||
def _get_help_string(self, action: argparse.Action) -> str:
|
||||
if action.default is None and action.help is not None:
|
||||
return action.help
|
||||
help_string = super()._get_help_string(action)
|
||||
if help_string is None:
|
||||
return ""
|
||||
return help_string
|
||||
|
||||
|
||||
def _build_parser() -> argparse.ArgumentParser:
|
||||
parser = argparse.ArgumentParser(
|
||||
description="pyro CLI for curated ephemeral Linux environments."
|
||||
description=(
|
||||
"Run ephemeral Firecracker microVM workflows from the CLI on supported "
|
||||
"Linux x86_64 KVM hosts."
|
||||
),
|
||||
epilog=dedent(
|
||||
"""
|
||||
Suggested first run:
|
||||
pyro doctor
|
||||
pyro env pull debian:12
|
||||
pyro run debian:12 -- git --version
|
||||
|
||||
Use `pyro mcp serve` only after the CLI validation path works.
|
||||
"""
|
||||
),
|
||||
formatter_class=_HelpFormatter,
|
||||
)
|
||||
parser.add_argument("--version", action="version", version=f"%(prog)s {__version__}")
|
||||
subparsers = parser.add_subparsers(dest="command", required=True)
|
||||
subparsers = parser.add_subparsers(dest="command", required=True, metavar="COMMAND")
|
||||
|
||||
env_parser = subparsers.add_parser("env", help="Inspect and manage curated environments.")
|
||||
env_subparsers = env_parser.add_subparsers(dest="env_command", required=True)
|
||||
list_parser = env_subparsers.add_parser("list", help="List official environments.")
|
||||
list_parser.add_argument("--json", action="store_true")
|
||||
env_parser = subparsers.add_parser(
|
||||
"env",
|
||||
help="Inspect and manage curated environments.",
|
||||
description="Inspect, install, and prune curated Linux environments.",
|
||||
epilog=dedent(
|
||||
"""
|
||||
Examples:
|
||||
pyro env list
|
||||
pyro env pull debian:12
|
||||
pyro env inspect debian:12
|
||||
"""
|
||||
),
|
||||
formatter_class=_HelpFormatter,
|
||||
)
|
||||
env_subparsers = env_parser.add_subparsers(dest="env_command", required=True, metavar="ENV")
|
||||
list_parser = env_subparsers.add_parser(
|
||||
"list",
|
||||
help="List official environments.",
|
||||
description="List the shipped environment catalog and show local install status.",
|
||||
epilog="Example:\n pyro env list",
|
||||
formatter_class=_HelpFormatter,
|
||||
)
|
||||
list_parser.add_argument(
|
||||
"--json",
|
||||
action="store_true",
|
||||
help="Print structured JSON instead of human-readable output.",
|
||||
)
|
||||
pull_parser = env_subparsers.add_parser(
|
||||
"pull",
|
||||
help="Install an environment into the local cache.",
|
||||
description="Download and install one official environment into the local cache.",
|
||||
epilog="Example:\n pyro env pull debian:12",
|
||||
formatter_class=_HelpFormatter,
|
||||
)
|
||||
pull_parser.add_argument(
|
||||
"environment",
|
||||
metavar="ENVIRONMENT",
|
||||
help="Environment name from `pyro env list`, for example `debian:12`.",
|
||||
)
|
||||
pull_parser.add_argument(
|
||||
"--json",
|
||||
action="store_true",
|
||||
help="Print structured JSON instead of human-readable output.",
|
||||
)
|
||||
inspect_parser = env_subparsers.add_parser(
|
||||
"inspect",
|
||||
help="Inspect one environment.",
|
||||
description="Show catalog and local cache details for one environment.",
|
||||
epilog="Example:\n pyro env inspect debian:12",
|
||||
formatter_class=_HelpFormatter,
|
||||
)
|
||||
inspect_parser.add_argument(
|
||||
"environment",
|
||||
metavar="ENVIRONMENT",
|
||||
help="Environment name from `pyro env list`, for example `debian:12`.",
|
||||
)
|
||||
inspect_parser.add_argument(
|
||||
"--json",
|
||||
action="store_true",
|
||||
help="Print structured JSON instead of human-readable output.",
|
||||
)
|
||||
prune_parser = env_subparsers.add_parser(
|
||||
"prune",
|
||||
help="Delete stale cached environments.",
|
||||
description="Remove cached environment installs that are no longer referenced.",
|
||||
epilog="Example:\n pyro env prune",
|
||||
formatter_class=_HelpFormatter,
|
||||
)
|
||||
prune_parser.add_argument(
|
||||
"--json",
|
||||
action="store_true",
|
||||
help="Print structured JSON instead of human-readable output.",
|
||||
)
|
||||
pull_parser.add_argument("environment")
|
||||
pull_parser.add_argument("--json", action="store_true")
|
||||
inspect_parser = env_subparsers.add_parser("inspect", help="Inspect one environment.")
|
||||
inspect_parser.add_argument("environment")
|
||||
inspect_parser.add_argument("--json", action="store_true")
|
||||
prune_parser = env_subparsers.add_parser("prune", help="Delete stale cached environments.")
|
||||
prune_parser.add_argument("--json", action="store_true")
|
||||
|
||||
mcp_parser = subparsers.add_parser("mcp", help="Run the MCP server.")
|
||||
mcp_subparsers = mcp_parser.add_subparsers(dest="mcp_command", required=True)
|
||||
mcp_subparsers.add_parser("serve", help="Run the MCP server over stdio.")
|
||||
mcp_parser = subparsers.add_parser(
|
||||
"mcp",
|
||||
help="Run the MCP server.",
|
||||
description=(
|
||||
"Run the MCP server after you have already validated the host and "
|
||||
"guest execution with `pyro doctor` and `pyro run`."
|
||||
),
|
||||
epilog="Example:\n pyro mcp serve",
|
||||
formatter_class=_HelpFormatter,
|
||||
)
|
||||
mcp_subparsers = mcp_parser.add_subparsers(dest="mcp_command", required=True, metavar="MCP")
|
||||
mcp_subparsers.add_parser(
|
||||
"serve",
|
||||
help="Run the MCP server over stdio.",
|
||||
description="Expose pyro tools over stdio for an MCP client.",
|
||||
epilog=dedent(
|
||||
"""
|
||||
Example:
|
||||
pyro mcp serve
|
||||
|
||||
run_parser = subparsers.add_parser("run", help="Run one command inside an ephemeral VM.")
|
||||
run_parser.add_argument("environment")
|
||||
run_parser.add_argument("--vcpu-count", type=int, default=DEFAULT_VCPU_COUNT)
|
||||
run_parser.add_argument("--mem-mib", type=int, default=DEFAULT_MEM_MIB)
|
||||
run_parser.add_argument("--timeout-seconds", type=int, default=30)
|
||||
run_parser.add_argument("--ttl-seconds", type=int, default=600)
|
||||
run_parser.add_argument("--network", action="store_true")
|
||||
run_parser.add_argument("--allow-host-compat", action="store_true")
|
||||
run_parser.add_argument("--json", action="store_true")
|
||||
run_parser.add_argument("command_args", nargs="*")
|
||||
Use this from an MCP client config after the CLI evaluation path works.
|
||||
"""
|
||||
),
|
||||
formatter_class=_HelpFormatter,
|
||||
)
|
||||
|
||||
doctor_parser = subparsers.add_parser("doctor", help="Inspect runtime and host diagnostics.")
|
||||
doctor_parser.add_argument("--platform", default=DEFAULT_PLATFORM)
|
||||
doctor_parser.add_argument("--json", action="store_true")
|
||||
run_parser = subparsers.add_parser(
|
||||
"run",
|
||||
help="Run one command inside an ephemeral VM.",
|
||||
description="Run one non-interactive command in a temporary Firecracker microVM.",
|
||||
epilog=dedent(
|
||||
"""
|
||||
Examples:
|
||||
pyro run debian:12 -- git --version
|
||||
pyro run debian:12 --network -- git ls-remote https://github.com/octocat/Hello-World.git
|
||||
"""
|
||||
),
|
||||
formatter_class=_HelpFormatter,
|
||||
)
|
||||
run_parser.add_argument(
|
||||
"environment",
|
||||
metavar="ENVIRONMENT",
|
||||
help="Curated environment to boot, for example `debian:12`.",
|
||||
)
|
||||
run_parser.add_argument(
|
||||
"--vcpu-count",
|
||||
type=int,
|
||||
default=DEFAULT_VCPU_COUNT,
|
||||
help="Number of virtual CPUs to allocate to the guest.",
|
||||
)
|
||||
run_parser.add_argument(
|
||||
"--mem-mib",
|
||||
type=int,
|
||||
default=DEFAULT_MEM_MIB,
|
||||
help="Guest memory allocation in MiB.",
|
||||
)
|
||||
run_parser.add_argument(
|
||||
"--timeout-seconds",
|
||||
type=int,
|
||||
default=30,
|
||||
help="Maximum time allowed for the guest command.",
|
||||
)
|
||||
run_parser.add_argument(
|
||||
"--ttl-seconds",
|
||||
type=int,
|
||||
default=600,
|
||||
help="Time-to-live for temporary VM artifacts before cleanup.",
|
||||
)
|
||||
run_parser.add_argument(
|
||||
"--network",
|
||||
action="store_true",
|
||||
help="Enable outbound guest networking. Requires TAP/NAT privileges on the host.",
|
||||
)
|
||||
run_parser.add_argument(
|
||||
"--allow-host-compat",
|
||||
action="store_true",
|
||||
help=(
|
||||
"Opt into host-side compatibility execution if guest boot or guest exec "
|
||||
"is unavailable."
|
||||
),
|
||||
)
|
||||
run_parser.add_argument(
|
||||
"--json",
|
||||
action="store_true",
|
||||
help="Print structured JSON instead of human-readable output.",
|
||||
)
|
||||
run_parser.add_argument(
|
||||
"command_args",
|
||||
nargs="*",
|
||||
metavar="ARG",
|
||||
help=(
|
||||
"Command and arguments to run inside the guest. Prefix them with `--`, "
|
||||
"for example `pyro run debian:12 -- git --version`."
|
||||
),
|
||||
)
|
||||
|
||||
demo_parser = subparsers.add_parser("demo", help="Run built-in demos.")
|
||||
demo_subparsers = demo_parser.add_subparsers(dest="demo_command")
|
||||
demo_parser.add_argument("--network", action="store_true")
|
||||
ollama_parser = demo_subparsers.add_parser("ollama", help="Run the Ollama MCP demo.")
|
||||
ollama_parser.add_argument("--base-url", default=DEFAULT_OLLAMA_BASE_URL)
|
||||
ollama_parser.add_argument("--model", default=DEFAULT_OLLAMA_MODEL)
|
||||
ollama_parser.add_argument("-v", "--verbose", action="store_true")
|
||||
doctor_parser = subparsers.add_parser(
|
||||
"doctor",
|
||||
help="Inspect runtime and host diagnostics.",
|
||||
description="Check host prerequisites and embedded runtime health before your first run.",
|
||||
epilog=dedent(
|
||||
"""
|
||||
Examples:
|
||||
pyro doctor
|
||||
pyro doctor --json
|
||||
"""
|
||||
),
|
||||
formatter_class=_HelpFormatter,
|
||||
)
|
||||
doctor_parser.add_argument(
|
||||
"--platform",
|
||||
default=DEFAULT_PLATFORM,
|
||||
help="Runtime platform to inspect.",
|
||||
)
|
||||
doctor_parser.add_argument(
|
||||
"--json",
|
||||
action="store_true",
|
||||
help="Print structured JSON instead of human-readable output.",
|
||||
)
|
||||
|
||||
demo_parser = subparsers.add_parser(
|
||||
"demo",
|
||||
help="Run built-in demos.",
|
||||
description="Run built-in demos after the basic CLI validation path works.",
|
||||
epilog=dedent(
|
||||
"""
|
||||
Examples:
|
||||
pyro demo
|
||||
pyro demo --network
|
||||
pyro demo ollama --verbose
|
||||
"""
|
||||
),
|
||||
formatter_class=_HelpFormatter,
|
||||
)
|
||||
demo_subparsers = demo_parser.add_subparsers(dest="demo_command", metavar="DEMO")
|
||||
demo_parser.add_argument(
|
||||
"--network",
|
||||
action="store_true",
|
||||
help="Enable outbound guest networking for the deterministic demo.",
|
||||
)
|
||||
ollama_parser = demo_subparsers.add_parser(
|
||||
"ollama",
|
||||
help="Run the Ollama MCP demo.",
|
||||
description="Run the Ollama tool-calling demo against the `vm_run` and lifecycle tools.",
|
||||
epilog=dedent(
|
||||
"""
|
||||
Example:
|
||||
pyro demo ollama --model llama3.2:3b --verbose
|
||||
"""
|
||||
),
|
||||
formatter_class=_HelpFormatter,
|
||||
)
|
||||
ollama_parser.add_argument(
|
||||
"--base-url",
|
||||
default=DEFAULT_OLLAMA_BASE_URL,
|
||||
help="OpenAI-compatible base URL for the Ollama server.",
|
||||
)
|
||||
ollama_parser.add_argument(
|
||||
"--model",
|
||||
default=DEFAULT_OLLAMA_MODEL,
|
||||
help="Ollama model name to use for tool calling.",
|
||||
)
|
||||
ollama_parser.add_argument(
|
||||
"-v",
|
||||
"--verbose",
|
||||
action="store_true",
|
||||
help="Print full tool loop output instead of only the summary.",
|
||||
)
|
||||
|
||||
return parser
|
||||
|
||||
|
|
|
|||
|
|
@ -3,13 +3,59 @@ from __future__ import annotations
|
|||
import argparse
|
||||
import json
|
||||
import sys
|
||||
from typing import Any
|
||||
from typing import Any, cast
|
||||
|
||||
import pytest
|
||||
|
||||
import pyro_mcp.cli as cli
|
||||
|
||||
|
||||
def _subparser_choice(parser: argparse.ArgumentParser, name: str) -> argparse.ArgumentParser:
|
||||
subparsers = getattr(parser, "_subparsers", None)
|
||||
if subparsers is None:
|
||||
raise AssertionError("parser does not define subparsers")
|
||||
group_actions = cast(list[Any], subparsers._group_actions) # noqa: SLF001
|
||||
if not group_actions:
|
||||
raise AssertionError("parser subparsers are empty")
|
||||
choices = cast(dict[str, argparse.ArgumentParser], group_actions[0].choices)
|
||||
return choices[name]
|
||||
|
||||
|
||||
def test_cli_help_guides_first_run() -> None:
|
||||
parser = cli._build_parser()
|
||||
help_text = parser.format_help()
|
||||
|
||||
assert "Suggested first run:" in help_text
|
||||
assert "pyro doctor" in help_text
|
||||
assert "pyro env pull debian:12" in help_text
|
||||
assert "pyro run debian:12 -- git --version" in help_text
|
||||
assert "Use `pyro mcp serve` only after the CLI validation path works." in help_text
|
||||
|
||||
|
||||
def test_cli_subcommand_help_includes_examples_and_guidance() -> None:
|
||||
parser = cli._build_parser()
|
||||
|
||||
run_help = _subparser_choice(parser, "run").format_help()
|
||||
assert "pyro run debian:12 -- git --version" in run_help
|
||||
assert "Opt into host-side compatibility execution" in run_help
|
||||
assert "Enable outbound guest networking" in run_help
|
||||
|
||||
env_help = _subparser_choice(_subparser_choice(parser, "env"), "pull").format_help()
|
||||
assert "Environment name from `pyro env list`" in env_help
|
||||
assert "pyro env pull debian:12" in env_help
|
||||
|
||||
doctor_help = _subparser_choice(parser, "doctor").format_help()
|
||||
assert "Check host prerequisites and embedded runtime health" in doctor_help
|
||||
assert "pyro doctor --json" in doctor_help
|
||||
|
||||
demo_help = _subparser_choice(parser, "demo").format_help()
|
||||
assert "pyro demo ollama --verbose" in demo_help
|
||||
|
||||
mcp_help = _subparser_choice(_subparser_choice(parser, "mcp"), "serve").format_help()
|
||||
assert "Expose pyro tools over stdio for an MCP client." in mcp_help
|
||||
assert "Use this from an MCP client config after the CLI evaluation path works." in mcp_help
|
||||
|
||||
|
||||
def test_cli_run_prints_json(
|
||||
monkeypatch: pytest.MonkeyPatch,
|
||||
capsys: pytest.CaptureFixture[str],
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue