weco 0.3.8__py3-none-any.whl → 0.3.10__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- weco/cli.py +34 -1
- weco/optimizer.py +37 -10
- weco/setup.py +367 -0
- weco/ui.py +106 -0
- {weco-0.3.8.dist-info → weco-0.3.10.dist-info}/METADATA +22 -1
- weco-0.3.10.dist-info/RECORD +19 -0
- {weco-0.3.8.dist-info → weco-0.3.10.dist-info}/WHEEL +1 -1
- weco-0.3.8.dist-info/RECORD +0 -18
- {weco-0.3.8.dist-info → weco-0.3.10.dist-info}/entry_points.txt +0 -0
- {weco-0.3.8.dist-info → weco-0.3.10.dist-info}/licenses/LICENSE +0 -0
- {weco-0.3.8.dist-info → weco-0.3.10.dist-info}/top_level.txt +0 -0
weco/cli.py
CHANGED
|
@@ -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(
|
|
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.
|
weco/optimizer.py
CHANGED
|
@@ -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,
|
|
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
|
-
|
|
435
|
-
|
|
436
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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(
|
weco/setup.py
ADDED
|
@@ -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)
|
weco/ui.py
CHANGED
|
@@ -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.
|
|
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
|
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
weco/__init__.py,sha256=ClO0uT6GKOA0iSptvP0xbtdycf0VpoPTq37jHtvlhtw,303
|
|
2
|
+
weco/api.py,sha256=hy0D01x-AJ26DURtKEywQAS7nQgo38A-wAJfPKHGGqM,17395
|
|
3
|
+
weco/auth.py,sha256=O31Hoj-Loi8DWJJG2LfeWgUMuNqAUeGDpd2ZGjA9Ah0,9997
|
|
4
|
+
weco/browser.py,sha256=nsqQtLqbNOe9Zhu9Zogc8rMmBMyuDxuHzKZQL_w10Ps,923
|
|
5
|
+
weco/cli.py,sha256=WaFkdIOQZIkBcPfWbUhOvUExNeDVdrmax5-tVGY3uNE,14000
|
|
6
|
+
weco/constants.py,sha256=rxL6yrpIzK8zvPTmPqOYl7LUMZ01vUJ9zUqfZD2n-0U,519
|
|
7
|
+
weco/credits.py,sha256=C08x-TRcLg3ccfKqMGNRY7zBn7t3r7LZ119bxgfztaI,7629
|
|
8
|
+
weco/optimizer.py,sha256=l9TrAsie5yPbhq71kStW5jfdz9jfIXuN6Azdn6hUMZo,25224
|
|
9
|
+
weco/panels.py,sha256=POHt0MdRKDykwUJYXcry92O41lpB9gxna55wFI9abWU,16272
|
|
10
|
+
weco/setup.py,sha256=Zv1CbxYHamnoOcVfpCn68ymHgjzCTHHGMKep_WSFLCk,13981
|
|
11
|
+
weco/ui.py,sha256=Y7ASIQydwuYFdSYrvme5aGvkplFMi-cjhsnCj5Ebgoc,15092
|
|
12
|
+
weco/utils.py,sha256=v_rvgw-ktRoXrpPA2copngI8QDCB8UXmbiN-wAiYvEE,9450
|
|
13
|
+
weco/validation.py,sha256=n5aDuF3BFgwVb4eZ9PuU48nogrseXYNI8S3ePqWZCoc,3736
|
|
14
|
+
weco-0.3.10.dist-info/licenses/LICENSE,sha256=9LUfoGHjLPtak2zps2kL2tm65HAZIICx_FbLaRuS4KU,11337
|
|
15
|
+
weco-0.3.10.dist-info/METADATA,sha256=1eJxhWSGbdrHcOeBxjmxJ_T6Hruva1YNNn9fxOb59FE,30804
|
|
16
|
+
weco-0.3.10.dist-info/WHEEL,sha256=wUyA8OaulRlbfwMtmQsvNngGrxQHAvkKcvRmdizlJi0,92
|
|
17
|
+
weco-0.3.10.dist-info/entry_points.txt,sha256=ixJ2uClALbCpBvnIR6BXMNck8SHAab8eVkM9pIUowcs,39
|
|
18
|
+
weco-0.3.10.dist-info/top_level.txt,sha256=F0N7v6e2zBSlsorFv-arAq2yDxQbzX3KVO8GxYhPUeE,5
|
|
19
|
+
weco-0.3.10.dist-info/RECORD,,
|
weco-0.3.8.dist-info/RECORD
DELETED
|
@@ -1,18 +0,0 @@
|
|
|
1
|
-
weco/__init__.py,sha256=ClO0uT6GKOA0iSptvP0xbtdycf0VpoPTq37jHtvlhtw,303
|
|
2
|
-
weco/api.py,sha256=hy0D01x-AJ26DURtKEywQAS7nQgo38A-wAJfPKHGGqM,17395
|
|
3
|
-
weco/auth.py,sha256=O31Hoj-Loi8DWJJG2LfeWgUMuNqAUeGDpd2ZGjA9Ah0,9997
|
|
4
|
-
weco/browser.py,sha256=nsqQtLqbNOe9Zhu9Zogc8rMmBMyuDxuHzKZQL_w10Ps,923
|
|
5
|
-
weco/cli.py,sha256=u5DSt2YGLOha-ldaW6qg3NO2z3rNfYw37FNtaIs-Kz4,12656
|
|
6
|
-
weco/constants.py,sha256=rxL6yrpIzK8zvPTmPqOYl7LUMZ01vUJ9zUqfZD2n-0U,519
|
|
7
|
-
weco/credits.py,sha256=C08x-TRcLg3ccfKqMGNRY7zBn7t3r7LZ119bxgfztaI,7629
|
|
8
|
-
weco/optimizer.py,sha256=XygxTOTPaPx0dDVI9oWaMQHZ0-YAg1JFcMvGPcnEx-A,23990
|
|
9
|
-
weco/panels.py,sha256=POHt0MdRKDykwUJYXcry92O41lpB9gxna55wFI9abWU,16272
|
|
10
|
-
weco/ui.py,sha256=1shfWxeyfhjTzRZsuODuMx5XzeP9SngqpNpN4vIiDCI,11203
|
|
11
|
-
weco/utils.py,sha256=v_rvgw-ktRoXrpPA2copngI8QDCB8UXmbiN-wAiYvEE,9450
|
|
12
|
-
weco/validation.py,sha256=n5aDuF3BFgwVb4eZ9PuU48nogrseXYNI8S3ePqWZCoc,3736
|
|
13
|
-
weco-0.3.8.dist-info/licenses/LICENSE,sha256=9LUfoGHjLPtak2zps2kL2tm65HAZIICx_FbLaRuS4KU,11337
|
|
14
|
-
weco-0.3.8.dist-info/METADATA,sha256=a9A-tvBbsaojDEXmi1DYsoBAJALFQ6kPeIlWlf2Of0Y,29861
|
|
15
|
-
weco-0.3.8.dist-info/WHEEL,sha256=qELbo2s1Yzl39ZmrAibXA2jjPLUYfnVhUNTlyF1rq0Y,92
|
|
16
|
-
weco-0.3.8.dist-info/entry_points.txt,sha256=ixJ2uClALbCpBvnIR6BXMNck8SHAab8eVkM9pIUowcs,39
|
|
17
|
-
weco-0.3.8.dist-info/top_level.txt,sha256=F0N7v6e2zBSlsorFv-arAq2yDxQbzX3KVO8GxYhPUeE,5
|
|
18
|
-
weco-0.3.8.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|