plato-sdk-v2 2.1.15__py3-none-any.whl → 2.1.17__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.
plato/sims/cli.py CHANGED
@@ -725,7 +725,7 @@ def _format_response_structure(
725
725
  return lines
726
726
 
727
727
 
728
- def cmd_info(sim_name: str) -> None:
728
+ def cmd_info(sim_name: str, job_id: str | None = None) -> None:
729
729
  """Show detailed information about a sim."""
730
730
  try:
731
731
  info = registry.get_sim_info(sim_name)
@@ -750,6 +750,15 @@ def cmd_info(sim_name: str) -> None:
750
750
  except ImportError:
751
751
  pass
752
752
 
753
+ # Handle instruction-based sims differently
754
+ if info.sim_type == "instruction":
755
+ _cmd_info_instruction(info, job_id)
756
+ else:
757
+ _cmd_info_api(info)
758
+
759
+
760
+ def _cmd_info_api(info) -> None:
761
+ """Show info for API-based sim."""
753
762
  if info.auth:
754
763
  print(f"Auth Type: {info.auth.type}")
755
764
 
@@ -768,6 +777,71 @@ def cmd_info(sim_name: str) -> None:
768
777
  print(f" client = await {info.name}.AsyncClient.create({base_url_example})")
769
778
 
770
779
 
780
+ def _cmd_info_instruction(info, job_id: str | None) -> None:
781
+ """Show info for instruction-based sim."""
782
+ print("Type: Instruction-based (no API client)")
783
+ print()
784
+
785
+ # Show available services
786
+ if info.services:
787
+ print("Services:")
788
+ for name, svc in info.services.items():
789
+ port = svc.get("port", "?")
790
+ desc = svc.get("description", "")
791
+ if job_id:
792
+ url = f"https://{job_id}--{port}.connect.plato.so"
793
+ print(f" {name}: {url}")
794
+ if desc:
795
+ print(f" {desc}")
796
+ else:
797
+ print(f" {name}: port {port}")
798
+ if desc:
799
+ print(f" {desc}")
800
+ print()
801
+
802
+ # Show env vars
803
+ if info.env_vars:
804
+ print("Environment Variables:")
805
+ for name, var_config in info.env_vars.items():
806
+ desc = var_config.get("description", "")
807
+ if "template" in var_config:
808
+ print(f" {name}: (from service URL)")
809
+ elif "default" in var_config:
810
+ print(f" {name}={var_config['default']}")
811
+ else:
812
+ print(f" {name}")
813
+ if desc:
814
+ print(f" {desc}")
815
+ print()
816
+
817
+ # Show instructions
818
+ if info.instructions:
819
+ instructions = info.instructions
820
+ if job_id and info.services:
821
+ # Build service URLs from job_id and replace placeholders
822
+ for svc_name, svc_config in info.services.items():
823
+ port = svc_config.get("port", 80)
824
+ svc_url = f"https://{job_id}--{port}.connect.plato.so"
825
+ instructions = instructions.replace(f"{{service:{svc_name}}}", svc_url)
826
+
827
+ print("Instructions:")
828
+ print(instructions)
829
+ print()
830
+
831
+ # Show usage
832
+ print("Usage:")
833
+ print(f" from plato.sims import {info.name}")
834
+ print()
835
+ print(" # Get service URLs from job ID")
836
+ print(f" service_urls = {info.name}.get_service_urls(job_id)")
837
+ print()
838
+ print(" # Get formatted instructions")
839
+ print(f" instructions = {info.name}.get_instructions(service_urls)")
840
+ print()
841
+ print(" # Get environment variables to set")
842
+ print(f" env_vars = {info.name}.get_env_vars(service_urls)")
843
+
844
+
771
845
  def cmd_endpoints(
772
846
  sim_name: str,
773
847
  spec_name: str | None = None,
@@ -950,144 +1024,203 @@ def cmd_publish(
950
1024
  pkg_dir = build_dir / "src" / "plato" / "sims" / service_name
951
1025
  pkg_dir.mkdir(parents=True)
952
1026
 
953
- # Find OpenAPI spec
954
1027
  config_dir = config_file.parent
955
- spec_file = None
956
1028
 
957
- # Priority 1: specs_dir (new format)
1029
+ # Check for instruction-based sim first
1030
+ instructions_file = None
958
1031
  if specs_dir:
959
- specs_path = config_dir / specs_dir
960
- for candidate in ["openapi.json", "openapi.yaml", "openapi.yml"]:
961
- candidate_path = specs_path / candidate
962
- if candidate_path.exists():
963
- spec_file = candidate_path
964
- break
965
- # Priority 2: spec_path (legacy format)
966
- elif spec_path:
967
- spec_file = config_dir / spec_path
968
- # Priority 3: Try common locations in root
969
- else:
970
- for candidate in ["openapi.yaml", "openapi.yml", "openapi.json", "spec.yaml", "spec.json"]:
971
- candidate_path = config_dir / candidate
972
- if candidate_path.exists():
973
- spec_file = candidate_path
974
- break
1032
+ instructions_path = config_dir / specs_dir / "instructions.yaml"
1033
+ if instructions_path.exists():
1034
+ instructions_file = instructions_path
1035
+ if not instructions_file:
1036
+ instructions_path = config_dir / "instructions.yaml"
1037
+ if instructions_path.exists():
1038
+ instructions_file = instructions_path
1039
+
1040
+ if instructions_file:
1041
+ # Generate instruction-based SDK
1042
+ print(f"Using instructions config: {instructions_file}")
1043
+ print("Generating instruction-based SDK...")
1044
+ try:
1045
+ import plato
1046
+ from plato._sims_generator import InstructionConfig, InstructionGenerator
975
1047
 
976
- if not spec_file or not spec_file.exists():
977
- print(
978
- "Error: OpenAPI spec not found. Set 'sdk.specs_dir' or 'sdk.spec_path' in plato-config.yml",
979
- file=sys.stderr,
980
- )
981
- sys.exit(1)
1048
+ generator_version = getattr(plato, "__version__", None)
1049
+ print(f" Generator version: {generator_version}")
982
1050
 
983
- print(f"Using OpenAPI spec: {spec_file}")
1051
+ instruction_config = InstructionConfig.from_yaml(instructions_file)
1052
+ # Override version from plato-config.yml if set
1053
+ instruction_config.version = version
1054
+
1055
+ generator = InstructionGenerator(
1056
+ config=instruction_config,
1057
+ output_path=pkg_dir,
1058
+ package_name=service_name,
1059
+ generator_version=generator_version,
1060
+ )
1061
+ generator.generate()
1062
+ print(f" Generated instruction-based SDK to: {pkg_dir}")
1063
+
1064
+ except ImportError as e:
1065
+ print(f"Error: Missing dependency for SDK generation: {e}", file=sys.stderr)
1066
+ print("Install with: uv add plato-sdk-v2", file=sys.stderr)
1067
+ sys.exit(1)
1068
+ except Exception as e:
1069
+ print(f"Error generating instruction SDK: {e}", file=sys.stderr)
1070
+ sys.exit(1)
984
1071
 
985
- # Load auth config if provided
986
- auth_yaml = None
987
- if auth_config_path:
988
- auth_file = config_dir / auth_config_path
989
- if auth_file.exists():
990
- auth_yaml = auth_file
991
1072
  else:
992
- # Try specs_dir first, then root
993
- search_dirs = []
994
- if specs_dir:
995
- search_dirs.append(config_dir / specs_dir)
996
- search_dirs.append(config_dir)
1073
+ # Generate OpenAPI-based SDK
1074
+ # Find OpenAPI spec
1075
+ spec_file = None
997
1076
 
998
- for search_dir in search_dirs:
999
- for candidate in ["auth.yaml", "auth.yml"]:
1000
- candidate_path = search_dir / candidate
1077
+ # Priority 1: specs_dir (new format)
1078
+ if specs_dir:
1079
+ specs_path = config_dir / specs_dir
1080
+ for candidate in ["openapi.json", "openapi.yaml", "openapi.yml"]:
1081
+ candidate_path = specs_path / candidate
1001
1082
  if candidate_path.exists():
1002
- auth_yaml = candidate_path
1083
+ spec_file = candidate_path
1084
+ break
1085
+ # Priority 2: spec_path (legacy format)
1086
+ elif spec_path:
1087
+ spec_file = config_dir / spec_path
1088
+ # Priority 3: Try common locations in root
1089
+ else:
1090
+ for candidate in ["openapi.yaml", "openapi.yml", "openapi.json", "spec.yaml", "spec.json"]:
1091
+ candidate_path = config_dir / candidate
1092
+ if candidate_path.exists():
1093
+ spec_file = candidate_path
1003
1094
  break
1004
- if auth_yaml:
1005
- break
1006
-
1007
- # Generate SDK code
1008
- print("Generating SDK code...")
1009
- try:
1010
- import plato
1011
- from plato._sims_generator import AuthConfig, PythonGenerator, parse_openapi
1012
1095
 
1013
- # Get current plato-sdk-v2 version
1014
- generator_version = getattr(plato, "__version__", None)
1015
- print(f" Generator version: {generator_version}")
1096
+ if not spec_file or not spec_file.exists():
1097
+ print(
1098
+ "Error: OpenAPI spec or instructions.yaml not found. "
1099
+ "Set 'sdk.specs_dir' or 'sdk.spec_path' in plato-config.yml",
1100
+ file=sys.stderr,
1101
+ )
1102
+ sys.exit(1)
1016
1103
 
1017
- # Check if .generator-version exists and matches
1018
- if specs_dir:
1019
- generator_version_file = config_dir / specs_dir / ".generator-version"
1020
- if generator_version_file.exists():
1021
- expected_version = generator_version_file.read_text().strip()
1022
- if expected_version and generator_version and expected_version != generator_version:
1023
- print(
1024
- f"Error: Generator version mismatch. "
1025
- f"Expected {expected_version} (from .generator-version), "
1026
- f"but running {generator_version}",
1027
- file=sys.stderr,
1028
- )
1029
- print(
1030
- f" Run: uvx --from 'plato-sdk-v2=={expected_version}' plato sims publish",
1031
- file=sys.stderr,
1032
- )
1033
- print(
1034
- f" Or update .generator-version to {generator_version} to use current version",
1035
- file=sys.stderr,
1036
- )
1037
- sys.exit(1)
1038
-
1039
- # Write/update .generator-version to specs dir
1040
- if generator_version:
1041
- generator_version_file.write_text(f"{generator_version}\n")
1042
- print(f" Updated {generator_version_file}")
1043
-
1044
- # Load spec
1045
- with open(spec_file) as f:
1046
- if spec_file.suffix == ".json":
1047
- spec = json.load(f)
1048
- else:
1049
- spec = yaml.safe_load(f)
1104
+ print(f"Using OpenAPI spec: {spec_file}")
1050
1105
 
1051
- # Load auth config
1052
- if auth_yaml and auth_yaml.exists():
1053
- auth = AuthConfig.from_yaml(auth_yaml)
1106
+ # Load auth config if provided
1107
+ auth_yaml = None
1108
+ if auth_config_path:
1109
+ auth_file = config_dir / auth_config_path
1110
+ if auth_file.exists():
1111
+ auth_yaml = auth_file
1054
1112
  else:
1055
- # Default auth config
1056
- auth = AuthConfig(
1057
- type="basic",
1058
- env_prefix=service_name.upper(),
1059
- )
1113
+ # Try specs_dir first, then root
1114
+ search_dirs = []
1115
+ if specs_dir:
1116
+ search_dirs.append(config_dir / specs_dir)
1117
+ search_dirs.append(config_dir)
1118
+
1119
+ for search_dir in search_dirs:
1120
+ for candidate in ["auth.yaml", "auth.yml"]:
1121
+ candidate_path = search_dir / candidate
1122
+ if candidate_path.exists():
1123
+ auth_yaml = candidate_path
1124
+ break
1125
+ if auth_yaml:
1126
+ break
1060
1127
 
1061
- # Parse and generate
1062
- api = parse_openapi(spec)
1063
- print(f" Parsed {len(api.endpoints)} endpoints")
1064
-
1065
- generator = PythonGenerator(
1066
- api=api,
1067
- output_path=pkg_dir,
1068
- spec=spec,
1069
- package_name=service_name,
1070
- auth_config=auth,
1071
- generator_version=generator_version,
1072
- )
1073
- generator.generate()
1074
- print(f" Generated to: {pkg_dir}")
1128
+ # Generate SDK code
1129
+ print("Generating SDK code...")
1130
+ try:
1131
+ import plato
1132
+ from plato._sims_generator import AuthConfig, PythonGenerator, parse_openapi
1133
+
1134
+ # Get current plato-sdk-v2 version
1135
+ generator_version = getattr(plato, "__version__", None)
1136
+ print(f" Generator version: {generator_version}")
1137
+
1138
+ # Check if .generator-version exists and matches
1139
+ if specs_dir:
1140
+ generator_version_file = config_dir / specs_dir / ".generator-version"
1141
+ if generator_version_file.exists():
1142
+ expected_version = generator_version_file.read_text().strip()
1143
+ if expected_version and generator_version and expected_version != generator_version:
1144
+ print(
1145
+ f"Error: Generator version mismatch. "
1146
+ f"Expected {expected_version} (from .generator-version), "
1147
+ f"but running {generator_version}",
1148
+ file=sys.stderr,
1149
+ )
1150
+ print(
1151
+ f" Run: uvx --from 'plato-sdk-v2=={expected_version}' plato sims publish",
1152
+ file=sys.stderr,
1153
+ )
1154
+ print(
1155
+ f" Or update .generator-version to {generator_version} to use current version",
1156
+ file=sys.stderr,
1157
+ )
1158
+ sys.exit(1)
1159
+
1160
+ # Write/update .generator-version to specs dir
1161
+ if generator_version:
1162
+ generator_version_file.write_text(f"{generator_version}\n")
1163
+ print(f" Updated {generator_version_file}")
1164
+
1165
+ # Load spec
1166
+ with open(spec_file) as f:
1167
+ if spec_file.suffix == ".json":
1168
+ spec = json.load(f)
1169
+ else:
1170
+ spec = yaml.safe_load(f)
1171
+
1172
+ # Load auth config
1173
+ if auth_yaml and auth_yaml.exists():
1174
+ auth = AuthConfig.from_yaml(auth_yaml)
1175
+ else:
1176
+ # Default auth config
1177
+ auth = AuthConfig(
1178
+ type="basic",
1179
+ env_prefix=service_name.upper(),
1180
+ )
1181
+
1182
+ # Parse and generate
1183
+ api = parse_openapi(spec)
1184
+ print(f" Parsed {len(api.endpoints)} endpoints")
1185
+
1186
+ generator = PythonGenerator(
1187
+ api=api,
1188
+ output_path=pkg_dir,
1189
+ spec=spec,
1190
+ package_name=service_name,
1191
+ auth_config=auth,
1192
+ generator_version=generator_version,
1193
+ )
1194
+ generator.generate()
1195
+ print(f" Generated to: {pkg_dir}")
1075
1196
 
1076
- except ImportError as e:
1077
- print(f"Error: Missing dependency for SDK generation: {e}", file=sys.stderr)
1078
- print("Install with: uv add plato-sdk-v2", file=sys.stderr)
1079
- sys.exit(1)
1080
- except Exception as e:
1081
- print(f"Error generating SDK: {e}", file=sys.stderr)
1082
- sys.exit(1)
1197
+ except ImportError as e:
1198
+ print(f"Error: Missing dependency for SDK generation: {e}", file=sys.stderr)
1199
+ print("Install with: uv add plato-sdk-v2", file=sys.stderr)
1200
+ sys.exit(1)
1201
+ except Exception as e:
1202
+ print(f"Error generating SDK: {e}", file=sys.stderr)
1203
+ sys.exit(1)
1083
1204
 
1084
1205
  # Create pyproject.toml with namespace package structure
1206
+ # Instruction sims need pyyaml; API sims need httpx and pydantic
1207
+ if instructions_file:
1208
+ dependencies = '["pyyaml>=6.0.0"]'
1209
+ # Include yaml file in package data
1210
+ extra_config = f"""
1211
+ [tool.hatch.build.targets.wheel.force-include]
1212
+ "src/plato/sims/{service_name}/instructions.yaml" = "plato/sims/{service_name}/instructions.yaml"
1213
+ """
1214
+ else:
1215
+ dependencies = '["httpx>=0.25.0", "pydantic>=2.0.0"]'
1216
+ extra_config = ""
1217
+
1085
1218
  pyproject_content = f'''[project]
1086
1219
  name = "{package_name}"
1087
1220
  version = "{version}"
1088
1221
  description = "{description}"
1089
1222
  requires-python = ">=3.10"
1090
- dependencies = ["httpx>=0.25.0", "pydantic>=2.0.0"]
1223
+ dependencies = {dependencies}
1091
1224
 
1092
1225
  [build-system]
1093
1226
  requires = ["hatchling"]
@@ -1095,11 +1228,38 @@ build-backend = "hatchling.build"
1095
1228
 
1096
1229
  [tool.hatch.build.targets.wheel]
1097
1230
  packages = ["src/plato"]
1098
- '''
1231
+ {extra_config}'''
1099
1232
  (build_dir / "pyproject.toml").write_text(pyproject_content)
1100
1233
 
1101
1234
  # Create README
1102
- readme_content = f"""# {package_name}
1235
+ if instructions_file:
1236
+ readme_content = f"""# {package_name}
1237
+
1238
+ Auto-generated instruction-based SDK for the {service_name} simulator.
1239
+
1240
+ ## Installation
1241
+
1242
+ ```bash
1243
+ uv add {package_name} --index-url https://plato.so/api/v2/pypi/{repo}/simple/
1244
+ ```
1245
+
1246
+ ## Usage
1247
+
1248
+ ```python
1249
+ from plato.sims.{service_name} import get_instructions, get_service_urls, setup_env
1250
+
1251
+ # Get service URLs from job ID
1252
+ service_urls = get_service_urls(job_id)
1253
+
1254
+ # Get formatted instructions
1255
+ instructions = get_instructions(service_urls)
1256
+
1257
+ # Set up environment variables
1258
+ setup_env(service_urls)
1259
+ ```
1260
+ """
1261
+ else:
1262
+ readme_content = f"""# {package_name}
1103
1263
 
1104
1264
  Auto-generated SDK for the {service_name} simulator.
1105
1265
 
@@ -1228,9 +1388,22 @@ def main(args: list[str] | None = None) -> None:
1228
1388
  elif command == "info":
1229
1389
  if len(args) < 2:
1230
1390
  print("Error: sim name required", file=sys.stderr)
1231
- print("Usage: plato sims info <sim_name>", file=sys.stderr)
1391
+ print("Usage: plato sims info <sim_name> [--job-id JOB_ID]", file=sys.stderr)
1232
1392
  sys.exit(1)
1233
- cmd_info(args[1])
1393
+
1394
+ sim_name = args[1]
1395
+ job_id = None
1396
+
1397
+ # Parse optional --job-id flag
1398
+ i = 2
1399
+ while i < len(args):
1400
+ if args[i] == "--job-id" and i + 1 < len(args):
1401
+ job_id = args[i + 1]
1402
+ i += 2
1403
+ else:
1404
+ i += 1
1405
+
1406
+ cmd_info(sim_name, job_id)
1234
1407
 
1235
1408
  elif command == "endpoints":
1236
1409
  if len(args) < 2:
plato/sims/registry.py CHANGED
@@ -38,6 +38,11 @@ class SimInfo:
38
38
  description: str | None
39
39
  auth: AuthRequirement | None
40
40
  base_url_suffix: str | None # e.g., "/api/v1" for EspoCRM
41
+ # New fields for instruction-based sims
42
+ sim_type: str = "api" # "api" or "instruction"
43
+ services: dict[str, dict[str, Any]] | None = None # {"main": {"port": 4566, "description": "..."}}
44
+ env_vars: dict[str, dict[str, Any]] | None = None # {"AWS_ENDPOINT_URL": {"template": "...", "description": "..."}}
45
+ instructions: str | None = None # Markdown instructions template
41
46
 
42
47
 
43
48
  class SimsRegistry:
@@ -76,8 +81,9 @@ class SimsRegistry:
76
81
  # Try to import the module to verify it's a valid sim
77
82
  mod = importlib.import_module(modname)
78
83
 
79
- # Check if it looks like a sim (has Client or AsyncClient)
80
- if hasattr(mod, "Client") or hasattr(mod, "AsyncClient"):
84
+ # Check if it looks like an API sim (has Client or AsyncClient)
85
+ # or an instruction sim (has SERVICES)
86
+ if hasattr(mod, "Client") or hasattr(mod, "AsyncClient") or hasattr(mod, "SERVICES"):
81
87
  self._installed_sims[short_name] = mod
82
88
  except ImportError:
83
89
  continue
@@ -98,8 +104,10 @@ class SimsRegistry:
98
104
  # Check local specs dir if provided
99
105
  if self.specs_dir and self.specs_dir.exists():
100
106
  for d in self.specs_dir.iterdir():
101
- if d.is_dir() and (d / "auth.yaml").exists():
102
- sims.add(d.name)
107
+ if d.is_dir():
108
+ # Check for API sim (auth.yaml) or instruction sim (instructions.yaml)
109
+ if (d / "auth.yaml").exists() or (d / "instructions.yaml").exists():
110
+ sims.add(d.name)
103
111
 
104
112
  return sorted(sims)
105
113
 
@@ -120,6 +128,11 @@ class SimsRegistry:
120
128
  except Exception:
121
129
  version = getattr(mod, "__version__", "unknown")
122
130
 
131
+ # Check if this is an instruction-based sim
132
+ if hasattr(mod, "SERVICES"):
133
+ return self._get_instruction_sim_info(name, mod, version)
134
+
135
+ # It's an API-based sim
123
136
  # Try to load auth config from package
124
137
  auth = None
125
138
  try:
@@ -141,12 +154,18 @@ class SimsRegistry:
141
154
  description=mod.__doc__,
142
155
  auth=auth,
143
156
  base_url_suffix=base_url_suffix,
157
+ sim_type="api",
144
158
  )
145
159
 
146
160
  # Fall back to local specs dir
147
161
  if self.specs_dir:
148
162
  sim_dir = self.specs_dir / name
149
163
  auth_path = sim_dir / "auth.yaml"
164
+ instructions_path = sim_dir / "instructions.yaml"
165
+
166
+ # Check for instruction-based sim first
167
+ if instructions_path.exists():
168
+ return self._load_instruction_sim_from_file(name, instructions_path)
150
169
 
151
170
  if auth_path.exists():
152
171
  auth = self._load_auth(auth_path)
@@ -157,10 +176,65 @@ class SimsRegistry:
157
176
  description=None,
158
177
  auth=auth,
159
178
  base_url_suffix=None,
179
+ sim_type="api",
160
180
  )
161
181
 
162
182
  raise ValueError(f"Sim '{name}' not found")
163
183
 
184
+ def _get_instruction_sim_info(self, name: str, mod: Any, version: str) -> SimInfo:
185
+ """Get SimInfo for an instruction-based sim from its module."""
186
+ import importlib.resources
187
+
188
+ services = getattr(mod, "SERVICES", {})
189
+ env_prefix = getattr(mod, "ENV_PREFIX", "")
190
+
191
+ # Try to load full config from bundled instructions.yaml
192
+ env_vars_config: dict[str, dict[str, Any]] = {}
193
+ instructions_text = ""
194
+ title = name.title()
195
+ description = mod.__doc__
196
+
197
+ try:
198
+ config_text = importlib.resources.files(mod.__name__).joinpath("instructions.yaml").read_text()
199
+ config_data = yaml.safe_load(config_text)
200
+ env_vars_config = config_data.get("env_vars", {})
201
+ instructions_text = config_data.get("instructions", "")
202
+ title = config_data.get("title", name.title())
203
+ description = config_data.get("description", mod.__doc__)
204
+ except Exception:
205
+ pass
206
+
207
+ return SimInfo(
208
+ name=name,
209
+ title=title,
210
+ version=version,
211
+ description=description,
212
+ auth=None, # Instruction sims don't use auth config
213
+ base_url_suffix=None,
214
+ sim_type="instruction",
215
+ services=services,
216
+ env_vars=env_vars_config,
217
+ instructions=instructions_text,
218
+ )
219
+
220
+ def _load_instruction_sim_from_file(self, name: str, instructions_path: Path) -> SimInfo:
221
+ """Load instruction sim info from instructions.yaml file."""
222
+ with open(instructions_path) as f:
223
+ data = yaml.safe_load(f)
224
+
225
+ return SimInfo(
226
+ name=name,
227
+ title=data.get("title", name.title()),
228
+ version=data.get("version", "unknown"),
229
+ description=data.get("description"),
230
+ auth=None,
231
+ base_url_suffix=None,
232
+ sim_type="instruction",
233
+ services=data.get("services", {}),
234
+ env_vars=data.get("env_vars", {}),
235
+ instructions=data.get("instructions", ""),
236
+ )
237
+
164
238
  def _load_auth(self, auth_path: Path) -> AuthRequirement:
165
239
  """Load auth requirements from auth.yaml."""
166
240
  with open(auth_path) as f:
plato/v1/cli/pm.py CHANGED
@@ -7,14 +7,17 @@ import re
7
7
  import shutil
8
8
  import tempfile
9
9
  from pathlib import Path
10
+ from typing import TYPE_CHECKING
10
11
 
11
12
  import httpx
12
13
  import typer
14
+ from rich.table import Table
13
15
 
14
16
  # UUID pattern for detecting artifact IDs in sim:artifact notation
15
17
  UUID_PATTERN = re.compile(r"^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$", re.IGNORECASE)
16
- from playwright.async_api import async_playwright
17
- from rich.table import Table
18
+
19
+ if TYPE_CHECKING:
20
+ pass
18
21
 
19
22
  from plato._generated.api.v1.env import get_simulator_by_name, get_simulators
20
23
  from plato._generated.api.v1.organization import get_organization_members
@@ -390,6 +393,8 @@ def review_base(
390
393
 
391
394
  # Launch Playwright browser and login
392
395
  console.print("[cyan]Launching browser and logging in...[/cyan]")
396
+ from playwright.async_api import async_playwright
397
+
393
398
  playwright = await async_playwright().start()
394
399
  browser = await playwright.chromium.launch(headless=False)
395
400
 
@@ -755,6 +760,8 @@ def review_data(
755
760
 
756
761
  console.print("[cyan]Launching Chrome with EnvGen Recorder extension...[/cyan]")
757
762
 
763
+ from playwright.async_api import async_playwright
764
+
758
765
  playwright = await async_playwright().start()
759
766
 
760
767
  browser = await playwright.chromium.launch_persistent_context(
plato/v1/cli/sandbox.py CHANGED
@@ -13,13 +13,16 @@ import tempfile
13
13
  import time
14
14
  from datetime import datetime, timezone
15
15
  from pathlib import Path
16
+ from typing import TYPE_CHECKING
16
17
  from urllib.parse import quote
17
18
 
18
19
  import typer
19
20
  import yaml
20
- from playwright.async_api import async_playwright
21
21
  from rich.logging import RichHandler
22
22
 
23
+ if TYPE_CHECKING:
24
+ pass
25
+
23
26
  # UUID pattern for detecting artifact IDs in colon notation
24
27
  UUID_PATTERN = re.compile(r"^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$", re.IGNORECASE)
25
28
 
@@ -1534,6 +1537,8 @@ def sandbox_flow(
1534
1537
  console.print(f"[cyan]Flow name: {flow_name}[/cyan]")
1535
1538
 
1536
1539
  async def _run():
1540
+ from playwright.async_api import async_playwright
1541
+
1537
1542
  browser = None
1538
1543
  try:
1539
1544
  async with async_playwright() as p: