Pivot persistent APIs to workspaces
Replace the public persistent-sandbox contract with workspace-first naming across CLI, SDK, MCP, payloads, and on-disk state. Rename the task surface to workspace equivalents, switch create-time seeding to `seed_path`, and store records under `workspaces/<workspace_id>/workspace.json` without carrying legacy task aliases or migrating old local task state. Keep `pyro run` and `vm_*` unchanged. Validation covered `uv lock`, focused public-contract/API/CLI/manager tests, `UV_CACHE_DIR=.uv-cache make check`, and `UV_CACHE_DIR=.uv-cache make dist-check`.
This commit is contained in:
parent
f57454bcb4
commit
48b82d8386
13 changed files with 743 additions and 618 deletions
|
|
@ -18,7 +18,7 @@ from pyro_mcp.vm_environments import DEFAULT_CATALOG_VERSION
|
|||
from pyro_mcp.vm_manager import (
|
||||
DEFAULT_MEM_MIB,
|
||||
DEFAULT_VCPU_COUNT,
|
||||
TASK_WORKSPACE_GUEST_PATH,
|
||||
WORKSPACE_GUEST_PATH,
|
||||
)
|
||||
|
||||
|
||||
|
|
@ -151,17 +151,17 @@ def _print_doctor_human(payload: dict[str, Any]) -> None:
|
|||
print(f"- {issue}")
|
||||
|
||||
|
||||
def _print_task_summary_human(payload: dict[str, Any], *, action: str) -> None:
|
||||
print(f"{action}: {str(payload.get('task_id', 'unknown'))}")
|
||||
def _print_workspace_summary_human(payload: dict[str, Any], *, action: str) -> None:
|
||||
print(f"{action} ID: {str(payload.get('workspace_id', 'unknown'))}")
|
||||
print(f"Environment: {str(payload.get('environment', 'unknown'))}")
|
||||
print(f"State: {str(payload.get('state', 'unknown'))}")
|
||||
print(f"Workspace: {str(payload.get('workspace_path', '/workspace'))}")
|
||||
workspace_seed = payload.get("workspace_seed")
|
||||
if isinstance(workspace_seed, dict):
|
||||
mode = str(workspace_seed.get("mode", "empty"))
|
||||
source_path = workspace_seed.get("source_path")
|
||||
if isinstance(source_path, str) and source_path != "":
|
||||
print(f"Workspace seed: {mode} from {source_path}")
|
||||
seed_path = workspace_seed.get("seed_path")
|
||||
if isinstance(seed_path, str) and seed_path != "":
|
||||
print(f"Workspace seed: {mode} from {seed_path}")
|
||||
else:
|
||||
print(f"Workspace seed: {mode}")
|
||||
print(f"Execution mode: {str(payload.get('execution_mode', 'pending'))}")
|
||||
|
|
@ -179,16 +179,16 @@ def _print_task_summary_human(payload: dict[str, Any], *, action: str) -> None:
|
|||
)
|
||||
|
||||
|
||||
def _print_task_exec_human(payload: dict[str, Any]) -> None:
|
||||
def _print_workspace_exec_human(payload: dict[str, Any]) -> None:
|
||||
stdout = str(payload.get("stdout", ""))
|
||||
stderr = str(payload.get("stderr", ""))
|
||||
_write_stream(stdout, stream=sys.stdout)
|
||||
_write_stream(stderr, stream=sys.stderr)
|
||||
print(
|
||||
"[task-exec] "
|
||||
f"task_id={str(payload.get('task_id', 'unknown'))} "
|
||||
"[workspace-exec] "
|
||||
f"workspace_id={str(payload.get('workspace_id', 'unknown'))} "
|
||||
f"sequence={int(payload.get('sequence', 0))} "
|
||||
f"cwd={str(payload.get('cwd', TASK_WORKSPACE_GUEST_PATH))} "
|
||||
f"cwd={str(payload.get('cwd', WORKSPACE_GUEST_PATH))} "
|
||||
f"execution_mode={str(payload.get('execution_mode', 'unknown'))} "
|
||||
f"exit_code={int(payload.get('exit_code', 1))} "
|
||||
f"duration_ms={int(payload.get('duration_ms', 0))}",
|
||||
|
|
@ -197,27 +197,27 @@ def _print_task_exec_human(payload: dict[str, Any]) -> None:
|
|||
)
|
||||
|
||||
|
||||
def _print_task_sync_human(payload: dict[str, Any]) -> None:
|
||||
def _print_workspace_sync_human(payload: dict[str, Any]) -> None:
|
||||
workspace_sync = payload.get("workspace_sync")
|
||||
if not isinstance(workspace_sync, dict):
|
||||
print(f"Synced task: {str(payload.get('task_id', 'unknown'))}")
|
||||
print(f"Synced workspace: {str(payload.get('workspace_id', 'unknown'))}")
|
||||
return
|
||||
print(
|
||||
"[task-sync] "
|
||||
f"task_id={str(payload.get('task_id', 'unknown'))} "
|
||||
"[workspace-sync] "
|
||||
f"workspace_id={str(payload.get('workspace_id', 'unknown'))} "
|
||||
f"mode={str(workspace_sync.get('mode', 'unknown'))} "
|
||||
f"source={str(workspace_sync.get('source_path', 'unknown'))} "
|
||||
f"destination={str(workspace_sync.get('destination', TASK_WORKSPACE_GUEST_PATH))} "
|
||||
f"destination={str(workspace_sync.get('destination', WORKSPACE_GUEST_PATH))} "
|
||||
f"entry_count={int(workspace_sync.get('entry_count', 0))} "
|
||||
f"bytes_written={int(workspace_sync.get('bytes_written', 0))} "
|
||||
f"execution_mode={str(payload.get('execution_mode', 'unknown'))}"
|
||||
)
|
||||
|
||||
|
||||
def _print_task_logs_human(payload: dict[str, Any]) -> None:
|
||||
def _print_workspace_logs_human(payload: dict[str, Any]) -> None:
|
||||
entries = payload.get("entries")
|
||||
if not isinstance(entries, list) or not entries:
|
||||
print("No task logs found.")
|
||||
print("No workspace logs found.")
|
||||
return
|
||||
for entry in entries:
|
||||
if not isinstance(entry, dict):
|
||||
|
|
@ -226,7 +226,7 @@ def _print_task_logs_human(payload: dict[str, Any]) -> None:
|
|||
f"#{int(entry.get('sequence', 0))} "
|
||||
f"exit_code={int(entry.get('exit_code', -1))} "
|
||||
f"duration_ms={int(entry.get('duration_ms', 0))} "
|
||||
f"cwd={str(entry.get('cwd', TASK_WORKSPACE_GUEST_PATH))}"
|
||||
f"cwd={str(entry.get('cwd', WORKSPACE_GUEST_PATH))}"
|
||||
)
|
||||
print(f"$ {str(entry.get('command', ''))}")
|
||||
stdout = str(entry.get("stdout", ""))
|
||||
|
|
@ -267,8 +267,8 @@ def _build_parser() -> argparse.ArgumentParser:
|
|||
pyro run debian:12 -- git --version
|
||||
|
||||
Need repeated commands in one workspace after that?
|
||||
pyro task create debian:12 --source-path ./repo
|
||||
pyro task sync push TASK_ID ./changes
|
||||
pyro workspace create debian:12 --seed-path ./repo
|
||||
pyro workspace sync push WORKSPACE_ID ./changes
|
||||
|
||||
Use `pyro mcp serve` only after the CLI validation path works.
|
||||
"""
|
||||
|
|
@ -463,9 +463,9 @@ def _build_parser() -> argparse.ArgumentParser:
|
|||
),
|
||||
)
|
||||
|
||||
task_parser = subparsers.add_parser(
|
||||
"task",
|
||||
help="Manage persistent task workspaces.",
|
||||
workspace_parser = subparsers.add_parser(
|
||||
"workspace",
|
||||
help="Manage persistent workspaces.",
|
||||
description=(
|
||||
"Create a persistent workspace when you need repeated commands in one "
|
||||
"sandbox instead of one-shot `pyro run`."
|
||||
|
|
@ -473,58 +473,62 @@ def _build_parser() -> argparse.ArgumentParser:
|
|||
epilog=dedent(
|
||||
"""
|
||||
Examples:
|
||||
pyro task create debian:12 --source-path ./repo
|
||||
pyro task sync push TASK_ID ./repo --dest src
|
||||
pyro task exec TASK_ID -- sh -lc 'printf "hello\\n" > note.txt'
|
||||
pyro task logs TASK_ID
|
||||
pyro workspace create debian:12 --seed-path ./repo
|
||||
pyro workspace sync push WORKSPACE_ID ./repo --dest src
|
||||
pyro workspace exec WORKSPACE_ID -- sh -lc 'printf "hello\\n" > note.txt'
|
||||
pyro workspace logs WORKSPACE_ID
|
||||
"""
|
||||
),
|
||||
formatter_class=_HelpFormatter,
|
||||
)
|
||||
task_subparsers = task_parser.add_subparsers(dest="task_command", required=True, metavar="TASK")
|
||||
task_create_parser = task_subparsers.add_parser(
|
||||
workspace_subparsers = workspace_parser.add_subparsers(
|
||||
dest="workspace_command",
|
||||
required=True,
|
||||
metavar="WORKSPACE",
|
||||
)
|
||||
workspace_create_parser = workspace_subparsers.add_parser(
|
||||
"create",
|
||||
help="Create and start a persistent task workspace.",
|
||||
description="Create a task workspace that stays alive across repeated exec calls.",
|
||||
help="Create and start a persistent workspace.",
|
||||
description="Create a persistent workspace that stays alive across repeated exec calls.",
|
||||
epilog=dedent(
|
||||
"""
|
||||
Examples:
|
||||
pyro task create debian:12
|
||||
pyro task create debian:12 --source-path ./repo
|
||||
pyro task sync push TASK_ID ./changes
|
||||
pyro workspace create debian:12
|
||||
pyro workspace create debian:12 --seed-path ./repo
|
||||
pyro workspace sync push WORKSPACE_ID ./changes
|
||||
"""
|
||||
),
|
||||
formatter_class=_HelpFormatter,
|
||||
)
|
||||
task_create_parser.add_argument(
|
||||
workspace_create_parser.add_argument(
|
||||
"environment",
|
||||
metavar="ENVIRONMENT",
|
||||
help="Curated environment to boot, for example `debian:12`.",
|
||||
)
|
||||
task_create_parser.add_argument(
|
||||
workspace_create_parser.add_argument(
|
||||
"--vcpu-count",
|
||||
type=int,
|
||||
default=DEFAULT_VCPU_COUNT,
|
||||
help="Number of virtual CPUs to allocate to the task guest.",
|
||||
help="Number of virtual CPUs to allocate to the guest.",
|
||||
)
|
||||
task_create_parser.add_argument(
|
||||
workspace_create_parser.add_argument(
|
||||
"--mem-mib",
|
||||
type=int,
|
||||
default=DEFAULT_MEM_MIB,
|
||||
help="Guest memory allocation in MiB.",
|
||||
)
|
||||
task_create_parser.add_argument(
|
||||
workspace_create_parser.add_argument(
|
||||
"--ttl-seconds",
|
||||
type=int,
|
||||
default=600,
|
||||
help="Time-to-live for the task before automatic cleanup.",
|
||||
help="Time-to-live for the workspace before automatic cleanup.",
|
||||
)
|
||||
task_create_parser.add_argument(
|
||||
workspace_create_parser.add_argument(
|
||||
"--network",
|
||||
action="store_true",
|
||||
help="Enable outbound guest networking for the task guest.",
|
||||
help="Enable outbound guest networking for the workspace guest.",
|
||||
)
|
||||
task_create_parser.add_argument(
|
||||
workspace_create_parser.add_argument(
|
||||
"--allow-host-compat",
|
||||
action="store_true",
|
||||
help=(
|
||||
|
|
@ -532,143 +536,153 @@ def _build_parser() -> argparse.ArgumentParser:
|
|||
"is unavailable."
|
||||
),
|
||||
)
|
||||
task_create_parser.add_argument(
|
||||
"--source-path",
|
||||
workspace_create_parser.add_argument(
|
||||
"--seed-path",
|
||||
help=(
|
||||
"Optional host directory or .tar/.tar.gz/.tgz archive to seed into `/workspace` "
|
||||
"before the task is returned."
|
||||
"before the workspace is returned."
|
||||
),
|
||||
)
|
||||
task_create_parser.add_argument(
|
||||
workspace_create_parser.add_argument(
|
||||
"--json",
|
||||
action="store_true",
|
||||
help="Print structured JSON instead of human-readable output.",
|
||||
)
|
||||
task_exec_parser = task_subparsers.add_parser(
|
||||
workspace_exec_parser = workspace_subparsers.add_parser(
|
||||
"exec",
|
||||
help="Run one command inside an existing task workspace.",
|
||||
description="Run one non-interactive command in the persistent `/workspace` for a task.",
|
||||
epilog="Example:\n pyro task exec TASK_ID -- cat note.txt",
|
||||
help="Run one command inside an existing workspace.",
|
||||
description=(
|
||||
"Run one non-interactive command in the persistent `/workspace` "
|
||||
"for a workspace."
|
||||
),
|
||||
epilog="Example:\n pyro workspace exec WORKSPACE_ID -- cat note.txt",
|
||||
formatter_class=_HelpFormatter,
|
||||
)
|
||||
task_exec_parser.add_argument("task_id", metavar="TASK_ID", help="Persistent task identifier.")
|
||||
task_exec_parser.add_argument(
|
||||
workspace_exec_parser.add_argument(
|
||||
"workspace_id",
|
||||
metavar="WORKSPACE_ID",
|
||||
help="Persistent workspace identifier.",
|
||||
)
|
||||
workspace_exec_parser.add_argument(
|
||||
"--timeout-seconds",
|
||||
type=int,
|
||||
default=30,
|
||||
help="Maximum time allowed for the task command.",
|
||||
help="Maximum time allowed for the workspace command.",
|
||||
)
|
||||
task_exec_parser.add_argument(
|
||||
workspace_exec_parser.add_argument(
|
||||
"--json",
|
||||
action="store_true",
|
||||
help="Print structured JSON instead of human-readable output.",
|
||||
)
|
||||
task_exec_parser.add_argument(
|
||||
workspace_exec_parser.add_argument(
|
||||
"command_args",
|
||||
nargs="*",
|
||||
metavar="ARG",
|
||||
help=(
|
||||
"Command and arguments to run inside the task workspace. Prefix them with `--`, "
|
||||
"for example `pyro task exec TASK_ID -- cat note.txt`."
|
||||
"Command and arguments to run inside the workspace. Prefix them with `--`, "
|
||||
"for example `pyro workspace exec WORKSPACE_ID -- cat note.txt`."
|
||||
),
|
||||
)
|
||||
task_sync_parser = task_subparsers.add_parser(
|
||||
workspace_sync_parser = workspace_subparsers.add_parser(
|
||||
"sync",
|
||||
help="Push host content into a started task workspace.",
|
||||
help="Push host content into a started workspace.",
|
||||
description=(
|
||||
"Push host directory or archive content into `/workspace` for an existing "
|
||||
"started task."
|
||||
"started workspace."
|
||||
),
|
||||
epilog=dedent(
|
||||
"""
|
||||
Examples:
|
||||
pyro task sync push TASK_ID ./repo
|
||||
pyro task sync push TASK_ID ./patches --dest src
|
||||
pyro workspace sync push WORKSPACE_ID ./repo
|
||||
pyro workspace sync push WORKSPACE_ID ./patches --dest src
|
||||
|
||||
Sync is non-atomic. If a sync fails partway through, delete and recreate the task.
|
||||
Sync is non-atomic. If a sync fails partway through, delete and recreate the workspace.
|
||||
"""
|
||||
),
|
||||
formatter_class=_HelpFormatter,
|
||||
)
|
||||
task_sync_subparsers = task_sync_parser.add_subparsers(
|
||||
dest="task_sync_command",
|
||||
workspace_sync_subparsers = workspace_sync_parser.add_subparsers(
|
||||
dest="workspace_sync_command",
|
||||
required=True,
|
||||
metavar="SYNC",
|
||||
)
|
||||
task_sync_push_parser = task_sync_subparsers.add_parser(
|
||||
workspace_sync_push_parser = workspace_sync_subparsers.add_parser(
|
||||
"push",
|
||||
help="Push one host directory or archive into a started task.",
|
||||
help="Push one host directory or archive into a started workspace.",
|
||||
description="Import host content into `/workspace` or a subdirectory of it.",
|
||||
epilog="Example:\n pyro task sync push TASK_ID ./repo --dest src",
|
||||
epilog="Example:\n pyro workspace sync push WORKSPACE_ID ./repo --dest src",
|
||||
formatter_class=_HelpFormatter,
|
||||
)
|
||||
task_sync_push_parser.add_argument(
|
||||
"task_id",
|
||||
metavar="TASK_ID",
|
||||
help="Persistent task identifier.",
|
||||
workspace_sync_push_parser.add_argument(
|
||||
"workspace_id",
|
||||
metavar="WORKSPACE_ID",
|
||||
help="Persistent workspace identifier.",
|
||||
)
|
||||
task_sync_push_parser.add_argument(
|
||||
workspace_sync_push_parser.add_argument(
|
||||
"source_path",
|
||||
metavar="SOURCE_PATH",
|
||||
help="Host directory or .tar/.tar.gz/.tgz archive to push into the task workspace.",
|
||||
help="Host directory or .tar/.tar.gz/.tgz archive to push into the workspace.",
|
||||
)
|
||||
task_sync_push_parser.add_argument(
|
||||
workspace_sync_push_parser.add_argument(
|
||||
"--dest",
|
||||
default=TASK_WORKSPACE_GUEST_PATH,
|
||||
default=WORKSPACE_GUEST_PATH,
|
||||
help="Workspace destination path. Relative values resolve inside `/workspace`.",
|
||||
)
|
||||
task_sync_push_parser.add_argument(
|
||||
workspace_sync_push_parser.add_argument(
|
||||
"--json",
|
||||
action="store_true",
|
||||
help="Print structured JSON instead of human-readable output.",
|
||||
)
|
||||
task_status_parser = task_subparsers.add_parser(
|
||||
workspace_status_parser = workspace_subparsers.add_parser(
|
||||
"status",
|
||||
help="Inspect one task workspace.",
|
||||
description="Show task state, sizing, workspace path, and latest command metadata.",
|
||||
epilog="Example:\n pyro task status TASK_ID",
|
||||
help="Inspect one workspace.",
|
||||
description="Show workspace state, sizing, workspace path, and latest command metadata.",
|
||||
epilog="Example:\n pyro workspace status WORKSPACE_ID",
|
||||
formatter_class=_HelpFormatter,
|
||||
)
|
||||
task_status_parser.add_argument(
|
||||
"task_id",
|
||||
metavar="TASK_ID",
|
||||
help="Persistent task identifier.",
|
||||
workspace_status_parser.add_argument(
|
||||
"workspace_id",
|
||||
metavar="WORKSPACE_ID",
|
||||
help="Persistent workspace identifier.",
|
||||
)
|
||||
task_status_parser.add_argument(
|
||||
workspace_status_parser.add_argument(
|
||||
"--json",
|
||||
action="store_true",
|
||||
help="Print structured JSON instead of human-readable output.",
|
||||
)
|
||||
task_logs_parser = task_subparsers.add_parser(
|
||||
workspace_logs_parser = workspace_subparsers.add_parser(
|
||||
"logs",
|
||||
help="Show command history for one task.",
|
||||
description="Show persisted command history, including stdout and stderr, for one task.",
|
||||
epilog="Example:\n pyro task logs TASK_ID",
|
||||
help="Show command history for one workspace.",
|
||||
description=(
|
||||
"Show persisted command history, including stdout and stderr, "
|
||||
"for one workspace."
|
||||
),
|
||||
epilog="Example:\n pyro workspace logs WORKSPACE_ID",
|
||||
formatter_class=_HelpFormatter,
|
||||
)
|
||||
task_logs_parser.add_argument(
|
||||
"task_id",
|
||||
metavar="TASK_ID",
|
||||
help="Persistent task identifier.",
|
||||
workspace_logs_parser.add_argument(
|
||||
"workspace_id",
|
||||
metavar="WORKSPACE_ID",
|
||||
help="Persistent workspace identifier.",
|
||||
)
|
||||
task_logs_parser.add_argument(
|
||||
workspace_logs_parser.add_argument(
|
||||
"--json",
|
||||
action="store_true",
|
||||
help="Print structured JSON instead of human-readable output.",
|
||||
)
|
||||
task_delete_parser = task_subparsers.add_parser(
|
||||
workspace_delete_parser = workspace_subparsers.add_parser(
|
||||
"delete",
|
||||
help="Delete one task workspace.",
|
||||
description="Stop the backing sandbox if needed and remove the task workspace.",
|
||||
epilog="Example:\n pyro task delete TASK_ID",
|
||||
help="Delete one workspace.",
|
||||
description="Stop the backing sandbox if needed and remove the workspace.",
|
||||
epilog="Example:\n pyro workspace delete WORKSPACE_ID",
|
||||
formatter_class=_HelpFormatter,
|
||||
)
|
||||
task_delete_parser.add_argument(
|
||||
"task_id",
|
||||
metavar="TASK_ID",
|
||||
help="Persistent task identifier.",
|
||||
workspace_delete_parser.add_argument(
|
||||
"workspace_id",
|
||||
metavar="WORKSPACE_ID",
|
||||
help="Persistent workspace identifier.",
|
||||
)
|
||||
task_delete_parser.add_argument(
|
||||
workspace_delete_parser.add_argument(
|
||||
"--json",
|
||||
action="store_true",
|
||||
help="Print structured JSON instead of human-readable output.",
|
||||
|
|
@ -847,28 +861,28 @@ def main() -> None:
|
|||
if exit_code != 0:
|
||||
raise SystemExit(exit_code)
|
||||
return
|
||||
if args.command == "task":
|
||||
if args.task_command == "create":
|
||||
payload = pyro.create_task(
|
||||
if args.command == "workspace":
|
||||
if args.workspace_command == "create":
|
||||
payload = pyro.create_workspace(
|
||||
environment=args.environment,
|
||||
vcpu_count=args.vcpu_count,
|
||||
mem_mib=args.mem_mib,
|
||||
ttl_seconds=args.ttl_seconds,
|
||||
network=args.network,
|
||||
allow_host_compat=args.allow_host_compat,
|
||||
source_path=args.source_path,
|
||||
seed_path=args.seed_path,
|
||||
)
|
||||
if bool(args.json):
|
||||
_print_json(payload)
|
||||
else:
|
||||
_print_task_summary_human(payload, action="Task")
|
||||
_print_workspace_summary_human(payload, action="Workspace")
|
||||
return
|
||||
if args.task_command == "exec":
|
||||
if args.workspace_command == "exec":
|
||||
command = _require_command(args.command_args)
|
||||
if bool(args.json):
|
||||
try:
|
||||
payload = pyro.exec_task(
|
||||
args.task_id,
|
||||
payload = pyro.exec_workspace(
|
||||
args.workspace_id,
|
||||
command=command,
|
||||
timeout_seconds=args.timeout_seconds,
|
||||
)
|
||||
|
|
@ -878,24 +892,24 @@ def main() -> None:
|
|||
_print_json(payload)
|
||||
else:
|
||||
try:
|
||||
payload = pyro.exec_task(
|
||||
args.task_id,
|
||||
payload = pyro.exec_workspace(
|
||||
args.workspace_id,
|
||||
command=command,
|
||||
timeout_seconds=args.timeout_seconds,
|
||||
)
|
||||
except Exception as exc: # noqa: BLE001
|
||||
print(f"[error] {exc}", file=sys.stderr, flush=True)
|
||||
raise SystemExit(1) from exc
|
||||
_print_task_exec_human(payload)
|
||||
_print_workspace_exec_human(payload)
|
||||
exit_code = int(payload.get("exit_code", 1))
|
||||
if exit_code != 0:
|
||||
raise SystemExit(exit_code)
|
||||
return
|
||||
if args.task_command == "sync" and args.task_sync_command == "push":
|
||||
if args.workspace_command == "sync" and args.workspace_sync_command == "push":
|
||||
if bool(args.json):
|
||||
try:
|
||||
payload = pyro.push_task_sync(
|
||||
args.task_id,
|
||||
payload = pyro.push_workspace_sync(
|
||||
args.workspace_id,
|
||||
args.source_path,
|
||||
dest=args.dest,
|
||||
)
|
||||
|
|
@ -905,36 +919,36 @@ def main() -> None:
|
|||
_print_json(payload)
|
||||
else:
|
||||
try:
|
||||
payload = pyro.push_task_sync(
|
||||
args.task_id,
|
||||
payload = pyro.push_workspace_sync(
|
||||
args.workspace_id,
|
||||
args.source_path,
|
||||
dest=args.dest,
|
||||
)
|
||||
except Exception as exc: # noqa: BLE001
|
||||
print(f"[error] {exc}", file=sys.stderr, flush=True)
|
||||
raise SystemExit(1) from exc
|
||||
_print_task_sync_human(payload)
|
||||
_print_workspace_sync_human(payload)
|
||||
return
|
||||
if args.task_command == "status":
|
||||
payload = pyro.status_task(args.task_id)
|
||||
if args.workspace_command == "status":
|
||||
payload = pyro.status_workspace(args.workspace_id)
|
||||
if bool(args.json):
|
||||
_print_json(payload)
|
||||
else:
|
||||
_print_task_summary_human(payload, action="Task")
|
||||
_print_workspace_summary_human(payload, action="Workspace")
|
||||
return
|
||||
if args.task_command == "logs":
|
||||
payload = pyro.logs_task(args.task_id)
|
||||
if args.workspace_command == "logs":
|
||||
payload = pyro.logs_workspace(args.workspace_id)
|
||||
if bool(args.json):
|
||||
_print_json(payload)
|
||||
else:
|
||||
_print_task_logs_human(payload)
|
||||
_print_workspace_logs_human(payload)
|
||||
return
|
||||
if args.task_command == "delete":
|
||||
payload = pyro.delete_task(args.task_id)
|
||||
if args.workspace_command == "delete":
|
||||
payload = pyro.delete_workspace(args.workspace_id)
|
||||
if bool(args.json):
|
||||
_print_json(payload)
|
||||
else:
|
||||
print(f"Deleted task: {str(payload.get('task_id', 'unknown'))}")
|
||||
print(f"Deleted workspace: {str(payload.get('workspace_id', 'unknown'))}")
|
||||
return
|
||||
if args.command == "doctor":
|
||||
payload = doctor_report(platform=args.platform)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue