cudag 0.3.10__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (69) hide show
  1. cudag/__init__.py +334 -0
  2. cudag/annotation/__init__.py +77 -0
  3. cudag/annotation/codegen.py +648 -0
  4. cudag/annotation/config.py +545 -0
  5. cudag/annotation/loader.py +342 -0
  6. cudag/annotation/scaffold.py +121 -0
  7. cudag/annotation/transcription.py +296 -0
  8. cudag/cli/__init__.py +5 -0
  9. cudag/cli/main.py +315 -0
  10. cudag/cli/new.py +873 -0
  11. cudag/core/__init__.py +364 -0
  12. cudag/core/button.py +137 -0
  13. cudag/core/canvas.py +222 -0
  14. cudag/core/config.py +70 -0
  15. cudag/core/coords.py +233 -0
  16. cudag/core/data_grid.py +804 -0
  17. cudag/core/dataset.py +678 -0
  18. cudag/core/distribution.py +136 -0
  19. cudag/core/drawing.py +75 -0
  20. cudag/core/fonts.py +156 -0
  21. cudag/core/generator.py +163 -0
  22. cudag/core/grid.py +367 -0
  23. cudag/core/grounding_task.py +247 -0
  24. cudag/core/icon.py +207 -0
  25. cudag/core/iconlist_task.py +301 -0
  26. cudag/core/models.py +1251 -0
  27. cudag/core/random.py +130 -0
  28. cudag/core/renderer.py +190 -0
  29. cudag/core/screen.py +402 -0
  30. cudag/core/scroll_task.py +254 -0
  31. cudag/core/scrollable_grid.py +447 -0
  32. cudag/core/state.py +110 -0
  33. cudag/core/task.py +293 -0
  34. cudag/core/taskbar.py +350 -0
  35. cudag/core/text.py +212 -0
  36. cudag/core/utils.py +82 -0
  37. cudag/data/surnames.txt +5000 -0
  38. cudag/modal_apps/__init__.py +4 -0
  39. cudag/modal_apps/archive.py +103 -0
  40. cudag/modal_apps/extract.py +138 -0
  41. cudag/modal_apps/preprocess.py +529 -0
  42. cudag/modal_apps/upload.py +317 -0
  43. cudag/prompts/SYSTEM_PROMPT.txt +104 -0
  44. cudag/prompts/__init__.py +33 -0
  45. cudag/prompts/system.py +43 -0
  46. cudag/prompts/tools.py +382 -0
  47. cudag/py.typed +0 -0
  48. cudag/schemas/filesystem.json +90 -0
  49. cudag/schemas/test_record.schema.json +113 -0
  50. cudag/schemas/train_record.schema.json +90 -0
  51. cudag/server/__init__.py +21 -0
  52. cudag/server/app.py +232 -0
  53. cudag/server/services/__init__.py +9 -0
  54. cudag/server/services/generator.py +128 -0
  55. cudag/templates/scripts/archive.sh +35 -0
  56. cudag/templates/scripts/build.sh +13 -0
  57. cudag/templates/scripts/extract.sh +54 -0
  58. cudag/templates/scripts/generate.sh +116 -0
  59. cudag/templates/scripts/pre-commit.sh +44 -0
  60. cudag/templates/scripts/preprocess.sh +46 -0
  61. cudag/templates/scripts/upload.sh +63 -0
  62. cudag/templates/scripts/verify.py +428 -0
  63. cudag/validation/__init__.py +35 -0
  64. cudag/validation/validate.py +508 -0
  65. cudag-0.3.10.dist-info/METADATA +570 -0
  66. cudag-0.3.10.dist-info/RECORD +69 -0
  67. cudag-0.3.10.dist-info/WHEEL +4 -0
  68. cudag-0.3.10.dist-info/entry_points.txt +2 -0
  69. cudag-0.3.10.dist-info/licenses/LICENSE +66 -0
@@ -0,0 +1,317 @@
1
+ #!/usr/bin/env python3
2
+ # Copyright (c) 2025 Tylt LLC. All rights reserved.
3
+ # CONFIDENTIAL AND PROPRIETARY. Unauthorized use, copying, or distribution
4
+ # is strictly prohibited. For licensing inquiries: hello@claimhawk.app
5
+
6
+ """Compress a dataset run into chunks and upload to Modal with resume support.
7
+
8
+ Pipeline: upload -> extract -> preprocess
9
+
10
+ Usage:
11
+ uv run python -m modal_apps.upload # Upload and auto-preprocess
12
+ uv run python -m modal_apps.upload --dry # Upload only, no preprocess
13
+ uv run python -m modal_apps.upload --no-resume # Force re-upload all chunks
14
+ """
15
+ from __future__ import annotations
16
+
17
+ import argparse
18
+ import hashlib
19
+ import json
20
+ import math
21
+ import shutil
22
+ import subprocess
23
+ import tarfile
24
+ import tempfile
25
+ from pathlib import Path
26
+
27
+ # =============================================================================
28
+ # CENTRALIZED CONFIGURATION
29
+ # =============================================================================
30
+ # Volume names are loaded from config/adapters.yaml via the SDK.
31
+ # Users can customize these by editing the YAML file.
32
+
33
+ try:
34
+ from sdk.modal_compat import get_volume_name
35
+ DEFAULT_VOLUME = get_volume_name("lora_training")
36
+ except ImportError:
37
+ # Fallback when SDK not available
38
+ DEFAULT_VOLUME = "claimhawk-lora-training"
39
+ DATASETS_ROOT = Path("datasets")
40
+ CHUNK_SIZE_MB = 500 # Size of each chunk in MB
41
+
42
+
43
+ def parse_args() -> argparse.Namespace:
44
+ """Parse command-line arguments."""
45
+ parser = argparse.ArgumentParser(description="Archive and upload a dataset run to Modal.")
46
+ parser.add_argument(
47
+ "run_dir",
48
+ type=Path,
49
+ nargs="?",
50
+ help="Dataset subdirectory under ./datasets to upload (defaults to newest run).",
51
+ )
52
+ parser.add_argument(
53
+ "--chunk-size",
54
+ type=int,
55
+ default=CHUNK_SIZE_MB,
56
+ help=f"Chunk size in MB (default: {CHUNK_SIZE_MB})",
57
+ )
58
+ parser.add_argument(
59
+ "--resume",
60
+ action="store_true",
61
+ default=True,
62
+ help="Resume a previously interrupted upload (default: True)",
63
+ )
64
+ parser.add_argument(
65
+ "--no-resume",
66
+ action="store_false",
67
+ dest="resume",
68
+ help="Disable resume and re-upload all chunks",
69
+ )
70
+ return parser.parse_args()
71
+
72
+
73
+ def pick_latest_run() -> Path:
74
+ """Find the most recently modified dataset run directory."""
75
+ if not DATASETS_ROOT.exists():
76
+ raise SystemExit("datasets/ directory not found")
77
+ runs = [p for p in DATASETS_ROOT.iterdir() if p.is_dir()]
78
+ if not runs:
79
+ raise SystemExit("No dataset runs found under ./datasets")
80
+ return max(runs, key=lambda p: p.stat().st_mtime)
81
+
82
+
83
+ def file_md5(path: Path) -> str:
84
+ """Calculate MD5 hash of a file."""
85
+ hasher = hashlib.md5()
86
+ with open(path, "rb") as f:
87
+ for chunk in iter(lambda: f.read(8192), b""):
88
+ hasher.update(chunk)
89
+ return hasher.hexdigest()
90
+
91
+
92
+ def create_archive(run_path: Path, temp_dir: Path) -> Path:
93
+ """Create a gzipped tarball of the dataset run."""
94
+ archive_path = temp_dir / f"{run_path.name}.tar.gz"
95
+ print(f"Creating archive {archive_path.name}...")
96
+ with tarfile.open(archive_path, "w:gz") as archive:
97
+ archive.add(run_path, arcname=run_path.name)
98
+ return archive_path
99
+
100
+
101
+ def split_archive(archive_path: Path, chunk_size_mb: int) -> list[Path]:
102
+ """Split archive into chunks of specified size."""
103
+ chunk_size = chunk_size_mb * 1024 * 1024
104
+ archive_size = archive_path.stat().st_size
105
+ num_chunks = math.ceil(archive_size / chunk_size)
106
+
107
+ if num_chunks == 1:
108
+ return [archive_path]
109
+
110
+ print(f"Splitting into {num_chunks} chunks of {chunk_size_mb}MB each...")
111
+ chunks = []
112
+ with open(archive_path, "rb") as f:
113
+ for i in range(num_chunks):
114
+ chunk_path = archive_path.parent / f"{archive_path.stem}.part{i:03d}"
115
+ with open(chunk_path, "wb") as chunk_file:
116
+ chunk_file.write(f.read(chunk_size))
117
+ chunks.append(chunk_path)
118
+ print(f" Created {chunk_path.name}")
119
+
120
+ return chunks
121
+
122
+
123
+ def create_manifest(
124
+ ds_name: str, chunks: list[Path], temp_dir: Path
125
+ ) -> tuple[Path, dict[str, str]]:
126
+ """Create a manifest file with chunk info and checksums."""
127
+ chunks_dict: dict[str, dict[str, object]] = {}
128
+ checksums: dict[str, str] = {}
129
+
130
+ for chunk in chunks:
131
+ checksum = file_md5(chunk)
132
+ chunks_dict[chunk.name] = {
133
+ "size": chunk.stat().st_size,
134
+ "md5": checksum,
135
+ }
136
+ checksums[chunk.name] = checksum
137
+
138
+ manifest: dict[str, object] = {
139
+ "ds_name": ds_name,
140
+ "num_chunks": len(chunks),
141
+ "chunks": chunks_dict,
142
+ }
143
+
144
+ manifest_path = temp_dir / f"{ds_name}.manifest.json"
145
+ with open(manifest_path, "w") as f:
146
+ json.dump(manifest, f, indent=2)
147
+
148
+ return manifest_path, checksums
149
+
150
+
151
+ def get_uploaded_chunks(ds_name: str) -> dict[str, str]:
152
+ """Get list of already uploaded chunks from Modal volume."""
153
+ try:
154
+ result = subprocess.run(
155
+ ["uvx", "modal", "volume", "ls", DEFAULT_VOLUME, f"/datasets/{ds_name}_chunks/"],
156
+ capture_output=True,
157
+ text=True,
158
+ check=False,
159
+ )
160
+ if result.returncode != 0:
161
+ return {}
162
+
163
+ # Parse the ls output to get filenames
164
+ uploaded = {}
165
+ for line in result.stdout.strip().split("\n"):
166
+ if line and not line.startswith("Directory"):
167
+ # Extract filename from ls output
168
+ parts = line.split()
169
+ if parts:
170
+ filename = parts[-1]
171
+ if filename.endswith(".md5"):
172
+ continue
173
+ # Try to get the checksum file
174
+ md5_result = subprocess.run(
175
+ [
176
+ "uvx",
177
+ "modal",
178
+ "volume",
179
+ "get",
180
+ DEFAULT_VOLUME,
181
+ f"/datasets/{ds_name}_chunks/{filename}.md5",
182
+ ],
183
+ capture_output=True,
184
+ text=True,
185
+ check=False,
186
+ )
187
+ if md5_result.returncode == 0:
188
+ uploaded[filename] = md5_result.stdout.strip()
189
+ return uploaded
190
+ except Exception:
191
+ return {}
192
+
193
+
194
+ def ensure_volume() -> None:
195
+ """Create the Modal volume if it doesn't exist."""
196
+ subprocess.run(["uvx", "modal", "volume", "create", DEFAULT_VOLUME], check=False)
197
+
198
+
199
+ def upload_chunk(chunk_path: Path, ds_name: str, checksum: str) -> None:
200
+ """Upload a single chunk to the Modal volume."""
201
+ chunk_name = chunk_path.name
202
+ remote_path = f"/datasets/{ds_name}_chunks/{chunk_name}"
203
+
204
+ # Upload the chunk
205
+ subprocess.run(
206
+ [
207
+ "uvx",
208
+ "modal",
209
+ "volume",
210
+ "put",
211
+ "-f",
212
+ DEFAULT_VOLUME,
213
+ str(chunk_path),
214
+ remote_path,
215
+ ],
216
+ check=True,
217
+ )
218
+
219
+ # Upload checksum file for resume verification
220
+ checksum_path = chunk_path.parent / f"{chunk_name}.md5"
221
+ with open(checksum_path, "w") as f:
222
+ f.write(checksum)
223
+
224
+ subprocess.run(
225
+ [
226
+ "uvx",
227
+ "modal",
228
+ "volume",
229
+ "put",
230
+ "-f",
231
+ DEFAULT_VOLUME,
232
+ str(checksum_path),
233
+ f"{remote_path}.md5",
234
+ ],
235
+ check=True,
236
+ )
237
+
238
+
239
+ def upload_manifest(manifest_path: Path, ds_name: str) -> None:
240
+ """Upload the manifest file."""
241
+ subprocess.run(
242
+ [
243
+ "uvx",
244
+ "modal",
245
+ "volume",
246
+ "put",
247
+ "-f",
248
+ DEFAULT_VOLUME,
249
+ str(manifest_path),
250
+ f"/datasets/{ds_name}_chunks/{manifest_path.name}",
251
+ ],
252
+ check=True,
253
+ )
254
+
255
+
256
+ def main() -> None:
257
+ """Archive and upload a dataset run to Modal with chunking and resume support."""
258
+ args = parse_args()
259
+ run_path = args.run_dir if args.run_dir else pick_latest_run()
260
+ run_path = run_path.resolve()
261
+ if not run_path.exists():
262
+ raise SystemExit(f"Run path {run_path} does not exist")
263
+
264
+ ds_name = run_path.name
265
+ temp_dir = Path(tempfile.mkdtemp(prefix="dataset_archive_"))
266
+
267
+ try:
268
+ ensure_volume()
269
+
270
+ # Check for existing uploads if resuming
271
+ uploaded_chunks: dict[str, str] = {}
272
+ if args.resume:
273
+ print("Checking for previously uploaded chunks...")
274
+ uploaded_chunks = get_uploaded_chunks(ds_name)
275
+ if uploaded_chunks:
276
+ print(f"Found {len(uploaded_chunks)} previously uploaded chunks")
277
+
278
+ # Create archive
279
+ archive_path = create_archive(run_path, temp_dir)
280
+ total_size = archive_path.stat().st_size
281
+ print(f"Archive size: {total_size / (1024 * 1024):.1f}MB")
282
+
283
+ # Split into chunks
284
+ chunks = split_archive(archive_path, args.chunk_size)
285
+
286
+ # Create manifest with checksums
287
+ manifest_path, checksums = create_manifest(ds_name, chunks, temp_dir)
288
+
289
+ # Upload chunks (skip already uploaded ones with matching checksums)
290
+ for chunk in chunks:
291
+ chunk_name = chunk.name
292
+ expected_checksum = checksums[chunk_name]
293
+
294
+ if chunk_name in uploaded_chunks:
295
+ if uploaded_chunks[chunk_name] == expected_checksum:
296
+ print(f"Skipping {chunk_name} (already uploaded, checksum matches)")
297
+ continue
298
+ else:
299
+ print(f"Re-uploading {chunk_name} (checksum mismatch)")
300
+
301
+ print(f"Uploading {chunk_name}...")
302
+ upload_chunk(chunk, ds_name, expected_checksum)
303
+
304
+ # Upload manifest
305
+ print("Uploading manifest...")
306
+ upload_manifest(manifest_path, ds_name)
307
+
308
+ finally:
309
+ shutil.rmtree(temp_dir, ignore_errors=True)
310
+
311
+ print(f"\nUploaded {ds_name} to Modal volume '{DEFAULT_VOLUME}' ({len(chunks)} chunks)")
312
+ # Output dataset name for shell script to use
313
+ print(f"DATASET_NAME={ds_name}")
314
+
315
+
316
+ if __name__ == "__main__":
317
+ main()
@@ -0,0 +1,104 @@
1
+ Use a mouse and keyboard to interact with a computer, and take screenshots.
2
+ * This is an interface to a desktop GUI. You do not have access to a terminal or applications menu. You must click on desktop icons to start applications.
3
+ * Some applications may take time to start or process actions, so you may need to wait and take successive screenshots to see the results of your actions. E.g. if you click on Firefox and a window doesn't open, try wait and taking another screenshot.
4
+ * The screen's resolution is 1000x1000.
5
+ * Whenever you intend to move the cursor to click on an element like an icon, you should consult a screenshot to determine the coordinates of the element before moving the cursor.
6
+ * If you tried clicking on a program or link but it failed to load even after waiting, try adjusting your cursor position so that the tip of the cursor visually falls on the element that you want to click.
7
+ * Make sure to click any buttons, links, icons, etc with the cursor tip in the center of the element. Don't click boxes on their edges unless asked.
8
+
9
+ # Tools
10
+
11
+ You may call one or more functions to assist with the user query.
12
+
13
+ You are provided with function signatures within <tools></tools> XML tags:
14
+ <tools>
15
+ {
16
+ "type": "function",
17
+ "function": {
18
+ "name_for_human": "computer_use",
19
+ "name": "computer_use",
20
+ "description": "Perform computer actions",
21
+ "parameters": {
22
+ "properties": {
23
+ "action": {
24
+ "description": "* `key`: Performs key down presses on the arguments passed in order, then performs key releases in reverse order.\n* `type`: Type a string of text on the keyboard.\n* `mouse_move`: Move the cursor to a specified (x, y) pixel coordinate on the screen.\n* `left_click`: Left click at a specified (x, y) pixel coordinate on the screen.\n* `left_click_drag`: Click and drag the cursor to a specified (x, y) pixel coordinate on the screen.\n* `right_click`: Right click at a specified (x, y) pixel coordinate on the screen.\n* `middle_click`: Middle click at a specified (x, y) pixel coordinate on the screen.\n* `double_click`: Double-click the left mouse button at a specified (x, y) pixel coordinate on the screen.\n* `triple_click`: Triple-click the left mouse button at a specified (x, y) pixel coordinate on the screen.\n* `scroll`: Performs a scroll of the mouse scroll wheel.\n* `hscroll`: Performs a horizontal scroll.\n* `wait`: Wait specified seconds for the change to happen.\n* `terminate`: Terminate the current task and report its completion status.\n* `answer`: Answer a question.",
25
+ "enum": ["key", "type", "mouse_move", "left_click", "left_click_drag", "right_click", "middle_click", "double_click", "triple_click", "scroll", "hscroll", "wait", "terminate", "answer"],
26
+ "type": "string"
27
+ },
28
+ "keys": {
29
+ "description": "Required only by `action=key`.",
30
+ "type": "array"
31
+ },
32
+ "text": {
33
+ "description": "Required only by `action=type`.",
34
+ "type": "string"
35
+ },
36
+ "coordinate": {
37
+ "description": "The x,y coordinates for mouse actions.",
38
+ "type": "array"
39
+ },
40
+ "pixels": {
41
+ "description": "The amount of scrolling.",
42
+ "type": "number"
43
+ },
44
+ "time": {
45
+ "description": "The seconds to wait.",
46
+ "type": "number"
47
+ },
48
+ "status": {
49
+ "description": "The status of the task.",
50
+ "type": "string",
51
+ "enum": ["success", "failure"]
52
+ }
53
+ },
54
+ "required": ["action"],
55
+ "type": "object"
56
+ },
57
+ "args_format": "Format the arguments as a JSON object."
58
+ }
59
+ }
60
+ {
61
+ "type": "function",
62
+ "function": {
63
+ "name_for_human": "get_bbox",
64
+ "name": "get_bbox",
65
+ "description": "Return the bounding box for a UI element",
66
+ "parameters": {
67
+ "properties": {
68
+ "bbox_2d": {
69
+ "description": "The bounding box coordinates [x1, y1, x2, y2] in resolution units (0-1000).",
70
+ "type": "array",
71
+ "items": {"type": "number"},
72
+ "minItems": 4,
73
+ "maxItems": 4
74
+ },
75
+ "label": {
76
+ "description": "The text label of the UI element.",
77
+ "type": "string"
78
+ }
79
+ },
80
+ "required": ["bbox_2d"],
81
+ "type": "object"
82
+ },
83
+ "args_format": "Format the arguments as a JSON object."
84
+ }
85
+ }
86
+ </tools>
87
+
88
+ For each function call, return a json object with function name and arguments within <tool_call></tool_call> XML tags:
89
+ <tool_call>
90
+ {"name": <function-name>, "arguments": <args-json-object>}
91
+ </tool_call>
92
+
93
+ # Response format
94
+
95
+ Response format for every step:
96
+ 1) Action: a short imperative describing what to do in the UI.
97
+ 2) One or more <tool_call>...</tool_call> blocks, one per line, each containing only the JSON.
98
+
99
+ Rules:
100
+ - Output exactly in the order: Action, <tool_call>(s).
101
+ - Be brief: one sentence for Action.
102
+ - Multiple tool calls can be output, one per line.
103
+ - Do not output anything else outside those parts.
104
+ - If finishing, use action=terminate in the tool call.
@@ -0,0 +1,33 @@
1
+ # Copyright (c) 2025 Tylt LLC. All rights reserved.
2
+ # CONFIDENTIAL AND PROPRIETARY. Unauthorized use, copying, or distribution
3
+ # is strictly prohibited. For licensing inquiries: hello@claimhawk.app
4
+
5
+ """System prompts and tool definitions for computer use training."""
6
+
7
+ from cudag.prompts.system import (
8
+ CUA_SYSTEM_PROMPT,
9
+ SYSTEM_PROMPT,
10
+ get_system_prompt,
11
+ )
12
+ from cudag.prompts.tools import (
13
+ COMPUTER_USE_TOOL,
14
+ TOOL_ACTIONS,
15
+ BboxCall,
16
+ ToolCall,
17
+ format_tool_call,
18
+ parse_tool_call,
19
+ validate_tool_call,
20
+ )
21
+
22
+ __all__ = [
23
+ "COMPUTER_USE_TOOL",
24
+ "TOOL_ACTIONS",
25
+ "BboxCall",
26
+ "ToolCall",
27
+ "format_tool_call",
28
+ "parse_tool_call",
29
+ "validate_tool_call",
30
+ "CUA_SYSTEM_PROMPT",
31
+ "SYSTEM_PROMPT",
32
+ "get_system_prompt",
33
+ ]
@@ -0,0 +1,43 @@
1
+ # Copyright (c) 2025 Tylt LLC. All rights reserved.
2
+ # CONFIDENTIAL AND PROPRIETARY. Unauthorized use, copying, or distribution
3
+ # is strictly prohibited. For licensing inquiries: hello@claimhawk.app
4
+
5
+ """System prompt for VLM training datasets.
6
+
7
+ IMPORTANT: The system prompt is managed by the system-prompt project.
8
+ Run `system-prompt/scripts/sync.sh` to update from the canonical source.
9
+ """
10
+
11
+ from __future__ import annotations
12
+
13
+ from pathlib import Path
14
+
15
+ # Load prompt from text file (managed by system-prompt project)
16
+ _PROMPTS_DIR = Path(__file__).parent
17
+
18
+
19
+ def _load_prompt() -> str:
20
+ """Load the system prompt from text file."""
21
+ filepath = _PROMPTS_DIR / "SYSTEM_PROMPT.txt"
22
+ if not filepath.exists():
23
+ raise FileNotFoundError(
24
+ f"System prompt file not found: {filepath}\n"
25
+ "Run system-prompt/scripts/sync.sh to generate prompt files."
26
+ )
27
+ return filepath.read_text().strip()
28
+
29
+
30
+ # The canonical system prompt
31
+ SYSTEM_PROMPT = _load_prompt()
32
+
33
+ # Aliases for backward compatibility
34
+ CUA_SYSTEM_PROMPT = SYSTEM_PROMPT
35
+
36
+
37
+ def get_system_prompt() -> str:
38
+ """Get the system prompt.
39
+
40
+ Returns:
41
+ System prompt string
42
+ """
43
+ return SYSTEM_PROMPT