clerk-sdk 0.5.2__tar.gz → 0.5.4__tar.gz

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 (85) hide show
  1. {clerk_sdk-0.5.2 → clerk_sdk-0.5.4}/PKG-INFO +2 -1
  2. {clerk_sdk-0.5.2 → clerk_sdk-0.5.4}/clerk/__init__.py +1 -1
  3. {clerk_sdk-0.5.2 → clerk_sdk-0.5.4}/clerk/development/cli.py +57 -4
  4. clerk_sdk-0.5.4/clerk/development/code_runner.py +384 -0
  5. clerk_sdk-0.5.4/clerk/development/gui/graph_checker.py +216 -0
  6. {clerk_sdk-0.5.2 → clerk_sdk-0.5.4}/clerk/development/gui/test_session.py +3 -3
  7. {clerk_sdk-0.5.2 → clerk_sdk-0.5.4}/clerk/development/init_project.py +22 -0
  8. clerk_sdk-0.5.4/clerk/development/templates/launch.json.template +20 -0
  9. {clerk_sdk-0.5.2 → clerk_sdk-0.5.4}/clerk/development/templates/main_gui.py.template +3 -3
  10. clerk_sdk-0.5.4/clerk/development/templates/tasks.json.template +25 -0
  11. clerk_sdk-0.5.4/clerk/development/templates/test_payload.py.template +32 -0
  12. {clerk_sdk-0.5.2 → clerk_sdk-0.5.4}/clerk/gui_automation/client_actor/client_actor.py +1 -1
  13. {clerk_sdk-0.5.2 → clerk_sdk-0.5.4}/pyproject.toml +2 -1
  14. {clerk_sdk-0.5.2 → clerk_sdk-0.5.4}/requirements.txt +1 -0
  15. {clerk_sdk-0.5.2 → clerk_sdk-0.5.4}/setup.py +1 -1
  16. {clerk_sdk-0.5.2 → clerk_sdk-0.5.4}/uv.lock +27 -0
  17. {clerk_sdk-0.5.2 → clerk_sdk-0.5.4}/.github/workflows/ci.yaml +0 -0
  18. {clerk_sdk-0.5.2 → clerk_sdk-0.5.4}/.github/workflows/pypi_publish.yml +0 -0
  19. {clerk_sdk-0.5.2 → clerk_sdk-0.5.4}/.gitignore +0 -0
  20. {clerk_sdk-0.5.2 → clerk_sdk-0.5.4}/LICENSE +0 -0
  21. {clerk_sdk-0.5.2 → clerk_sdk-0.5.4}/MANIFEST.in +0 -0
  22. {clerk_sdk-0.5.2 → clerk_sdk-0.5.4}/README.md +0 -0
  23. {clerk_sdk-0.5.2 → clerk_sdk-0.5.4}/clerk/base.py +0 -0
  24. {clerk_sdk-0.5.2 → clerk_sdk-0.5.4}/clerk/client.py +0 -0
  25. {clerk_sdk-0.5.2 → clerk_sdk-0.5.4}/clerk/decorator/__init__.py +0 -0
  26. {clerk_sdk-0.5.2 → clerk_sdk-0.5.4}/clerk/decorator/models.py +0 -0
  27. {clerk_sdk-0.5.2 → clerk_sdk-0.5.4}/clerk/decorator/task_decorator.py +0 -0
  28. {clerk_sdk-0.5.2 → clerk_sdk-0.5.4}/clerk/development/__init__.py +0 -0
  29. {clerk_sdk-0.5.2 → clerk_sdk-0.5.4}/clerk/development/schema/fetch_schema.py +0 -0
  30. {clerk_sdk-0.5.2 → clerk_sdk-0.5.4}/clerk/development/templates/exceptions.py.template +0 -0
  31. {clerk_sdk-0.5.2 → clerk_sdk-0.5.4}/clerk/development/templates/main_basic.py.template +0 -0
  32. {clerk_sdk-0.5.2 → clerk_sdk-0.5.4}/clerk/development/templates/rollbacks.py.template +0 -0
  33. {clerk_sdk-0.5.2 → clerk_sdk-0.5.4}/clerk/development/templates/states.py.template +0 -0
  34. {clerk_sdk-0.5.2 → clerk_sdk-0.5.4}/clerk/development/templates/transitions.py.template +0 -0
  35. {clerk_sdk-0.5.2 → clerk_sdk-0.5.4}/clerk/exceptions/__init__.py +0 -0
  36. {clerk_sdk-0.5.2 → clerk_sdk-0.5.4}/clerk/exceptions/exceptions.py +0 -0
  37. {clerk_sdk-0.5.2 → clerk_sdk-0.5.4}/clerk/exceptions/remote_device.py +0 -0
  38. {clerk_sdk-0.5.2 → clerk_sdk-0.5.4}/clerk/gui_automation/__init__.py +0 -0
  39. {clerk_sdk-0.5.2 → clerk_sdk-0.5.4}/clerk/gui_automation/action_model/__init__.py +0 -0
  40. {clerk_sdk-0.5.2 → clerk_sdk-0.5.4}/clerk/gui_automation/action_model/model.py +0 -0
  41. {clerk_sdk-0.5.2 → clerk_sdk-0.5.4}/clerk/gui_automation/action_model/utils.py +0 -0
  42. {clerk_sdk-0.5.2 → clerk_sdk-0.5.4}/clerk/gui_automation/client.py +0 -0
  43. {clerk_sdk-0.5.2 → clerk_sdk-0.5.4}/clerk/gui_automation/client_actor/__init__.py +0 -0
  44. {clerk_sdk-0.5.2 → clerk_sdk-0.5.4}/clerk/gui_automation/client_actor/exception.py +0 -0
  45. {clerk_sdk-0.5.2 → clerk_sdk-0.5.4}/clerk/gui_automation/client_actor/model.py +0 -0
  46. {clerk_sdk-0.5.2 → clerk_sdk-0.5.4}/clerk/gui_automation/decorators/__init__.py +0 -0
  47. {clerk_sdk-0.5.2 → clerk_sdk-0.5.4}/clerk/gui_automation/decorators/gui_automation.py +0 -0
  48. {clerk_sdk-0.5.2 → clerk_sdk-0.5.4}/clerk/gui_automation/exceptions/__init__.py +0 -0
  49. {clerk_sdk-0.5.2 → clerk_sdk-0.5.4}/clerk/gui_automation/exceptions/agent_manager.py +0 -0
  50. {clerk_sdk-0.5.2 → clerk_sdk-0.5.4}/clerk/gui_automation/exceptions/modality/__init__.py +0 -0
  51. {clerk_sdk-0.5.2 → clerk_sdk-0.5.4}/clerk/gui_automation/exceptions/modality/exc.py +0 -0
  52. {clerk_sdk-0.5.2 → clerk_sdk-0.5.4}/clerk/gui_automation/exceptions/websocket.py +0 -0
  53. {clerk_sdk-0.5.2 → clerk_sdk-0.5.4}/clerk/gui_automation/requirements.txt +0 -0
  54. {clerk_sdk-0.5.2 → clerk_sdk-0.5.4}/clerk/gui_automation/ui_actions/__init__.py +0 -0
  55. {clerk_sdk-0.5.2 → clerk_sdk-0.5.4}/clerk/gui_automation/ui_actions/actions.py +0 -0
  56. {clerk_sdk-0.5.2 → clerk_sdk-0.5.4}/clerk/gui_automation/ui_actions/base.py +0 -0
  57. {clerk_sdk-0.5.2 → clerk_sdk-0.5.4}/clerk/gui_automation/ui_actions/support.py +0 -0
  58. {clerk_sdk-0.5.2 → clerk_sdk-0.5.4}/clerk/gui_automation/ui_state_inspector/__init__.py +0 -0
  59. {clerk_sdk-0.5.2 → clerk_sdk-0.5.4}/clerk/gui_automation/ui_state_inspector/gui_vision.py +0 -0
  60. {clerk_sdk-0.5.2 → clerk_sdk-0.5.4}/clerk/gui_automation/ui_state_inspector/models.py +0 -0
  61. {clerk_sdk-0.5.2 → clerk_sdk-0.5.4}/clerk/gui_automation/ui_state_machine/Readme.md +0 -0
  62. {clerk_sdk-0.5.2 → clerk_sdk-0.5.4}/clerk/gui_automation/ui_state_machine/__init__.py +0 -0
  63. {clerk_sdk-0.5.2 → clerk_sdk-0.5.4}/clerk/gui_automation/ui_state_machine/ai_recovery.py +0 -0
  64. {clerk_sdk-0.5.2 → clerk_sdk-0.5.4}/clerk/gui_automation/ui_state_machine/decorators.py +0 -0
  65. {clerk_sdk-0.5.2 → clerk_sdk-0.5.4}/clerk/gui_automation/ui_state_machine/exceptions.py +0 -0
  66. {clerk_sdk-0.5.2 → clerk_sdk-0.5.4}/clerk/gui_automation/ui_state_machine/state_machine.py +0 -0
  67. {clerk_sdk-0.5.2 → clerk_sdk-0.5.4}/clerk/models/__init__.py +0 -0
  68. {clerk_sdk-0.5.2 → clerk_sdk-0.5.4}/clerk/models/document.py +0 -0
  69. {clerk_sdk-0.5.2 → clerk_sdk-0.5.4}/clerk/models/document_statuses.py +0 -0
  70. {clerk_sdk-0.5.2 → clerk_sdk-0.5.4}/clerk/models/file.py +0 -0
  71. {clerk_sdk-0.5.2 → clerk_sdk-0.5.4}/clerk/models/remote_device.py +0 -0
  72. {clerk_sdk-0.5.2 → clerk_sdk-0.5.4}/clerk/models/response_model.py +0 -0
  73. {clerk_sdk-0.5.2 → clerk_sdk-0.5.4}/clerk/models/ui_operator.py +0 -0
  74. {clerk_sdk-0.5.2 → clerk_sdk-0.5.4}/clerk/utils/__init__.py +0 -0
  75. {clerk_sdk-0.5.2 → clerk_sdk-0.5.4}/clerk/utils/logger.py +0 -0
  76. {clerk_sdk-0.5.2 → clerk_sdk-0.5.4}/clerk/utils/save_artifact.py +0 -0
  77. {clerk_sdk-0.5.2 → clerk_sdk-0.5.4}/tests/conftest.py +0 -0
  78. {clerk_sdk-0.5.2 → clerk_sdk-0.5.4}/tests/test_base.py +0 -0
  79. {clerk_sdk-0.5.2 → clerk_sdk-0.5.4}/tests/test_client.py +0 -0
  80. {clerk_sdk-0.5.2 → clerk_sdk-0.5.4}/tests/test_document_models.py +0 -0
  81. {clerk_sdk-0.5.2 → clerk_sdk-0.5.4}/tests/test_exceptions.py +0 -0
  82. {clerk_sdk-0.5.2 → clerk_sdk-0.5.4}/tests/test_file_models.py +0 -0
  83. {clerk_sdk-0.5.2 → clerk_sdk-0.5.4}/tests/test_gui_automation.py +0 -0
  84. {clerk_sdk-0.5.2 → clerk_sdk-0.5.4}/tests/test_task_decorator.py +0 -0
  85. {clerk_sdk-0.5.2 → clerk_sdk-0.5.4}/tests/test_utils.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: clerk-sdk
3
- Version: 0.5.2
3
+ Version: 0.5.4
4
4
  Summary: Library for interacting with Clerk
5
5
  Project-URL: Homepage, https://github.com/F-ONE-Group/clerk_pypi
6
6
  Author-email: F-One <contact@f-one.group>
@@ -10,6 +10,7 @@ Classifier: Operating System :: OS Independent
10
10
  Classifier: Programming Language :: Python :: 3
11
11
  Requires-Python: >=3.11
12
12
  Requires-Dist: backoff<3.0.0,>=2.0.0
13
+ Requires-Dist: debugpy<2.0.0,>=1.8.0
13
14
  Requires-Dist: pydantic<3.0.0,>=2.0.0
14
15
  Requires-Dist: python-dotenv>=1.0.0
15
16
  Requires-Dist: requests<3.0.0,>=2.32.3
@@ -1,4 +1,4 @@
1
1
  from .client import Clerk
2
2
 
3
3
 
4
- __version__ = "0.5.2"
4
+ __version__ = "0.5.4"
@@ -53,26 +53,58 @@ def main():
53
53
  help="GUI automation commands"
54
54
  )
55
55
  gui_subparsers = gui_parser.add_subparsers(dest="gui_command", help="GUI subcommands")
56
-
56
+
57
57
  # GUI connect subcommand
58
58
  gui_connect_parser = gui_subparsers.add_parser(
59
59
  "connect",
60
60
  help="Start interactive GUI automation test session"
61
61
  )
62
62
 
63
+ # GUI graph check subcommand
64
+ gui_graph_parser = gui_subparsers.add_parser(
65
+ "graph", help="Graph analysis commands"
66
+ )
67
+ gui_graph_subparsers = gui_graph_parser.add_subparsers(
68
+ dest="graph_command", help="Graph subcommands"
69
+ )
70
+
71
+ gui_graph_check_parser = gui_graph_subparsers.add_parser(
72
+ "check", help="Check and visualize state machine graph structure"
73
+ )
74
+ gui_graph_check_parser.add_argument(
75
+ "--module-path",
76
+ type=str,
77
+ required=False,
78
+ default=None,
79
+ help="Path to the Python file containing the state machine (defaults to src/main.py)",
80
+ )
81
+
63
82
  # Schema command group
64
83
  schema_parser = subparsers.add_parser(
65
84
  "schema",
66
85
  help="Schema management commands"
67
86
  )
68
87
  schema_subparsers = schema_parser.add_subparsers(dest="schema_command", help="Schema subcommands")
69
-
88
+
70
89
  # Schema fetch subcommand
71
90
  schema_fetch_parser = schema_subparsers.add_parser(
72
91
  "fetch",
73
92
  help="Fetch and generate Pydantic models from project schema"
74
93
  )
75
94
 
95
+ # Code command group
96
+ code_parser = subparsers.add_parser(
97
+ "code", help="Custom code development and testing commands"
98
+ )
99
+ code_subparsers = code_parser.add_subparsers(
100
+ dest="code_command", help="Code subcommands"
101
+ )
102
+
103
+ # Code run subcommand
104
+ code_run_parser = code_subparsers.add_parser(
105
+ "run", help="Run custom code with test payloads"
106
+ )
107
+
76
108
  args = parser.parse_args()
77
109
 
78
110
  # Show help if no command specified
@@ -90,16 +122,27 @@ def main():
90
122
  if not hasattr(args, 'gui_command') or not args.gui_command:
91
123
  gui_parser.print_help()
92
124
  sys.exit(1)
93
-
125
+
94
126
  if args.gui_command == "connect":
95
127
  from clerk.development.gui.test_session import main as gui_main
96
128
  gui_main()
97
129
 
130
+ elif args.gui_command == "graph":
131
+ if not hasattr(args, "graph_command") or not args.graph_command:
132
+ print("Error: graph command requires a subcommand")
133
+ print("Available subcommands: check")
134
+ sys.exit(1)
135
+
136
+ if args.graph_command == "check":
137
+ from clerk.development.gui.graph_checker import check_graph
138
+
139
+ check_graph(args.module_path)
140
+
98
141
  elif args.command == "schema":
99
142
  if not hasattr(args, 'schema_command') or not args.schema_command:
100
143
  schema_parser.print_help()
101
144
  sys.exit(1)
102
-
145
+
103
146
  if args.schema_command == "fetch":
104
147
  from clerk.development.schema.fetch_schema import main_with_args
105
148
  project_id = os.getenv("PROJECT_ID")
@@ -108,6 +151,16 @@ def main():
108
151
  sys.exit(1)
109
152
  main_with_args(project_id, project_root)
110
153
 
154
+ elif args.command == "code":
155
+ if not hasattr(args, "code_command") or not args.code_command:
156
+ code_parser.print_help()
157
+ sys.exit(1)
158
+
159
+ if args.code_command == "run":
160
+ from clerk.development.code_runner import main_with_args
161
+
162
+ main_with_args(project_root)
163
+
111
164
 
112
165
  if __name__ == "__main__":
113
166
  main()
@@ -0,0 +1,384 @@
1
+ """Code runner module for testing custom code with payloads."""
2
+ import json
3
+ import sys
4
+ from pathlib import Path
5
+ from typing import Any, Dict, Optional
6
+ from importlib import import_module
7
+ import importlib.util
8
+
9
+ from rich.console import Console
10
+ from rich.panel import Panel
11
+ from rich.prompt import Confirm, Prompt
12
+ from rich import print as rprint
13
+
14
+ console = Console()
15
+
16
+
17
+ def _generate_structured_data_code(structured_data_class) -> str:
18
+ """Generate code for StructuredData initialization with all fields.
19
+
20
+ Args:
21
+ structured_data_class: The StructuredData class from schema
22
+
23
+ Returns:
24
+ String with indented field assignments
25
+ """
26
+ from typing import get_origin, get_args
27
+ from pydantic import BaseModel
28
+
29
+ lines = []
30
+
31
+ # Get model fields
32
+ if hasattr(structured_data_class, 'model_fields'):
33
+ fields = structured_data_class.model_fields
34
+
35
+ for field_name, field_info in fields.items():
36
+ annotation = field_info.annotation
37
+
38
+ # Check if it's a List type
39
+ origin = get_origin(annotation)
40
+ if origin is list:
41
+ lines.append(f" {field_name}=[],")
42
+ # Check if it's an Optional type
43
+ elif origin is type(None) or (hasattr(annotation, '__origin__') and annotation.__origin__ is type(None)):
44
+ lines.append(f" {field_name}=None,")
45
+ # Check if the annotation is a BaseModel subclass
46
+ else:
47
+ # Try to check if it's a BaseModel (handle Optional types)
48
+ actual_type = annotation
49
+ if origin:
50
+ # For Optional[Type], get the actual type
51
+ args = get_args(annotation)
52
+ if args:
53
+ # Filter out NoneType
54
+ non_none_args = [arg for arg in args if arg is not type(None)]
55
+ if non_none_args:
56
+ actual_type = non_none_args[0]
57
+
58
+ # Check if actual_type is a class and subclass of BaseModel
59
+ try:
60
+ if isinstance(actual_type, type) and issubclass(actual_type, BaseModel):
61
+ class_name = actual_type.__name__
62
+ lines.append(f" {field_name}={class_name}(),")
63
+ else:
64
+ lines.append(f" {field_name}=None,")
65
+ except (TypeError, AttributeError):
66
+ lines.append(f" {field_name}=None,")
67
+
68
+ return "\n".join(lines)
69
+
70
+
71
+ def find_test_payloads(project_root: Path) -> list[Path]:
72
+ """Find all test payload Python files in test/payloads directory.
73
+
74
+ Args:
75
+ project_root: Project root directory
76
+
77
+ Returns:
78
+ List of Path objects for payload files
79
+ """
80
+ payload_dir = project_root / "test" / "payloads"
81
+
82
+ if not payload_dir.exists():
83
+ return []
84
+
85
+ # Find all .py files except __init__.py
86
+ return [p for p in payload_dir.glob("*.py") if p.name != "__init__.py"]
87
+
88
+
89
+ def create_test_payload_template(project_root: Path) -> Path:
90
+ """Create a template test payload Python file.
91
+
92
+ Args:
93
+ project_root: Project root directory
94
+
95
+ Returns:
96
+ Path to the created template file
97
+ """
98
+ payload_dir = project_root / "test" / "payloads"
99
+ payload_dir.mkdir(parents=True, exist_ok=True)
100
+
101
+ # Check if schema exists
102
+ schema_path = project_root / "src" / "schema.py"
103
+ if not schema_path.exists():
104
+ console.print("[red]x[/red] No schema found. Run 'clerk schema fetch' first.")
105
+ console.print("[dim]Cannot generate test payload without schema.[/dim]")
106
+ sys.exit(1)
107
+
108
+ console.print("[green]✓[/green] Found schema at src/schema.py")
109
+
110
+ # Load schema to generate structured data template
111
+ structured_data_code = None
112
+ try:
113
+ # Add src to path
114
+ src_path = str(project_root / "src")
115
+ if src_path not in sys.path:
116
+ sys.path.insert(0, src_path)
117
+
118
+ spec = importlib.util.spec_from_file_location("schema", schema_path)
119
+ if spec and spec.loader:
120
+ schema_module = importlib.util.module_from_spec(spec)
121
+ spec.loader.exec_module(schema_module)
122
+
123
+ if hasattr(schema_module, "StructuredData"):
124
+ structured_data_class = getattr(schema_module, "StructuredData")
125
+ # Generate code with all fields
126
+ structured_data_code = _generate_structured_data_code(structured_data_class)
127
+ except Exception as e:
128
+ console.print(f"[red]x[/red] Could not load schema: {str(e)}")
129
+ sys.exit(1)
130
+
131
+ if not structured_data_code:
132
+ console.print("[red]x[/red] Could not generate structured data code from schema.")
133
+ sys.exit(1)
134
+
135
+ # Get name from user
136
+ name = Prompt.ask(
137
+ "Enter a name for this test payload",
138
+ default="test_payload_1"
139
+ )
140
+
141
+ # Ensure .py extension
142
+ if not name.endswith(".py"):
143
+ name = f"{name}.py"
144
+
145
+ payload_path = payload_dir / name
146
+
147
+ # Load and populate template
148
+ template_dir = Path(__file__).parent / "templates"
149
+ template_path = template_dir / "test_payload.py.template"
150
+ template_code = template_path.read_text(encoding="utf-8")
151
+ # Replace placeholder with actual fields
152
+ template_code = template_code.replace("{structured_data_fields}", structured_data_code)
153
+
154
+ # Write the template
155
+ with open(payload_path, "w", encoding="utf-8") as f:
156
+ f.write(template_code)
157
+
158
+ console.print(f"\n[green]✓[/green] Created template payload: {payload_path}")
159
+ console.print("\n[yellow]Please edit this file to customize your test data before continuing.[/yellow]")
160
+
161
+ return payload_path
162
+
163
+
164
+ def select_payload(payloads: list[Path]) -> Path:
165
+ """Let user select a payload by number.
166
+
167
+ Args:
168
+ payloads: List of payload file paths
169
+
170
+ Returns:
171
+ Selected payload path
172
+ """
173
+ console.print("\n[bold]Available test payloads:[/bold]")
174
+ for i, payload in enumerate(payloads, 1):
175
+ console.print(f" [cyan]{i}[/cyan]. {payload.stem}")
176
+
177
+ while True:
178
+ try:
179
+ choice = Prompt.ask(
180
+ "\nSelect a payload",
181
+ default="1"
182
+ )
183
+ idx = int(choice) - 1
184
+ if 0 <= idx < len(payloads):
185
+ return payloads[idx]
186
+ else:
187
+ console.print(f"[red]Please enter a number between 1 and {len(payloads)}[/red]")
188
+ except ValueError:
189
+ console.print("[red]Please enter a valid number[/red]")
190
+
191
+
192
+ def load_payload(payload_path: Path, project_root: Path):
193
+ """Load payload from Python module.
194
+
195
+ Args:
196
+ payload_path: Path to payload Python file
197
+ project_root: Project root directory
198
+
199
+ Returns:
200
+ ClerkCodePayload object
201
+ """
202
+ # Add project root and src to path so imports work
203
+ project_root_str = str(project_root)
204
+ src_path = str(project_root / "src")
205
+
206
+ if project_root_str not in sys.path:
207
+ sys.path.insert(0, project_root_str)
208
+ if src_path not in sys.path:
209
+ sys.path.insert(0, src_path)
210
+
211
+ # Load the payload module
212
+ spec = importlib.util.spec_from_file_location(
213
+ f"test_payload_{payload_path.stem}",
214
+ payload_path
215
+ )
216
+ if not spec or not spec.loader:
217
+ raise ImportError(f"Could not load payload module from {payload_path}")
218
+
219
+ payload_module = importlib.util.module_from_spec(spec)
220
+ spec.loader.exec_module(payload_module)
221
+
222
+ # Get the payload object
223
+ if not hasattr(payload_module, "payload"):
224
+ raise AttributeError(f"Payload module must define a 'payload' variable")
225
+
226
+ return payload_module.payload
227
+
228
+
229
+ def run_main_with_payload(project_root: Path, payload_path: Path):
230
+ """Run main() from src/main.py with the selected payload.
231
+
232
+ Args:
233
+ project_root: Project root directory
234
+ payload_path: Path to the payload Python file
235
+ """
236
+ console.print()
237
+ console.print(Panel(
238
+ f"[bold]Running main() with payload: {payload_path.name}[/bold]",
239
+ style="cyan"
240
+ ))
241
+
242
+ # Load payload
243
+ try:
244
+ payload_obj = load_payload(payload_path, project_root)
245
+ console.print("[green]✓[/green] Loaded payload")
246
+ except Exception as e:
247
+ console.print(f"[red]x[/red] Failed to load payload: {str(e)}")
248
+ import traceback
249
+ console.print("[dim]" + traceback.format_exc() + "[/dim]")
250
+ sys.exit(1)
251
+
252
+ # Find main.py
253
+ main_path = project_root / "src" / "main.py"
254
+ if not main_path.exists():
255
+ console.print(f"[red]x[/red] main.py not found at {main_path}")
256
+ sys.exit(1)
257
+
258
+ # Add src to path
259
+ src_path = str(project_root / "src")
260
+ if src_path not in sys.path:
261
+ sys.path.insert(0, src_path)
262
+
263
+ # Start debugpy server and wait for VS Code to attach
264
+ import debugpy
265
+
266
+ debug_port = 5678
267
+
268
+ # Check if already running under debugger
269
+ if not debugpy.is_client_connected():
270
+ console.print(f"\n[cyan]Starting debug server on port {debug_port}...[/cyan]")
271
+ debugpy.listen(("localhost", debug_port))
272
+
273
+ console.print()
274
+ console.print("[bold yellow]⚡ Ready for debugging![/bold yellow]")
275
+ console.print()
276
+ console.print("[bold]To start debugging:[/bold]")
277
+ console.print(" [cyan]→ Press F5 in VS Code[/cyan]")
278
+ console.print(" [dim]or select 'Clerk: Debug Code Run' from the debug panel[/dim]")
279
+ console.print()
280
+ console.print("[dim]Press Ctrl+C to skip debugging and run without debugger[/dim]\n")
281
+
282
+ try:
283
+ debugpy.wait_for_client()
284
+ console.print("[green]✓[/green] Debugger attached!\n")
285
+ except KeyboardInterrupt:
286
+ console.print("\n[yellow]Skipping debugger, running without debug...[/yellow]\n")
287
+ else:
288
+ console.print("\n[green]✓[/green] Already running under debugger\n")
289
+
290
+ # Import and run
291
+ try:
292
+ console.print()
293
+ console.print("[bold cyan]═══════════════════════════════════════════════════════[/bold cyan]")
294
+ console.print("[bold cyan] Starting Execution [/bold cyan]")
295
+ console.print("[bold cyan]═══════════════════════════════════════════════════════[/bold cyan]")
296
+ console.print()
297
+
298
+ # Import main module
299
+ spec = importlib.util.spec_from_file_location("main", main_path)
300
+ if spec and spec.loader:
301
+ main_module = importlib.util.module_from_spec(spec)
302
+ spec.loader.exec_module(main_module)
303
+
304
+ # Call main
305
+ if hasattr(main_module, "main"):
306
+ # Run main with the loaded payload
307
+ result = main_module.main(payload_obj)
308
+
309
+ console.print()
310
+ console.print("[bold cyan]═══════════════════════════════════════════════════════[/bold cyan]")
311
+ console.print("[bold cyan] Execution Complete [/bold cyan]")
312
+ console.print("[bold cyan]═══════════════════════════════════════════════════════[/bold cyan]")
313
+ console.print()
314
+
315
+ # Show result
316
+ if result:
317
+ console.print("[bold]Result:[/bold]")
318
+ console.print(Panel(
319
+ f"Document ID: {result.document.id}\n"
320
+ f"Run ID: {result.run_id}",
321
+ title="Execution Result",
322
+ style="green"
323
+ ))
324
+
325
+ # Show updated structured_data
326
+ if result.structured_data:
327
+ console.print("\n[bold]Updated Structured Data:[/bold]")
328
+ rprint(result.structured_data)
329
+ else:
330
+ console.print("[yellow]![/yellow] No result returned")
331
+ else:
332
+ console.print(f"[red]x[/red] No main() function found in {main_path}")
333
+ sys.exit(1)
334
+ else:
335
+ console.print(f"[red]x[/red] Could not load {main_path}")
336
+ sys.exit(1)
337
+
338
+ except Exception as e:
339
+ console.print()
340
+ console.print(f"[red]x Error during execution:[/red] {str(e)}")
341
+ import traceback
342
+ console.print("[dim]" + traceback.format_exc() + "[/dim]")
343
+ sys.exit(1)
344
+
345
+
346
+ def main_with_args(project_root: Path):
347
+ """Main entry point for code runner.
348
+
349
+ Args:
350
+ project_root: Project root directory
351
+ """
352
+ console.print()
353
+ console.print(Panel(
354
+ "[bold]Clerk Code Runner[/bold]\n"
355
+ "Run your custom code with test payloads",
356
+ style="cyan"
357
+ ))
358
+
359
+ # Find payloads
360
+ payloads = find_test_payloads(project_root)
361
+
362
+ if not payloads:
363
+ console.print("\n[yellow]No test payloads found in test/payloads/[/yellow]")
364
+
365
+ if Confirm.ask("Would you like to generate a template payload?", default=True):
366
+ payload_path = create_test_payload_template(project_root)
367
+
368
+ console.print("\n[bold]Next steps:[/bold]")
369
+ console.print(f"1. Edit {payload_path} with your test data")
370
+ console.print("2. Run [cyan]clerk code run[/cyan] again to execute")
371
+ return
372
+ else:
373
+ console.print("\n[dim]Create a Python file in test/payloads/ and run again.[/dim]")
374
+ return
375
+
376
+ # Show available payloads
377
+ console.print(f"\n[green]✓[/green] Found {len(payloads)} test payload(s)")
378
+
379
+ # Let user select
380
+ selected_payload = select_payload(payloads)
381
+ console.print(f"\n[green]→[/green] Selected: {selected_payload.name}")
382
+
383
+ # Run with selected payload
384
+ run_main_with_payload(project_root, selected_payload)
@@ -0,0 +1,216 @@
1
+ """Graph checker utility for ScreenPilot state machines"""
2
+ import sys
3
+ import importlib.util
4
+ import logging
5
+ from pathlib import Path
6
+ from typing import Set, List, Optional
7
+
8
+ from rich.console import Console
9
+ from rich.panel import Panel
10
+ from rich.table import Table
11
+
12
+ console = Console()
13
+
14
+
15
+ class ErrorCapturingHandler(logging.Handler):
16
+ """Custom logging handler to capture error messages during module load"""
17
+
18
+ def __init__(self):
19
+ super().__init__()
20
+ self.errors: List[str] = []
21
+ self.setLevel(logging.ERROR)
22
+
23
+ def emit(self, record):
24
+ if "involves undefined state" in record.getMessage():
25
+ self.errors.append(record.getMessage())
26
+
27
+
28
+ def load_module_from_path(module_path: str) -> ErrorCapturingHandler:
29
+ """
30
+ Load a Python module from a file path.
31
+
32
+ Args:
33
+ module_path: Path to the Python file containing the state machine
34
+
35
+ Returns:
36
+ ErrorCapturingHandler with any errors captured during load
37
+ """
38
+ path = Path(module_path).resolve()
39
+ if not path.exists():
40
+ raise FileNotFoundError(f"Module not found: {module_path}")
41
+
42
+ # Set up error capturing handler
43
+ error_handler = ErrorCapturingHandler()
44
+ logger = logging.getLogger("state_machine.py")
45
+ logger.addHandler(error_handler)
46
+
47
+ try:
48
+ # Add the parent directory to sys.path for relative imports
49
+ parent_dir = str(path.parent)
50
+ if parent_dir not in sys.path:
51
+ sys.path.insert(0, parent_dir)
52
+
53
+ # Load the module
54
+ spec = importlib.util.spec_from_file_location(path.stem, path)
55
+ if spec is None or spec.loader is None:
56
+ raise ImportError(f"Could not load module from {module_path}")
57
+
58
+ module = importlib.util.module_from_spec(spec)
59
+ sys.modules[path.stem] = module
60
+
61
+ # Capture ValueError exceptions during module execution
62
+ try:
63
+ spec.loader.exec_module(module)
64
+ except ValueError as e:
65
+ error_msg = str(e)
66
+ if "already registered" in error_msg or "provide a condition function" in error_msg:
67
+ # This is a fatal error - the transition can't be registered
68
+ # Re-raise it so we can show it properly
69
+ raise ValueError(f"Duplicate transition error: {error_msg}") from e
70
+ else:
71
+ raise
72
+ finally:
73
+ # Clean up handler
74
+ logger.removeHandler(error_handler)
75
+
76
+ return error_handler
77
+
78
+
79
+ def check_graph(module_path: Optional[str] = None):
80
+ """
81
+ Main function to check a ScreenPilot state machine graph.
82
+
83
+ Args:
84
+ module_path: Path to the Python file containing the state machine.
85
+ If None, defaults to src/main.py in current directory.
86
+ """
87
+ try:
88
+ # Clear any existing graph state from ScreenPilot before loading
89
+ from clerk.gui_automation.ui_state_machine import ScreenPilot
90
+ import networkx as nx
91
+ ScreenPilot._graph = nx.MultiDiGraph()
92
+
93
+ # Default to src/main.py if no path provided
94
+ if module_path is None:
95
+ default_path = Path.cwd() / "src" / "main.py"
96
+ if not default_path.exists():
97
+ console.print(f"\n[red]Error: Default file not found: {default_path}[/red]")
98
+ console.print("[dim]Specify --module-path to check a different file[/dim]")
99
+ sys.exit(1)
100
+ module_path = str(default_path)
101
+ console.print(f"[dim]Using default: {module_path}[/dim]")
102
+
103
+ # Load the module (this will trigger state/transition registrations)
104
+ console.print(f"\n[dim]Loading module: {module_path}[/dim]")
105
+ error_handler = load_module_from_path(module_path)
106
+
107
+ graph = ScreenPilot._graph
108
+
109
+ if len(graph.nodes()) == 0:
110
+ console.print("\n[yellow]⚠️ Warning: No states found in the graph.[/yellow]")
111
+ console.print("[dim]Make sure your module imports and registers states using decorators[/dim]")
112
+ return
113
+
114
+ # Count states and transitions
115
+ state_count = len(graph.nodes())
116
+ transition_count = len(graph.edges())
117
+
118
+ # Show statistics
119
+ console.print()
120
+ stats_table = Table(show_header=False, box=None, padding=(0, 2))
121
+ stats_table.add_column(style="cyan bold")
122
+ stats_table.add_column(style="white")
123
+ stats_table.add_row("States:", str(state_count))
124
+ stats_table.add_row("Transitions:", str(transition_count))
125
+
126
+ console.print(Panel(
127
+ stats_table,
128
+ title="[bold]Graph Statistics[/bold]",
129
+ style="cyan"
130
+ ))
131
+
132
+ # Collect all valid state names
133
+ valid_states: Set[str] = set(graph.nodes())
134
+
135
+ # Collect all state names referenced in transitions
136
+ referenced_states: Set[str] = set()
137
+ for u, v in graph.edges():
138
+ referenced_states.add(u)
139
+ referenced_states.add(v)
140
+
141
+ console.print()
142
+ console.print(Panel("[bold]Graph Checks[/bold]", style="cyan"))
143
+ console.print()
144
+
145
+ has_warnings = False
146
+ has_info = False
147
+
148
+ # Check for invalid state names in transitions (from captured errors)
149
+ if error_handler.errors:
150
+ has_warnings = True
151
+ console.print(f"[yellow]⚠️ WARNING: Found {len(error_handler.errors)} transition(s) with invalid state names:[/yellow]")
152
+ for error in error_handler.errors:
153
+ # Extract the relevant info from the error message
154
+ console.print(f" [yellow]• {error.replace('Error: ', '')}[/yellow]")
155
+ console.print()
156
+
157
+ # Check for orphaned states (no incoming AND no outgoing transitions)
158
+ orphaned = [
159
+ node for node in graph.nodes()
160
+ if graph.in_degree(node) == 0 and graph.out_degree(node) == 0
161
+ ]
162
+ if orphaned:
163
+ has_warnings = True
164
+ console.print(f"[yellow]⚠️ WARNING: Found {len(orphaned)} orphaned state(s) (no incoming or outgoing transitions):[/yellow]")
165
+ for state in sorted(orphaned):
166
+ console.print(f" [yellow]• {state}[/yellow]")
167
+ console.print()
168
+
169
+ # Info: States with no incoming transitions
170
+ no_incoming = [node for node in graph.nodes() if graph.in_degree(node) == 0 and graph.out_degree(node) > 0]
171
+ if no_incoming:
172
+ has_info = True
173
+ console.print(f"[blue]ℹ️ INFO: Found {len(no_incoming)} state(s) with no incoming transitions (entry points):[/blue]")
174
+ for state in sorted(no_incoming):
175
+ console.print(f" [blue]• {state}[/blue]")
176
+ console.print()
177
+
178
+ # Info: States with no outgoing transitions
179
+ no_outgoing = [node for node in graph.nodes() if graph.out_degree(node) == 0 and graph.in_degree(node) > 0]
180
+ if no_outgoing:
181
+ has_info = True
182
+ console.print(f"[blue]ℹ️ INFO: Found {len(no_outgoing)} state(s) with no outgoing transitions (terminal states):[/blue]")
183
+ for state in sorted(no_outgoing):
184
+ console.print(f" [blue]• {state}[/blue]")
185
+ console.print()
186
+
187
+ # All good message
188
+ if not has_warnings:
189
+ console.print("[green]✅ No issues detected.[/green]")
190
+ console.print()
191
+
192
+ except Exception as e:
193
+ console.print(f"\n[red]Error checking graph: {e}[/red]")
194
+ import traceback
195
+ console.print(f"[dim]{traceback.format_exc()}[/dim]")
196
+ sys.exit(1)
197
+
198
+
199
+ def main():
200
+ """Entry point for standalone script execution"""
201
+ import argparse
202
+
203
+ parser = argparse.ArgumentParser(description="Check ScreenPilot state machine graph")
204
+ parser.add_argument(
205
+ "--module-path",
206
+ required=False,
207
+ default=None,
208
+ help="Path to the Python file containing the state machine (defaults to src/main.py)"
209
+ )
210
+
211
+ args = parser.parse_args()
212
+ check_graph(args.module_path)
213
+
214
+
215
+ if __name__ == "__main__":
216
+ main()
@@ -318,9 +318,9 @@ def main():
318
318
  run_id="test-session-run",
319
319
  )
320
320
 
321
- # Show spinner while the decorator establishes WebSocket connection
322
- with console.status("[dim]Waiting for tool to connect...", spinner="dots"):
323
- start_interactive_session(payload)
321
+ # The @gui_automation decorator will establish the connection
322
+ # and the function itself will print the connection status
323
+ start_interactive_session(payload)
324
324
 
325
325
 
326
326
  if __name__ == "__main__":
@@ -145,6 +145,25 @@ def create_init_py(target_dir: Path) -> None:
145
145
  console.print(f"[green]+[/green] Created {init_path}")
146
146
 
147
147
 
148
+ def create_vscode_launch_config() -> None:
149
+ """Create .vscode/launch.json for debugging."""
150
+ vscode_dir = Path(".vscode")
151
+ vscode_dir.mkdir(exist_ok=True)
152
+
153
+ launch_path = vscode_dir / "launch.json"
154
+
155
+ if launch_path.exists():
156
+ console.print(f"[yellow]![/yellow] {launch_path} already exists, skipping...")
157
+ return
158
+
159
+ content = read_template("launch.json.template")
160
+
161
+ with open(launch_path, "w", encoding='utf-8') as f:
162
+ f.write(content)
163
+
164
+ console.print(f"[green]+[/green] Created {launch_path}")
165
+
166
+
148
167
  def create_gui_structure(target_dir: Path) -> None:
149
168
  """Create GUI automation folder structure with template files.
150
169
 
@@ -241,6 +260,9 @@ def init_project(
241
260
  # Create __init__.py
242
261
  create_init_py(target_dir)
243
262
 
263
+ # Create VS Code launch configuration
264
+ create_vscode_launch_config()
265
+
244
266
  # Create GUI automation structure if requested
245
267
  if with_gui:
246
268
  create_gui_structure(target_dir)
@@ -0,0 +1,20 @@
1
+ {
2
+ "version": "0.2.0",
3
+ "configurations": [
4
+ {
5
+ "name": "Clerk: Debug Code Run",
6
+ "type": "debugpy",
7
+ "request": "attach",
8
+ "connect": {
9
+ "host": "localhost",
10
+ "port": 5678
11
+ },
12
+ "pathMappings": [
13
+ {
14
+ "localRoot": "${workspaceFolder}/src",
15
+ "remoteRoot": "${workspaceFolder}/src"
16
+ }
17
+ ]
18
+ }
19
+ ]
20
+ }
@@ -19,7 +19,7 @@ def main(payload: ClerkCodePayload):
19
19
  data = StructuredData.model_validate(payload.structured_data)
20
20
  logger.info("Custom code started")
21
21
 
22
- def goal_function(current_state: str, process_variables: StructuredData) -> None:
22
+ def goal_function(current_state: str, data: StructuredData) -> None:
23
23
  """
24
24
  Goal function for the state machine.
25
25
  Args:
@@ -31,7 +31,7 @@ def main(payload: ClerkCodePayload):
31
31
  if True: # Use an actual condition based on state and/or process variables to complete the automation
32
32
  raise SuccessfulCompletion()
33
33
 
34
- ai_recovery_instructions = "\\n - ".join(
34
+ ai_recovery_instructions = "\n - ".join(
35
35
  [
36
36
  "Instructions on how to deal with possible issues using Clerk UI Actions.",
37
37
  ]
@@ -39,7 +39,7 @@ def main(payload: ClerkCodePayload):
39
39
 
40
40
  ScreenPilot.configure(ai_recovery_instructions=ai_recovery_instructions)
41
41
  exit_reason = ScreenPilot.run(
42
- goal_function, process_variables=process_variables, doc_id=payload.document.id
42
+ goal_function, data=data, doc_id=payload.document.id
43
43
  )
44
44
 
45
45
 
@@ -0,0 +1,25 @@
1
+ {
2
+ "version": "2.0.0",
3
+ "tasks": [
4
+ {
5
+ "label": "Start Clerk Code Run Debug Server",
6
+ "type": "shell",
7
+ "command": "clerk",
8
+ "args": ["code", "run"],
9
+ "isBackground": true,
10
+ "problemMatcher": {
11
+ "pattern": {
12
+ "regexp": "^$",
13
+ "file": 1,
14
+ "location": 2,
15
+ "message": 3
16
+ },
17
+ "background": {
18
+ "activeOnStart": true,
19
+ "beginsPattern": "Starting debug server",
20
+ "endsPattern": "Waiting for debugger to attach"
21
+ }
22
+ }
23
+ }
24
+ ]
25
+ }
@@ -0,0 +1,32 @@
1
+ """Test payload for local testing."""
2
+ from datetime import datetime
3
+ from clerk.decorator.models import ClerkCodePayload, Document, File
4
+ from src.schema import *
5
+
6
+ # Define your test payload here
7
+ #
8
+ # NOTE: Files can be local! The mock Clerk client will read from local paths.
9
+ # Put your test files in test/payloads/ and reference them like:
10
+ # File(name="invoice.pdf", url="test/payloads/invoice.pdf")
11
+ #
12
+ # When your code calls client.get_files_document(), it will return ParsedFile
13
+ # objects with the actual content from these local files.
14
+
15
+ payload = ClerkCodePayload(
16
+ document=Document(
17
+ id="test-doc-123",
18
+ message_subject="Test Document",
19
+ message_content="This is a test document for local testing",
20
+ files=[
21
+ File(
22
+ name="example.pdf",
23
+ url="test/payloads/example.pdf" # Use local file path
24
+ )
25
+ ],
26
+ upload_date=datetime(2025, 1, 1, 0, 0, 0)
27
+ ),
28
+ structured_data=StructuredData(
29
+ {structured_data_fields}
30
+ ).model_dump(),
31
+ run_id="test-run-001"
32
+ )
@@ -58,7 +58,7 @@ async def _perform_action_ws(payload: Dict[str, Any]) -> PerformActionResponse:
58
58
  try:
59
59
  ack = await asyncio.wait_for(global_ws.recv(), 10)
60
60
  if ack == "OK":
61
- action_info = await asyncio.wait_for(global_ws.recv(), 10)
61
+ action_info = await asyncio.wait_for(global_ws.recv(), 60)
62
62
  return PerformActionResponse(**json.loads(action_info))
63
63
  else:
64
64
  raise RuntimeError("Received ACK != OK")
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "clerk-sdk"
7
- version = "0.5.2"
7
+ version = "0.5.4"
8
8
  description = "Library for interacting with Clerk"
9
9
  readme = "README.md"
10
10
  authors = [
@@ -22,6 +22,7 @@ dependencies = [
22
22
  "requests>=2.32.3,<3.0.0",
23
23
  "python-dotenv>=1.0.0",
24
24
  "rich>=14.2.0",
25
+ "debugpy>=1.8.0,<2.0.0",
25
26
  ]
26
27
 
27
28
  [project.optional-dependencies]
@@ -3,3 +3,4 @@ backoff >=2.0.0, <3.0.0
3
3
  requests >=2.32.3, <3.0.0
4
4
  dotenv
5
5
  rich >=14.2.0, <15.0.0
6
+ debugpy >=1.8.0, <2.0.0
@@ -13,7 +13,7 @@ gui_requirements = get_requirements("./clerk/gui_automation")
13
13
 
14
14
  setup(
15
15
  name="clerk-sdk",
16
- version="0.5.2",
16
+ version="0.5.4",
17
17
  description="Library for interacting with Clerk",
18
18
  long_description=open("README.md").read(),
19
19
  long_description_content_type="text/markdown",
@@ -108,6 +108,7 @@ version = "0.5.0.dev0"
108
108
  source = { editable = "." }
109
109
  dependencies = [
110
110
  { name = "backoff" },
111
+ { name = "debugpy" },
111
112
  { name = "pydantic" },
112
113
  { name = "python-dotenv" },
113
114
  { name = "requests" },
@@ -127,6 +128,7 @@ gui-automation = [
127
128
  [package.metadata]
128
129
  requires-dist = [
129
130
  { name = "backoff", specifier = ">=2.0.0,<3.0.0" },
131
+ { name = "debugpy", specifier = ">=1.8.0,<2.0.0" },
130
132
  { name = "networkx", marker = "extra == 'all'", specifier = ">=3.5.0,<4.0.0" },
131
133
  { name = "networkx", marker = "extra == 'gui-automation'", specifier = ">=3.5.0,<4.0.0" },
132
134
  { name = "pydantic", specifier = ">=2.0.0,<3.0.0" },
@@ -138,6 +140,31 @@ requires-dist = [
138
140
  ]
139
141
  provides-extras = ["gui-automation", "all"]
140
142
 
143
+ [[package]]
144
+ name = "debugpy"
145
+ version = "1.8.19"
146
+ source = { registry = "https://pypi.org/simple" }
147
+ sdist = { url = "https://files.pythonhosted.org/packages/73/75/9e12d4d42349b817cd545b89247696c67917aab907012ae5b64bbfea3199/debugpy-1.8.19.tar.gz", hash = "sha256:eea7e5987445ab0b5ed258093722d5ecb8bb72217c5c9b1e21f64efe23ddebdb", size = 1644590, upload-time = "2025-12-15T21:53:28.044Z" }
148
+ wheels = [
149
+ { url = "https://files.pythonhosted.org/packages/80/e2/48531a609b5a2aa94c6b6853afdfec8da05630ab9aaa96f1349e772119e9/debugpy-1.8.19-cp311-cp311-macosx_15_0_universal2.whl", hash = "sha256:c5dcfa21de1f735a4f7ced4556339a109aa0f618d366ede9da0a3600f2516d8b", size = 2207620, upload-time = "2025-12-15T21:53:37.1Z" },
150
+ { url = "https://files.pythonhosted.org/packages/1b/d4/97775c01d56071969f57d93928899e5616a4cfbbf4c8cc75390d3a51c4a4/debugpy-1.8.19-cp311-cp311-manylinux_2_34_x86_64.whl", hash = "sha256:806d6800246244004625d5222d7765874ab2d22f3ba5f615416cf1342d61c488", size = 3170796, upload-time = "2025-12-15T21:53:38.513Z" },
151
+ { url = "https://files.pythonhosted.org/packages/8d/7e/8c7681bdb05be9ec972bbb1245eb7c4c7b0679bb6a9e6408d808bc876d3d/debugpy-1.8.19-cp311-cp311-win32.whl", hash = "sha256:783a519e6dfb1f3cd773a9bda592f4887a65040cb0c7bd38dde410f4e53c40d4", size = 5164287, upload-time = "2025-12-15T21:53:40.857Z" },
152
+ { url = "https://files.pythonhosted.org/packages/f2/a8/aaac7ff12ddf5d68a39e13a423a8490426f5f661384f5ad8d9062761bd8e/debugpy-1.8.19-cp311-cp311-win_amd64.whl", hash = "sha256:14035cbdbb1fe4b642babcdcb5935c2da3b1067ac211c5c5a8fdc0bb31adbcaa", size = 5188269, upload-time = "2025-12-15T21:53:42.359Z" },
153
+ { url = "https://files.pythonhosted.org/packages/4a/15/d762e5263d9e25b763b78be72dc084c7a32113a0bac119e2f7acae7700ed/debugpy-1.8.19-cp312-cp312-macosx_15_0_universal2.whl", hash = "sha256:bccb1540a49cde77edc7ce7d9d075c1dbeb2414751bc0048c7a11e1b597a4c2e", size = 2549995, upload-time = "2025-12-15T21:53:43.773Z" },
154
+ { url = "https://files.pythonhosted.org/packages/a7/88/f7d25c68b18873b7c53d7c156ca7a7ffd8e77073aa0eac170a9b679cf786/debugpy-1.8.19-cp312-cp312-manylinux_2_34_x86_64.whl", hash = "sha256:e9c68d9a382ec754dc05ed1d1b4ed5bd824b9f7c1a8cd1083adb84b3c93501de", size = 4309891, upload-time = "2025-12-15T21:53:45.26Z" },
155
+ { url = "https://files.pythonhosted.org/packages/c5/4f/a65e973aba3865794da65f71971dca01ae66666132c7b2647182d5be0c5f/debugpy-1.8.19-cp312-cp312-win32.whl", hash = "sha256:6599cab8a783d1496ae9984c52cb13b7c4a3bd06a8e6c33446832a5d97ce0bee", size = 5286355, upload-time = "2025-12-15T21:53:46.763Z" },
156
+ { url = "https://files.pythonhosted.org/packages/d8/3a/d3d8b48fec96e3d824e404bf428276fb8419dfa766f78f10b08da1cb2986/debugpy-1.8.19-cp312-cp312-win_amd64.whl", hash = "sha256:66e3d2fd8f2035a8f111eb127fa508469dfa40928a89b460b41fd988684dc83d", size = 5328239, upload-time = "2025-12-15T21:53:48.868Z" },
157
+ { url = "https://files.pythonhosted.org/packages/71/3d/388035a31a59c26f1ecc8d86af607d0c42e20ef80074147cd07b180c4349/debugpy-1.8.19-cp313-cp313-macosx_15_0_universal2.whl", hash = "sha256:91e35db2672a0abaf325f4868fcac9c1674a0d9ad9bb8a8c849c03a5ebba3e6d", size = 2538859, upload-time = "2025-12-15T21:53:50.478Z" },
158
+ { url = "https://files.pythonhosted.org/packages/4a/19/c93a0772d0962294f083dbdb113af1a7427bb632d36e5314297068f55db7/debugpy-1.8.19-cp313-cp313-manylinux_2_34_x86_64.whl", hash = "sha256:85016a73ab84dea1c1f1dcd88ec692993bcbe4532d1b49ecb5f3c688ae50c606", size = 4292575, upload-time = "2025-12-15T21:53:51.821Z" },
159
+ { url = "https://files.pythonhosted.org/packages/5c/56/09e48ab796b0a77e3d7dc250f95251832b8bf6838c9632f6100c98bdf426/debugpy-1.8.19-cp313-cp313-win32.whl", hash = "sha256:b605f17e89ba0ecee994391194285fada89cee111cfcd29d6f2ee11cbdc40976", size = 5286209, upload-time = "2025-12-15T21:53:53.602Z" },
160
+ { url = "https://files.pythonhosted.org/packages/fb/4e/931480b9552c7d0feebe40c73725dd7703dcc578ba9efc14fe0e6d31cfd1/debugpy-1.8.19-cp313-cp313-win_amd64.whl", hash = "sha256:c30639998a9f9cd9699b4b621942c0179a6527f083c72351f95c6ab1728d5b73", size = 5328206, upload-time = "2025-12-15T21:53:55.433Z" },
161
+ { url = "https://files.pythonhosted.org/packages/f6/b9/cbec520c3a00508327476c7fce26fbafef98f412707e511eb9d19a2ef467/debugpy-1.8.19-cp314-cp314-macosx_15_0_universal2.whl", hash = "sha256:1e8c4d1bd230067bf1bbcdbd6032e5a57068638eb28b9153d008ecde288152af", size = 2537372, upload-time = "2025-12-15T21:53:57.318Z" },
162
+ { url = "https://files.pythonhosted.org/packages/88/5e/cf4e4dc712a141e10d58405c58c8268554aec3c35c09cdcda7535ff13f76/debugpy-1.8.19-cp314-cp314-manylinux_2_34_x86_64.whl", hash = "sha256:d40c016c1f538dbf1762936e3aeb43a89b965069d9f60f9e39d35d9d25e6b809", size = 4268729, upload-time = "2025-12-15T21:53:58.712Z" },
163
+ { url = "https://files.pythonhosted.org/packages/82/a3/c91a087ab21f1047db328c1d3eb5d1ff0e52de9e74f9f6f6fa14cdd93d58/debugpy-1.8.19-cp314-cp314-win32.whl", hash = "sha256:0601708223fe1cd0e27c6cce67a899d92c7d68e73690211e6788a4b0e1903f5b", size = 5286388, upload-time = "2025-12-15T21:54:00.687Z" },
164
+ { url = "https://files.pythonhosted.org/packages/17/b8/bfdc30b6e94f1eff09f2dc9cc1f9cd1c6cde3d996bcbd36ce2d9a4956e99/debugpy-1.8.19-cp314-cp314-win_amd64.whl", hash = "sha256:8e19a725f5d486f20e53a1dde2ab8bb2c9607c40c00a42ab646def962b41125f", size = 5327741, upload-time = "2025-12-15T21:54:02.148Z" },
165
+ { url = "https://files.pythonhosted.org/packages/25/3e/e27078370414ef35fafad2c06d182110073daaeb5d3bf734b0b1eeefe452/debugpy-1.8.19-py2.py3-none-any.whl", hash = "sha256:360ffd231a780abbc414ba0f005dad409e71c78637efe8f2bd75837132a41d38", size = 5292321, upload-time = "2025-12-15T21:54:16.024Z" },
166
+ ]
167
+
141
168
  [[package]]
142
169
  name = "idna"
143
170
  version = "3.11"
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes