plato-sdk-v2 2.6.2__py3-none-any.whl → 2.7.1__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (32) hide show
  1. plato/_generated/__init__.py +1 -1
  2. plato/_generated/api/v2/__init__.py +2 -1
  3. plato/_generated/api/v2/networks/__init__.py +23 -0
  4. plato/_generated/api/v2/networks/add_member.py +75 -0
  5. plato/_generated/api/v2/networks/create_network.py +70 -0
  6. plato/_generated/api/v2/networks/delete_network.py +68 -0
  7. plato/_generated/api/v2/networks/get_network.py +69 -0
  8. plato/_generated/api/v2/networks/list_members.py +69 -0
  9. plato/_generated/api/v2/networks/list_networks.py +74 -0
  10. plato/_generated/api/v2/networks/remove_member.py +73 -0
  11. plato/_generated/api/v2/networks/update_member.py +80 -0
  12. plato/_generated/api/v2/sessions/__init__.py +4 -0
  13. plato/_generated/api/v2/sessions/add_ssh_key.py +81 -0
  14. plato/_generated/api/v2/sessions/connect_network.py +89 -0
  15. plato/_generated/models/__init__.py +145 -24
  16. plato/v1/cli/agent.py +45 -52
  17. plato/v1/cli/chronos.py +46 -58
  18. plato/v1/cli/main.py +14 -25
  19. plato/v1/cli/pm.py +129 -98
  20. plato/v1/cli/proxy.py +343 -0
  21. plato/v1/cli/sandbox.py +421 -425
  22. plato/v1/cli/ssh.py +12 -167
  23. plato/v1/cli/verify.py +79 -55
  24. plato/v1/cli/world.py +13 -12
  25. plato/v2/async_/client.py +24 -2
  26. plato/v2/async_/session.py +48 -0
  27. plato/v2/sync/client.py +24 -2
  28. plato/v2/sync/session.py +48 -0
  29. {plato_sdk_v2-2.6.2.dist-info → plato_sdk_v2-2.7.1.dist-info}/METADATA +1 -1
  30. {plato_sdk_v2-2.6.2.dist-info → plato_sdk_v2-2.7.1.dist-info}/RECORD +32 -20
  31. {plato_sdk_v2-2.6.2.dist-info → plato_sdk_v2-2.7.1.dist-info}/WHEEL +0 -0
  32. {plato_sdk_v2-2.6.2.dist-info → plato_sdk_v2-2.7.1.dist-info}/entry_points.txt +0 -0
plato/v1/cli/chronos.py CHANGED
@@ -48,30 +48,21 @@ def launch(
48
48
  help="Wait for job completion and stream logs",
49
49
  ),
50
50
  ):
51
- """
52
- Launch a Chronos job from a config file.
53
-
54
- \b
55
- Config format:
56
- {
57
- "world": {
58
- "package": "plato-world-structured-execution:0.1.17",
59
- "config": {
60
- "skill_runner": {
61
- "image": "computer-use:1.1.0",
62
- "config": { ... }
63
- },
64
- "secrets": { ... }
65
- }
66
- },
67
- "runtime": {
68
- "artifact_id": "..." // optional
69
- }
70
- }
71
-
72
- Examples:
73
- plato chronos launch config.json
74
- plato chronos launch config.json --wait
51
+ """Launch a Chronos job from a config file.
52
+
53
+ Submits a job configuration to the Chronos service to run a world with its
54
+ configured agents and secrets.
55
+
56
+ Arguments:
57
+ config: Path to the job configuration JSON file
58
+
59
+ Options:
60
+ -u, --url: Chronos API URL (default: https://chronos.plato.so, or CHRONOS_URL env var)
61
+ -k, --api-key: Plato API key for authentication (or PLATO_API_KEY env var)
62
+ -w, --wait: Wait for job completion and stream logs (not yet implemented)
63
+
64
+ The config file should contain world.package (required) and optionally world.config,
65
+ runtime.artifact_id, and tags.
75
66
  """
76
67
  import httpx
77
68
 
@@ -148,12 +139,18 @@ def example(
148
139
  help="Output file path (prints to stdout if not specified)",
149
140
  ),
150
141
  ):
151
- """
152
- Generate an example job config file.
142
+ """Generate an example job config file.
143
+
144
+ Creates a sample JSON configuration for launching Chronos jobs, which can be
145
+ customized for your use case.
146
+
147
+ Arguments:
148
+ world: World type to generate example for (default: "structured-execution")
153
149
 
154
- Examples:
155
- plato chronos example
156
- plato chronos example structured-execution -o config.json
150
+ Options:
151
+ -o, --output: Output file path. If not specified, prints to stdout.
152
+
153
+ Available worlds: structured-execution, code-world
157
154
  """
158
155
  examples = {
159
156
  "structured-execution": {
@@ -724,14 +721,17 @@ def stop(
724
721
  help="Plato API key for authentication",
725
722
  ),
726
723
  ):
727
- """
728
- Stop a running Chronos session.
724
+ """Stop a running Chronos session.
729
725
 
730
- This marks the session as cancelled with status reason "User cancelled".
726
+ Marks the session as cancelled with status reason "User cancelled" and terminates
727
+ any running containers.
731
728
 
732
- Examples:
733
- plato chronos stop abc123
734
- plato chronos stop abc123 --url https://chronos.plato.so
729
+ Arguments:
730
+ session_id: The session ID to stop (from 'plato chronos launch' output)
731
+
732
+ Options:
733
+ -u, --url: Chronos API URL (default: https://chronos.plato.so, or CHRONOS_URL env var)
734
+ -k, --api-key: Plato API key for authentication (or PLATO_API_KEY env var)
735
735
  """
736
736
  # Set defaults
737
737
  if not chronos_url:
@@ -784,32 +784,20 @@ def dev(
784
784
  typer.Option("--env-timeout", help="Timeout for environment creation (seconds)"),
785
785
  ] = 7200,
786
786
  ):
787
- """
788
- Run a world locally for development/debugging.
787
+ """Run a world locally for development/debugging.
789
788
 
790
- This runs the world in a Docker container with docker.sock mounted,
791
- allowing the world to spawn agent containers.
789
+ Builds and runs the world in a Docker container with docker.sock mounted,
790
+ allowing the world to spawn agent containers. Mounts local source code for
791
+ live development without rebuilding.
792
792
 
793
- \b
794
- Config format (same as Chronos launch):
795
- {
796
- "world": {
797
- "package": "plato-world-structured-execution:0.1.17",
798
- "config": {
799
- "skill_runner": {
800
- "image": "computer-use:1.1.0",
801
- "config": { ... }
802
- },
803
- "secrets": { ... }
804
- }
805
- },
806
- "runtime": {
807
- "artifact_id": "..."
808
- }
809
- }
793
+ Arguments:
794
+ config: Path to job config JSON file (same format as 'plato chronos launch')
810
795
 
811
- Example:
812
- plato chronos dev config.json -w ~/worlds/my-world -a ~/agents --platform linux/amd64
796
+ Options:
797
+ -w, --world-dir: Directory containing world source code to mount into the container
798
+ -a, --agents-dir: Directory containing agent source code to mount (optional)
799
+ -p, --platform: Docker platform for building (e.g., 'linux/amd64' for M1 Macs)
800
+ --env-timeout: Timeout in seconds for environment creation (default: 7200 = 2 hours)
813
801
  """
814
802
  logging.basicConfig(
815
803
  level=logging.INFO,
plato/v1/cli/main.py CHANGED
@@ -11,6 +11,7 @@ from dotenv import load_dotenv
11
11
  from plato.v1.cli.agent import agent_app
12
12
  from plato.v1.cli.chronos import chronos_app
13
13
  from plato.v1.cli.pm import pm_app
14
+ from plato.v1.cli.proxy import app as proxy_app
14
15
  from plato.v1.cli.sandbox import sandbox_app
15
16
  from plato.v1.cli.utils import console
16
17
  from plato.v1.cli.world import world_app
@@ -73,6 +74,7 @@ app.add_typer(pm_app, name="pm")
73
74
  app.add_typer(agent_app, name="agent")
74
75
  app.add_typer(world_app, name="world")
75
76
  app.add_typer(chronos_app, name="chronos")
77
+ app.add_typer(proxy_app, name="proxy")
76
78
 
77
79
 
78
80
  # =============================================================================
@@ -84,21 +86,13 @@ app.add_typer(chronos_app, name="chronos")
84
86
  def hub(
85
87
  ctx: typer.Context,
86
88
  ):
87
- """
88
- Launch the Plato Hub CLI (interactive TUI for managing simulators).
89
-
90
- The hub command opens the Go-based Plato CLI which provides an interactive
91
- terminal UI for browsing simulators, launching environments, and managing VMs.
89
+ """Launch the Plato Hub CLI (interactive TUI for managing simulators).
92
90
 
93
- Available subcommands:
94
- - clone <service>: Clone a service from Plato Hub
95
- - credentials: Display your Plato Hub credentials
96
- - (no args): Start interactive TUI mode
91
+ Opens the Go-based Plato CLI which provides an interactive terminal UI for
92
+ browsing simulators, launching environments, and managing VMs. Any additional
93
+ arguments are passed through to the Go CLI.
97
94
 
98
- Examples:
99
- plato hub clone espocrm
100
- plato hub credentials
101
- plato hub
95
+ Common subcommands: 'clone <service>', 'credentials', or no args for interactive mode.
102
96
  """
103
97
  # Find the bundled CLI binary
104
98
  plato_bin = _find_bundled_cli()
@@ -129,14 +123,12 @@ def hub(
129
123
  def clone(
130
124
  service: str = typer.Argument(..., help="Service name to clone (e.g., espocrm)"),
131
125
  ):
132
- """
133
- Clone a service repository from Plato Hub (Gitea).
126
+ """Clone a service repository from Plato Hub (Gitea).
134
127
 
135
- This clones the simulator source code to your local machine.
128
+ Clones the simulator source code to your local machine for development or review.
136
129
 
137
- Examples:
138
- plato clone espocrm
139
- plato clone gitea
130
+ Arguments:
131
+ service: Service name to clone (e.g., 'espocrm', 'gitea')
140
132
  """
141
133
  plato_bin = _find_bundled_cli()
142
134
  if not plato_bin:
@@ -153,13 +145,10 @@ def clone(
153
145
 
154
146
  @app.command()
155
147
  def credentials():
156
- """
157
- Display your Plato Hub (Gitea) credentials.
158
-
159
- Shows the username and password needed to access Plato's Gitea repositories.
148
+ """Display your Plato Hub (Gitea) credentials.
160
149
 
161
- Example:
162
- plato credentials
150
+ Shows the username and password needed to access Plato's Gitea repositories
151
+ for cloning and pushing simulator code.
163
152
  """
164
153
  plato_bin = _find_bundled_cli()
165
154
  if not plato_bin:
plato/v1/cli/pm.py CHANGED
@@ -6,10 +6,12 @@ import os
6
6
  import re
7
7
  import shutil
8
8
  import tempfile
9
+ from datetime import datetime, timedelta
9
10
  from pathlib import Path
10
11
 
11
12
  import httpx
12
13
  import typer
14
+ import yaml
13
15
  from rich.table import Table
14
16
 
15
17
  from plato._generated.api.v1.env import get_simulator_by_name, get_simulators
@@ -25,6 +27,7 @@ from plato._generated.models import (
25
27
  AddReviewRequest,
26
28
  AppApiV1SimulatorRoutesUpdateSimulatorRequest,
27
29
  Authentication,
30
+ Flow,
28
31
  Outcome,
29
32
  ReviewType,
30
33
  UpdateStatusRequest,
@@ -41,6 +44,7 @@ from plato.v1.cli.utils import (
41
44
  )
42
45
  from plato.v1.cli.verify import pm_verify_app
43
46
  from plato.v2.async_.client import AsyncPlato
47
+ from plato.v2.async_.flow_executor import FlowExecutor
44
48
  from plato.v2.types import Env
45
49
 
46
50
  # =============================================================================
@@ -240,44 +244,20 @@ def _list_pending_reviews(review_type: str):
240
244
 
241
245
  @list_app.command(name="base")
242
246
  def list_base():
243
- """
244
- List simulators pending base/environment review.
245
-
246
- Shows simulators waiting for environment review (status: env_review_requested).
247
- Use this to see what needs reviewing before running 'plato pm review base'.
248
-
249
- USAGE:
247
+ """List simulators pending base/environment review.
250
248
 
251
- plato pm list base
252
-
253
- OUTPUT COLUMNS:
254
-
255
- - Name: Simulator name
256
- - Assignees: Who's assigned to review
257
- - Notes: Any notes about the simulator
258
- - base_artifact_id: The artifact to review
249
+ Shows simulators with status 'env_review_requested' in a table format.
250
+ Displays name, assignees, notes, and base_artifact_id for each simulator.
259
251
  """
260
252
  _list_pending_reviews("base")
261
253
 
262
254
 
263
255
  @list_app.command(name="data")
264
256
  def list_data():
265
- """
266
- List simulators pending data review.
257
+ """List simulators pending data review.
267
258
 
268
- Shows simulators waiting for data review (status: data_review_requested).
269
- Use this to see what needs reviewing before running 'plato pm review data'.
270
-
271
- USAGE:
272
-
273
- plato pm list data
274
-
275
- OUTPUT COLUMNS:
276
-
277
- - Name: Simulator name
278
- - Assignees: Who's assigned to review
279
- - Notes: Any notes about the simulator
280
- - data_artifact_id: The artifact to review
259
+ Shows simulators with status 'data_review_requested' in a table format.
260
+ Displays name, assignees, notes, and data_artifact_id for each simulator.
281
261
  """
282
262
  _list_pending_reviews("data")
283
263
 
@@ -306,26 +286,31 @@ def review_base(
306
286
  "--skip-review",
307
287
  help="Run login flow and check state, but skip interactive review. For automated verification.",
308
288
  ),
289
+ local: str = typer.Option(
290
+ None,
291
+ "--local",
292
+ "-l",
293
+ help="Path to a local flow YAML file to run instead of the default login flow.",
294
+ ),
295
+ clock: str = typer.Option(
296
+ None,
297
+ "--clock",
298
+ help="Set fake browser time (ISO format or offset like '-30d' for 30 days ago).",
299
+ ),
309
300
  ):
310
- """
311
- Review base/environment artifact for a simulator.
312
-
313
- Opens the simulator in a browser for manual testing. After testing,
314
- you can pass (→ env_approved) or reject (→ env_in_progress).
315
-
316
- SPECIFYING SIMULATOR AND ARTIFACT:
317
-
318
- -s <simulator> Use server's base_artifact_id
319
- -s <simulator> -a <artifact-uuid> Explicit artifact
320
- -s <simulator>:<artifact-uuid> Colon notation (same as above)
301
+ """Review base/environment artifact for a simulator.
321
302
 
322
- EXAMPLES:
323
-
324
- plato pm review base -s espocrm
325
- plato pm review base -s espocrm -a e9c25ca5-1234-5678-9abc-def012345678
326
- plato pm review base -s espocrm:e9c25ca5-1234-5678-9abc-def012345678
303
+ Creates an environment from the artifact, launches a browser for testing,
304
+ runs the login flow, and checks for database mutations. After testing,
305
+ choose pass (→ env_approved) or reject (→ env_in_progress).
327
306
 
328
307
  Requires simulator status: env_review_requested
308
+
309
+ Options:
310
+ -s, --simulator: Simulator name. Supports colon notation for artifact:
311
+ '-s sim' (uses server's base_artifact_id) or '-s sim:<uuid>'
312
+ -a, --artifact: Explicit artifact UUID to review. Overrides server's value.
313
+ --skip-review: Run automated checks without interactive review session.
329
314
  """
330
315
  api_key = require_api_key()
331
316
 
@@ -403,16 +388,87 @@ def review_base(
403
388
  playwright = await async_playwright().start()
404
389
  browser = await playwright.chromium.launch(headless=False)
405
390
 
406
- try:
407
- login_result = await session.login(browser, dataset="base")
408
- page = list(login_result.pages.values())[0] if login_result.pages else None
409
- console.print("[green]✅ Logged into environment[/green]")
410
- except Exception as e:
411
- console.print(f"[yellow]⚠️ Login error: {e}[/yellow]")
391
+ # Install fake clock if requested
392
+ fake_time = None
393
+ if clock:
394
+ # Parse clock option: ISO format or offset like '-30d'
395
+ if clock.startswith("-") and clock[-1] in "dhms":
396
+ # Offset format: -30d, -1h, -30m, -60s
397
+ unit = clock[-1]
398
+ amount = int(clock[1:-1])
399
+ if unit == "d":
400
+ fake_time = datetime.now() - timedelta(days=amount)
401
+ elif unit == "h":
402
+ fake_time = datetime.now() - timedelta(hours=amount)
403
+ elif unit == "m":
404
+ fake_time = datetime.now() - timedelta(minutes=amount)
405
+ elif unit == "s":
406
+ fake_time = datetime.now() - timedelta(seconds=amount)
407
+ else:
408
+ # ISO format
409
+ fake_time = datetime.fromisoformat(clock)
410
+
411
+ console.print(f"[cyan]Setting fake browser time to:[/cyan] {fake_time.isoformat()}")
412
+
413
+ if local:
414
+ # Use local flow file instead of default login
415
+ local_path = Path(local)
416
+ if not local_path.exists():
417
+ console.print(f"[red]❌ Local flow file not found: {local}[/red]")
418
+ raise typer.Exit(1)
419
+
420
+ console.print(f"[cyan]Loading local flow from: {local}[/cyan]")
421
+ with open(local_path) as f:
422
+ flow_dict = yaml.safe_load(f)
423
+
424
+ # Find login flow (or first flow if only one)
425
+ flows = flow_dict.get("flows", [])
426
+ if not flows:
427
+ console.print("[red]❌ No flows found in flow file[/red]")
428
+ raise typer.Exit(1)
429
+
430
+ # Try to find 'login' flow, otherwise use first flow
431
+ flow_data = next((f for f in flows if f.get("name") == "login"), flows[0])
432
+ flow = Flow.model_validate(flow_data)
433
+ console.print(f"[cyan]Running flow: {flow.name}[/cyan]")
434
+
435
+ # Create page and navigate to public URL
412
436
  page = await browser.new_page()
437
+
438
+ # Install fake clock if requested
439
+ if fake_time:
440
+ await page.clock.install(time=fake_time)
441
+ console.print(f"[green]✅ Fake clock installed: {fake_time.isoformat()}[/green]")
442
+
413
443
  if public_url:
414
444
  await page.goto(public_url)
415
445
 
446
+ # Execute the flow
447
+ try:
448
+ executor = FlowExecutor(page, flow)
449
+ await executor.execute()
450
+ console.print("[green]✅ Local flow executed successfully[/green]")
451
+ except Exception as e:
452
+ console.print(f"[yellow]⚠️ Flow execution error: {e}[/yellow]")
453
+ else:
454
+ # Use default login via session.login()
455
+ if fake_time:
456
+ console.print("[yellow]⚠️ --clock with default login may not work correctly.[/yellow]")
457
+ console.print("[yellow] Use --local with a flow file for reliable clock testing.[/yellow]")
458
+ try:
459
+ login_result = await session.login(browser, dataset="base")
460
+ page = list(login_result.pages.values())[0] if login_result.pages else None
461
+ console.print("[green]✅ Logged into environment[/green]")
462
+ except Exception as e:
463
+ console.print(f"[yellow]⚠️ Login error: {e}[/yellow]")
464
+ page = await browser.new_page()
465
+ # Install fake clock on fallback page
466
+ if fake_time:
467
+ await page.clock.install(time=fake_time)
468
+ console.print(f"[green]✅ Fake clock installed: {fake_time.isoformat()}[/green]")
469
+ if public_url:
470
+ await page.goto(public_url)
471
+
416
472
  # ALWAYS check state after login to verify no mutations
417
473
  console.print("\n[cyan]Checking environment state after login...[/cyan]")
418
474
  has_mutations = False
@@ -676,25 +732,18 @@ def review_data(
676
732
  help="Artifact UUID to review. If not provided, uses server's data_artifact_id.",
677
733
  ),
678
734
  ):
679
- """
680
- Launch browser with EnvGen Recorder extension for data review.
735
+ """Launch browser with EnvGen Recorder extension for data review.
681
736
 
682
737
  Opens Chrome with the EnvGen Recorder extension installed for reviewing
683
- data artifacts. Close the browser when done.
684
-
685
- SPECIFYING SIMULATOR AND ARTIFACT:
686
-
687
- -s <simulator> Use server's data_artifact_id
688
- -s <simulator> -a <artifact-uuid> Explicit artifact
689
- -s <simulator>:<artifact-uuid> Colon notation (same as above)
690
-
691
- EXAMPLES:
692
-
693
- plato pm review data -s fathom
694
- plato pm review data -s fathom -a e9c25ca5-1234-5678-9abc-def012345678
695
- plato pm review data -s fathom:e9c25ca5-1234-5678-9abc-def012345678
738
+ data artifacts. The extension sidebar can be used to record and submit reviews.
739
+ Press Control-C to exit when done.
696
740
 
697
741
  Requires simulator status: data_review_requested
742
+
743
+ Options:
744
+ -s, --simulator: Simulator name. Supports colon notation for artifact:
745
+ '-s sim' (uses server's data_artifact_id) or '-s sim:<uuid>'
746
+ -a, --artifact: Explicit artifact UUID to review. Overrides server's value.
698
747
  """
699
748
  api_key = require_api_key()
700
749
 
@@ -924,27 +973,14 @@ def review_data(
924
973
 
925
974
  @submit_app.command(name="base")
926
975
  def submit_base():
927
- """
928
- Submit base/environment artifact for review after snapshot.
976
+ """Submit base/environment artifact for review after snapshot.
929
977
 
930
- Reads simulator name and artifact ID from .sandbox.yaml (created by
931
- plato sandbox start). Run this from the simulator directory after
932
- creating a snapshot.
933
-
934
- Transitions simulator from env_in_progress → env_review_requested.
935
-
936
- USAGE:
937
-
938
- plato pm submit base # No args needed - reads from .sandbox.yaml
939
-
940
- PREREQUISITES:
941
-
942
- 1. plato sandbox start --from-config
943
- 2. plato sandbox start-services
944
- 3. plato sandbox snapshot
945
- 4. plato pm submit base ← you are here
978
+ Reads simulator name and artifact_id from .sandbox.yaml, syncs metadata from
979
+ plato-config.yml to the server, and transitions status to env_review_requested.
980
+ Run from the simulator directory after creating a snapshot.
946
981
 
947
982
  Requires simulator status: env_in_progress
983
+ No arguments needed - reads everything from .sandbox.yaml and plato-config.yml.
948
984
  """
949
985
  api_key = require_api_key()
950
986
 
@@ -1091,22 +1127,17 @@ def submit_data(
1091
1127
  help="Artifact UUID to submit for data review (required).",
1092
1128
  ),
1093
1129
  ):
1094
- """
1095
- Submit data artifact for review after data generation.
1130
+ """Submit data artifact for review after data generation.
1096
1131
 
1097
- Transitions simulator from data_in_progress → data_review_requested.
1098
-
1099
- SPECIFYING SIMULATOR AND ARTIFACT (both required):
1100
-
1101
- -s <simulator> -a <artifact-uuid> Explicit artifact
1102
- -s <simulator>:<artifact-uuid> Colon notation (same as above)
1103
-
1104
- EXAMPLES:
1105
-
1106
- plato pm submit data -s espocrm -a e9c25ca5-1234-5678-9abc-def012345678
1107
- plato pm submit data -s espocrm:e9c25ca5-1234-5678-9abc-def012345678
1132
+ Transitions simulator from data_in_progress → data_review_requested and
1133
+ tags the artifact as 'data-pending-review'.
1108
1134
 
1109
1135
  Requires simulator status: data_in_progress
1136
+
1137
+ Options:
1138
+ -s, --simulator: Simulator name. Supports colon notation:
1139
+ '-s sim:<uuid>' or use separate -a flag
1140
+ -a, --artifact: Artifact UUID to submit (required)
1110
1141
  """
1111
1142
  api_key = require_api_key()
1112
1143