davinci-resolve-mcp 2.23.0

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 (92) hide show
  1. package/AGENTS.md +85 -0
  2. package/CHANGELOG.md +802 -0
  3. package/CLAUDE.md +15 -0
  4. package/LICENSE +21 -0
  5. package/README.md +159 -0
  6. package/SECURITY.md +53 -0
  7. package/bin/davinci-resolve-mcp.mjs +376 -0
  8. package/docs/README.md +56 -0
  9. package/docs/SKILL.md +1145 -0
  10. package/docs/authoring/fuse-dctl-authoring.md +242 -0
  11. package/docs/authoring/script-plugin-authoring.md +195 -0
  12. package/docs/contributing.md +82 -0
  13. package/docs/guides/color-decision-guide.md +387 -0
  14. package/docs/guides/editorial-decision-guide.md +136 -0
  15. package/docs/guides/media-analysis-guide.md +615 -0
  16. package/docs/guides/multicam-setup-guide.md +138 -0
  17. package/docs/install.md +198 -0
  18. package/docs/integrations/workflow-integrations.md +120 -0
  19. package/docs/kernels/README.md +28 -0
  20. package/docs/kernels/audio-fairlight-kernel.md +86 -0
  21. package/docs/kernels/color-grade-kernel.md +103 -0
  22. package/docs/kernels/extension-authoring-kernel.md +101 -0
  23. package/docs/kernels/fusion-composition-kernel.md +91 -0
  24. package/docs/kernels/media-pool-ingest-kernel.md +147 -0
  25. package/docs/kernels/project-lifecycle-kernel.md +120 -0
  26. package/docs/kernels/render-deliver-kernel.md +92 -0
  27. package/docs/kernels/review-annotation-kernel.md +110 -0
  28. package/docs/kernels/timeline-conform-interchange-kernel.md +99 -0
  29. package/docs/kernels/timeline-edit-kernel.md +189 -0
  30. package/docs/notes/codec-plugin-notes.md +136 -0
  31. package/docs/notes/dctl-notes.md +234 -0
  32. package/docs/notes/fusion-template-notes.md +136 -0
  33. package/docs/notes/lut-notes.md +136 -0
  34. package/docs/notes/openfx-notes.md +120 -0
  35. package/docs/process/release-process.md +152 -0
  36. package/docs/reference/api-coverage.md +488 -0
  37. package/docs/reference/resolve_scripting_api.txt +1012 -0
  38. package/examples/README.md +53 -0
  39. package/examples/markers/README.md +81 -0
  40. package/examples/media/README.md +94 -0
  41. package/examples/timeline/README.md +98 -0
  42. package/install.py +1196 -0
  43. package/package.json +52 -0
  44. package/scripts/audit_api_parity.py +275 -0
  45. package/scripts/live_media_analysis_polish_probe.py +65 -0
  46. package/src/__init__.py +3 -0
  47. package/src/analysis_dashboard.py +4936 -0
  48. package/src/control_panel.py +13 -0
  49. package/src/granular/__init__.py +17 -0
  50. package/src/granular/common.py +727 -0
  51. package/src/granular/folder.py +287 -0
  52. package/src/granular/gallery.py +306 -0
  53. package/src/granular/graph.py +309 -0
  54. package/src/granular/media_pool.py +679 -0
  55. package/src/granular/media_pool_item.py +852 -0
  56. package/src/granular/media_storage.py +179 -0
  57. package/src/granular/project.py +1594 -0
  58. package/src/granular/resolve_control.py +521 -0
  59. package/src/granular/timeline.py +1074 -0
  60. package/src/granular/timeline_item.py +2251 -0
  61. package/src/resolve_mcp_server.py +43 -0
  62. package/src/server.py +15691 -0
  63. package/src/utils/__init__.py +3 -0
  64. package/src/utils/app_control.py +319 -0
  65. package/src/utils/audio_fairlight_live_probe.py +263 -0
  66. package/src/utils/cdl.py +20 -0
  67. package/src/utils/cloud_operations.py +192 -0
  68. package/src/utils/color_grade_live_probe.py +444 -0
  69. package/src/utils/dctl_templates.py +368 -0
  70. package/src/utils/extension_authoring_live_probe.py +292 -0
  71. package/src/utils/fuse_templates.py +1968 -0
  72. package/src/utils/fusion_composition_live_probe.py +284 -0
  73. package/src/utils/layout_presets.py +333 -0
  74. package/src/utils/mcp_stdio.py +32 -0
  75. package/src/utils/media_analysis.py +3618 -0
  76. package/src/utils/media_analysis_jobs.py +796 -0
  77. package/src/utils/media_pool_ingest_live_probe.py +592 -0
  78. package/src/utils/multicam.py +393 -0
  79. package/src/utils/object_inspection.py +287 -0
  80. package/src/utils/platform.py +157 -0
  81. package/src/utils/project_lifecycle_live_probe.py +376 -0
  82. package/src/utils/project_properties.py +601 -0
  83. package/src/utils/render_deliver_live_probe.py +384 -0
  84. package/src/utils/resolve_connection.py +77 -0
  85. package/src/utils/review_annotation_live_probe.py +352 -0
  86. package/src/utils/script_templates.py +1193 -0
  87. package/src/utils/sync_detection.py +887 -0
  88. package/src/utils/timeline_conform_live_probe.py +280 -0
  89. package/src/utils/timeline_kernel_live_probe.py +1091 -0
  90. package/src/utils/timeline_kernel_probe.py +185 -0
  91. package/src/utils/timeline_title_text.py +87 -0
  92. package/src/utils/update_check.py +610 -0
@@ -0,0 +1,384 @@
1
+ #!/usr/bin/env python3
2
+ """Live Render / Deliver boundary probe.
3
+
4
+ Creates a disposable Resolve project with generated synthetic media, probes
5
+ render format/codec/settings/job/quick-export surfaces, writes JSON/Markdown
6
+ evidence reports, deletes the project, and removes generated media/render
7
+ outputs.
8
+ """
9
+
10
+ from __future__ import annotations
11
+
12
+ import json
13
+ import platform
14
+ import shutil
15
+ import subprocess
16
+ import sys
17
+ import tempfile
18
+ import time
19
+ from pathlib import Path
20
+ from typing import Any, Dict, Optional
21
+
22
+ from src.utils.timeline_kernel_probe import ProbeRecorder, render_markdown_report, utc_timestamp
23
+
24
+
25
+ def _require_success(label: str, result: Dict[str, Any]) -> Dict[str, Any]:
26
+ if not isinstance(result, dict):
27
+ raise AssertionError(f"{label}: expected dict, got {result!r}")
28
+ if result.get("error"):
29
+ raise AssertionError(f"{label}: {result['error']}")
30
+ if "success" in result and result["success"] is not True:
31
+ raise AssertionError(f"{label}: expected success=True, got {result!r}")
32
+ return result
33
+
34
+
35
+ def _record_tool_result(
36
+ recorder: ProbeRecorder,
37
+ category: str,
38
+ name: str,
39
+ result: Dict[str, Any],
40
+ *,
41
+ partial_on_false: bool = True,
42
+ ) -> None:
43
+ if not isinstance(result, dict):
44
+ recorder.record(category, name, "error", details={"reason": "non-dict result", "result": repr(result)})
45
+ return
46
+ if result.get("error"):
47
+ recorder.record(category, name, "error", details={"reason": result.get("error")}, evidence=result)
48
+ return
49
+ if "success" in result and result["success"] is not True:
50
+ recorder.record(
51
+ category,
52
+ name,
53
+ "partially_supported" if partial_on_false else "unsupported",
54
+ details={"reason": "success returned false"},
55
+ evidence=result,
56
+ )
57
+ return
58
+ recorder.record(category, name, "supported", evidence=result)
59
+
60
+
61
+ def _run_ffmpeg(args: list[str]) -> None:
62
+ subprocess.run(["ffmpeg", "-hide_banner", "-loglevel", "error", *args], check=True)
63
+
64
+
65
+ def _make_synthetic_video(work_dir: Path) -> Path:
66
+ video = work_dir / "render_probe_source.mov"
67
+ _run_ffmpeg(
68
+ [
69
+ "-f",
70
+ "lavfi",
71
+ "-i",
72
+ "testsrc2=size=320x180:rate=24:duration=2",
73
+ "-f",
74
+ "lavfi",
75
+ "-i",
76
+ "sine=frequency=880:sample_rate=48000:duration=2",
77
+ "-shortest",
78
+ "-pix_fmt",
79
+ "yuv420p",
80
+ "-c:v",
81
+ "libx264",
82
+ "-c:a",
83
+ "aac",
84
+ "-y",
85
+ str(video),
86
+ ]
87
+ )
88
+ return video
89
+
90
+
91
+ def _first_imported_clip(imported_items):
92
+ for item in imported_items or []:
93
+ try:
94
+ if item.GetUniqueId():
95
+ return item
96
+ except Exception:
97
+ pass
98
+ return None
99
+
100
+
101
+ def _choose_render_format_codec(server) -> Dict[str, Optional[str]]:
102
+ formats_result = server.render("get_formats")
103
+ formats = formats_result.get("formats") if isinstance(formats_result, dict) else {}
104
+ if not isinstance(formats, dict) or not formats:
105
+ return {"format": None, "codec": None, "label": None}
106
+
107
+ preferred_formats = ["mp4", "QuickTime"]
108
+ format_names = [fmt for fmt in preferred_formats if fmt in formats]
109
+ format_names.extend([fmt for fmt in formats if fmt not in format_names])
110
+
111
+ for fmt in format_names:
112
+ codecs_result = server.render("get_codecs", {"format": fmt})
113
+ codecs = codecs_result.get("codecs") if isinstance(codecs_result, dict) else {}
114
+ if not isinstance(codecs, dict) or not codecs:
115
+ continue
116
+ preferred = []
117
+ for label, codec in codecs.items():
118
+ text = f"{label} {codec}".lower()
119
+ if "h.264" in text or "h264" in text:
120
+ preferred.insert(0, (label, codec))
121
+ else:
122
+ preferred.append((label, codec))
123
+ label, codec = preferred[0]
124
+ return {"format": fmt, "codec": codec, "label": label}
125
+ return {"format": None, "codec": None, "label": None}
126
+
127
+
128
+ def _render_output_files(render_dir: Path):
129
+ if not render_dir.exists():
130
+ return []
131
+ return sorted(str(path) for path in render_dir.iterdir() if path.is_file())
132
+
133
+
134
+ def run_probe(server, output_dir: Path, keep_open: bool = False) -> Dict[str, Any]:
135
+ output_dir.mkdir(parents=True, exist_ok=True)
136
+ work_dir = Path(tempfile.mkdtemp(prefix="mcp_render_deliver_probe_"))
137
+ render_dir = output_dir / "renders"
138
+ render_dir.mkdir(parents=True, exist_ok=True)
139
+ project_name = f"_mcp_render_deliver_probe_{int(time.time())}"
140
+ timeline_name = "Render Deliver Probe Timeline"
141
+ preset_name = f"_mcp_render_probe_preset_{int(time.time())}"
142
+ recorder = ProbeRecorder()
143
+ created_project = False
144
+ delete_result: Optional[Dict[str, Any]] = None
145
+
146
+ metadata: Dict[str, Any] = {
147
+ "title": "Render Deliver Kernel Capability Probe",
148
+ "timestamp_utc": utc_timestamp(),
149
+ "python": sys.version,
150
+ "platform": platform.platform(),
151
+ "output_dir": str(output_dir),
152
+ "project_name": project_name,
153
+ "render_dir": str(render_dir),
154
+ }
155
+
156
+ try:
157
+ version = _require_success("resolve_control.get_version", server.resolve_control("get_version"))
158
+ metadata.update(
159
+ {
160
+ "product": version.get("product"),
161
+ "version": version.get("version"),
162
+ "version_string": version.get("version_string"),
163
+ }
164
+ )
165
+ print(f"Connected to {metadata['product']} {metadata['version_string']}")
166
+
167
+ _require_success("project_manager.create", server.project_manager("create", {"name": project_name}))
168
+ created_project = True
169
+ print(f"Created disposable project: {project_name}")
170
+ server.resolve_control("open_page", {"page": "edit"})
171
+
172
+ video = _make_synthetic_video(work_dir)
173
+ metadata["synthetic_media"] = {"video": str(video)}
174
+ print(f"Generated synthetic media under: {work_dir}")
175
+
176
+ resolve = server.get_resolve()
177
+ project = resolve.GetProjectManager().GetCurrentProject()
178
+ media_pool = project.GetMediaPool()
179
+ imported = media_pool.ImportMedia([str(video)]) or []
180
+ clip = _first_imported_clip(imported)
181
+ if not clip:
182
+ raise AssertionError("Failed to import synthetic render media")
183
+ timeline = media_pool.CreateTimelineFromClips(timeline_name, [clip])
184
+ if not timeline:
185
+ raise AssertionError("Failed to create synthetic render timeline")
186
+ project.SetCurrentTimeline(timeline)
187
+ print(f"Created timeline: {timeline_name}")
188
+
189
+ chosen = _choose_render_format_codec(server)
190
+ metadata["chosen_format_codec"] = chosen
191
+ render_settings = {
192
+ "TargetDir": str(render_dir),
193
+ "CustomName": "mcp_render_probe",
194
+ "SelectAllFrames": True,
195
+ "ExportVideo": True,
196
+ "ExportAudio": True,
197
+ "FormatWidth": 320,
198
+ "FormatHeight": 180,
199
+ "FrameRate": 24,
200
+ "VideoQuality": "Least",
201
+ "EnableUpload": False,
202
+ }
203
+ safe_settings = {key: value for key, value in render_settings.items() if key != "EnableUpload"}
204
+
205
+ _record_tool_result(recorder, "capabilities", "render_capabilities", server.render("render_capabilities"))
206
+ _record_tool_result(recorder, "matrix", "probe_render_matrix", server.render("probe_render_matrix"))
207
+ settings_snapshot = server.render("probe_render_settings")
208
+ _record_tool_result(recorder, "settings", "probe_render_settings", settings_snapshot)
209
+ if isinstance(settings_snapshot.get("settings"), dict) and settings_snapshot["settings"].get("error"):
210
+ recorder.record(
211
+ "settings",
212
+ "get_render_settings_readback",
213
+ "version_or_page_dependent",
214
+ details={"reason": settings_snapshot["settings"]["error"]},
215
+ )
216
+ if chosen["format"] and chosen["codec"]:
217
+ _record_tool_result(
218
+ recorder,
219
+ "format_codec",
220
+ "set_format_and_codec",
221
+ server.render(
222
+ "set_format_and_codec",
223
+ {"format": chosen["format"], "codec": chosen["codec"]},
224
+ ),
225
+ )
226
+ _record_tool_result(
227
+ recorder,
228
+ "format_codec",
229
+ "get_resolutions_chosen",
230
+ server.render("get_resolutions", {"format": chosen["format"], "codec": chosen["codec"]}),
231
+ )
232
+ else:
233
+ recorder.record("format_codec", "choose_format_codec", "unsupported", details={"reason": "No render codec available"})
234
+ _record_tool_result(
235
+ recorder,
236
+ "settings",
237
+ "validate_render_settings",
238
+ server.render(
239
+ "validate_render_settings",
240
+ {"settings": safe_settings, "require_temp_target": True},
241
+ ),
242
+ )
243
+ _record_tool_result(
244
+ recorder,
245
+ "settings",
246
+ "safe_set_render_settings",
247
+ server.render(
248
+ "safe_set_render_settings",
249
+ {"settings": safe_settings, "require_temp_target": True},
250
+ ),
251
+ )
252
+
253
+ _record_tool_result(recorder, "mode", "get_mode", server.render("get_mode"))
254
+ current_mode = server.render("get_mode").get("mode", 0)
255
+ _record_tool_result(recorder, "mode", "set_mode_restore", server.render("set_mode", {"mode": current_mode}))
256
+
257
+ _record_tool_result(recorder, "presets", "list_presets", server.render("list_presets"))
258
+ _record_tool_result(recorder, "presets", "save_preset", server.render("save_preset", {"name": preset_name}))
259
+ _record_tool_result(recorder, "presets", "delete_preset", server.render("delete_preset", {"name": preset_name}))
260
+
261
+ prepare_params = {
262
+ "target_dir": str(render_dir),
263
+ "settings": safe_settings,
264
+ "custom_name": "mcp_render_probe_prepare",
265
+ "dry_run": True,
266
+ }
267
+ if chosen["format"] and chosen["codec"]:
268
+ prepare_params.update({"format": chosen["format"], "codec": chosen["codec"]})
269
+ _record_tool_result(recorder, "jobs", "prepare_render_job_dry_run", server.render("prepare_render_job", prepare_params))
270
+
271
+ lifecycle_params = {
272
+ "target_dir": str(render_dir),
273
+ "settings": safe_settings,
274
+ "custom_name": "mcp_render_probe_lifecycle",
275
+ }
276
+ if chosen["format"] and chosen["codec"]:
277
+ lifecycle_params.update({"format": chosen["format"], "codec": chosen["codec"]})
278
+ _record_tool_result(
279
+ recorder,
280
+ "jobs",
281
+ "render_job_lifecycle_probe",
282
+ server.render("render_job_lifecycle_probe", lifecycle_params),
283
+ )
284
+
285
+ actual_params = {
286
+ "target_dir": str(render_dir),
287
+ "settings": {**safe_settings, "CustomName": "mcp_render_probe_actual"},
288
+ "custom_name": "mcp_render_probe_actual",
289
+ }
290
+ if chosen["format"] and chosen["codec"]:
291
+ actual_params.update({"format": chosen["format"], "codec": chosen["codec"]})
292
+ prepared = server.render("prepare_render_job", actual_params)
293
+ _record_tool_result(recorder, "jobs", "prepare_render_job", prepared)
294
+ job_id = prepared.get("job_id") if isinstance(prepared, dict) else None
295
+ if job_id:
296
+ _record_tool_result(recorder, "jobs", "get_job_status_before_start", server.render("get_job_status", {"job_id": job_id}))
297
+ start_result = server.render("start", {"job_ids": [job_id], "interactive": False})
298
+ _record_tool_result(recorder, "jobs", "start_rendering", start_result)
299
+ deadline = time.time() + 45
300
+ while time.time() < deadline:
301
+ rendering = server.render("is_rendering")
302
+ if not rendering.get("rendering"):
303
+ break
304
+ time.sleep(1)
305
+ final_rendering = server.render("is_rendering")
306
+ if final_rendering.get("rendering"):
307
+ server.render("stop")
308
+ recorder.record(
309
+ "jobs",
310
+ "render_completion",
311
+ "partially_supported",
312
+ details={"reason": "Render exceeded 45 second probe timeout and was stopped"},
313
+ )
314
+ else:
315
+ files = _render_output_files(render_dir)
316
+ status = "supported" if files else "write_only_unverifiable"
317
+ recorder.record("jobs", "render_completion", status, evidence={"files": files})
318
+ _record_tool_result(recorder, "jobs", "get_job_status_after_start", server.render("get_job_status", {"job_id": job_id}))
319
+ _record_tool_result(recorder, "jobs", "delete_render_job", server.render("delete_job", {"job_id": job_id}))
320
+
321
+ quick_caps = server.render("quick_export_capabilities")
322
+ _record_tool_result(recorder, "quick_export", "quick_export_capabilities", quick_caps)
323
+ quick_presets = quick_caps.get("presets") if isinstance(quick_caps, dict) else []
324
+ if quick_presets:
325
+ preset = quick_presets[0] if isinstance(quick_presets[0], str) else quick_presets[0].get("Name")
326
+ _record_tool_result(
327
+ recorder,
328
+ "quick_export",
329
+ "safe_quick_export_dry_run",
330
+ server.render(
331
+ "safe_quick_export",
332
+ {
333
+ "preset": preset,
334
+ "target_dir": str(render_dir),
335
+ "custom_name": "mcp_quick_export_probe",
336
+ "dry_run": True,
337
+ },
338
+ ),
339
+ )
340
+ else:
341
+ recorder.record("quick_export", "safe_quick_export_dry_run", "not_applicable", details={"reason": "No quick export presets"})
342
+
343
+ _record_tool_result(
344
+ recorder,
345
+ "report",
346
+ "export_render_boundary_report",
347
+ server.render("export_render_boundary_report", {"max_pairs": 20}),
348
+ )
349
+
350
+ if keep_open:
351
+ server.project_manager("save")
352
+ print(f"LEFT PROJECT OPEN FOR INSPECTION: {project_name}")
353
+ created_project = False
354
+
355
+ finally:
356
+ if created_project:
357
+ server.project_manager("save")
358
+ server.project_manager("close")
359
+ delete_result = server.project_manager("delete", {"name": project_name})
360
+ print(f"Deleted disposable project: {delete_result}")
361
+
362
+ report = recorder.to_report(
363
+ metadata,
364
+ {
365
+ "json": str(output_dir / "render-deliver-probe.json"),
366
+ "markdown": str(output_dir / "render-deliver-probe.md"),
367
+ },
368
+ )
369
+ json_path = output_dir / "render-deliver-probe.json"
370
+ markdown_path = output_dir / "render-deliver-probe.md"
371
+ json_path.write_text(json.dumps(report, indent=2, sort_keys=True), encoding="utf-8")
372
+ markdown_path.write_text(render_markdown_report(report), encoding="utf-8")
373
+ print(f"Wrote JSON report: {json_path}")
374
+ print(f"Wrote Markdown report: {markdown_path}")
375
+ print(f"Counts: {json.dumps(report['counts'], sort_keys=True)}")
376
+ if not keep_open:
377
+ shutil.rmtree(work_dir, ignore_errors=True)
378
+ shutil.rmtree(render_dir, ignore_errors=True)
379
+ print(f"Removed synthetic media directory: {work_dir}")
380
+ print(f"Removed render output directory: {render_dir}")
381
+
382
+ if delete_result and delete_result.get("success") is not True:
383
+ raise AssertionError(f"Cleanup failed for {project_name}: {delete_result!r}")
384
+ return report
@@ -0,0 +1,77 @@
1
+ #!/usr/bin/env python3
2
+ """
3
+ DaVinci Resolve Connection Utilities
4
+ """
5
+
6
+ import os
7
+ import logging
8
+ from .platform import get_platform, get_resolve_paths, setup_environment
9
+
10
+ logger = logging.getLogger("davinci-resolve-mcp.connection")
11
+
12
+ def initialize_resolve():
13
+ """Initialize connection to DaVinci Resolve application."""
14
+ try:
15
+ # Import the DaVinci Resolve scripting module
16
+ import DaVinciResolveScript as dvr_script
17
+
18
+ # Get the resolve object
19
+ resolve = dvr_script.scriptapp("Resolve")
20
+
21
+ if resolve is None:
22
+ logger.error("Failed to get Resolve object. Is DaVinci Resolve running?")
23
+ return None
24
+
25
+ logger.info(f"Connected to DaVinci Resolve: {resolve.GetProductName()} {resolve.GetVersionString()}")
26
+ return resolve
27
+
28
+ except ImportError:
29
+ platform_name = get_platform()
30
+ paths = get_resolve_paths()
31
+
32
+ logger.error("Failed to import DaVinciResolveScript. Check environment variables.")
33
+ logger.error("RESOLVE_SCRIPT_API, RESOLVE_SCRIPT_LIB, and PYTHONPATH must be set correctly.")
34
+
35
+ if platform_name == 'darwin':
36
+ logger.error("On macOS, typically:")
37
+ logger.error(f'export RESOLVE_SCRIPT_API="{paths["api_path"]}"')
38
+ logger.error(f'export RESOLVE_SCRIPT_LIB="{paths["lib_path"]}"')
39
+ logger.error(f'export PYTHONPATH="$PYTHONPATH:{paths["modules_path"]}"')
40
+ elif platform_name == 'windows':
41
+ logger.error("On Windows, typically:")
42
+ logger.error(f'set RESOLVE_SCRIPT_API={paths["api_path"]}')
43
+ logger.error(f'set RESOLVE_SCRIPT_LIB={paths["lib_path"]}')
44
+ logger.error(f'set PYTHONPATH=%PYTHONPATH%;{paths["modules_path"]}')
45
+ elif platform_name == 'linux':
46
+ logger.error("On Linux, typically:")
47
+ logger.error(f'export RESOLVE_SCRIPT_API="{paths["api_path"]}"')
48
+ logger.error(f'export RESOLVE_SCRIPT_LIB="{paths["lib_path"]}"')
49
+ logger.error(f'export PYTHONPATH="$PYTHONPATH:{paths["modules_path"]}"')
50
+
51
+ return None
52
+
53
+ except Exception as e:
54
+ logger.error(f"Unexpected error initializing Resolve: {str(e)}")
55
+ return None
56
+
57
+ def check_environment_variables():
58
+ """Check if the required environment variables are set."""
59
+ resolve_script_api = os.environ.get("RESOLVE_SCRIPT_API")
60
+ resolve_script_lib = os.environ.get("RESOLVE_SCRIPT_LIB")
61
+
62
+ missing_vars = []
63
+ if not resolve_script_api:
64
+ missing_vars.append("RESOLVE_SCRIPT_API")
65
+ if not resolve_script_lib:
66
+ missing_vars.append("RESOLVE_SCRIPT_LIB")
67
+
68
+ return {
69
+ "all_set": len(missing_vars) == 0,
70
+ "missing": missing_vars,
71
+ "resolve_script_api": resolve_script_api,
72
+ "resolve_script_lib": resolve_script_lib
73
+ }
74
+
75
+ def set_default_environment_variables():
76
+ """Set the default environment variables based on platform."""
77
+ return setup_environment()