fleet-python 0.2.66a2__tar.gz → 0.2.99__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 (125) hide show
  1. {fleet_python-0.2.66a2/fleet_python.egg-info → fleet_python-0.2.99}/PKG-INFO +14 -1
  2. {fleet_python-0.2.66a2 → fleet_python-0.2.99}/README.md +4 -0
  3. {fleet_python-0.2.66a2 → fleet_python-0.2.99}/examples/export_tasks.py +11 -1
  4. fleet_python-0.2.99/examples/fetch_tasks.py +230 -0
  5. {fleet_python-0.2.66a2 → fleet_python-0.2.99}/examples/import_tasks.py +15 -0
  6. fleet_python-0.2.99/examples/iterate_verifiers.py +725 -0
  7. {fleet_python-0.2.66a2 → fleet_python-0.2.99}/fleet/__init__.py +128 -5
  8. {fleet_python-0.2.66a2 → fleet_python-0.2.99}/fleet/_async/__init__.py +27 -3
  9. {fleet_python-0.2.66a2 → fleet_python-0.2.99}/fleet/_async/base.py +24 -9
  10. fleet_python-0.2.99/fleet/_async/client.py +1752 -0
  11. fleet_python-0.2.99/fleet/_async/env/client.py +104 -0
  12. {fleet_python-0.2.66a2 → fleet_python-0.2.99}/fleet/_async/instance/client.py +46 -5
  13. {fleet_python-0.2.66a2 → fleet_python-0.2.99}/fleet/_async/models.py +4 -0
  14. fleet_python-0.2.99/fleet/_async/resources/api.py +200 -0
  15. fleet_python-0.2.99/fleet/_async/resources/sqlite.py +2405 -0
  16. {fleet_python-0.2.66a2 → fleet_python-0.2.99}/fleet/_async/tasks.py +85 -17
  17. {fleet_python-0.2.66a2 → fleet_python-0.2.99}/fleet/_async/verifiers/bundler.py +22 -21
  18. {fleet_python-0.2.66a2 → fleet_python-0.2.99}/fleet/_async/verifiers/verifier.py +25 -19
  19. fleet_python-0.2.99/fleet/agent/__init__.py +32 -0
  20. fleet_python-0.2.99/fleet/agent/gemini_cua/Dockerfile +45 -0
  21. fleet_python-0.2.99/fleet/agent/gemini_cua/__init__.py +10 -0
  22. fleet_python-0.2.99/fleet/agent/gemini_cua/agent.py +541 -0
  23. fleet_python-0.2.99/fleet/agent/gemini_cua/mcp/main.py +108 -0
  24. fleet_python-0.2.99/fleet/agent/gemini_cua/mcp_server/__init__.py +5 -0
  25. fleet_python-0.2.99/fleet/agent/gemini_cua/mcp_server/main.py +105 -0
  26. fleet_python-0.2.99/fleet/agent/gemini_cua/mcp_server/tools.py +178 -0
  27. fleet_python-0.2.99/fleet/agent/gemini_cua/requirements.txt +5 -0
  28. fleet_python-0.2.99/fleet/agent/gemini_cua/start.sh +30 -0
  29. fleet_python-0.2.99/fleet/agent/orchestrator.py +854 -0
  30. fleet_python-0.2.99/fleet/agent/types.py +49 -0
  31. fleet_python-0.2.99/fleet/agent/utils.py +34 -0
  32. {fleet_python-0.2.66a2 → fleet_python-0.2.99}/fleet/base.py +34 -9
  33. fleet_python-0.2.99/fleet/cli.py +1061 -0
  34. fleet_python-0.2.99/fleet/client.py +1866 -0
  35. {fleet_python-0.2.66a2 → fleet_python-0.2.99}/fleet/config.py +1 -1
  36. {fleet_python-0.2.66a2 → fleet_python-0.2.99}/fleet/env/__init__.py +16 -0
  37. fleet_python-0.2.99/fleet/env/client.py +104 -0
  38. fleet_python-0.2.99/fleet/eval/__init__.py +15 -0
  39. fleet_python-0.2.99/fleet/eval/uploader.py +231 -0
  40. {fleet_python-0.2.66a2 → fleet_python-0.2.99}/fleet/exceptions.py +8 -0
  41. {fleet_python-0.2.66a2 → fleet_python-0.2.99}/fleet/instance/client.py +47 -6
  42. {fleet_python-0.2.66a2 → fleet_python-0.2.99}/fleet/instance/models.py +1 -0
  43. {fleet_python-0.2.66a2 → fleet_python-0.2.99}/fleet/models.py +290 -0
  44. fleet_python-0.2.99/fleet/proxy/__init__.py +25 -0
  45. fleet_python-0.2.99/fleet/proxy/proxy.py +453 -0
  46. fleet_python-0.2.99/fleet/proxy/whitelist.py +244 -0
  47. fleet_python-0.2.99/fleet/resources/api.py +200 -0
  48. fleet_python-0.2.99/fleet/resources/sqlite.py +2439 -0
  49. {fleet_python-0.2.66a2 → fleet_python-0.2.99}/fleet/tasks.py +84 -16
  50. fleet_python-0.2.99/fleet/utils/__init__.py +7 -0
  51. fleet_python-0.2.99/fleet/utils/http_logging.py +178 -0
  52. fleet_python-0.2.99/fleet/utils/logging.py +13 -0
  53. fleet_python-0.2.99/fleet/utils/playwright.py +440 -0
  54. {fleet_python-0.2.66a2 → fleet_python-0.2.99}/fleet/verifiers/bundler.py +22 -21
  55. {fleet_python-0.2.66a2 → fleet_python-0.2.99}/fleet/verifiers/db.py +488 -142
  56. {fleet_python-0.2.66a2 → fleet_python-0.2.99}/fleet/verifiers/decorator.py +1 -1
  57. {fleet_python-0.2.66a2 → fleet_python-0.2.99}/fleet/verifiers/verifier.py +25 -19
  58. {fleet_python-0.2.66a2 → fleet_python-0.2.99/fleet_python.egg-info}/PKG-INFO +14 -1
  59. {fleet_python-0.2.66a2 → fleet_python-0.2.99}/fleet_python.egg-info/SOURCES.txt +32 -0
  60. fleet_python-0.2.99/fleet_python.egg-info/entry_points.txt +2 -0
  61. {fleet_python-0.2.66a2 → fleet_python-0.2.99}/fleet_python.egg-info/requires.txt +13 -0
  62. {fleet_python-0.2.66a2 → fleet_python-0.2.99}/pyproject.toml +19 -1
  63. fleet_python-0.2.99/tests/test_app_method.py +85 -0
  64. fleet_python-0.2.99/tests/test_expect_only.py +2593 -0
  65. fleet_python-0.2.99/tests/test_instance_dispatch.py +607 -0
  66. fleet_python-0.2.99/tests/test_sqlite_resource_dual_mode.py +263 -0
  67. fleet_python-0.2.99/tests/test_sqlite_shared_memory_behavior.py +117 -0
  68. fleet_python-0.2.66a2/fleet/_async/client.py +0 -874
  69. fleet_python-0.2.66a2/fleet/_async/env/client.py +0 -47
  70. fleet_python-0.2.66a2/fleet/_async/resources/sqlite.py +0 -831
  71. fleet_python-0.2.66a2/fleet/client.py +0 -873
  72. fleet_python-0.2.66a2/fleet/env/client.py +0 -47
  73. fleet_python-0.2.66a2/fleet/resources/sqlite.py +0 -821
  74. fleet_python-0.2.66a2/tests/test_expect_only.py +0 -1098
  75. {fleet_python-0.2.66a2 → fleet_python-0.2.99}/LICENSE +0 -0
  76. {fleet_python-0.2.66a2 → fleet_python-0.2.99}/examples/diff_example.py +0 -0
  77. {fleet_python-0.2.66a2 → fleet_python-0.2.99}/examples/dsl_example.py +0 -0
  78. {fleet_python-0.2.66a2 → fleet_python-0.2.99}/examples/example.py +0 -0
  79. {fleet_python-0.2.66a2 → fleet_python-0.2.99}/examples/exampleResume.py +0 -0
  80. {fleet_python-0.2.66a2 → fleet_python-0.2.99}/examples/example_account.py +0 -0
  81. {fleet_python-0.2.66a2 → fleet_python-0.2.99}/examples/example_action_log.py +0 -0
  82. {fleet_python-0.2.66a2 → fleet_python-0.2.99}/examples/example_client.py +0 -0
  83. {fleet_python-0.2.66a2 → fleet_python-0.2.99}/examples/example_mcp_anthropic.py +0 -0
  84. {fleet_python-0.2.66a2 → fleet_python-0.2.99}/examples/example_mcp_openai.py +0 -0
  85. {fleet_python-0.2.66a2 → fleet_python-0.2.99}/examples/example_sync.py +0 -0
  86. {fleet_python-0.2.66a2 → fleet_python-0.2.99}/examples/example_task.py +0 -0
  87. {fleet_python-0.2.66a2 → fleet_python-0.2.99}/examples/example_tasks.py +0 -0
  88. {fleet_python-0.2.66a2 → fleet_python-0.2.99}/examples/example_verifier.py +0 -0
  89. {fleet_python-0.2.66a2 → fleet_python-0.2.99}/examples/gemini_example.py +0 -0
  90. {fleet_python-0.2.66a2 → fleet_python-0.2.99}/examples/json_tasks_example.py +0 -0
  91. {fleet_python-0.2.66a2 → fleet_python-0.2.99}/examples/nova_act_example.py +0 -0
  92. {fleet_python-0.2.66a2 → fleet_python-0.2.99}/examples/openai_example.py +0 -0
  93. {fleet_python-0.2.66a2 → fleet_python-0.2.99}/examples/openai_simple_example.py +0 -0
  94. {fleet_python-0.2.66a2 → fleet_python-0.2.99}/examples/query_builder_example.py +0 -0
  95. {fleet_python-0.2.66a2 → fleet_python-0.2.99}/examples/quickstart.py +0 -0
  96. {fleet_python-0.2.66a2 → fleet_python-0.2.99}/examples/test_cdp_logging.py +0 -0
  97. {fleet_python-0.2.66a2 → fleet_python-0.2.99}/fleet/_async/env/__init__.py +0 -0
  98. {fleet_python-0.2.66a2 → fleet_python-0.2.99}/fleet/_async/exceptions.py +0 -0
  99. {fleet_python-0.2.66a2 → fleet_python-0.2.99}/fleet/_async/global_client.py +0 -0
  100. {fleet_python-0.2.66a2 → fleet_python-0.2.99}/fleet/_async/instance/__init__.py +0 -0
  101. {fleet_python-0.2.66a2 → fleet_python-0.2.99}/fleet/_async/instance/base.py +0 -0
  102. {fleet_python-0.2.66a2 → fleet_python-0.2.99}/fleet/_async/resources/__init__.py +0 -0
  103. {fleet_python-0.2.66a2 → fleet_python-0.2.99}/fleet/_async/resources/base.py +0 -0
  104. {fleet_python-0.2.66a2 → fleet_python-0.2.99}/fleet/_async/resources/browser.py +0 -0
  105. {fleet_python-0.2.66a2 → fleet_python-0.2.99}/fleet/_async/resources/mcp.py +0 -0
  106. {fleet_python-0.2.66a2 → fleet_python-0.2.99}/fleet/_async/verifiers/__init__.py +0 -0
  107. {fleet_python-0.2.66a2 → fleet_python-0.2.99}/fleet/global_client.py +0 -0
  108. {fleet_python-0.2.66a2 → fleet_python-0.2.99}/fleet/instance/__init__.py +0 -0
  109. {fleet_python-0.2.66a2 → fleet_python-0.2.99}/fleet/instance/base.py +0 -0
  110. {fleet_python-0.2.66a2 → fleet_python-0.2.99}/fleet/resources/__init__.py +0 -0
  111. {fleet_python-0.2.66a2 → fleet_python-0.2.99}/fleet/resources/base.py +0 -0
  112. {fleet_python-0.2.66a2 → fleet_python-0.2.99}/fleet/resources/browser.py +0 -0
  113. {fleet_python-0.2.66a2 → fleet_python-0.2.99}/fleet/resources/mcp.py +0 -0
  114. {fleet_python-0.2.66a2 → fleet_python-0.2.99}/fleet/types.py +0 -0
  115. {fleet_python-0.2.66a2 → fleet_python-0.2.99}/fleet/verifiers/__init__.py +0 -0
  116. {fleet_python-0.2.66a2 → fleet_python-0.2.99}/fleet/verifiers/code.py +0 -0
  117. {fleet_python-0.2.66a2 → fleet_python-0.2.99}/fleet/verifiers/parse.py +0 -0
  118. {fleet_python-0.2.66a2 → fleet_python-0.2.99}/fleet/verifiers/sql_differ.py +0 -0
  119. {fleet_python-0.2.66a2 → fleet_python-0.2.99}/fleet_python.egg-info/dependency_links.txt +0 -0
  120. {fleet_python-0.2.66a2 → fleet_python-0.2.99}/fleet_python.egg-info/top_level.txt +0 -0
  121. {fleet_python-0.2.66a2 → fleet_python-0.2.99}/scripts/fix_sync_imports.py +0 -0
  122. {fleet_python-0.2.66a2 → fleet_python-0.2.99}/scripts/unasync.py +0 -0
  123. {fleet_python-0.2.66a2 → fleet_python-0.2.99}/setup.cfg +0 -0
  124. {fleet_python-0.2.66a2 → fleet_python-0.2.99}/tests/__init__.py +0 -0
  125. {fleet_python-0.2.66a2 → fleet_python-0.2.99}/tests/test_verifier_from_string.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: fleet-python
3
- Version: 0.2.66a2
3
+ Version: 0.2.99
4
4
  Summary: Python SDK for Fleet environments
5
5
  Author-email: Fleet AI <nic@fleet.so>
6
6
  License: Apache-2.0
@@ -26,6 +26,9 @@ Requires-Dist: httpx-retries>=0.4.0
26
26
  Requires-Dist: typing-extensions>=4.0.0
27
27
  Requires-Dist: modulegraph2>=0.2.0
28
28
  Requires-Dist: cloudpickle==3.1.1
29
+ Provides-Extra: cli
30
+ Requires-Dist: typer>=0.9.0; extra == "cli"
31
+ Requires-Dist: rich>=10.0.0; extra == "cli"
29
32
  Provides-Extra: dev
30
33
  Requires-Dist: pytest>=7.0.0; extra == "dev"
31
34
  Requires-Dist: pytest-asyncio>=0.21.0; extra == "dev"
@@ -35,12 +38,22 @@ Requires-Dist: mypy>=1.0.0; extra == "dev"
35
38
  Requires-Dist: ruff>=0.1.0; extra == "dev"
36
39
  Requires-Dist: unasync>=0.6.0; extra == "dev"
37
40
  Requires-Dist: python-dotenv>=1.1.1; extra == "dev"
41
+ Requires-Dist: typer>=0.9.0; extra == "dev"
42
+ Requires-Dist: rich>=10.0.0; extra == "dev"
38
43
  Provides-Extra: playwright
39
44
  Requires-Dist: playwright>=1.40.0; extra == "playwright"
45
+ Provides-Extra: eval
46
+ Requires-Dist: aiohttp>=3.9.0; extra == "eval"
47
+ Requires-Dist: google-genai>=1.0.0; extra == "eval"
48
+ Requires-Dist: mcp==1.24.0; python_version >= "3.10" and extra == "eval"
40
49
  Dynamic: license-file
41
50
 
42
51
  # Fleet SDK
43
52
 
53
+ [![PyPI version](https://img.shields.io/pypi/v/fleet-python.svg)](https://pypi.org/project/fleet-python/)
54
+ [![Python versions](https://img.shields.io/pypi/pyversions/fleet-python.svg)](https://pypi.org/project/fleet-python/)
55
+ [![License](https://img.shields.io/pypi/l/fleet-python.svg)](https://pypi.org/project/fleet-python/)
56
+
44
57
  The Fleet Python SDK provides programmatic access to Fleet's environment infrastructure.
45
58
 
46
59
  ## Installation
@@ -1,5 +1,9 @@
1
1
  # Fleet SDK
2
2
 
3
+ [![PyPI version](https://img.shields.io/pypi/v/fleet-python.svg)](https://pypi.org/project/fleet-python/)
4
+ [![Python versions](https://img.shields.io/pypi/pyversions/fleet-python.svg)](https://pypi.org/project/fleet-python/)
5
+ [![License](https://img.shields.io/pypi/l/fleet-python.svg)](https://pypi.org/project/fleet-python/)
6
+
3
7
  The Fleet Python SDK provides programmatic access to Fleet's environment infrastructure.
4
8
 
5
9
  ## Installation
@@ -27,6 +27,12 @@ def main():
27
27
  help="Optional task project key to filter tasks",
28
28
  default=None,
29
29
  )
30
+ parser.add_argument(
31
+ "--env-key",
32
+ "-e",
33
+ help="Optional environment key to filter tasks",
34
+ default=None,
35
+ )
30
36
  parser.add_argument(
31
37
  "--output",
32
38
  "-o",
@@ -42,12 +48,13 @@ def main():
42
48
  args.project_key is not None,
43
49
  args.task_keys is not None,
44
50
  args.task_project_key is not None,
51
+ args.env_key is not None,
45
52
  ]
46
53
  )
47
54
 
48
55
  if filters_specified > 1:
49
56
  parser.error(
50
- "Cannot specify multiple filters. Use only one of --project-key, --task-keys, or --task-project-key."
57
+ "Cannot specify multiple filters. Use only one of --project-key, --task-keys, --task-project-key, or --env-key."
51
58
  )
52
59
 
53
60
  # Get account info
@@ -66,6 +73,9 @@ def main():
66
73
  elif args.task_project_key:
67
74
  print(f"Loading tasks from task project: {args.task_project_key}")
68
75
  tasks = fleet.load_tasks(task_project_key=args.task_project_key)
76
+ elif args.env_key:
77
+ print(f"Loading tasks from environment: {args.env_key}")
78
+ tasks = fleet.load_tasks(env_key=args.env_key)
69
79
  else:
70
80
  print("Loading all tasks")
71
81
  tasks = fleet.load_tasks()
@@ -0,0 +1,230 @@
1
+ import argparse
2
+ import asyncio
3
+ import json
4
+ import sys
5
+ from typing import List, Dict, Any, Optional, Tuple
6
+ import fleet
7
+ from dotenv import load_dotenv
8
+
9
+ load_dotenv()
10
+
11
+
12
+ async def fetch_task(
13
+ task_key: str, semaphore: asyncio.Semaphore
14
+ ) -> Tuple[str, Optional[Dict[str, Any]], Optional[str]]:
15
+ """
16
+ Fetch a single task from the Fleet API.
17
+
18
+ Args:
19
+ task_key: Task key to fetch
20
+ semaphore: Semaphore to limit concurrent requests
21
+
22
+ Returns:
23
+ Tuple of (task_key, task_data_dict, error_message)
24
+ """
25
+ async with semaphore:
26
+ try:
27
+ # Use load_tasks with keys parameter to get Task objects
28
+ tasks = await fleet.load_tasks_async(keys=[task_key])
29
+ if tasks:
30
+ task = tasks[0]
31
+ # Convert to dict using model_dump() like export_tasks.py does
32
+ return task_key, task.model_dump(), None
33
+ else:
34
+ return task_key, None, "Task not found"
35
+ except Exception as e:
36
+ error_msg = f"{type(e).__name__}: {str(e)}"
37
+ return task_key, None, error_msg
38
+
39
+
40
+ async def fetch_tasks_batch(
41
+ task_keys: List[str], max_concurrent: int = 20
42
+ ) -> Dict[str, Dict[str, Any]]:
43
+ """
44
+ Fetch multiple tasks concurrently from the Fleet API.
45
+
46
+ Args:
47
+ task_keys: List of task keys to fetch
48
+ max_concurrent: Maximum number of concurrent requests
49
+
50
+ Returns:
51
+ Dictionary mapping task_key to task data
52
+ """
53
+ print(f"\nFetching {len(task_keys)} task(s) from Fleet API...")
54
+ print(f"Max concurrent requests: {max_concurrent}")
55
+
56
+ semaphore = asyncio.Semaphore(max_concurrent)
57
+
58
+ # Fetch all tasks concurrently
59
+ results = await asyncio.gather(
60
+ *[fetch_task(key, semaphore) for key in task_keys],
61
+ return_exceptions=True,
62
+ )
63
+
64
+ # Process results
65
+ fetched_tasks = {}
66
+ errors = []
67
+
68
+ for result in results:
69
+ if isinstance(result, Exception):
70
+ errors.append(f"Unexpected error: {result}")
71
+ continue
72
+
73
+ task_key, task_data, error = result
74
+
75
+ if error:
76
+ errors.append(f"{task_key}: {error}")
77
+ print(f" ✗ {task_key}: {error}")
78
+ elif task_data:
79
+ # Task data is already a dict from model_dump()
80
+ fetched_tasks[task_key] = task_data
81
+ print(f" ✓ {task_key}")
82
+
83
+ print(f"\n✓ Successfully fetched {len(fetched_tasks)} task(s)")
84
+
85
+ if errors:
86
+ print(f"\n⚠ {len(errors)} error(s) occurred:")
87
+ for error in errors[:10]: # Show first 10
88
+ print(f" - {error}")
89
+ if len(errors) > 10:
90
+ print(f" ... and {len(errors) - 10} more")
91
+
92
+ return fetched_tasks
93
+
94
+
95
+ async def main():
96
+ parser = argparse.ArgumentParser(
97
+ description="Fetch tasks from Fleet API and update JSON file",
98
+ formatter_class=argparse.RawDescriptionHelpFormatter,
99
+ epilog="""
100
+ Examples:
101
+ # Fetch tasks and update in-place
102
+ %(prog)s tasks.json
103
+
104
+ # Fetch tasks and save to a new file
105
+ %(prog)s tasks.json --output updated_tasks.json
106
+
107
+ # Limit concurrent requests
108
+ %(prog)s tasks.json --max-concurrent 10
109
+ """,
110
+ )
111
+
112
+ parser.add_argument("json_file", help="Path to JSON file containing tasks")
113
+ parser.add_argument(
114
+ "--output",
115
+ "-o",
116
+ help="Path to output JSON file (defaults to overwriting input)",
117
+ default=None,
118
+ )
119
+ parser.add_argument(
120
+ "--max-concurrent",
121
+ "-c",
122
+ type=int,
123
+ default=20,
124
+ help="Maximum number of concurrent API requests (default: 20)",
125
+ )
126
+
127
+ args = parser.parse_args()
128
+
129
+ # Load JSON file
130
+ print(f"Reading tasks from: {args.json_file}")
131
+ try:
132
+ with open(args.json_file, "r", encoding="utf-8") as f:
133
+ tasks_data = json.load(f)
134
+ except FileNotFoundError:
135
+ print(f"✗ Error: File '{args.json_file}' not found")
136
+ sys.exit(1)
137
+ except json.JSONDecodeError as e:
138
+ print(f"✗ Error: Invalid JSON in '{args.json_file}': {e}")
139
+ sys.exit(1)
140
+
141
+ if not isinstance(tasks_data, list):
142
+ print("✗ Error: JSON file must contain an array of tasks")
143
+ sys.exit(1)
144
+
145
+ print(f"Found {len(tasks_data)} task(s) in file")
146
+
147
+ # Extract task keys
148
+ task_keys = []
149
+ missing_keys = []
150
+
151
+ for i, task in enumerate(tasks_data):
152
+ task_key = task.get("key") or task.get("id")
153
+ if task_key:
154
+ task_keys.append(task_key)
155
+ else:
156
+ missing_keys.append(f"Task at index {i}")
157
+
158
+ if missing_keys:
159
+ print(f"\n⚠ Warning: {len(missing_keys)} task(s) missing key/id:")
160
+ for key in missing_keys[:10]:
161
+ print(f" - {key}")
162
+ if len(missing_keys) > 10:
163
+ print(f" ... and {len(missing_keys) - 10} more")
164
+
165
+ if not task_keys:
166
+ print("\n✗ Error: No valid task keys found in JSON file")
167
+ sys.exit(1)
168
+
169
+ print(f"\nExtracted {len(task_keys)} task key(s)")
170
+
171
+ # Get account info
172
+ account = await fleet.env.account_async()
173
+ print(f"Fetching from team: {account.team_name}")
174
+
175
+ # Fetch tasks from API
176
+ fetched_tasks = await fetch_tasks_batch(task_keys, args.max_concurrent)
177
+
178
+ if not fetched_tasks:
179
+ print("\n✗ Error: No tasks were successfully fetched")
180
+ sys.exit(1)
181
+
182
+ # Update tasks in the original data
183
+ updated_count = 0
184
+ not_found = []
185
+
186
+ print("\nUpdating task data...")
187
+ for i, task in enumerate(tasks_data):
188
+ task_key = task.get("key") or task.get("id")
189
+
190
+ if task_key in fetched_tasks:
191
+ # Replace entire task with fetched data
192
+ tasks_data[i] = fetched_tasks[task_key]
193
+ updated_count += 1
194
+ else:
195
+ not_found.append(task_key or f"index {i}")
196
+
197
+ print(f"✓ Updated {updated_count} task(s)")
198
+
199
+ if not_found:
200
+ print(f"\n⚠ Warning: {len(not_found)} task(s) not fetched:")
201
+ for key in not_found[:10]:
202
+ print(f" - {key}")
203
+ if len(not_found) > 10:
204
+ print(f" ... and {len(not_found) - 10} more")
205
+
206
+ # Write output
207
+ output_file = args.output or args.json_file
208
+ print(f"\nWriting updated tasks to: {output_file}")
209
+
210
+ try:
211
+ with open(output_file, "w", encoding="utf-8") as f:
212
+ json.dump(tasks_data, f, indent=2, ensure_ascii=False)
213
+ print(f"✓ Successfully wrote {len(tasks_data)} task(s) to '{output_file}'")
214
+ except Exception as e:
215
+ print(f"✗ Error writing output file: {e}")
216
+ sys.exit(1)
217
+
218
+ # Summary
219
+ print("\n" + "=" * 60)
220
+ print("Summary:")
221
+ print(f" Total tasks in file: {len(tasks_data)}")
222
+ print(f" Successfully fetched: {len(fetched_tasks)}")
223
+ print(f" Updated in file: {updated_count}")
224
+ print(f" Failed to fetch: {len(task_keys) - len(fetched_tasks)}")
225
+ print("=" * 60)
226
+
227
+
228
+ if __name__ == "__main__":
229
+ asyncio.run(main())
230
+
@@ -210,9 +210,18 @@ async def main():
210
210
  action="store_true",
211
211
  help="Skip the verifier sanity check (not recommended)",
212
212
  )
213
+ parser.add_argument(
214
+ "--sanity-check-only",
215
+ action="store_true",
216
+ help="Only run the sanity check without importing tasks",
217
+ )
213
218
 
214
219
  args = parser.parse_args()
215
220
 
221
+ # Validate conflicting flags
222
+ if args.skip_sanity_check and args.sanity_check_only:
223
+ parser.error("Cannot use --skip-sanity-check and --sanity-check-only together")
224
+
216
225
  # Load and parse the JSON file
217
226
  try:
218
227
  with open(args.json_file, "r", encoding="utf-8") as f:
@@ -286,6 +295,12 @@ async def main():
286
295
  success, errors = await run_verifier_sanity_check(tasks, client)
287
296
  if not success:
288
297
  sys.exit(1)
298
+
299
+ # If only doing sanity check, exit successfully here
300
+ if args.sanity_check_only:
301
+ print("\n✓ Sanity check complete! (--sanity-check-only)")
302
+ print("Tasks are ready to import.")
303
+ sys.exit(0)
289
304
  else:
290
305
  print("\n⚠️ Skipping sanity check (--skip-sanity-check)")
291
306