weco 0.3.7__tar.gz → 0.3.8__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 (64) hide show
  1. {weco-0.3.7 → weco-0.3.8}/PKG-INFO +2 -2
  2. {weco-0.3.7 → weco-0.3.8}/README.md +1 -1
  3. {weco-0.3.7 → weco-0.3.8}/pyproject.toml +1 -1
  4. {weco-0.3.7 → weco-0.3.8}/weco/api.py +125 -0
  5. weco-0.3.8/weco/browser.py +29 -0
  6. {weco-0.3.7 → weco-0.3.8}/weco/cli.py +49 -7
  7. weco-0.3.8/weco/optimizer.py +651 -0
  8. weco-0.3.8/weco/ui.py +315 -0
  9. weco-0.3.8/weco/validation.py +112 -0
  10. {weco-0.3.7 → weco-0.3.8}/weco.egg-info/PKG-INFO +2 -2
  11. {weco-0.3.7 → weco-0.3.8}/weco.egg-info/SOURCES.txt +3 -0
  12. weco-0.3.7/weco/optimizer.py +0 -958
  13. {weco-0.3.7 → weco-0.3.8}/.github/workflows/lint.yml +0 -0
  14. {weco-0.3.7 → weco-0.3.8}/.github/workflows/release.yml +0 -0
  15. {weco-0.3.7 → weco-0.3.8}/.gitignore +0 -0
  16. {weco-0.3.7 → weco-0.3.8}/LICENSE +0 -0
  17. {weco-0.3.7 → weco-0.3.8}/assets/example-optimization.gif +0 -0
  18. {weco-0.3.7 → weco-0.3.8}/assets/weco.svg +0 -0
  19. {weco-0.3.7 → weco-0.3.8}/contributing.md +0 -0
  20. {weco-0.3.7 → weco-0.3.8}/examples/README.md +0 -0
  21. {weco-0.3.7 → weco-0.3.8}/examples/cuda/README.md +0 -0
  22. {weco-0.3.7 → weco-0.3.8}/examples/cuda/evaluate.py +0 -0
  23. {weco-0.3.7 → weco-0.3.8}/examples/cuda/module.py +0 -0
  24. {weco-0.3.7 → weco-0.3.8}/examples/cuda/requirements.txt +0 -0
  25. {weco-0.3.7 → weco-0.3.8}/examples/extract-line-plot/README.md +0 -0
  26. {weco-0.3.7 → weco-0.3.8}/examples/extract-line-plot/eval.py +0 -0
  27. {weco-0.3.7 → weco-0.3.8}/examples/extract-line-plot/guide.md +0 -0
  28. {weco-0.3.7 → weco-0.3.8}/examples/extract-line-plot/optimize.py +0 -0
  29. {weco-0.3.7 → weco-0.3.8}/examples/extract-line-plot/prepare_data.py +0 -0
  30. {weco-0.3.7 → weco-0.3.8}/examples/extract-line-plot/pyproject.toml +0 -0
  31. {weco-0.3.7 → weco-0.3.8}/examples/hello-world/README.md +0 -0
  32. {weco-0.3.7 → weco-0.3.8}/examples/hello-world/colab_notebook_walkthrough.ipynb +0 -0
  33. {weco-0.3.7 → weco-0.3.8}/examples/hello-world/evaluate.py +0 -0
  34. {weco-0.3.7 → weco-0.3.8}/examples/hello-world/module.py +0 -0
  35. {weco-0.3.7 → weco-0.3.8}/examples/hello-world/requirements.txt +0 -0
  36. {weco-0.3.7 → weco-0.3.8}/examples/prompt/README.md +0 -0
  37. {weco-0.3.7 → weco-0.3.8}/examples/prompt/eval.py +0 -0
  38. {weco-0.3.7 → weco-0.3.8}/examples/prompt/optimize.py +0 -0
  39. {weco-0.3.7 → weco-0.3.8}/examples/prompt/prompt_guide.md +0 -0
  40. {weco-0.3.7 → weco-0.3.8}/examples/spaceship-titanic/README.md +0 -0
  41. {weco-0.3.7 → weco-0.3.8}/examples/spaceship-titanic/competition_description.md +0 -0
  42. {weco-0.3.7 → weco-0.3.8}/examples/spaceship-titanic/data/sample_submission.csv +0 -0
  43. {weco-0.3.7 → weco-0.3.8}/examples/spaceship-titanic/data/test.csv +0 -0
  44. {weco-0.3.7 → weco-0.3.8}/examples/spaceship-titanic/data/train.csv +0 -0
  45. {weco-0.3.7 → weco-0.3.8}/examples/spaceship-titanic/evaluate.py +0 -0
  46. {weco-0.3.7 → weco-0.3.8}/examples/spaceship-titanic/train.py +0 -0
  47. {weco-0.3.7 → weco-0.3.8}/examples/triton/README.md +0 -0
  48. {weco-0.3.7 → weco-0.3.8}/examples/triton/evaluate.py +0 -0
  49. {weco-0.3.7 → weco-0.3.8}/examples/triton/module.py +0 -0
  50. {weco-0.3.7 → weco-0.3.8}/examples/triton/requirements.txt +0 -0
  51. {weco-0.3.7 → weco-0.3.8}/setup.cfg +0 -0
  52. {weco-0.3.7 → weco-0.3.8}/tests/__init__.py +0 -0
  53. {weco-0.3.7 → weco-0.3.8}/tests/test_byok.py +0 -0
  54. {weco-0.3.7 → weco-0.3.8}/tests/test_cli.py +0 -0
  55. {weco-0.3.7 → weco-0.3.8}/weco/__init__.py +0 -0
  56. {weco-0.3.7 → weco-0.3.8}/weco/auth.py +0 -0
  57. {weco-0.3.7 → weco-0.3.8}/weco/constants.py +0 -0
  58. {weco-0.3.7 → weco-0.3.8}/weco/credits.py +0 -0
  59. {weco-0.3.7 → weco-0.3.8}/weco/panels.py +0 -0
  60. {weco-0.3.7 → weco-0.3.8}/weco/utils.py +0 -0
  61. {weco-0.3.7 → weco-0.3.8}/weco.egg-info/dependency_links.txt +0 -0
  62. {weco-0.3.7 → weco-0.3.8}/weco.egg-info/entry_points.txt +0 -0
  63. {weco-0.3.7 → weco-0.3.8}/weco.egg-info/requires.txt +0 -0
  64. {weco-0.3.7 → weco-0.3.8}/weco.egg-info/top_level.txt +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: weco
3
- Version: 0.3.7
3
+ Version: 0.3.8
4
4
  Summary: Documentation for `weco`, a CLI for using Weco AI's code optimizer.
5
5
  Author-email: Weco AI Team <contact@weco.ai>
6
6
  License:
@@ -357,7 +357,7 @@ weco run --model gpt-5 --source optimize.py [other options...]
357
357
  - `claude-opus-4-5`, `claude-opus-4-1`, `claude-opus-4`, `claude-sonnet-4-5`, `claude-sonnet-4`, `claude-haiku-4-5`
358
358
 
359
359
  **Google Gemini:**
360
- - `gemini-3-pro-preview`, `gemini-2.5-pro`, `gemini-2.5-flash`, `gemini-2.5-flash-lite`
360
+ - `gemini-3-pro-preview`, `gemini-3-flash-preview`, `gemini-2.5-pro`, `gemini-2.5-flash`, `gemini-2.5-flash-lite`
361
361
 
362
362
  All models are available through Weco. If no model is specified, Weco automatically selects the best model for your optimization task.
363
363
 
@@ -128,7 +128,7 @@ weco run --model gpt-5 --source optimize.py [other options...]
128
128
  - `claude-opus-4-5`, `claude-opus-4-1`, `claude-opus-4`, `claude-sonnet-4-5`, `claude-sonnet-4`, `claude-haiku-4-5`
129
129
 
130
130
  **Google Gemini:**
131
- - `gemini-3-pro-preview`, `gemini-2.5-pro`, `gemini-2.5-flash`, `gemini-2.5-flash-lite`
131
+ - `gemini-3-pro-preview`, `gemini-3-flash-preview`, `gemini-2.5-pro`, `gemini-2.5-flash`, `gemini-2.5-flash-lite`
132
132
 
133
133
  All models are available through Weco. If no model is specified, Weco automatically selects the best model for your optimization task.
134
134
 
@@ -8,7 +8,7 @@ name = "weco"
8
8
  authors = [{ name = "Weco AI Team", email = "contact@weco.ai" }]
9
9
  description = "Documentation for `weco`, a CLI for using Weco AI's code optimizer."
10
10
  readme = "README.md"
11
- version = "0.3.7"
11
+ version = "0.3.8"
12
12
  license = { file = "LICENSE" }
13
13
  requires-python = ">=3.8"
14
14
  dependencies = [
@@ -1,4 +1,5 @@
1
1
  import sys
2
+ from dataclasses import dataclass
2
3
  from typing import Dict, Any, Optional, Union, Tuple
3
4
  import requests
4
5
  from rich.console import Console
@@ -7,6 +8,24 @@ from weco import __pkg_version__, __base_url__
7
8
  from .utils import truncate_output
8
9
 
9
10
 
11
+ @dataclass
12
+ class RunSummary:
13
+ """Brief run summary from execution task response."""
14
+
15
+ id: str
16
+ status: str
17
+ name: Optional[str] = None
18
+ require_review: bool = False
19
+
20
+
21
+ @dataclass
22
+ class ExecutionTasksResult:
23
+ """Result from get_execution_tasks containing tasks and run info."""
24
+
25
+ tasks: list
26
+ run: Optional[RunSummary] = None
27
+
28
+
10
29
  def handle_api_error(e: requests.exceptions.HTTPError, console: Console) -> None:
11
30
  """Extract and display error messages from API responses in a structured format."""
12
31
  status = getattr(e.response, "status_code", None)
@@ -110,6 +129,7 @@ def start_optimization_run(
110
129
  auth_headers: dict = {},
111
130
  timeout: Union[int, Tuple[int, int]] = (10, 3650),
112
131
  api_keys: Optional[Dict[str, str]] = None,
132
+ require_review: bool = False,
113
133
  ) -> Optional[Dict[str, Any]]:
114
134
  """Start the optimization run."""
115
135
  with console.status("[bold green]Starting Optimization..."):
@@ -128,6 +148,7 @@ def start_optimization_run(
128
148
  "eval_timeout": eval_timeout,
129
149
  "save_logs": save_logs,
130
150
  "log_dir": log_dir,
151
+ "require_review": require_review,
131
152
  "metadata": {"client_name": "cli", "client_version": __pkg_version__},
132
153
  }
133
154
  if api_keys:
@@ -315,3 +336,107 @@ def report_termination(
315
336
  except Exception as e:
316
337
  print(f"Warning: Failed to report termination to backend for run {run_id}: {e}", file=sys.stderr)
317
338
  return False
339
+
340
+
341
+ # --- Execution Queue API Functions ---
342
+
343
+
344
+ def get_execution_tasks(
345
+ run_id: str, auth_headers: dict = {}, timeout: Union[int, Tuple[int, int]] = (5, 30)
346
+ ) -> Optional[ExecutionTasksResult]:
347
+ """Poll for ready execution tasks.
348
+
349
+ Args:
350
+ run_id: The run ID to get tasks for.
351
+ auth_headers: Authentication headers.
352
+ timeout: Request timeout.
353
+
354
+ Returns:
355
+ ExecutionTasksResult with tasks and run summary, or None if request failed.
356
+ """
357
+ try:
358
+ response = requests.get(
359
+ f"{__base_url__}/execution-tasks/", params={"run_id": run_id}, headers=auth_headers, timeout=timeout
360
+ )
361
+ response.raise_for_status()
362
+ data = response.json()
363
+
364
+ # Extract run summary from top-level run field
365
+ run_summary = None
366
+ if data.get("run"):
367
+ run_data = data["run"]
368
+ run_summary = RunSummary(
369
+ id=run_data["id"],
370
+ status=run_data["status"],
371
+ name=run_data.get("name"),
372
+ require_review=run_data.get("require_review", False),
373
+ )
374
+
375
+ return ExecutionTasksResult(tasks=data.get("tasks", []), run=run_summary)
376
+ except requests.exceptions.HTTPError:
377
+ return None
378
+ except Exception:
379
+ return None
380
+
381
+
382
+ def claim_execution_task(
383
+ task_id: str, auth_headers: dict = {}, timeout: Union[int, Tuple[int, int]] = (5, 30)
384
+ ) -> Optional[Dict[str, Any]]:
385
+ """Claim an execution task.
386
+
387
+ Args:
388
+ task_id: The task ID to claim.
389
+ auth_headers: Authentication headers.
390
+ timeout: Request timeout.
391
+
392
+ Returns:
393
+ The claimed task with revision, or None if already claimed or error.
394
+ """
395
+ try:
396
+ response = requests.post(f"{__base_url__}/execution-tasks/{task_id}/claim", headers=auth_headers, timeout=timeout)
397
+ if response.status_code == 409:
398
+ return None # Already claimed
399
+ response.raise_for_status()
400
+ return response.json()
401
+ except requests.exceptions.HTTPError:
402
+ return None
403
+ except Exception:
404
+ return None
405
+
406
+
407
+ def submit_execution_result(
408
+ run_id: str,
409
+ task_id: str,
410
+ execution_output: str,
411
+ auth_headers: dict = {},
412
+ timeout: Union[int, Tuple[int, int]] = (10, 3650),
413
+ api_keys: Optional[Dict[str, str]] = None,
414
+ ) -> Optional[Dict[str, Any]]:
415
+ """Submit execution result for a task.
416
+
417
+ Args:
418
+ run_id: The run ID.
419
+ task_id: The task ID being completed.
420
+ execution_output: The execution output to submit.
421
+ auth_headers: Authentication headers.
422
+ timeout: Request timeout.
423
+ api_keys: Optional API keys for LLM providers.
424
+
425
+ Returns:
426
+ The suggest response, or None if request failed.
427
+ """
428
+ try:
429
+ truncated_output = truncate_output(execution_output)
430
+ request_json = {"execution_output": truncated_output, "task_id": task_id, "metadata": {}}
431
+ if api_keys:
432
+ request_json["api_keys"] = api_keys
433
+
434
+ response = requests.post(
435
+ f"{__base_url__}/runs/{run_id}/suggest", json=request_json, headers=auth_headers, timeout=timeout
436
+ )
437
+ response.raise_for_status()
438
+ return response.json()
439
+ except requests.exceptions.HTTPError:
440
+ return None
441
+ except Exception:
442
+ return None
@@ -0,0 +1,29 @@
1
+ """Cross-platform browser utilities."""
2
+
3
+ import webbrowser
4
+
5
+
6
+ def open_browser(url: str) -> bool:
7
+ """
8
+ Open a URL in the user's default web browser.
9
+
10
+ This function is cross-platform compatible (Windows, macOS, Linux).
11
+ It uses Python's built-in webbrowser module which handles platform
12
+ detection automatically.
13
+
14
+ Args:
15
+ url: The URL to open in the browser.
16
+
17
+ Returns:
18
+ True if the browser was opened successfully, False otherwise.
19
+ """
20
+ try:
21
+ # webbrowser.open() is cross-platform and uses the default browser
22
+ # - On macOS: uses 'open' command
23
+ # - On Windows: uses 'start' command
24
+ # - On Linux: tries common browsers (xdg-open, gnome-open, etc.)
25
+ return webbrowser.open(url)
26
+ except Exception:
27
+ # Silently fail - browser opening is a convenience feature
28
+ # and should not interrupt the optimization flow
29
+ return False
@@ -3,9 +3,10 @@ import sys
3
3
  from rich.console import Console
4
4
  from rich.traceback import install
5
5
 
6
- from .auth import clear_api_key
6
+ from .auth import clear_api_key, perform_login, load_weco_api_key
7
7
  from .constants import DEFAULT_MODELS
8
8
  from .utils import check_for_cli_updates, get_default_model, UnrecognizedAPIKeysError, DefaultModelNotFoundError
9
+ from .validation import validate_source_file, validate_log_directory, ValidationError, print_validation_error
9
10
 
10
11
 
11
12
  install(show_locals=True)
@@ -108,6 +109,11 @@ def configure_run_parser(run_parser: argparse.ArgumentParser) -> None:
108
109
  action="store_true",
109
110
  help="Automatically apply the best solution to the source file without prompting",
110
111
  )
112
+ run_parser.add_argument(
113
+ "--require-review",
114
+ action="store_true",
115
+ help="Require manual review and approval of each proposed change before execution",
116
+ )
111
117
 
112
118
  default_api_keys = " ".join([f"{provider}=xxx" for provider, _ in DEFAULT_MODELS])
113
119
  supported_providers = ", ".join([provider for provider, _ in DEFAULT_MODELS])
@@ -206,7 +212,15 @@ Supported provider names: {supported_providers}.
206
212
 
207
213
  def execute_run_command(args: argparse.Namespace) -> None:
208
214
  """Execute the 'weco run' command with all its logic."""
209
- from .optimizer import execute_optimization
215
+ from .optimizer import optimize
216
+
217
+ # Early validation — fail fast with helpful errors
218
+ try:
219
+ validate_source_file(args.source)
220
+ validate_log_directory(args.log_dir)
221
+ except ValidationError as e:
222
+ print_validation_error(e, console)
223
+ sys.exit(1)
210
224
 
211
225
  try:
212
226
  api_keys = parse_api_keys(args.api_key)
@@ -225,7 +239,7 @@ def execute_run_command(args: argparse.Namespace) -> None:
225
239
  if api_keys:
226
240
  console.print(f"[bold yellow]Custom API keys provided. Using default model: {model} for the run.[/]")
227
241
 
228
- success = execute_optimization(
242
+ success = optimize(
229
243
  source=args.source,
230
244
  eval_command=args.eval_command,
231
245
  metric=args.metric,
@@ -234,12 +248,13 @@ def execute_run_command(args: argparse.Namespace) -> None:
234
248
  steps=args.steps,
235
249
  log_dir=args.log_dir,
236
250
  additional_instructions=args.additional_instructions,
237
- console=console,
238
251
  eval_timeout=args.eval_timeout,
239
252
  save_logs=args.save_logs,
240
- apply_change=args.apply_change,
241
253
  api_keys=api_keys,
254
+ apply_change=args.apply_change,
255
+ require_review=args.require_review,
242
256
  )
257
+
243
258
  exit_code = 0 if success else 1
244
259
  sys.exit(exit_code)
245
260
 
@@ -254,12 +269,23 @@ def execute_resume_command(args: argparse.Namespace) -> None:
254
269
  console.print(f"[bold red]Error parsing API keys: {e}[/]")
255
270
  sys.exit(1)
256
271
 
257
- success = resume_optimization(run_id=args.run_id, console=console, api_keys=api_keys, apply_change=args.apply_change)
272
+ success = resume_optimization(run_id=args.run_id, api_keys=api_keys, apply_change=args.apply_change)
273
+
258
274
  sys.exit(0 if success else 1)
259
275
 
260
276
 
261
277
  def main() -> None:
262
278
  """Main function for the Weco CLI."""
279
+ try:
280
+ _main()
281
+ except KeyboardInterrupt:
282
+ # Clean exit on Ctrl+C without traceback
283
+ console.print("\n[yellow]Interrupted.[/]")
284
+ sys.exit(130) # Standard exit code for SIGINT
285
+
286
+
287
+ def _main() -> None:
288
+ """Internal main function containing the CLI logic."""
263
289
  check_for_cli_updates()
264
290
 
265
291
  parser = argparse.ArgumentParser(
@@ -277,6 +303,9 @@ def main() -> None:
277
303
  )
278
304
  configure_run_parser(run_parser) # Use the helper to add arguments
279
305
 
306
+ # --- Login Command Parser Setup ---
307
+ _ = subparsers.add_parser("login", help="Log in to Weco and save your API key.")
308
+
280
309
  # --- Logout Command Parser Setup ---
281
310
  _ = subparsers.add_parser("logout", help="Log out from Weco and clear saved API key.")
282
311
 
@@ -295,7 +324,20 @@ def main() -> None:
295
324
 
296
325
  args = parser.parse_args()
297
326
 
298
- if args.command == "logout":
327
+ if args.command == "login":
328
+ # Check if already logged in
329
+ existing_key = load_weco_api_key()
330
+ if existing_key:
331
+ console.print("[bold green]You are already logged in.[/]")
332
+ console.print("[dim]Use 'weco logout' to log out first if you want to switch accounts.[/]")
333
+ sys.exit(0)
334
+
335
+ # Perform the login flow
336
+ if perform_login(console):
337
+ sys.exit(0)
338
+ else:
339
+ sys.exit(1)
340
+ elif args.command == "logout":
299
341
  clear_api_key()
300
342
  sys.exit(0)
301
343
  elif args.command == "run":