weco 0.3.8__tar.gz → 0.3.10__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.8 → weco-0.3.10}/PKG-INFO +22 -1
  2. {weco-0.3.8 → weco-0.3.10}/README.md +21 -0
  3. {weco-0.3.8 → weco-0.3.10}/pyproject.toml +1 -1
  4. {weco-0.3.8 → weco-0.3.10}/weco/cli.py +34 -1
  5. {weco-0.3.8 → weco-0.3.10}/weco/optimizer.py +37 -10
  6. weco-0.3.10/weco/setup.py +367 -0
  7. {weco-0.3.8 → weco-0.3.10}/weco/ui.py +106 -0
  8. {weco-0.3.8 → weco-0.3.10}/weco.egg-info/PKG-INFO +22 -1
  9. {weco-0.3.8 → weco-0.3.10}/weco.egg-info/SOURCES.txt +1 -0
  10. {weco-0.3.8 → weco-0.3.10}/.github/workflows/lint.yml +0 -0
  11. {weco-0.3.8 → weco-0.3.10}/.github/workflows/release.yml +0 -0
  12. {weco-0.3.8 → weco-0.3.10}/.gitignore +0 -0
  13. {weco-0.3.8 → weco-0.3.10}/LICENSE +0 -0
  14. {weco-0.3.8 → weco-0.3.10}/assets/example-optimization.gif +0 -0
  15. {weco-0.3.8 → weco-0.3.10}/assets/weco.svg +0 -0
  16. {weco-0.3.8 → weco-0.3.10}/contributing.md +0 -0
  17. {weco-0.3.8 → weco-0.3.10}/examples/README.md +0 -0
  18. {weco-0.3.8 → weco-0.3.10}/examples/cuda/README.md +0 -0
  19. {weco-0.3.8 → weco-0.3.10}/examples/cuda/evaluate.py +0 -0
  20. {weco-0.3.8 → weco-0.3.10}/examples/cuda/module.py +0 -0
  21. {weco-0.3.8 → weco-0.3.10}/examples/cuda/requirements.txt +0 -0
  22. {weco-0.3.8 → weco-0.3.10}/examples/extract-line-plot/README.md +0 -0
  23. {weco-0.3.8 → weco-0.3.10}/examples/extract-line-plot/eval.py +0 -0
  24. {weco-0.3.8 → weco-0.3.10}/examples/extract-line-plot/guide.md +0 -0
  25. {weco-0.3.8 → weco-0.3.10}/examples/extract-line-plot/optimize.py +0 -0
  26. {weco-0.3.8 → weco-0.3.10}/examples/extract-line-plot/prepare_data.py +0 -0
  27. {weco-0.3.8 → weco-0.3.10}/examples/extract-line-plot/pyproject.toml +0 -0
  28. {weco-0.3.8 → weco-0.3.10}/examples/hello-world/README.md +0 -0
  29. {weco-0.3.8 → weco-0.3.10}/examples/hello-world/colab_notebook_walkthrough.ipynb +0 -0
  30. {weco-0.3.8 → weco-0.3.10}/examples/hello-world/evaluate.py +0 -0
  31. {weco-0.3.8 → weco-0.3.10}/examples/hello-world/module.py +0 -0
  32. {weco-0.3.8 → weco-0.3.10}/examples/hello-world/requirements.txt +0 -0
  33. {weco-0.3.8 → weco-0.3.10}/examples/prompt/README.md +0 -0
  34. {weco-0.3.8 → weco-0.3.10}/examples/prompt/eval.py +0 -0
  35. {weco-0.3.8 → weco-0.3.10}/examples/prompt/optimize.py +0 -0
  36. {weco-0.3.8 → weco-0.3.10}/examples/prompt/prompt_guide.md +0 -0
  37. {weco-0.3.8 → weco-0.3.10}/examples/spaceship-titanic/README.md +0 -0
  38. {weco-0.3.8 → weco-0.3.10}/examples/spaceship-titanic/competition_description.md +0 -0
  39. {weco-0.3.8 → weco-0.3.10}/examples/spaceship-titanic/data/sample_submission.csv +0 -0
  40. {weco-0.3.8 → weco-0.3.10}/examples/spaceship-titanic/data/test.csv +0 -0
  41. {weco-0.3.8 → weco-0.3.10}/examples/spaceship-titanic/data/train.csv +0 -0
  42. {weco-0.3.8 → weco-0.3.10}/examples/spaceship-titanic/evaluate.py +0 -0
  43. {weco-0.3.8 → weco-0.3.10}/examples/spaceship-titanic/train.py +0 -0
  44. {weco-0.3.8 → weco-0.3.10}/examples/triton/README.md +0 -0
  45. {weco-0.3.8 → weco-0.3.10}/examples/triton/evaluate.py +0 -0
  46. {weco-0.3.8 → weco-0.3.10}/examples/triton/module.py +0 -0
  47. {weco-0.3.8 → weco-0.3.10}/examples/triton/requirements.txt +0 -0
  48. {weco-0.3.8 → weco-0.3.10}/setup.cfg +0 -0
  49. {weco-0.3.8 → weco-0.3.10}/tests/__init__.py +0 -0
  50. {weco-0.3.8 → weco-0.3.10}/tests/test_byok.py +0 -0
  51. {weco-0.3.8 → weco-0.3.10}/tests/test_cli.py +0 -0
  52. {weco-0.3.8 → weco-0.3.10}/weco/__init__.py +0 -0
  53. {weco-0.3.8 → weco-0.3.10}/weco/api.py +0 -0
  54. {weco-0.3.8 → weco-0.3.10}/weco/auth.py +0 -0
  55. {weco-0.3.8 → weco-0.3.10}/weco/browser.py +0 -0
  56. {weco-0.3.8 → weco-0.3.10}/weco/constants.py +0 -0
  57. {weco-0.3.8 → weco-0.3.10}/weco/credits.py +0 -0
  58. {weco-0.3.8 → weco-0.3.10}/weco/panels.py +0 -0
  59. {weco-0.3.8 → weco-0.3.10}/weco/utils.py +0 -0
  60. {weco-0.3.8 → weco-0.3.10}/weco/validation.py +0 -0
  61. {weco-0.3.8 → weco-0.3.10}/weco.egg-info/dependency_links.txt +0 -0
  62. {weco-0.3.8 → weco-0.3.10}/weco.egg-info/entry_points.txt +0 -0
  63. {weco-0.3.8 → weco-0.3.10}/weco.egg-info/requires.txt +0 -0
  64. {weco-0.3.8 → weco-0.3.10}/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.8
3
+ Version: 0.3.10
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:
@@ -336,7 +336,28 @@ For more advanced examples, including [Triton](/examples/triton/README.md), [CUD
336
336
  |---------|-------------|-------------|
337
337
  | `weco run [options]` | Direct optimization execution | **For advanced users** - When you know exactly what to optimize and how |
338
338
  | `weco resume <run-id>` | Resume an interrupted run | Continue from the last completed step |
339
+ | `weco login` | Authenticate with Weco | First-time setup or switching accounts |
339
340
  | `weco logout` | Clear authentication credentials | To switch accounts or troubleshoot authentication issues |
341
+ | `weco credits balance` | Check your current credit balance | Monitor usage |
342
+ | `weco credits topup [amount]` | Purchase additional credits | When you need more credits (default: 10) |
343
+ | `weco credits autotopup` | Configure automatic top-up | Set up automatic credit replenishment |
344
+
345
+ ### Setup Commands (Experimental)
346
+
347
+ | Command | Description |
348
+ |---------|-------------|
349
+ | `weco setup claude-code` | Set up Weco skill for Claude Code |
350
+ | `weco setup cursor` | Set up Weco rules for Cursor |
351
+
352
+ The `setup` command installs Weco skills for AI coding assistants:
353
+
354
+ ```bash
355
+ weco setup claude-code # For Claude Code
356
+ weco setup cursor # For Cursor
357
+ ```
358
+
359
+ - **Claude Code**: Clones the Weco skill to `~/.claude/skills/weco/` and updates `~/.claude/CLAUDE.md`
360
+ - **Cursor**: Clones the Weco skill to `~/.cursor/skills/weco/` and creates `~/.cursor/rules/weco.mdc`
340
361
 
341
362
  ### Model Selection
342
363
 
@@ -107,7 +107,28 @@ For more advanced examples, including [Triton](/examples/triton/README.md), [CUD
107
107
  |---------|-------------|-------------|
108
108
  | `weco run [options]` | Direct optimization execution | **For advanced users** - When you know exactly what to optimize and how |
109
109
  | `weco resume <run-id>` | Resume an interrupted run | Continue from the last completed step |
110
+ | `weco login` | Authenticate with Weco | First-time setup or switching accounts |
110
111
  | `weco logout` | Clear authentication credentials | To switch accounts or troubleshoot authentication issues |
112
+ | `weco credits balance` | Check your current credit balance | Monitor usage |
113
+ | `weco credits topup [amount]` | Purchase additional credits | When you need more credits (default: 10) |
114
+ | `weco credits autotopup` | Configure automatic top-up | Set up automatic credit replenishment |
115
+
116
+ ### Setup Commands (Experimental)
117
+
118
+ | Command | Description |
119
+ |---------|-------------|
120
+ | `weco setup claude-code` | Set up Weco skill for Claude Code |
121
+ | `weco setup cursor` | Set up Weco rules for Cursor |
122
+
123
+ The `setup` command installs Weco skills for AI coding assistants:
124
+
125
+ ```bash
126
+ weco setup claude-code # For Claude Code
127
+ weco setup cursor # For Cursor
128
+ ```
129
+
130
+ - **Claude Code**: Clones the Weco skill to `~/.claude/skills/weco/` and updates `~/.claude/CLAUDE.md`
131
+ - **Cursor**: Clones the Weco skill to `~/.cursor/skills/weco/` and creates `~/.cursor/rules/weco.mdc`
111
132
 
112
133
  ### Model Selection
113
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.8"
11
+ version = "0.3.10"
12
12
  license = { file = "LICENSE" }
13
13
  requires-python = ">=3.8"
14
14
  dependencies = [
@@ -137,6 +137,13 @@ Default models for providers:
137
137
  {default_models_for_providers}
138
138
  """,
139
139
  )
140
+ run_parser.add_argument(
141
+ "--output",
142
+ type=str,
143
+ choices=["rich", "plain"],
144
+ default="rich",
145
+ help="Output mode: 'rich' for interactive terminal UI (default), 'plain' for machine-readable text output suitable for LLM agents.",
146
+ )
140
147
 
141
148
 
142
149
  def configure_credits_parser(credits_parser: argparse.ArgumentParser) -> None:
@@ -178,6 +185,13 @@ def configure_credits_parser(credits_parser: argparse.ArgumentParser) -> None:
178
185
  )
179
186
 
180
187
 
188
+ def configure_setup_parser(setup_parser: argparse.ArgumentParser) -> None:
189
+ """Configure the setup command parser and its subcommands."""
190
+ setup_subparsers = setup_parser.add_subparsers(dest="tool", help="AI tool to set up")
191
+ setup_subparsers.add_parser("claude-code", help="Set up Weco skill for Claude Code")
192
+ setup_subparsers.add_parser("cursor", help="Set up Weco rules for Cursor")
193
+
194
+
181
195
  def configure_resume_parser(resume_parser: argparse.ArgumentParser) -> None:
182
196
  """Configure arguments for the resume command."""
183
197
  resume_parser.add_argument(
@@ -208,6 +222,13 @@ Example:
208
222
  Supported provider names: {supported_providers}.
209
223
  """,
210
224
  )
225
+ resume_parser.add_argument(
226
+ "--output",
227
+ type=str,
228
+ choices=["rich", "plain"],
229
+ default="rich",
230
+ help="Output mode: 'rich' for interactive terminal UI (default), 'plain' for machine-readable text output suitable for LLM agents.",
231
+ )
211
232
 
212
233
 
213
234
  def execute_run_command(args: argparse.Namespace) -> None:
@@ -253,6 +274,7 @@ def execute_run_command(args: argparse.Namespace) -> None:
253
274
  api_keys=api_keys,
254
275
  apply_change=args.apply_change,
255
276
  require_review=args.require_review,
277
+ output_mode=args.output,
256
278
  )
257
279
 
258
280
  exit_code = 0 if success else 1
@@ -269,7 +291,9 @@ def execute_resume_command(args: argparse.Namespace) -> None:
269
291
  console.print(f"[bold red]Error parsing API keys: {e}[/]")
270
292
  sys.exit(1)
271
293
 
272
- success = resume_optimization(run_id=args.run_id, api_keys=api_keys, apply_change=args.apply_change)
294
+ success = resume_optimization(
295
+ run_id=args.run_id, api_keys=api_keys, apply_change=args.apply_change, output_mode=args.output
296
+ )
273
297
 
274
298
  sys.exit(0 if success else 1)
275
299
 
@@ -322,6 +346,10 @@ def _main() -> None:
322
346
  )
323
347
  configure_resume_parser(resume_parser)
324
348
 
349
+ # --- Setup Command Parser Setup ---
350
+ setup_parser = subparsers.add_parser("setup", help="Set up Weco for use with AI tools")
351
+ configure_setup_parser(setup_parser)
352
+
325
353
  args = parser.parse_args()
326
354
 
327
355
  if args.command == "login":
@@ -349,6 +377,11 @@ def _main() -> None:
349
377
  sys.exit(0)
350
378
  elif args.command == "resume":
351
379
  execute_resume_command(args)
380
+ elif args.command == "setup":
381
+ from .setup import handle_setup_command
382
+
383
+ handle_setup_command(args, console)
384
+ sys.exit(0)
352
385
  else:
353
386
  # This case should be hit if 'weco' is run alone and chatbot logic didn't catch it,
354
387
  # or if an invalid command is provided.
@@ -25,7 +25,7 @@ from .api import (
25
25
  )
26
26
  from .auth import handle_authentication
27
27
  from .browser import open_browser
28
- from .ui import OptimizationUI, LiveOptimizationUI
28
+ from .ui import OptimizationUI, LiveOptimizationUI, PlainOptimizationUI
29
29
  from .utils import read_additional_instructions, read_from_path, write_to_path, run_evaluation_with_file_swap
30
30
 
31
31
 
@@ -324,7 +324,11 @@ def _offer_apply_best_solution(
324
324
 
325
325
 
326
326
  def resume_optimization(
327
- run_id: str, api_keys: Optional[dict] = None, poll_interval: float = 2.0, apply_change: bool = False
327
+ run_id: str,
328
+ api_keys: Optional[dict] = None,
329
+ poll_interval: float = 2.0,
330
+ apply_change: bool = False,
331
+ output_mode: str = "rich",
328
332
  ) -> bool:
329
333
  """
330
334
  Resume an interrupted run using the queue-based optimization loop.
@@ -337,11 +341,12 @@ def resume_optimization(
337
341
  api_keys: Optional API keys for LLM providers.
338
342
  poll_interval: Seconds between polling attempts.
339
343
  apply_change: If True, automatically apply best solution; if False, prompt user.
344
+ output_mode: "rich" for interactive terminal UI, "plain" for machine-readable output.
340
345
 
341
346
  Returns:
342
347
  True if optimization completed successfully, False otherwise.
343
348
  """
344
- console = Console()
349
+ console = Console(force_terminal=output_mode == "rich")
345
350
 
346
351
  # Authenticate
347
352
  weco_api_key, auth_headers = handle_authentication(console)
@@ -431,9 +436,17 @@ def resume_optimization(
431
436
 
432
437
  result: Optional[OptimizationResult] = None
433
438
  try:
434
- with LiveOptimizationUI(
435
- console, run_id, run_name, total_steps, dashboard_url, model=model_name, metric_name=metric_name
436
- ) as ui:
439
+ # Select UI implementation based on output mode
440
+ if output_mode == "plain":
441
+ ui_instance = PlainOptimizationUI(
442
+ run_id, run_name, total_steps, dashboard_url, model=model_name, metric_name=metric_name
443
+ )
444
+ else:
445
+ ui_instance = LiveOptimizationUI(
446
+ console, run_id, run_name, total_steps, dashboard_url, model=model_name, metric_name=metric_name
447
+ )
448
+
449
+ with ui_instance as ui:
437
450
  # Populate UI with best solution from previous run if available
438
451
  if best_metric_value is not None and best_step is not None:
439
452
  ui.on_metric(best_step, best_metric_value)
@@ -459,7 +472,10 @@ def resume_optimization(
459
472
 
460
473
  # Show resume message if interrupted
461
474
  if result.status == "terminated":
462
- console.print(f"\n[cyan]To resume this run, use:[/] [bold]weco resume {run_id}[/]\n")
475
+ if output_mode == "plain":
476
+ print(f"\nTo resume this run, use: weco resume {run_id}\n", flush=True)
477
+ else:
478
+ console.print(f"\n[cyan]To resume this run, use:[/] [bold]weco resume {run_id}[/]\n")
463
479
 
464
480
  # Offer to apply best solution
465
481
  _offer_apply_best_solution(
@@ -507,6 +523,7 @@ def optimize(
507
523
  poll_interval: float = 2.0,
508
524
  apply_change: bool = False,
509
525
  require_review: bool = False,
526
+ output_mode: str = "rich",
510
527
  ) -> bool:
511
528
  """
512
529
  Simplified queue-based optimization loop.
@@ -528,11 +545,12 @@ def optimize(
528
545
  api_keys: Optional API keys for LLM providers.
529
546
  poll_interval: Seconds between polling attempts.
530
547
  apply_change: If True, automatically apply best solution; if False, prompt user.
548
+ output_mode: "rich" for interactive terminal UI, "plain" for machine-readable output.
531
549
 
532
550
  Returns:
533
551
  True if optimization completed successfully, False otherwise.
534
552
  """
535
- console = Console()
553
+ console = Console(force_terminal=output_mode == "rich")
536
554
 
537
555
  # Authenticate
538
556
  weco_api_key, auth_headers = handle_authentication(console)
@@ -596,7 +614,13 @@ def optimize(
596
614
 
597
615
  result: Optional[OptimizationResult] = None
598
616
  try:
599
- with LiveOptimizationUI(console, run_id, run_name, steps, dashboard_url, model=model, metric_name=metric) as ui:
617
+ # Select UI implementation based on output mode
618
+ if output_mode == "plain":
619
+ ui_instance = PlainOptimizationUI(run_id, run_name, steps, dashboard_url, model=model, metric_name=metric)
620
+ else:
621
+ ui_instance = LiveOptimizationUI(console, run_id, run_name, steps, dashboard_url, model=model, metric_name=metric)
622
+
623
+ with ui_instance as ui:
600
624
  result = _run_optimization_loop(
601
625
  ui=ui,
602
626
  run_id=run_id,
@@ -618,7 +642,10 @@ def optimize(
618
642
 
619
643
  # Show resume message if interrupted
620
644
  if result.status == "terminated":
621
- console.print(f"\n[cyan]To resume this run, use:[/] [bold]weco resume {run_id}[/]\n")
645
+ if output_mode == "plain":
646
+ print(f"\nTo resume this run, use: weco resume {run_id}\n", flush=True)
647
+ else:
648
+ console.print(f"\n[cyan]To resume this run, use:[/] [bold]weco resume {run_id}[/]\n")
622
649
 
623
650
  # Offer to apply best solution
624
651
  _offer_apply_best_solution(
@@ -0,0 +1,367 @@
1
+ # weco/setup.py
2
+ """
3
+ Setup commands for integrating Weco with various AI tools.
4
+ """
5
+
6
+ import pathlib
7
+ import shutil
8
+ import subprocess
9
+ from rich.console import Console
10
+ from rich.prompt import Confirm
11
+
12
+ # Claude Code paths
13
+ CLAUDE_DIR = pathlib.Path.home() / ".claude"
14
+ CLAUDE_SKILLS_DIR = CLAUDE_DIR / "skills"
15
+ CLAUDE_MD_PATH = CLAUDE_DIR / "CLAUDE.md"
16
+ WECO_SKILL_DIR = CLAUDE_SKILLS_DIR / "weco"
17
+ WECO_SKILL_REPO = "git@github.com:WecoAI/weco-skill.git"
18
+ WECO_RULES_SNIPPET_PATH = WECO_SKILL_DIR / "rules-snippet.md"
19
+
20
+ # Cursor paths
21
+ CURSOR_DIR = pathlib.Path.home() / ".cursor"
22
+ CURSOR_RULES_DIR = CURSOR_DIR / "rules"
23
+ CURSOR_WECO_RULES_PATH = CURSOR_RULES_DIR / "weco.mdc"
24
+ CURSOR_SKILLS_DIR = CURSOR_DIR / "skills"
25
+ CURSOR_WECO_SKILL_DIR = CURSOR_SKILLS_DIR / "weco"
26
+ CURSOR_RULES_SNIPPET_PATH = CURSOR_WECO_SKILL_DIR / "rules-snippet.md"
27
+
28
+ # Delimiters for agent rules files - allows automatic updates
29
+ WECO_RULES_BEGIN_DELIMITER = "<!-- BEGIN WECO_RULES -->"
30
+ WECO_RULES_END_DELIMITER = "<!-- END WECO_RULES -->"
31
+
32
+
33
+ def is_git_available() -> bool:
34
+ """Check if git is available on the system."""
35
+ return shutil.which("git") is not None
36
+
37
+
38
+ def read_rules_snippet(snippet_path: pathlib.Path, console: Console) -> str | None:
39
+ """
40
+ Read the rules snippet from the cloned skill repository.
41
+
42
+ Args:
43
+ snippet_path: Path to the rules-snippet.md file.
44
+ console: Rich console for output.
45
+
46
+ Returns:
47
+ The snippet content wrapped in delimiters, or None if not found.
48
+ """
49
+ if not snippet_path.exists():
50
+ console.print(f"[bold red]Error:[/] Snippet file not found at {snippet_path}")
51
+ return None
52
+
53
+ try:
54
+ snippet_content = snippet_path.read_text().strip()
55
+ return f"\n{WECO_RULES_BEGIN_DELIMITER}\n{snippet_content}\n{WECO_RULES_END_DELIMITER}\n"
56
+ except Exception as e:
57
+ console.print(f"[bold red]Error:[/] Failed to read snippet file: {e}")
58
+ return None
59
+
60
+
61
+ def read_rules_snippet_raw(snippet_path: pathlib.Path, console: Console) -> str | None:
62
+ """
63
+ Read the raw rules snippet from the cloned skill repository (without delimiters).
64
+
65
+ Args:
66
+ snippet_path: Path to the rules-snippet.md file.
67
+ console: Rich console for output.
68
+
69
+ Returns:
70
+ The raw snippet content, or None if not found.
71
+ """
72
+ if not snippet_path.exists():
73
+ console.print(f"[bold red]Error:[/] Snippet file not found at {snippet_path}")
74
+ return None
75
+
76
+ try:
77
+ return snippet_path.read_text().strip()
78
+ except Exception as e:
79
+ console.print(f"[bold red]Error:[/] Failed to read snippet file: {e}")
80
+ return None
81
+
82
+
83
+ def generate_cursor_mdc_content(snippet_content: str) -> str:
84
+ """
85
+ Generate Cursor MDC file content with YAML frontmatter.
86
+
87
+ Args:
88
+ snippet_content: The raw rules snippet content.
89
+
90
+ Returns:
91
+ MDC formatted content with frontmatter.
92
+ """
93
+ return f"""---
94
+ description: Weco code optimization skill - invoke for speed, accuracy, loss optimization
95
+ alwaysApply: true
96
+ ---
97
+ {snippet_content}
98
+ """
99
+
100
+
101
+ def is_git_repo(path: pathlib.Path) -> bool:
102
+ """Check if a directory is a git repository."""
103
+ return (path / ".git").is_dir()
104
+
105
+
106
+ def clone_skill_repo(skill_dir: pathlib.Path, console: Console) -> bool:
107
+ """
108
+ Clone or update the weco-skill repository to the specified directory.
109
+
110
+ Args:
111
+ skill_dir: The directory to clone/update the skill repository in.
112
+ console: Rich console for output.
113
+
114
+ Returns:
115
+ True if successful, False otherwise.
116
+ """
117
+ if not is_git_available():
118
+ console.print("[bold red]Error:[/] git is not installed or not in PATH.")
119
+ console.print("Please install git and try again.")
120
+ return False
121
+
122
+ # Ensure the parent skills directory exists
123
+ skill_dir.parent.mkdir(parents=True, exist_ok=True)
124
+
125
+ if skill_dir.exists():
126
+ if is_git_repo(skill_dir):
127
+ # Directory exists and is a git repo - pull latest
128
+ console.print(f"[cyan]Updating existing skill at {skill_dir}...[/]")
129
+ try:
130
+ result = subprocess.run(["git", "pull"], cwd=skill_dir, capture_output=True, text=True)
131
+ if result.returncode != 0:
132
+ console.print("[bold red]Error:[/] Failed to update skill repository.")
133
+ console.print(f"[dim]{result.stderr}[/]")
134
+ return False
135
+ console.print("[green]Skill updated successfully.[/]")
136
+ return True
137
+ except Exception as e:
138
+ console.print(f"[bold red]Error:[/] Failed to update skill repository: {e}")
139
+ return False
140
+ else:
141
+ # Directory exists but is not a git repo
142
+ console.print(f"[bold red]Error:[/] Directory {skill_dir} exists but is not a git repository.")
143
+ console.print("Please remove it manually and try again.")
144
+ return False
145
+ else:
146
+ # Clone the repository
147
+ console.print(f"[cyan]Cloning Weco skill to {skill_dir}...[/]")
148
+ try:
149
+ result = subprocess.run(["git", "clone", WECO_SKILL_REPO, str(skill_dir)], capture_output=True, text=True)
150
+ if result.returncode != 0:
151
+ console.print("[bold red]Error:[/] Failed to clone skill repository.")
152
+ console.print(f"[dim]{result.stderr}[/]")
153
+ return False
154
+ console.print("[green]Skill cloned successfully.[/]")
155
+ return True
156
+ except Exception as e:
157
+ console.print(f"[bold red]Error:[/] Failed to clone skill repository: {e}")
158
+ return False
159
+
160
+
161
+ def update_agent_rules_file(
162
+ rules_file: pathlib.Path, snippet_path: pathlib.Path, skill_dir: pathlib.Path, console: Console
163
+ ) -> bool:
164
+ """
165
+ Update an agent's rules file with the Weco skill reference.
166
+
167
+ Uses delimiters to allow automatic updates if the snippet changes.
168
+
169
+ Args:
170
+ rules_file: Path to the agent's rules file (e.g., ~/.claude/CLAUDE.md)
171
+ snippet_path: Path to the rules-snippet.md file.
172
+ skill_dir: Path to the skill directory (for user messaging).
173
+ console: Rich console for output.
174
+
175
+ Returns:
176
+ True if updated or user declined, False on error.
177
+ """
178
+ import re
179
+
180
+ rules_file_name = rules_file.name
181
+
182
+ # Read the snippet from the cloned skill repo
183
+ snippet_section = read_rules_snippet(snippet_path, console)
184
+ if snippet_section is None:
185
+ return False
186
+
187
+ # Check if the section already exists with delimiters
188
+ existing_content = ""
189
+ has_existing_section = False
190
+ if rules_file.exists():
191
+ try:
192
+ existing_content = rules_file.read_text()
193
+ has_existing_section = (
194
+ WECO_RULES_BEGIN_DELIMITER in existing_content and WECO_RULES_END_DELIMITER in existing_content
195
+ )
196
+ except Exception as e:
197
+ console.print(f"[bold yellow]Warning:[/] Could not read {rules_file_name}: {e}")
198
+
199
+ # Determine what action to take
200
+ if has_existing_section:
201
+ # Check if content is already up to date
202
+ pattern = re.escape(WECO_RULES_BEGIN_DELIMITER) + r".*?" + re.escape(WECO_RULES_END_DELIMITER)
203
+ match = re.search(pattern, existing_content, re.DOTALL)
204
+ if match and match.group(0).strip() == snippet_section.strip():
205
+ console.print(f"[dim]{rules_file_name} already contains the latest Weco rules.[/]")
206
+ return True
207
+
208
+ # Prompt for update
209
+ console.print(f"\n[bold yellow]{rules_file_name} Update[/]")
210
+ console.print(f"The Weco rules in your {rules_file_name} can be updated to the latest version.")
211
+ should_update = Confirm.ask("Would you like to update the Weco section?", default=True)
212
+ elif rules_file.exists():
213
+ console.print(f"\n[bold yellow]{rules_file_name} Update[/]")
214
+ console.print(f"To enable automatic skill discovery, we can add Weco rules to your {rules_file_name} file.")
215
+ should_update = Confirm.ask(f"Would you like to update your {rules_file_name}?", default=True)
216
+ else:
217
+ console.print(f"\n[bold yellow]{rules_file_name} Creation[/]")
218
+ console.print(f"To enable automatic skill discovery, we can create a {rules_file_name} file.")
219
+ should_update = Confirm.ask(f"Would you like to create {rules_file_name}?", default=True)
220
+
221
+ if not should_update:
222
+ console.print(f"\n[yellow]Skipping {rules_file_name} update.[/]")
223
+ console.print(
224
+ "[dim]The Weco skill has been installed but may not be discovered automatically.\n"
225
+ f"You can manually reference it at {skill_dir}[/]"
226
+ )
227
+ return True
228
+
229
+ # Update or create the file
230
+ try:
231
+ rules_file.parent.mkdir(parents=True, exist_ok=True)
232
+
233
+ if has_existing_section:
234
+ # Replace existing section between delimiters
235
+ pattern = re.escape(WECO_RULES_BEGIN_DELIMITER) + r".*?" + re.escape(WECO_RULES_END_DELIMITER)
236
+ new_content = re.sub(pattern, snippet_section.strip(), existing_content, flags=re.DOTALL)
237
+ rules_file.write_text(new_content)
238
+ console.print(f"[green]{rules_file_name} updated successfully.[/]")
239
+ elif rules_file.exists():
240
+ # Append to existing file
241
+ with open(rules_file, "a") as f:
242
+ f.write(snippet_section)
243
+ console.print(f"[green]{rules_file_name} updated successfully.[/]")
244
+ else:
245
+ # Create new file
246
+ with open(rules_file, "w") as f:
247
+ f.write(snippet_section.lstrip())
248
+ console.print(f"[green]{rules_file_name} created successfully.[/]")
249
+ return True
250
+ except Exception as e:
251
+ console.print(f"[bold red]Error:[/] Failed to update {rules_file_name}: {e}")
252
+ return False
253
+
254
+
255
+ def setup_claude_code(console: Console) -> bool:
256
+ """
257
+ Set up Weco skill for Claude Code.
258
+
259
+ Returns:
260
+ True if setup was successful, False otherwise.
261
+ """
262
+ console.print("[bold blue]Setting up Weco for Claude Code...[/]\n")
263
+
264
+ # Step 1: Clone or update the skill repository
265
+ if not clone_skill_repo(WECO_SKILL_DIR, console):
266
+ return False
267
+
268
+ # Step 2: Update CLAUDE.md
269
+ if not update_agent_rules_file(CLAUDE_MD_PATH, WECO_RULES_SNIPPET_PATH, WECO_SKILL_DIR, console):
270
+ return False
271
+
272
+ console.print("\n[bold green]Setup complete![/]")
273
+ console.print(f"[dim]Skill installed at: {WECO_SKILL_DIR}[/]")
274
+ return True
275
+
276
+
277
+ def setup_cursor(console: Console) -> bool:
278
+ """
279
+ Set up Weco rules for Cursor.
280
+
281
+ Creates a weco.mdc file in ~/.cursor/rules/ with the Weco optimization rules.
282
+
283
+ Returns:
284
+ True if setup was successful, False otherwise.
285
+ """
286
+ console.print("[bold blue]Setting up Weco for Cursor...[/]\n")
287
+
288
+ # Step 1: Clone or update the skill repository to Cursor's path
289
+ if not clone_skill_repo(CURSOR_WECO_SKILL_DIR, console):
290
+ return False
291
+
292
+ # Step 2: Read the rules snippet
293
+ snippet_content = read_rules_snippet_raw(CURSOR_RULES_SNIPPET_PATH, console)
294
+ if snippet_content is None:
295
+ return False
296
+
297
+ # Step 3: Check if weco.mdc already exists
298
+ if CURSOR_WECO_RULES_PATH.exists():
299
+ try:
300
+ existing_content = CURSOR_WECO_RULES_PATH.read_text()
301
+ new_content = generate_cursor_mdc_content(snippet_content)
302
+ if existing_content.strip() == new_content.strip():
303
+ console.print("[dim]weco.mdc already contains the latest Weco rules.[/]")
304
+ console.print("\n[bold green]Setup complete![/]")
305
+ console.print(f"[dim]Rules file at: {CURSOR_WECO_RULES_PATH}[/]")
306
+ return True
307
+ except Exception as e:
308
+ console.print(f"[bold yellow]Warning:[/] Could not read existing weco.mdc: {e}")
309
+
310
+ console.print("\n[bold yellow]weco.mdc Update[/]")
311
+ console.print("The Weco rules file can be updated to the latest version.")
312
+ should_update = Confirm.ask("Would you like to update weco.mdc?", default=True)
313
+ else:
314
+ console.print("\n[bold yellow]weco.mdc Creation[/]")
315
+ console.print("To enable Weco optimization rules, we can create a weco.mdc file.")
316
+ should_update = Confirm.ask("Would you like to create weco.mdc?", default=True)
317
+
318
+ if not should_update:
319
+ console.print("\n[yellow]Skipping weco.mdc update.[/]")
320
+ console.print(
321
+ "[dim]The Weco skill has been installed but rules are not configured.\n"
322
+ f"You can manually create the rules file at {CURSOR_WECO_RULES_PATH}[/]"
323
+ )
324
+ return True
325
+
326
+ # Step 4: Write the MDC file
327
+ try:
328
+ CURSOR_RULES_DIR.mkdir(parents=True, exist_ok=True)
329
+ mdc_content = generate_cursor_mdc_content(snippet_content)
330
+ CURSOR_WECO_RULES_PATH.write_text(mdc_content)
331
+ console.print("[green]weco.mdc created successfully.[/]")
332
+ except Exception as e:
333
+ console.print(f"[bold red]Error:[/] Failed to write weco.mdc: {e}")
334
+ return False
335
+
336
+ console.print("\n[bold green]Setup complete![/]")
337
+ console.print(f"[dim]Rules file at: {CURSOR_WECO_RULES_PATH}[/]")
338
+ return True
339
+
340
+
341
+ def handle_setup_command(args, console: Console) -> None:
342
+ """Handle the setup command with its subcommands."""
343
+ if args.tool == "claude-code":
344
+ success = setup_claude_code(console)
345
+ if not success:
346
+ import sys
347
+
348
+ sys.exit(1)
349
+ elif args.tool == "cursor":
350
+ success = setup_cursor(console)
351
+ if not success:
352
+ import sys
353
+
354
+ sys.exit(1)
355
+ elif args.tool is None:
356
+ console.print("[bold red]Error:[/] Please specify a tool to set up.")
357
+ console.print("Available tools: claude-code, cursor")
358
+ console.print("\nUsage: weco setup <tool>")
359
+ import sys
360
+
361
+ sys.exit(1)
362
+ else:
363
+ console.print(f"[bold red]Error:[/] Unknown tool: {args.tool}")
364
+ console.print("Available tools: claude-code, cursor")
365
+ import sys
366
+
367
+ sys.exit(1)
@@ -313,3 +313,109 @@ class LiveOptimizationUI:
313
313
  self.state.error = message
314
314
  self.state.status = "error"
315
315
  self._update()
316
+
317
+
318
+ class PlainOptimizationUI:
319
+ """
320
+ Plain text implementation of OptimizationUI for machine-readable output.
321
+
322
+ Designed to be consumed by LLM agents - outputs structured, parseable text
323
+ without Rich formatting, ANSI codes, or interactive elements.
324
+ Includes full execution output for agent consumption.
325
+ """
326
+
327
+ def __init__(
328
+ self, run_id: str, run_name: str, total_steps: int, dashboard_url: str, model: str = "", metric_name: str = ""
329
+ ):
330
+ self.run_id = run_id
331
+ self.run_name = run_name
332
+ self.total_steps = total_steps
333
+ self.dashboard_url = dashboard_url
334
+ self.model = model
335
+ self.metric_name = metric_name
336
+ self.current_step = 0
337
+ self.metrics: List[tuple] = [] # (step, value)
338
+ self._header_printed = False
339
+
340
+ def _print(self, message: str) -> None:
341
+ """Print a message to stdout with flush for immediate output."""
342
+ print(message, flush=True)
343
+
344
+ def _print_header(self) -> None:
345
+ """Print run header info once at start."""
346
+ if self._header_printed:
347
+ return
348
+ self._header_printed = True
349
+ self._print("=" * 60)
350
+ self._print("WECO OPTIMIZATION RUN")
351
+ self._print("=" * 60)
352
+ self._print(f"Run ID: {self.run_id}")
353
+ self._print(f"Run Name: {self.run_name}")
354
+ self._print(f"Dashboard: {self.dashboard_url}")
355
+ if self.model:
356
+ self._print(f"Model: {self.model}")
357
+ if self.metric_name:
358
+ self._print(f"Metric: {self.metric_name}")
359
+ self._print(f"Total Steps: {self.total_steps}")
360
+ self._print("=" * 60)
361
+ self._print("")
362
+
363
+ # --- Context manager (no-op for plain output) ---
364
+ def __enter__(self) -> "PlainOptimizationUI":
365
+ self._print_header()
366
+ return self
367
+
368
+ def __exit__(self, *args) -> None:
369
+ pass
370
+
371
+ # --- OptimizationUI Protocol Implementation ---
372
+ def on_polling(self, step: int) -> None:
373
+ self.current_step = step
374
+ self._print(f"[STEP {step}/{self.total_steps}] Polling for task...")
375
+
376
+ def on_task_claimed(self, task_id: str, plan: Optional[str]) -> None:
377
+ self._print(f"[TASK CLAIMED] {task_id}")
378
+ if plan:
379
+ self._print(f"[PLAN] {plan}")
380
+
381
+ def on_executing(self, step: int) -> None:
382
+ self.current_step = step
383
+ self._print(f"[STEP {step}/{self.total_steps}] Executing code...")
384
+
385
+ def on_output(self, output: str, max_preview: int = 200) -> None:
386
+ # For plain mode, output the full execution result for LLM consumption
387
+ self._print("[EXECUTION OUTPUT START]")
388
+ self._print(output)
389
+ self._print("[EXECUTION OUTPUT END]")
390
+
391
+ def on_submitting(self) -> None:
392
+ self._print("[SUBMITTING] Sending result to backend...")
393
+
394
+ def on_metric(self, step: int, value: float) -> None:
395
+ self.metrics.append((step, value))
396
+ best = max(m[1] for m in self.metrics) if self.metrics else value
397
+ self._print(f"[METRIC] Step {step}: {value:.6g} (best so far: {best:.6g})")
398
+
399
+ def on_complete(self, total_steps: int) -> None:
400
+ self._print("")
401
+ self._print("=" * 60)
402
+ self._print("[COMPLETE] Optimization finished successfully")
403
+ self._print(f"Total steps completed: {total_steps}")
404
+ if self.metrics:
405
+ values = [m[1] for m in self.metrics]
406
+ self._print(f"Best metric value: {max(values):.6g}")
407
+ self._print("=" * 60)
408
+
409
+ def on_stop_requested(self) -> None:
410
+ self._print("")
411
+ self._print("[STOPPED] Run stopped by user request")
412
+
413
+ def on_interrupted(self) -> None:
414
+ self._print("")
415
+ self._print("[INTERRUPTED] Run interrupted (Ctrl+C)")
416
+
417
+ def on_warning(self, message: str) -> None:
418
+ self._print(f"[WARNING] {message}")
419
+
420
+ def on_error(self, message: str) -> None:
421
+ self._print(f"[ERROR] {message}")
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: weco
3
- Version: 0.3.8
3
+ Version: 0.3.10
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:
@@ -336,7 +336,28 @@ For more advanced examples, including [Triton](/examples/triton/README.md), [CUD
336
336
  |---------|-------------|-------------|
337
337
  | `weco run [options]` | Direct optimization execution | **For advanced users** - When you know exactly what to optimize and how |
338
338
  | `weco resume <run-id>` | Resume an interrupted run | Continue from the last completed step |
339
+ | `weco login` | Authenticate with Weco | First-time setup or switching accounts |
339
340
  | `weco logout` | Clear authentication credentials | To switch accounts or troubleshoot authentication issues |
341
+ | `weco credits balance` | Check your current credit balance | Monitor usage |
342
+ | `weco credits topup [amount]` | Purchase additional credits | When you need more credits (default: 10) |
343
+ | `weco credits autotopup` | Configure automatic top-up | Set up automatic credit replenishment |
344
+
345
+ ### Setup Commands (Experimental)
346
+
347
+ | Command | Description |
348
+ |---------|-------------|
349
+ | `weco setup claude-code` | Set up Weco skill for Claude Code |
350
+ | `weco setup cursor` | Set up Weco rules for Cursor |
351
+
352
+ The `setup` command installs Weco skills for AI coding assistants:
353
+
354
+ ```bash
355
+ weco setup claude-code # For Claude Code
356
+ weco setup cursor # For Cursor
357
+ ```
358
+
359
+ - **Claude Code**: Clones the Weco skill to `~/.claude/skills/weco/` and updates `~/.claude/CLAUDE.md`
360
+ - **Cursor**: Clones the Weco skill to `~/.cursor/skills/weco/` and creates `~/.cursor/rules/weco.mdc`
340
361
 
341
362
  ### Model Selection
342
363
 
@@ -50,6 +50,7 @@ weco/constants.py
50
50
  weco/credits.py
51
51
  weco/optimizer.py
52
52
  weco/panels.py
53
+ weco/setup.py
53
54
  weco/ui.py
54
55
  weco/utils.py
55
56
  weco/validation.py
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes