weco 0.3.2__py3-none-any.whl → 0.3.4__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.
weco/api.py CHANGED
@@ -178,7 +178,6 @@ def evaluate_feedback_then_suggest_next_solution(
178
178
  run_id: str,
179
179
  step: int,
180
180
  execution_output: str,
181
- additional_instructions: str = None,
182
181
  auth_headers: dict = {},
183
182
  timeout: Union[int, Tuple[int, int]] = (10, 3650),
184
183
  ) -> Dict[str, Any]:
@@ -189,7 +188,7 @@ def evaluate_feedback_then_suggest_next_solution(
189
188
 
190
189
  response = requests.post(
191
190
  f"{__base_url__}/runs/{run_id}/suggest",
192
- json={"execution_output": truncated_output, "additional_instructions": additional_instructions, "metadata": {}},
191
+ json={"execution_output": truncated_output, "metadata": {}},
193
192
  headers=auth_headers,
194
193
  timeout=timeout,
195
194
  )
weco/cli.py CHANGED
@@ -49,7 +49,7 @@ def configure_run_parser(run_parser: argparse.ArgumentParser) -> None:
49
49
  "--model",
50
50
  type=str,
51
51
  default=None,
52
- help="Model to use for optimization. Defaults to `o4-mini` when `OPENAI_API_KEY` is set, `claude-sonnet-4-0` when `ANTHROPIC_API_KEY` is set, and `gemini-2.5-pro` when `GEMINI_API_KEY` is set. When multiple keys are set, the priority is `OPENAI_API_KEY` > `ANTHROPIC_API_KEY` > `GEMINI_API_KEY`.",
52
+ help="Model to use for optimization. Defaults to `o4-mini`. See full list at https://docs.weco.ai/cli/supported-models",
53
53
  )
54
54
  run_parser.add_argument(
55
55
  "-l", "--log-dir", type=str, default=".runs", help="Directory to store logs and results. Defaults to `.runs`."
@@ -72,6 +72,11 @@ def configure_run_parser(run_parser: argparse.ArgumentParser) -> None:
72
72
  action="store_true",
73
73
  help="Save execution output to .runs/<run-id>/outputs/step_<n>.out.txt with JSONL index",
74
74
  )
75
+ run_parser.add_argument(
76
+ "--apply-change",
77
+ action="store_true",
78
+ help="Automatically apply the best solution to the source file without prompting",
79
+ )
75
80
 
76
81
 
77
82
  def configure_credits_parser(credits_parser: argparse.ArgumentParser) -> None:
@@ -118,6 +123,11 @@ def configure_resume_parser(resume_parser: argparse.ArgumentParser) -> None:
118
123
  resume_parser.add_argument(
119
124
  "run_id", type=str, help="The UUID of the run to resume (e.g., '0002e071-1b67-411f-a514-36947f0c4b31')"
120
125
  )
126
+ resume_parser.add_argument(
127
+ "--apply-change",
128
+ action="store_true",
129
+ help="Automatically apply the best solution to the source file without prompting",
130
+ )
121
131
 
122
132
 
123
133
  def execute_run_command(args: argparse.Namespace) -> None:
@@ -136,6 +146,7 @@ def execute_run_command(args: argparse.Namespace) -> None:
136
146
  console=console,
137
147
  eval_timeout=args.eval_timeout,
138
148
  save_logs=args.save_logs,
149
+ apply_change=args.apply_change,
139
150
  )
140
151
  exit_code = 0 if success else 1
141
152
  sys.exit(exit_code)
@@ -145,7 +156,7 @@ def execute_resume_command(args: argparse.Namespace) -> None:
145
156
  """Execute the 'weco resume' command with all its logic."""
146
157
  from .optimizer import resume_optimization
147
158
 
148
- success = resume_optimization(run_id=args.run_id, console=console)
159
+ success = resume_optimization(run_id=args.run_id, console=console, apply_change=args.apply_change)
149
160
  sys.exit(0 if success else 1)
150
161
 
151
162
 
@@ -164,7 +175,7 @@ def main() -> None:
164
175
  "--model",
165
176
  type=str,
166
177
  default=None,
167
- help="Model to use for optimization. Defaults to `o4-mini` when `OPENAI_API_KEY` is set, `claude-sonnet-4-0` when `ANTHROPIC_API_KEY` is set, and `gemini-2.5-pro` when `GEMINI_API_KEY` is set. When multiple keys are set, the priority is `OPENAI_API_KEY` > `ANTHROPIC_API_KEY` > `GEMINI_API_KEY`.",
178
+ help="Model to use for optimization. Defaults to `o4-mini`. See full list at docs.weco.ai/cli/supported-models",
168
179
  )
169
180
 
170
181
  subparsers = parser.add_subparsers(
weco/optimizer.py CHANGED
@@ -30,7 +30,7 @@ from .panels import (
30
30
  create_optimization_layout,
31
31
  create_end_optimization_layout,
32
32
  )
33
- from .utils import read_additional_instructions, read_from_path, write_to_path, run_evaluation, smooth_update
33
+ from .utils import read_additional_instructions, read_from_path, write_to_path, run_evaluation_with_file_swap, smooth_update
34
34
 
35
35
 
36
36
  def save_execution_output(runs_dir: pathlib.Path, step: int, output: str) -> None:
@@ -134,6 +134,7 @@ def execute_optimization(
134
134
  console: Optional[Console] = None,
135
135
  eval_timeout: Optional[int] = None,
136
136
  save_logs: bool = False,
137
+ apply_change: bool = False,
137
138
  ) -> bool:
138
139
  """
139
140
  Execute the core optimization logic.
@@ -182,6 +183,8 @@ def execute_optimization(
182
183
  optimization_completed_normally = False
183
184
  user_stop_requested_flag = False
184
185
 
186
+ best_solution_code = None
187
+ original_source_code = None # Make available to the finally block
185
188
  try:
186
189
  # --- Login/Authentication Handling (now mandatory) ---
187
190
  weco_api_key, auth_headers = handle_authentication(console)
@@ -209,6 +212,7 @@ def execute_optimization(
209
212
  processed_additional_instructions = read_additional_instructions(additional_instructions=additional_instructions)
210
213
  source_fp = pathlib.Path(source)
211
214
  source_code = read_from_path(fp=source_fp, is_json=False)
215
+ original_source_code = source_code
212
216
 
213
217
  # --- Panel Initialization ---
214
218
  summary_panel = SummaryPanel(maximize=maximize, metric_name=metric, total_steps=steps, model=model, runs_dir=log_dir)
@@ -272,10 +276,6 @@ def execute_optimization(
272
276
  }
273
277
  with open(jsonl_file, "w", encoding="utf-8") as f:
274
278
  f.write(json.dumps(metadata) + "\n")
275
- # Write the initial code string to the logs
276
- write_to_path(fp=runs_dir / f"step_0{source_fp.suffix}", content=run_response["code"])
277
- # Write the initial code string to the source file path
278
- write_to_path(fp=source_fp, content=run_response["code"])
279
279
 
280
280
  # Update the panels with the initial solution
281
281
  # Add run id and run name now that we have it
@@ -307,6 +307,7 @@ def execute_optimization(
307
307
  best_node=None,
308
308
  )
309
309
  current_solution_panel, best_solution_panel = solution_panels.get_display(current_step=0)
310
+
310
311
  # Update the live layout with the initial solution panels
311
312
  smooth_update(
312
313
  live=live,
@@ -321,8 +322,17 @@ def execute_optimization(
321
322
  transition_delay=0.1,
322
323
  )
323
324
 
324
- # Run evaluation on the initial solution
325
- term_out = run_evaluation(eval_command=eval_command, timeout=eval_timeout)
325
+ # Write the initial code string to the logs
326
+ write_to_path(fp=runs_dir / f"step_0{source_fp.suffix}", content=run_response["code"])
327
+ # Run evaluation on the initial solution (file swap ensures original is restored)
328
+ term_out = run_evaluation_with_file_swap(
329
+ file_path=source_fp,
330
+ new_content=run_response["code"],
331
+ original_content=source_code,
332
+ eval_command=eval_command,
333
+ timeout=eval_timeout,
334
+ )
335
+
326
336
  # Save logs if requested
327
337
  if save_logs:
328
338
  save_execution_output(runs_dir, step=0, output=term_out)
@@ -337,8 +347,6 @@ def execute_optimization(
337
347
 
338
348
  # Starting from step 1 to steps (inclusive) because the baseline solution is step 0, so we want to optimize for steps worth of steps
339
349
  for step in range(1, steps + 1):
340
- # Re-read instructions from the original source (file path or string) BEFORE each suggest call
341
- current_additional_instructions = read_additional_instructions(additional_instructions=additional_instructions)
342
350
  if run_id:
343
351
  try:
344
352
  current_status_response = get_optimization_run_status(
@@ -356,17 +364,11 @@ def execute_optimization(
356
364
 
357
365
  # Send feedback and get next suggestion
358
366
  eval_and_next_solution_response = evaluate_feedback_then_suggest_next_solution(
359
- console=console,
360
- step=step,
361
- run_id=run_id,
362
- execution_output=term_out,
363
- additional_instructions=current_additional_instructions,
364
- auth_headers=auth_headers,
367
+ console=console, step=step, run_id=run_id, execution_output=term_out, auth_headers=auth_headers
365
368
  )
366
369
  # Save next solution (.runs/<run-id>/step_<step>.<extension>)
367
370
  write_to_path(fp=runs_dir / f"step_{step}{source_fp.suffix}", content=eval_and_next_solution_response["code"])
368
- # Write the next solution to the source file
369
- write_to_path(fp=source_fp, content=eval_and_next_solution_response["code"])
371
+
370
372
  status_response = get_optimization_run_status(
371
373
  console=console, run_id=run_id, include_history=True, auth_headers=auth_headers
372
374
  )
@@ -402,7 +404,16 @@ def execute_optimization(
402
404
  ],
403
405
  transition_delay=0.08, # Slightly longer delay for more noticeable transitions
404
406
  )
405
- term_out = run_evaluation(eval_command=eval_command, timeout=eval_timeout)
407
+
408
+ # Run evaluation and restore original code after
409
+ term_out = run_evaluation_with_file_swap(
410
+ file_path=source_fp,
411
+ new_content=eval_and_next_solution_response["code"],
412
+ original_content=source_code,
413
+ eval_command=eval_command,
414
+ timeout=eval_timeout,
415
+ )
416
+
406
417
  # Save logs if requested
407
418
  if save_logs:
408
419
  save_execution_output(runs_dir, step=step, output=term_out)
@@ -415,16 +426,9 @@ def execute_optimization(
415
426
  )
416
427
 
417
428
  if not user_stop_requested_flag:
418
- # Re-read instructions from the original source (file path or string) BEFORE each suggest call
419
- current_additional_instructions = read_additional_instructions(additional_instructions=additional_instructions)
420
429
  # Evaluate the final solution thats been generated
421
430
  eval_and_next_solution_response = evaluate_feedback_then_suggest_next_solution(
422
- console=console,
423
- step=steps,
424
- run_id=run_id,
425
- execution_output=term_out,
426
- additional_instructions=current_additional_instructions,
427
- auth_headers=auth_headers,
431
+ console=console, step=steps, run_id=run_id, execution_output=term_out, auth_headers=auth_headers
428
432
  )
429
433
  summary_panel.set_step(step=steps)
430
434
  status_response = get_optimization_run_status(
@@ -458,15 +462,14 @@ def execute_optimization(
458
462
  # Save optimization results
459
463
  # If the best solution does not exist or is has not been measured at the end of the optimization
460
464
  # save the original solution as the best solution
461
- if best_solution_node is not None:
462
- best_solution_content = best_solution_node.code
463
- else:
464
- best_solution_content = read_from_path(fp=runs_dir / f"step_0{source_fp.suffix}", is_json=False)
465
+ try:
466
+ best_solution_code = best_solution_node.code
467
+ except AttributeError:
468
+ best_solution_code = read_from_path(fp=runs_dir / f"step_0{source_fp.suffix}", is_json=False)
465
469
 
466
470
  # Save best solution to .runs/<run-id>/best.<extension>
467
- write_to_path(fp=runs_dir / f"best{source_fp.suffix}", content=best_solution_content)
468
- # write the best solution to the source file
469
- write_to_path(fp=source_fp, content=best_solution_content)
471
+ write_to_path(fp=runs_dir / f"best{source_fp.suffix}", content=best_solution_code)
472
+
470
473
  # Mark as completed normally for the finally block
471
474
  optimization_completed_normally = True
472
475
  live.update(end_optimization_layout)
@@ -505,6 +508,21 @@ def execute_optimization(
505
508
  else "CLI terminated unexpectedly without a specific exception captured."
506
509
  )
507
510
 
511
+ # raise Exception(best_solution_code, original_source_code)
512
+ if best_solution_code and best_solution_code != original_source_code:
513
+ # Determine whether to apply: automatically if --apply-change is set, otherwise ask user
514
+ should_apply = apply_change or summary_panel.ask_user_feedback(
515
+ live=live,
516
+ layout=end_optimization_layout,
517
+ question="Would you like to apply the best solution to the source file?",
518
+ default=True,
519
+ )
520
+ if should_apply:
521
+ write_to_path(fp=source_fp, content=best_solution_code)
522
+ console.print("[green]Best solution applied to the source file.[/]\n")
523
+ else:
524
+ console.print("[green]A better solution was not found. No changes to apply.[/]\n")
525
+
508
526
  report_termination(
509
527
  run_id=run_id,
510
528
  status_update=status,
@@ -521,7 +539,7 @@ def execute_optimization(
521
539
  return optimization_completed_normally or user_stop_requested_flag
522
540
 
523
541
 
524
- def resume_optimization(run_id: str, console: Optional[Console] = None) -> bool:
542
+ def resume_optimization(run_id: str, console: Optional[Console] = None, apply_change: bool = False) -> bool:
525
543
  """Resume an interrupted run from the most recent node and continue optimization."""
526
544
  if console is None:
527
545
  console = Console()
@@ -557,6 +575,9 @@ def resume_optimization(run_id: str, console: Optional[Console] = None) -> bool:
557
575
  optimization_completed_normally = False
558
576
  user_stop_requested_flag = False
559
577
 
578
+ best_solution_code = None
579
+ original_source_code = None
580
+
560
581
  try:
561
582
  # --- Login/Authentication Handling (now mandatory) ---
562
583
  weco_api_key, auth_headers = handle_authentication(console)
@@ -632,11 +653,13 @@ def resume_optimization(run_id: str, console: Optional[Console] = None) -> bool:
632
653
  log_dir = resume_resp.get("log_dir", ".runs")
633
654
  save_logs = bool(resume_resp.get("save_logs", False))
634
655
  eval_timeout = resume_resp.get("eval_timeout")
635
- additional_instructions = resume_resp.get("additional_instructions")
636
656
 
637
- # Write last solution code to source path
657
+ # Read the original source code from the file before we start modifying it
638
658
  source_fp = pathlib.Path(source_path)
639
659
  source_fp.parent.mkdir(parents=True, exist_ok=True)
660
+ # Store the original content to restore after each evaluation
661
+ original_source_code = read_from_path(fp=source_fp, is_json=False) if source_fp.exists() else ""
662
+
640
663
  code_to_restore = resume_resp.get("code") or resume_resp.get("source_code") or ""
641
664
  write_to_path(fp=source_fp, content=code_to_restore)
642
665
 
@@ -704,7 +727,13 @@ def resume_optimization(run_id: str, console: Optional[Console] = None) -> bool:
704
727
 
705
728
  # If missing output, evaluate once before first suggest
706
729
  if term_out is None or len(term_out.strip()) == 0:
707
- term_out = run_evaluation(eval_command=eval_command, timeout=eval_timeout)
730
+ term_out = run_evaluation_with_file_swap(
731
+ file_path=source_fp,
732
+ new_content=code_to_restore,
733
+ original_content=original_source_code,
734
+ eval_command=eval_command,
735
+ timeout=eval_timeout,
736
+ )
708
737
  eval_output_panel.update(output=term_out)
709
738
  # Update the evaluation output panel
710
739
  smooth_update(
@@ -739,13 +768,11 @@ def resume_optimization(run_id: str, console: Optional[Console] = None) -> bool:
739
768
  step=step,
740
769
  run_id=resume_resp["run_id"],
741
770
  execution_output=term_out,
742
- additional_instructions=additional_instructions,
743
771
  auth_headers=auth_headers,
744
772
  )
745
773
 
746
- # Save next solution file(s)
774
+ # Save next solution to logs
747
775
  write_to_path(fp=runs_dir / f"step_{step}{source_fp.suffix}", content=eval_and_next_solution_response["code"])
748
- write_to_path(fp=source_fp, content=eval_and_next_solution_response["code"])
749
776
 
750
777
  # Refresh status with history and update panels
751
778
  status_response = get_optimization_run_status(
@@ -760,6 +787,7 @@ def resume_optimization(run_id: str, console: Optional[Console] = None) -> bool:
760
787
  current_solution_node = get_node_from_status(
761
788
  status_response=status_response, solution_id=eval_and_next_solution_response["solution_id"]
762
789
  )
790
+
763
791
  solution_panels.update(current_node=current_solution_node, best_node=best_solution_node)
764
792
  current_solution_panel, best_solution_panel = solution_panels.get_display(current_step=step)
765
793
  eval_output_panel.clear()
@@ -776,8 +804,14 @@ def resume_optimization(run_id: str, console: Optional[Console] = None) -> bool:
776
804
  transition_delay=0.08,
777
805
  )
778
806
 
779
- # Evaluate this new solution
780
- term_out = run_evaluation(eval_command=eval_command, timeout=eval_timeout)
807
+ # Evaluate this new solution and restore original code after
808
+ term_out = run_evaluation_with_file_swap(
809
+ file_path=source_fp,
810
+ new_content=eval_and_next_solution_response["code"],
811
+ original_content=original_source_code,
812
+ eval_command=eval_command,
813
+ timeout=eval_timeout,
814
+ )
781
815
  if save_logs:
782
816
  save_execution_output(runs_dir, step=step, output=term_out)
783
817
  eval_output_panel.update(output=term_out)
@@ -795,7 +829,6 @@ def resume_optimization(run_id: str, console: Optional[Console] = None) -> bool:
795
829
  step=total_steps,
796
830
  run_id=resume_resp["run_id"],
797
831
  execution_output=term_out,
798
- additional_instructions=additional_instructions,
799
832
  auth_headers=auth_headers,
800
833
  )
801
834
  summary_panel.set_step(step=total_steps)
@@ -818,13 +851,13 @@ def resume_optimization(run_id: str, console: Optional[Console] = None) -> bool:
818
851
  end_optimization_layout["best_solution"].update(best_solution_panel)
819
852
 
820
853
  # Save best
821
- if best_solution_node is not None:
822
- best_solution_content = best_solution_node.code
823
- else:
824
- best_solution_content = read_from_path(fp=runs_dir / f"step_0{source_fp.suffix}", is_json=False)
854
+ try:
855
+ best_solution_code = best_solution_node.code
856
+ except AttributeError:
857
+ best_solution_code = read_from_path(fp=runs_dir / f"step_0{source_fp.suffix}", is_json=False)
858
+
859
+ write_to_path(fp=runs_dir / f"best{source_fp.suffix}", content=best_solution_code)
825
860
 
826
- write_to_path(fp=runs_dir / f"best{source_fp.suffix}", content=best_solution_content)
827
- write_to_path(fp=source_fp, content=best_solution_content)
828
861
  optimization_completed_normally = True
829
862
  live.update(end_optimization_layout)
830
863
 
@@ -843,7 +876,11 @@ def resume_optimization(run_id: str, console: Optional[Console] = None) -> bool:
843
876
  if heartbeat_thread and heartbeat_thread.is_alive():
844
877
  heartbeat_thread.join(timeout=2)
845
878
 
846
- run_id = resume_resp.get("run_id")
879
+ try:
880
+ run_id = resume_resp.get("run_id")
881
+ except Exception:
882
+ run_id = None
883
+
847
884
  # Report final status if run exists
848
885
  if run_id:
849
886
  if optimization_completed_normally:
@@ -857,6 +894,20 @@ def resume_optimization(run_id: str, console: Optional[Console] = None) -> bool:
857
894
  if "e" in locals() and isinstance(locals()["e"], Exception)
858
895
  else "CLI terminated unexpectedly without a specific exception captured."
859
896
  )
897
+
898
+ if best_solution_code and best_solution_code != original_source_code:
899
+ should_apply = apply_change or summary_panel.ask_user_feedback(
900
+ live=live,
901
+ layout=end_optimization_layout,
902
+ question="Would you like to apply the best solution to the source file?",
903
+ default=True,
904
+ )
905
+ if should_apply:
906
+ write_to_path(fp=source_fp, content=best_solution_code)
907
+ console.print("[green]Best solution applied to the source file.[/]\n")
908
+ else:
909
+ console.print("[green]A better solution was not found. No changes to apply.[/]\n")
910
+
860
911
  report_termination(
861
912
  run_id=run_id,
862
913
  status_update=status,
weco/panels.py CHANGED
@@ -5,6 +5,10 @@ from rich.layout import Layout
5
5
  from rich.panel import Panel
6
6
  from rich.syntax import Syntax
7
7
  from rich import box
8
+ from rich.console import Console
9
+ from rich.live import Live
10
+
11
+ from rich.prompt import Confirm
8
12
  from typing import Dict, List, Optional, Union, Tuple
9
13
  from pathlib import Path
10
14
  from .__init__ import __dashboard_url__
@@ -22,6 +26,7 @@ class SummaryPanel:
22
26
  runs_dir: str,
23
27
  run_id: str = None,
24
28
  run_name: str = None,
29
+ console: Optional[Console] = None,
25
30
  ):
26
31
  self.maximize = maximize
27
32
  self.metric_name = metric_name
@@ -32,6 +37,8 @@ class SummaryPanel:
32
37
  self.run_name = run_name if run_name is not None else "N/A"
33
38
  self.dashboard_url = "N/A"
34
39
  self.thinking_content = ""
40
+ self.user_input = ""
41
+ self.console = Console()
35
42
  self.progress = Progress(
36
43
  TextColumn("[progress.description]{task.description}"),
37
44
  BarColumn(bar_width=20),
@@ -67,6 +74,25 @@ class SummaryPanel:
67
74
  """Clear the thinking content."""
68
75
  self.thinking_content = ""
69
76
 
77
+ def ask_user_feedback(self, live: Live, layout: Layout, question: str, default: bool = True) -> bool:
78
+ """
79
+ Ask a yes/no question while keeping the main layout fixed.
80
+ Uses Rich's Confirm for a clean user experience.
81
+ """
82
+ # Stop live updates temporarily to prevent layout from moving
83
+ live.stop()
84
+
85
+ try:
86
+ # Use Rich's built-in Confirm
87
+ result = Confirm.ask(question, default=default)
88
+ except (KeyboardInterrupt, EOFError):
89
+ result = default
90
+ finally:
91
+ # Resume live updates
92
+ live.start()
93
+
94
+ return result
95
+
70
96
  def get_display(self, final_message: Optional[str] = None) -> Panel:
71
97
  """Return a Rich panel summarising the current run."""
72
98
  # ───────────────────── summary grid ──────────────────────
weco/utils.py CHANGED
@@ -106,6 +106,41 @@ def truncate_output(output: str) -> str:
106
106
  return output
107
107
 
108
108
 
109
+ def run_evaluation_with_file_swap(
110
+ file_path: pathlib.Path, new_content: str, original_content: str, eval_command: str, timeout: int | None = None
111
+ ) -> str:
112
+ """
113
+ Temporarily write new content to a file, run evaluation, then restore original.
114
+
115
+ This function ensures the file is always restored to its original state,
116
+ even if an exception occurs during evaluation.
117
+
118
+ Args:
119
+ file_path: Path to the file to temporarily modify
120
+ new_content: The new content to write for evaluation
121
+ original_content: The original content to restore after evaluation
122
+ eval_command: The shell command to run for evaluation
123
+ timeout: Optional timeout for the evaluation command
124
+
125
+ Returns:
126
+ The output from running the evaluation command
127
+
128
+ Raises:
129
+ Any exception raised by run_evaluation will be re-raised after
130
+ the file is restored to its original state.
131
+ """
132
+ # Write the new content
133
+ write_to_path(fp=file_path, content=new_content)
134
+
135
+ try:
136
+ # Run the evaluation
137
+ output = run_evaluation(eval_command=eval_command, timeout=timeout)
138
+ return output
139
+ finally:
140
+ # Always restore the original file, even if evaluation fails
141
+ write_to_path(fp=file_path, content=original_content)
142
+
143
+
109
144
  def run_evaluation(eval_command: str, timeout: int | None = None) -> str:
110
145
  """Run the evaluation command on the code and return the output."""
111
146
  process = subprocess.Popen(
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: weco
3
- Version: 0.3.2
3
+ Version: 0.3.4
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:
@@ -230,7 +230,7 @@ Dynamic: license-file
230
230
 
231
231
  <div align="center">
232
232
  <img src="assets/weco.svg" alt="Weco Logo" width="120" height="120" style="margin-bottom: 20px;">
233
- <h1>Weco: The Platform for Self-Improving Code</h1>
233
+ <h1>Weco: The Code Optimization Agent</h1>
234
234
  </div>
235
235
 
236
236
  [![Python](https://img.shields.io/badge/Python-3.8.0+-blue)](https://www.python.org)
@@ -238,7 +238,7 @@ Dynamic: license-file
238
238
  [![docs](https://img.shields.io/website?url=https://docs.weco.ai/&label=docs)](https://docs.weco.ai/)
239
239
  [![PyPI Downloads](https://static.pepy.tech/badge/weco?color=4c1)](https://pepy.tech/projects/weco)
240
240
  [![arXiv on AIDE](https://img.shields.io/badge/arXiv-AIDE-b31b1b?logo=arxiv&logoColor=white)](https://arxiv.org/abs/2502.13138)
241
- [![Open In Colab](https://colab.research.google.com/assets/colab-badge.svg?labelColor=ffffff&color=F17E01)](https://colab.research.google.com/github/WecoAI/weco-cli/blob/main/examples/hello-kernel-world/colab_notebook_walkthrough.ipynb)
241
+ [![Open In Colab](https://colab.research.google.com/assets/colab-badge.svg?labelColor=ffffff&color=F17E01)](https://colab.research.google.com/github/WecoAI/weco-cli/blob/main/examples/hello-world/colab_notebook_walkthrough.ipynb)
242
242
 
243
243
  `pip install weco`
244
244
 
@@ -262,77 +262,32 @@ Example applications include:
262
262
 
263
263
  The `weco` CLI leverages a tree search approach guided by LLMs to iteratively explore and refine your code. It automatically applies changes, runs your evaluation script, parses the results, and proposes further improvements based on the specified goal.
264
264
 
265
- ![image](https://github.com/user-attachments/assets/a6ed63fa-9c40-498e-aa98-a873e5786509)
266
265
 
267
- ---
268
-
269
- ## Setup
270
-
271
- 1. **Install the Package:**
272
-
273
- ```bash
274
- pip install weco
275
- ```
276
-
277
- 2. **Authenticate (Required):**
278
-
279
- `weco` now uses a **credit-based billing system** with centralized LLM access. You need to authenticate to use the service:
280
-
281
- - **Run the CLI**: `weco` will prompt you to authenticate via your web browser
282
- - **Free Credits**: New users receive **free credits** upon signup
283
- - **Centralized Keys**: All LLM provider API keys are managed by Weco (no BYOK required)
284
- - **Credit Top-ups**: Purchase additional credits through the dashboard at [dashboard.weco.ai](https://dashboard.weco.ai)
285
-
286
- ---
287
-
288
- ## Get Started
289
-
290
- ### Quick Start (Recommended for New Users)
291
-
292
- The easiest way to get started with Weco is to use the **interactive copilot**. Simply navigate to your project directory and run:
293
-
294
- ```bash
295
- weco
296
- ```
297
-
298
- Or specify a project path:
266
+ ## Install the Package
299
267
 
300
268
  ```bash
301
- weco /path/to/your/project
269
+ pip install weco
302
270
  ```
303
271
 
304
- This launches Weco's interactive copilot that will:
272
+ ## Getting Started
305
273
 
306
- 1. **Analyze your codebase** using AI to understand your project structure and identify optimization opportunities
307
- 2. **Suggest specific optimizations** tailored to your code (e.g., GPU kernel optimization, model improvements, prompt engineering)
308
- 3. **Generate evaluation scripts** automatically or help you configure existing ones
309
- 4. **Set up the complete optimization pipeline** with appropriate metrics and commands
310
- 5. **Run the optimization** or provide you with the exact command to execute
311
-
312
- <div style="background-color: #fff3cd; border: 1px solid #ffeeba; padding: 15px; border-radius: 4px; margin-bottom: 15px;">
313
- <strong>⚠️ Warning: Code Modification</strong><br>
314
- <code>weco</code> directly modifies the file specified by <code>--source</code> during the optimization process. It is <strong>strongly recommended</strong> to use version control (like Git) to track changes and revert if needed. Alternatively, ensure you have a backup of your original file before running the command. Upon completion, the file will contain the best-performing version of the code found during the run.
315
- </div>
316
-
317
- ### Manual Setup
274
+ ### Quickstart with an example project
318
275
 
319
276
  **Configure optimization parameters yourself** - If you need precise control over the optimization parameters, you can use the direct `weco run` command:
320
277
 
321
278
  **Example: Optimizing Simple PyTorch Operations**
322
279
 
323
280
  ```bash
324
- # Navigate to the example directory
325
- cd examples/hello-kernel-world
326
-
327
- # Install dependencies
328
- pip install torch
281
+ git clone https://github.com/WecoAI/weco-cli.git
282
+ cd weco-cli/examples/hello-world/
283
+ pip install -r requirements.txt
329
284
 
330
- # Run Weco with manual configuration
331
- weco run --source optimize.py \
332
- --eval-command "python evaluate.py --solution-path optimize.py --device cpu" \
285
+ # Run Weco with configuration
286
+ weco run --source module.py \
287
+ --eval-command "python evaluate.py --path module.py" \
333
288
  --metric speedup \
334
289
  --goal maximize \
335
- --steps 15 \
290
+ --steps 10 \
336
291
  --additional-instructions "Fuse operations in the forward method while ensuring the max float deviation remains small. Maintain the same format of the code."
337
292
  ```
338
293
 
@@ -362,34 +317,12 @@ For more advanced examples, including [Triton](/examples/triton/README.md), [CUD
362
317
  | Argument | Description | Default | Example |
363
318
  | :----------------------------- | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | :------------------------------------------------------------------------------------------------------------------------------------------------------ | :------------------ |
364
319
  | `-n, --steps` | Number of optimization steps (LLM iterations) to run. | 100 | `-n 50` |
365
- | `-M, --model` | Model identifier for the LLM to use (e.g., `o4-mini`, `claude-sonnet-4-0`). | `o4-mini` | `-M o4-mini` |
320
+ | `-M, --model` | Model identifier for the LLM to use (e.g., `o4-mini`, `claude-sonnet-4-5`, `gpt-5`). | `o4-mini` | `-M o4-mini` |
366
321
  | `-i, --additional-instructions`| Natural language description of specific instructions **or** path to a file containing detailed instructions to guide the LLM. Supported file formats include - `.txt`, `.md`, and `.rst`. | `None` | `-i instructions.md` or `-i "Optimize the model for faster inference"`|
367
322
  | `-l, --log-dir` | Path to the directory to log intermediate steps and final optimization result. | `.runs/` | `-l ./logs/` |
368
323
  | `--eval-timeout` | Timeout in seconds for each step in evaluation. | No timeout (unlimited) | `--eval-timeout 3600` |
369
324
  | `--save-logs` | Save execution output from each optimization step to disk. Creates timestamped directories with raw output files and a JSONL index for tracking execution history. | `False` | `--save-logs` |
370
-
371
- ---
372
-
373
- ### Authentication & Dashboard
374
-
375
- The CLI requires a Weco account for authentication and billing.
376
-
377
- #### Credit-Based Authentication (Required)
378
- Weco now requires authentication for all operations. This enables our credit-based billing system and provides access to powerful optimizations:
379
-
380
- 1. **During onboarding**: When you run `weco` for the first time, you'll be prompted to log in
381
- 2. **Manual login**: Use `weco logout` to clear credentials, then run `weco` again to re-authenticate
382
- 3. **Device flow**: Weco will open your browser automatically and guide you through a secure OAuth-style authentication
383
-
384
- ![image (16)](https://github.com/user-attachments/assets/8a0a285b-4894-46fa-b6a2-4990017ca0c6)
385
-
386
- **Benefits:**
387
- - **No API Key Management**: All LLM provider keys are managed centrally
388
- - **Cost Transparency**: See exactly how many credits each optimization consumes
389
- - **Free Trial**: Free credits to get started with optimization projects
390
- - **Run History**: View all your optimization runs on the Weco dashboard
391
- - **Progress Tracking**: Monitor long-running optimizations remotely
392
- - **Budget Control**: Set spending limits and auto top-up preferences
325
+ | `--apply-change` | Automatically apply the best solution to the source file without prompting. | `False` | `--apply-change` |
393
326
 
394
327
  ---
395
328
 
@@ -399,8 +332,6 @@ Weco now requires authentication for all operations. This enables our credit-bas
399
332
 
400
333
  | Command | Description | When to Use |
401
334
  |---------|-------------|-------------|
402
- | `weco` | Launch interactive onboarding | **Recommended for beginners** - Analyzes your codebase and guides you through setup |
403
- | `weco /path/to/project` | Launch onboarding for specific project | When working with a project in a different directory |
404
335
  | `weco run [options]` | Direct optimization execution | **For advanced users** - When you know exactly what to optimize and how |
405
336
  | `weco resume <run-id>` | Resume an interrupted run | Continue from the last completed step |
406
337
  | `weco logout` | Clear authentication credentials | To switch accounts or troubleshoot authentication issues |
@@ -410,19 +341,23 @@ Weco now requires authentication for all operations. This enables our credit-bas
410
341
  You can specify which LLM model to use with the `-M` or `--model` flag:
411
342
 
412
343
  ```bash
413
- # Use with onboarding
414
- weco --model gpt-4o
415
-
416
- # Use with direct execution
417
- weco run --model claude-3.5-sonnet --source optimize.py [other options...]
344
+ weco run --model gpt-5 --source optimize.py [other options...]
418
345
  ```
419
346
 
420
- **Available models:**
421
- - `o4-mini`, `o3-mini`, `gpt-4o` (OpenAI models)
422
- - `claude-sonnet-4-0`, `claude-opus-4-0` (Anthropic models)
423
- - `gemini-2.5-pro`, `gemini-2.5-flash` (Google models)
347
+ **Available models (30 total):**
348
+
349
+ **OpenAI Models:**
350
+ - GPT-5 Series: `gpt-5.1`, `gpt-5.1-codex`, `gpt-5.1-codex-mini`, `gpt-5-codex`, `gpt-5-pro`, `gpt-5`, `gpt-5-mini`, `gpt-5-nano`
351
+ - O-Series Reasoning: `o3-pro`, `o3`, `o3-mini`, `o4-mini`, `o1-pro`, `o1`, `codex-mini-latest`
352
+ - GPT-4 Series: `gpt-4.1`, `gpt-4.1-mini`, `gpt-4.1-nano`, `gpt-4o`, `gpt-4o-mini`
353
+
354
+ **Anthropic Claude (via Vertex AI):**
355
+ - `claude-opus-4-5`, `claude-opus-4-1`, `claude-opus-4`, `claude-sonnet-4-5`, `claude-sonnet-4`, `claude-haiku-4-5`
356
+
357
+ **Google Gemini:**
358
+ - `gemini-3-pro-preview`, `gemini-2.5-pro`, `gemini-2.5-flash`, `gemini-2.5-flash-lite`
424
359
 
425
- All models are available through Weco's centralized system. If no model is specified, Weco automatically selects the best model for your optimization task.
360
+ All models are available through Weco. If no model is specified, Weco automatically selects the best model for your optimization task.
426
361
 
427
362
  ---
428
363
 
@@ -441,6 +376,7 @@ Arguments for `weco resume`:
441
376
  | Argument | Description | Example |
442
377
  |----------|-------------|---------|
443
378
  | `run-id` | The UUID of the run to resume (shown at the start of each run) | `0002e071-1b67-411f-a514-36947f0c4b31` |
379
+ | `--apply-change` | Automatically apply the best solution to the source file without prompting | `--apply-change` |
444
380
 
445
381
  Notes:
446
382
  - Works only for interrupted runs (status: `error`, `terminated`, etc.).
@@ -0,0 +1,16 @@
1
+ weco/__init__.py,sha256=ClO0uT6GKOA0iSptvP0xbtdycf0VpoPTq37jHtvlhtw,303
2
+ weco/api.py,sha256=zKcI4riwruK6CjV_vcL8RlsJGRXO40iP0WxeETtzPIY,18430
3
+ weco/auth.py,sha256=O31Hoj-Loi8DWJJG2LfeWgUMuNqAUeGDpd2ZGjA9Ah0,9997
4
+ weco/chatbot.py,sha256=EIK2WaOul9gn_yHLThjsZV7RnE8t3XQPwgRkO5tybSU,38415
5
+ weco/cli.py,sha256=7Q6ZwkD8N2x_PRovaFkF9OvtKkKX3hs_CobvS8xKXkQ,11535
6
+ weco/constants.py,sha256=V6yFugTznKm5EC2_jr4I_whd7sqI80HiPggRn0az580,406
7
+ weco/credits.py,sha256=C08x-TRcLg3ccfKqMGNRY7zBn7t3r7LZ119bxgfztaI,7629
8
+ weco/optimizer.py,sha256=fvxpKHeET84vIBWEM_50iCInbxMYs0e44sJqe7zVzK0,43848
9
+ weco/panels.py,sha256=OLss2PLgJqdo9w-Gq0TozyE42lsJvgjtqmfyCHpHGtA,16966
10
+ weco/utils.py,sha256=SMFh7ngo6gsjb2g20wpPe0O0T8xR7U_7X4-kqxJHkGc,8308
11
+ weco-0.3.4.dist-info/licenses/LICENSE,sha256=9LUfoGHjLPtak2zps2kL2tm65HAZIICx_FbLaRuS4KU,11337
12
+ weco-0.3.4.dist-info/METADATA,sha256=TGIKIIAl_0xQ9UBIjr3opE45ni_ZImzhLe29aBWQo3Q,29244
13
+ weco-0.3.4.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
14
+ weco-0.3.4.dist-info/entry_points.txt,sha256=ixJ2uClALbCpBvnIR6BXMNck8SHAab8eVkM9pIUowcs,39
15
+ weco-0.3.4.dist-info/top_level.txt,sha256=F0N7v6e2zBSlsorFv-arAq2yDxQbzX3KVO8GxYhPUeE,5
16
+ weco-0.3.4.dist-info/RECORD,,
@@ -1,16 +0,0 @@
1
- weco/__init__.py,sha256=ClO0uT6GKOA0iSptvP0xbtdycf0VpoPTq37jHtvlhtw,303
2
- weco/api.py,sha256=dUjzuOKKvayzZ_1B4j40eK9Ofk264jsc6vOR1afsszY,18523
3
- weco/auth.py,sha256=O31Hoj-Loi8DWJJG2LfeWgUMuNqAUeGDpd2ZGjA9Ah0,9997
4
- weco/chatbot.py,sha256=EIK2WaOul9gn_yHLThjsZV7RnE8t3XQPwgRkO5tybSU,38415
5
- weco/cli.py,sha256=579f6jf-ZWuFAmNXDisRY7zWr7vw2YZQuC_QX8-qxx0,11460
6
- weco/constants.py,sha256=V6yFugTznKm5EC2_jr4I_whd7sqI80HiPggRn0az580,406
7
- weco/credits.py,sha256=C08x-TRcLg3ccfKqMGNRY7zBn7t3r7LZ119bxgfztaI,7629
8
- weco/optimizer.py,sha256=mJU8_0bo_6dS2PEj1E3dQHvNH9V4e8NSLNE55tmvspw,42291
9
- weco/panels.py,sha256=fnGPtmvxpx21AuBCtCFu1f_BpSxybNr2lhjIIKIutrY,16133
10
- weco/utils.py,sha256=erDDrA_g3KSlel6YEAGALlV_k8ftT-VQnPT1BrmzK8k,7021
11
- weco-0.3.2.dist-info/licenses/LICENSE,sha256=9LUfoGHjLPtak2zps2kL2tm65HAZIICx_FbLaRuS4KU,11337
12
- weco-0.3.2.dist-info/METADATA,sha256=TDJIvT1vw3VFrjEj9o8VkLuxis2MWWAL0pnDYqpFfak,31878
13
- weco-0.3.2.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
14
- weco-0.3.2.dist-info/entry_points.txt,sha256=ixJ2uClALbCpBvnIR6BXMNck8SHAab8eVkM9pIUowcs,39
15
- weco-0.3.2.dist-info/top_level.txt,sha256=F0N7v6e2zBSlsorFv-arAq2yDxQbzX3KVO8GxYhPUeE,5
16
- weco-0.3.2.dist-info/RECORD,,
File without changes