weco 0.3.10__tar.gz → 0.3.11__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.
- {weco-0.3.10 → weco-0.3.11}/PKG-INFO +1 -1
- {weco-0.3.10 → weco-0.3.11}/pyproject.toml +1 -1
- {weco-0.3.10 → weco-0.3.11}/weco/cli.py +21 -2
- weco-0.3.11/weco/git.py +121 -0
- weco-0.3.11/weco/setup.py +295 -0
- {weco-0.3.10 → weco-0.3.11}/weco/utils.py +68 -2
- {weco-0.3.10 → weco-0.3.11}/weco.egg-info/PKG-INFO +1 -1
- {weco-0.3.10 → weco-0.3.11}/weco.egg-info/SOURCES.txt +1 -0
- weco-0.3.10/weco/setup.py +0 -367
- {weco-0.3.10 → weco-0.3.11}/.github/workflows/lint.yml +0 -0
- {weco-0.3.10 → weco-0.3.11}/.github/workflows/release.yml +0 -0
- {weco-0.3.10 → weco-0.3.11}/.gitignore +0 -0
- {weco-0.3.10 → weco-0.3.11}/LICENSE +0 -0
- {weco-0.3.10 → weco-0.3.11}/README.md +0 -0
- {weco-0.3.10 → weco-0.3.11}/assets/example-optimization.gif +0 -0
- {weco-0.3.10 → weco-0.3.11}/assets/weco.svg +0 -0
- {weco-0.3.10 → weco-0.3.11}/contributing.md +0 -0
- {weco-0.3.10 → weco-0.3.11}/examples/README.md +0 -0
- {weco-0.3.10 → weco-0.3.11}/examples/cuda/README.md +0 -0
- {weco-0.3.10 → weco-0.3.11}/examples/cuda/evaluate.py +0 -0
- {weco-0.3.10 → weco-0.3.11}/examples/cuda/module.py +0 -0
- {weco-0.3.10 → weco-0.3.11}/examples/cuda/requirements.txt +0 -0
- {weco-0.3.10 → weco-0.3.11}/examples/extract-line-plot/README.md +0 -0
- {weco-0.3.10 → weco-0.3.11}/examples/extract-line-plot/eval.py +0 -0
- {weco-0.3.10 → weco-0.3.11}/examples/extract-line-plot/guide.md +0 -0
- {weco-0.3.10 → weco-0.3.11}/examples/extract-line-plot/optimize.py +0 -0
- {weco-0.3.10 → weco-0.3.11}/examples/extract-line-plot/prepare_data.py +0 -0
- {weco-0.3.10 → weco-0.3.11}/examples/extract-line-plot/pyproject.toml +0 -0
- {weco-0.3.10 → weco-0.3.11}/examples/hello-world/README.md +0 -0
- {weco-0.3.10 → weco-0.3.11}/examples/hello-world/colab_notebook_walkthrough.ipynb +0 -0
- {weco-0.3.10 → weco-0.3.11}/examples/hello-world/evaluate.py +0 -0
- {weco-0.3.10 → weco-0.3.11}/examples/hello-world/module.py +0 -0
- {weco-0.3.10 → weco-0.3.11}/examples/hello-world/requirements.txt +0 -0
- {weco-0.3.10 → weco-0.3.11}/examples/prompt/README.md +0 -0
- {weco-0.3.10 → weco-0.3.11}/examples/prompt/eval.py +0 -0
- {weco-0.3.10 → weco-0.3.11}/examples/prompt/optimize.py +0 -0
- {weco-0.3.10 → weco-0.3.11}/examples/prompt/prompt_guide.md +0 -0
- {weco-0.3.10 → weco-0.3.11}/examples/spaceship-titanic/README.md +0 -0
- {weco-0.3.10 → weco-0.3.11}/examples/spaceship-titanic/competition_description.md +0 -0
- {weco-0.3.10 → weco-0.3.11}/examples/spaceship-titanic/data/sample_submission.csv +0 -0
- {weco-0.3.10 → weco-0.3.11}/examples/spaceship-titanic/data/test.csv +0 -0
- {weco-0.3.10 → weco-0.3.11}/examples/spaceship-titanic/data/train.csv +0 -0
- {weco-0.3.10 → weco-0.3.11}/examples/spaceship-titanic/evaluate.py +0 -0
- {weco-0.3.10 → weco-0.3.11}/examples/spaceship-titanic/train.py +0 -0
- {weco-0.3.10 → weco-0.3.11}/examples/triton/README.md +0 -0
- {weco-0.3.10 → weco-0.3.11}/examples/triton/evaluate.py +0 -0
- {weco-0.3.10 → weco-0.3.11}/examples/triton/module.py +0 -0
- {weco-0.3.10 → weco-0.3.11}/examples/triton/requirements.txt +0 -0
- {weco-0.3.10 → weco-0.3.11}/setup.cfg +0 -0
- {weco-0.3.10 → weco-0.3.11}/tests/__init__.py +0 -0
- {weco-0.3.10 → weco-0.3.11}/tests/test_byok.py +0 -0
- {weco-0.3.10 → weco-0.3.11}/tests/test_cli.py +0 -0
- {weco-0.3.10 → weco-0.3.11}/weco/__init__.py +0 -0
- {weco-0.3.10 → weco-0.3.11}/weco/api.py +0 -0
- {weco-0.3.10 → weco-0.3.11}/weco/auth.py +0 -0
- {weco-0.3.10 → weco-0.3.11}/weco/browser.py +0 -0
- {weco-0.3.10 → weco-0.3.11}/weco/constants.py +0 -0
- {weco-0.3.10 → weco-0.3.11}/weco/credits.py +0 -0
- {weco-0.3.10 → weco-0.3.11}/weco/optimizer.py +0 -0
- {weco-0.3.10 → weco-0.3.11}/weco/panels.py +0 -0
- {weco-0.3.10 → weco-0.3.11}/weco/ui.py +0 -0
- {weco-0.3.10 → weco-0.3.11}/weco/validation.py +0 -0
- {weco-0.3.10 → weco-0.3.11}/weco.egg-info/dependency_links.txt +0 -0
- {weco-0.3.10 → weco-0.3.11}/weco.egg-info/entry_points.txt +0 -0
- {weco-0.3.10 → weco-0.3.11}/weco.egg-info/requires.txt +0 -0
- {weco-0.3.10 → weco-0.3.11}/weco.egg-info/top_level.txt +0 -0
|
@@ -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.
|
|
11
|
+
version = "0.3.11"
|
|
12
12
|
license = { file = "LICENSE" }
|
|
13
13
|
requires-python = ">=3.8"
|
|
14
14
|
dependencies = [
|
|
@@ -185,11 +185,30 @@ def configure_credits_parser(credits_parser: argparse.ArgumentParser) -> None:
|
|
|
185
185
|
)
|
|
186
186
|
|
|
187
187
|
|
|
188
|
+
def _add_setup_source_args(parser: argparse.ArgumentParser) -> None:
|
|
189
|
+
"""Add common source arguments to a setup subparser."""
|
|
190
|
+
source_group = parser.add_mutually_exclusive_group()
|
|
191
|
+
source_group.add_argument(
|
|
192
|
+
"--local", type=str, metavar="PATH", help="Use a local weco-skill directory (creates symlink for development)"
|
|
193
|
+
)
|
|
194
|
+
source_group.add_argument("--repo", type=str, metavar="URL", help="Use a different git repo URL (for forks or testing)")
|
|
195
|
+
parser.add_argument(
|
|
196
|
+
"--ref",
|
|
197
|
+
type=str,
|
|
198
|
+
metavar="REF",
|
|
199
|
+
help="Checkout a specific branch, tag, or commit hash (used with git clone, not --local)",
|
|
200
|
+
)
|
|
201
|
+
|
|
202
|
+
|
|
188
203
|
def configure_setup_parser(setup_parser: argparse.ArgumentParser) -> None:
|
|
189
204
|
"""Configure the setup command parser and its subcommands."""
|
|
190
205
|
setup_subparsers = setup_parser.add_subparsers(dest="tool", help="AI tool to set up")
|
|
191
|
-
|
|
192
|
-
setup_subparsers.add_parser("
|
|
206
|
+
|
|
207
|
+
claude_parser = setup_subparsers.add_parser("claude-code", help="Set up Weco skill for Claude Code")
|
|
208
|
+
_add_setup_source_args(claude_parser)
|
|
209
|
+
|
|
210
|
+
cursor_parser = setup_subparsers.add_parser("cursor", help="Set up Weco rules for Cursor")
|
|
211
|
+
_add_setup_source_args(cursor_parser)
|
|
193
212
|
|
|
194
213
|
|
|
195
214
|
def configure_resume_parser(resume_parser: argparse.ArgumentParser) -> None:
|
weco-0.3.11/weco/git.py
ADDED
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
# weco/git.py
|
|
2
|
+
"""
|
|
3
|
+
Git utilities for command execution and validation.
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
import pathlib
|
|
7
|
+
import shutil
|
|
8
|
+
import subprocess
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class GitError(Exception):
|
|
12
|
+
"""Raised when a git command fails."""
|
|
13
|
+
|
|
14
|
+
def __init__(self, message: str, stderr: str = ""):
|
|
15
|
+
super().__init__(message)
|
|
16
|
+
self.stderr = stderr
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class GitNotFoundError(Exception):
|
|
20
|
+
"""Raised when git is not available on the system."""
|
|
21
|
+
|
|
22
|
+
pass
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def is_available() -> bool:
|
|
26
|
+
"""Check if git is available on the system."""
|
|
27
|
+
return shutil.which("git") is not None
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def is_repo(path: pathlib.Path) -> bool:
|
|
31
|
+
"""Check if a directory is a git repository."""
|
|
32
|
+
return (path / ".git").is_dir()
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def validate_ref(ref: str) -> None:
|
|
36
|
+
"""
|
|
37
|
+
Validate a git ref to prevent option injection.
|
|
38
|
+
|
|
39
|
+
Raises:
|
|
40
|
+
ValueError: If ref could be interpreted as a git option.
|
|
41
|
+
"""
|
|
42
|
+
if ref.startswith("-"):
|
|
43
|
+
raise ValueError(f"Invalid git ref: {ref!r} (cannot start with '-')")
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
def validate_repo_url(url: str) -> None:
|
|
47
|
+
"""
|
|
48
|
+
Validate a git repository URL.
|
|
49
|
+
|
|
50
|
+
Raises:
|
|
51
|
+
ValueError: If URL doesn't match expected patterns.
|
|
52
|
+
"""
|
|
53
|
+
valid_prefixes = ("git@", "https://", "http://", "ssh://", "file://", "/", "./", "../")
|
|
54
|
+
if not any(url.startswith(prefix) for prefix in valid_prefixes):
|
|
55
|
+
raise ValueError(f"Invalid repository URL: {url!r}")
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
def run(*args: str, cwd: pathlib.Path | None = None, error_msg: str = "Git command failed") -> subprocess.CompletedProcess:
|
|
59
|
+
"""
|
|
60
|
+
Run a git command and return the result.
|
|
61
|
+
|
|
62
|
+
Args:
|
|
63
|
+
*args: Git subcommand and arguments (e.g., "clone", url, path).
|
|
64
|
+
Do NOT include "git" — it's prepended automatically.
|
|
65
|
+
cwd: Working directory for the command.
|
|
66
|
+
error_msg: Message to include in exception on failure.
|
|
67
|
+
|
|
68
|
+
Raises:
|
|
69
|
+
GitError: If the command fails or returns non-zero.
|
|
70
|
+
"""
|
|
71
|
+
cmd = ["git", *args]
|
|
72
|
+
try:
|
|
73
|
+
result = subprocess.run(cmd, cwd=cwd, capture_output=True, text=True)
|
|
74
|
+
except Exception as e:
|
|
75
|
+
raise GitError(f"{error_msg}: {e}") from e
|
|
76
|
+
|
|
77
|
+
if result.returncode != 0:
|
|
78
|
+
raise GitError(error_msg, result.stderr)
|
|
79
|
+
return result
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
def clone(repo_url: str, dest: pathlib.Path, ref: str | None = None) -> None:
|
|
83
|
+
"""
|
|
84
|
+
Clone a git repository.
|
|
85
|
+
|
|
86
|
+
Args:
|
|
87
|
+
repo_url: The repository URL to clone.
|
|
88
|
+
dest: Destination directory.
|
|
89
|
+
ref: Optional branch, tag, or commit to checkout after cloning.
|
|
90
|
+
|
|
91
|
+
Raises:
|
|
92
|
+
GitError: If clone or checkout fails.
|
|
93
|
+
"""
|
|
94
|
+
run("clone", repo_url, str(dest), error_msg="Failed to clone repository")
|
|
95
|
+
if ref:
|
|
96
|
+
run("checkout", ref, cwd=dest, error_msg=f"Failed to checkout '{ref}'")
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
def pull(repo_path: pathlib.Path) -> None:
|
|
100
|
+
"""
|
|
101
|
+
Pull latest changes in a git repository.
|
|
102
|
+
|
|
103
|
+
Raises:
|
|
104
|
+
GitError: If pull fails.
|
|
105
|
+
"""
|
|
106
|
+
run("pull", cwd=repo_path, error_msg="Failed to pull repository")
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
def fetch_and_checkout(repo_path: pathlib.Path, ref: str) -> None:
|
|
110
|
+
"""
|
|
111
|
+
Fetch all remotes and checkout a specific ref.
|
|
112
|
+
|
|
113
|
+
Args:
|
|
114
|
+
repo_path: Path to the git repository.
|
|
115
|
+
ref: Branch, tag, or commit to checkout.
|
|
116
|
+
|
|
117
|
+
Raises:
|
|
118
|
+
GitError: If fetch or checkout fails.
|
|
119
|
+
"""
|
|
120
|
+
run("fetch", "--all", cwd=repo_path, error_msg="Failed to fetch from repository")
|
|
121
|
+
run("checkout", ref, cwd=repo_path, error_msg=f"Failed to checkout '{ref}'")
|
|
@@ -0,0 +1,295 @@
|
|
|
1
|
+
# weco/setup.py
|
|
2
|
+
"""
|
|
3
|
+
Setup commands for integrating Weco with various AI tools.
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
import pathlib
|
|
7
|
+
import sys
|
|
8
|
+
|
|
9
|
+
from rich.console import Console
|
|
10
|
+
from rich.prompt import Confirm
|
|
11
|
+
|
|
12
|
+
from . import git
|
|
13
|
+
from .utils import copy_directory, copy_file, read_from_path, remove_directory, write_to_path
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
# =============================================================================
|
|
17
|
+
# Exceptions
|
|
18
|
+
# =============================================================================
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
class SetupError(Exception):
|
|
22
|
+
"""Base exception for setup failures."""
|
|
23
|
+
|
|
24
|
+
pass
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
class InvalidLocalRepoError(SetupError):
|
|
28
|
+
"""Raised when a local path is not a valid skill repository."""
|
|
29
|
+
|
|
30
|
+
pass
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
# =============================================================================
|
|
34
|
+
# Path constants
|
|
35
|
+
# =============================================================================
|
|
36
|
+
|
|
37
|
+
# Claude Code paths
|
|
38
|
+
CLAUDE_DIR = pathlib.Path.home() / ".claude"
|
|
39
|
+
CLAUDE_SKILLS_DIR = CLAUDE_DIR / "skills"
|
|
40
|
+
WECO_SKILL_DIR = CLAUDE_SKILLS_DIR / "weco"
|
|
41
|
+
WECO_SKILL_REPO = "git@github.com:WecoAI/weco-skill.git"
|
|
42
|
+
WECO_CLAUDE_SNIPPET_PATH = WECO_SKILL_DIR / "snippets" / "claude.md"
|
|
43
|
+
WECO_CLAUDE_MD_PATH = WECO_SKILL_DIR / "CLAUDE.md"
|
|
44
|
+
|
|
45
|
+
# Cursor paths
|
|
46
|
+
CURSOR_DIR = pathlib.Path.home() / ".cursor"
|
|
47
|
+
CURSOR_RULES_DIR = CURSOR_DIR / "rules"
|
|
48
|
+
CURSOR_WECO_RULES_PATH = CURSOR_RULES_DIR / "weco.mdc"
|
|
49
|
+
CURSOR_SKILLS_DIR = CURSOR_DIR / "skills"
|
|
50
|
+
CURSOR_WECO_SKILL_DIR = CURSOR_SKILLS_DIR / "weco"
|
|
51
|
+
CURSOR_RULES_SNIPPET_PATH = CURSOR_WECO_SKILL_DIR / "snippets" / "cursor.md"
|
|
52
|
+
|
|
53
|
+
# Files/directories to skip when copying local repos
|
|
54
|
+
_COPY_IGNORE_PATTERNS = {".git", "__pycache__", ".DS_Store"}
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
# =============================================================================
|
|
58
|
+
# Domain helpers
|
|
59
|
+
# =============================================================================
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
def generate_cursor_mdc_content(snippet_content: str) -> str:
|
|
63
|
+
"""Generate Cursor MDC file content with YAML frontmatter."""
|
|
64
|
+
return f"""---
|
|
65
|
+
description: Weco code optimization skill. Weco automates optimization by iteratively refining code against any metric you define — invoke for speed, accuracy, latency, cost, or anything else you can measure.
|
|
66
|
+
alwaysApply: true
|
|
67
|
+
---
|
|
68
|
+
{snippet_content}
|
|
69
|
+
"""
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
def validate_local_skill_repo(local_path: pathlib.Path) -> None:
|
|
73
|
+
"""
|
|
74
|
+
Validate that a local path is a valid weco-skill repository.
|
|
75
|
+
|
|
76
|
+
Raises:
|
|
77
|
+
InvalidLocalRepoError: If validation fails.
|
|
78
|
+
"""
|
|
79
|
+
if not local_path.exists():
|
|
80
|
+
raise InvalidLocalRepoError(f"Local path does not exist: {local_path}")
|
|
81
|
+
if not local_path.is_dir():
|
|
82
|
+
raise InvalidLocalRepoError(f"Local path is not a directory: {local_path}")
|
|
83
|
+
if not (local_path / "SKILL.md").exists():
|
|
84
|
+
raise InvalidLocalRepoError(
|
|
85
|
+
f"Local path does not appear to be a weco-skill repository (expected SKILL.md at {local_path / 'SKILL.md'})"
|
|
86
|
+
)
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
# =============================================================================
|
|
90
|
+
# Installation functions
|
|
91
|
+
# =============================================================================
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
def install_skill_from_git(skill_dir: pathlib.Path, console: Console, repo_url: str | None, ref: str | None) -> None:
|
|
95
|
+
"""
|
|
96
|
+
Clone or update skill from git.
|
|
97
|
+
|
|
98
|
+
Raises:
|
|
99
|
+
git.GitNotFoundError: If git is not available.
|
|
100
|
+
git.GitError: If git operations fail.
|
|
101
|
+
"""
|
|
102
|
+
if not git.is_available():
|
|
103
|
+
raise git.GitNotFoundError("git is not installed or not in PATH")
|
|
104
|
+
|
|
105
|
+
skill_dir.parent.mkdir(parents=True, exist_ok=True)
|
|
106
|
+
|
|
107
|
+
if skill_dir.exists():
|
|
108
|
+
if git.is_repo(skill_dir):
|
|
109
|
+
console.print(f"[cyan]Updating existing skill at {skill_dir}...[/]")
|
|
110
|
+
if ref:
|
|
111
|
+
git.fetch_and_checkout(skill_dir, ref)
|
|
112
|
+
console.print(f"[green]Checked out {ref}.[/]")
|
|
113
|
+
else:
|
|
114
|
+
git.pull(skill_dir)
|
|
115
|
+
console.print("[green]Skill updated successfully.[/]")
|
|
116
|
+
return
|
|
117
|
+
|
|
118
|
+
# Not a git repo — clear and re-clone
|
|
119
|
+
console.print(f"[cyan]Removing existing directory at {skill_dir}...[/]")
|
|
120
|
+
remove_directory(skill_dir)
|
|
121
|
+
|
|
122
|
+
console.print(f"[cyan]Cloning Weco skill to {skill_dir}...[/]")
|
|
123
|
+
git.clone(repo_url or WECO_SKILL_REPO, skill_dir, ref=ref)
|
|
124
|
+
if ref:
|
|
125
|
+
console.print(f"[green]Cloned and checked out {ref}.[/]")
|
|
126
|
+
else:
|
|
127
|
+
console.print("[green]Skill cloned successfully.[/]")
|
|
128
|
+
|
|
129
|
+
|
|
130
|
+
def install_skill_from_local(skill_dir: pathlib.Path, console: Console, local_path: pathlib.Path) -> None:
|
|
131
|
+
"""
|
|
132
|
+
Copy skill from local path.
|
|
133
|
+
|
|
134
|
+
Raises:
|
|
135
|
+
InvalidLocalRepoError: If local path is invalid.
|
|
136
|
+
OSError: If copy fails.
|
|
137
|
+
"""
|
|
138
|
+
validate_local_skill_repo(local_path)
|
|
139
|
+
|
|
140
|
+
skill_dir.parent.mkdir(parents=True, exist_ok=True)
|
|
141
|
+
|
|
142
|
+
if skill_dir.exists():
|
|
143
|
+
console.print(f"[cyan]Removing existing directory at {skill_dir}...[/]")
|
|
144
|
+
remove_directory(skill_dir)
|
|
145
|
+
|
|
146
|
+
copy_directory(local_path, skill_dir, ignore_patterns=_COPY_IGNORE_PATTERNS)
|
|
147
|
+
console.print(f"[green]Copied local repo from: {local_path}[/]")
|
|
148
|
+
|
|
149
|
+
|
|
150
|
+
def install_skill(
|
|
151
|
+
skill_dir: pathlib.Path, console: Console, local_path: pathlib.Path | None, repo_url: str | None, ref: str | None
|
|
152
|
+
) -> None:
|
|
153
|
+
"""Install skill by copying from local path or cloning from git."""
|
|
154
|
+
if local_path:
|
|
155
|
+
if ref:
|
|
156
|
+
console.print("[bold yellow]Warning:[/] --ref is ignored when using --local")
|
|
157
|
+
install_skill_from_local(skill_dir, console, local_path)
|
|
158
|
+
else:
|
|
159
|
+
install_skill_from_git(skill_dir, console, repo_url, ref)
|
|
160
|
+
|
|
161
|
+
|
|
162
|
+
# =============================================================================
|
|
163
|
+
# Setup commands
|
|
164
|
+
# =============================================================================
|
|
165
|
+
|
|
166
|
+
|
|
167
|
+
def setup_claude_code(
|
|
168
|
+
console: Console, local_path: pathlib.Path | None = None, repo_url: str | None = None, ref: str | None = None
|
|
169
|
+
) -> None:
|
|
170
|
+
"""Set up Weco skill for Claude Code."""
|
|
171
|
+
console.print("[bold blue]Setting up Weco for Claude Code...[/]\n")
|
|
172
|
+
|
|
173
|
+
# Claude Code setup is intentionally "skill-centric":
|
|
174
|
+
# - Install the skill into Claude's skills directory.
|
|
175
|
+
# - `CLAUDE.md` lives *inside* that installed skill folder, so configuration is just
|
|
176
|
+
# copying a file within the skill directory.
|
|
177
|
+
# - There is no separate global config file to reconcile or prompt before overwriting.
|
|
178
|
+
install_skill(WECO_SKILL_DIR, console, local_path, repo_url, ref)
|
|
179
|
+
|
|
180
|
+
# Copy snippets/claude.md to CLAUDE.md (skip for local - user manages their own)
|
|
181
|
+
if not local_path:
|
|
182
|
+
copy_file(WECO_CLAUDE_SNIPPET_PATH, WECO_CLAUDE_MD_PATH)
|
|
183
|
+
console.print("[green]CLAUDE.md installed to skill directory.[/]")
|
|
184
|
+
|
|
185
|
+
console.print("\n[bold green]Setup complete![/]")
|
|
186
|
+
if local_path:
|
|
187
|
+
console.print(f"[dim]Skill copied from: {local_path}[/]")
|
|
188
|
+
console.print(f"[dim]Skill installed at: {WECO_SKILL_DIR}[/]")
|
|
189
|
+
|
|
190
|
+
|
|
191
|
+
def setup_cursor(
|
|
192
|
+
console: Console, local_path: pathlib.Path | None = None, repo_url: str | None = None, ref: str | None = None
|
|
193
|
+
) -> None:
|
|
194
|
+
"""Set up Weco rules for Cursor."""
|
|
195
|
+
console.print("[bold blue]Setting up Weco for Cursor...[/]\n")
|
|
196
|
+
|
|
197
|
+
# Cursor setup is intentionally "editor-config-centric":
|
|
198
|
+
# - Install/copy the skill into Cursor's skills directory (so we can read snippets).
|
|
199
|
+
# - The behavior change is controlled by `~/.cursor/rules/weco.mdc`, which is *global*
|
|
200
|
+
# editor state (not part of the installed skill folder).
|
|
201
|
+
# - Because users may have customized that file, we:
|
|
202
|
+
# 1) compute desired content from the snippet
|
|
203
|
+
# 2) check if it is already up to date
|
|
204
|
+
# 3) prompt before creating/updating it
|
|
205
|
+
install_skill(CURSOR_WECO_SKILL_DIR, console, local_path, repo_url, ref)
|
|
206
|
+
|
|
207
|
+
snippet_content = read_from_path(CURSOR_RULES_SNIPPET_PATH)
|
|
208
|
+
mdc_content = generate_cursor_mdc_content(snippet_content.strip())
|
|
209
|
+
|
|
210
|
+
# Check if already up to date
|
|
211
|
+
existing_content = None
|
|
212
|
+
if CURSOR_WECO_RULES_PATH.exists():
|
|
213
|
+
try:
|
|
214
|
+
existing_content = read_from_path(CURSOR_WECO_RULES_PATH)
|
|
215
|
+
except Exception:
|
|
216
|
+
pass
|
|
217
|
+
|
|
218
|
+
if existing_content is not None and existing_content.strip() == mdc_content.strip():
|
|
219
|
+
console.print("[dim]weco.mdc already contains the latest Weco rules.[/]")
|
|
220
|
+
console.print("\n[bold green]Setup complete![/]")
|
|
221
|
+
console.print(f"[dim]Rules file at: {CURSOR_WECO_RULES_PATH}[/]")
|
|
222
|
+
return
|
|
223
|
+
|
|
224
|
+
# Prompt user for creation/update
|
|
225
|
+
if existing_content is not None:
|
|
226
|
+
console.print("\n[bold yellow]weco.mdc Update[/]")
|
|
227
|
+
console.print("The Weco rules file can be updated to the latest version.")
|
|
228
|
+
if not Confirm.ask("Would you like to update weco.mdc?", default=True):
|
|
229
|
+
console.print("\n[yellow]Skipping weco.mdc update.[/]")
|
|
230
|
+
console.print(f"[dim]Skill installed but rules not configured. Create manually at {CURSOR_WECO_RULES_PATH}[/]")
|
|
231
|
+
return
|
|
232
|
+
else:
|
|
233
|
+
console.print("\n[bold yellow]weco.mdc Creation[/]")
|
|
234
|
+
console.print("To enable Weco optimization rules, we can create a weco.mdc file.")
|
|
235
|
+
if not Confirm.ask("Would you like to create weco.mdc?", default=True):
|
|
236
|
+
console.print("\n[yellow]Skipping weco.mdc creation.[/]")
|
|
237
|
+
console.print(f"[dim]Skill installed but rules not configured. Create manually at {CURSOR_WECO_RULES_PATH}[/]")
|
|
238
|
+
return
|
|
239
|
+
|
|
240
|
+
write_to_path(CURSOR_WECO_RULES_PATH, mdc_content, mkdir=True)
|
|
241
|
+
console.print("[green]weco.mdc created successfully.[/]")
|
|
242
|
+
|
|
243
|
+
console.print("\n[bold green]Setup complete![/]")
|
|
244
|
+
if local_path:
|
|
245
|
+
console.print(f"[dim]Skill copied from: {local_path}[/]")
|
|
246
|
+
console.print(f"[dim]Skill installed at: {CURSOR_WECO_SKILL_DIR}[/]")
|
|
247
|
+
console.print(f"[dim]Rules file at: {CURSOR_WECO_RULES_PATH}[/]")
|
|
248
|
+
|
|
249
|
+
|
|
250
|
+
# =============================================================================
|
|
251
|
+
# CLI entry point
|
|
252
|
+
# =============================================================================
|
|
253
|
+
|
|
254
|
+
SETUP_HANDLERS = {"claude-code": setup_claude_code, "cursor": setup_cursor}
|
|
255
|
+
|
|
256
|
+
|
|
257
|
+
def handle_setup_command(args, console: Console) -> None:
|
|
258
|
+
"""Handle the setup command with its subcommands."""
|
|
259
|
+
available_tools = ", ".join(SETUP_HANDLERS)
|
|
260
|
+
|
|
261
|
+
if args.tool is None:
|
|
262
|
+
console.print("[bold red]Error:[/] Please specify a tool to set up.")
|
|
263
|
+
console.print(f"Available tools: {available_tools}")
|
|
264
|
+
console.print("\nUsage: weco setup <tool>")
|
|
265
|
+
sys.exit(1)
|
|
266
|
+
|
|
267
|
+
handler = SETUP_HANDLERS.get(args.tool)
|
|
268
|
+
if handler is None:
|
|
269
|
+
console.print(f"[bold red]Error:[/] Unknown tool: {args.tool}")
|
|
270
|
+
console.print(f"Available tools: {available_tools}")
|
|
271
|
+
sys.exit(1)
|
|
272
|
+
|
|
273
|
+
# Validate and extract args
|
|
274
|
+
repo_url = getattr(args, "repo", None)
|
|
275
|
+
ref = getattr(args, "ref", None)
|
|
276
|
+
local_path = None
|
|
277
|
+
if hasattr(args, "local") and args.local:
|
|
278
|
+
local_path = pathlib.Path(args.local).expanduser().resolve()
|
|
279
|
+
|
|
280
|
+
try:
|
|
281
|
+
if repo_url:
|
|
282
|
+
git.validate_repo_url(repo_url)
|
|
283
|
+
if ref:
|
|
284
|
+
git.validate_ref(ref)
|
|
285
|
+
|
|
286
|
+
handler(console, local_path=local_path, repo_url=repo_url, ref=ref)
|
|
287
|
+
|
|
288
|
+
except git.GitError as e:
|
|
289
|
+
console.print(f"[bold red]Error:[/] {e}")
|
|
290
|
+
if e.stderr:
|
|
291
|
+
console.print(f"[dim]{e.stderr}[/]")
|
|
292
|
+
sys.exit(1)
|
|
293
|
+
except (SetupError, git.GitNotFoundError, FileNotFoundError, OSError, ValueError) as e:
|
|
294
|
+
console.print(f"[bold red]Error:[/] {e}")
|
|
295
|
+
sys.exit(1)
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
from typing import Any, Dict, List, Tuple, Union
|
|
2
2
|
import json
|
|
3
|
+
import shutil
|
|
3
4
|
import time
|
|
4
5
|
import subprocess
|
|
5
6
|
import psutil
|
|
@@ -63,8 +64,19 @@ def read_from_path(fp: pathlib.Path, is_json: bool = False) -> Union[str, Dict[s
|
|
|
63
64
|
return f.read()
|
|
64
65
|
|
|
65
66
|
|
|
66
|
-
def write_to_path(fp: pathlib.Path, content: Union[str, Dict[str, Any]], is_json: bool = False) -> None:
|
|
67
|
-
"""
|
|
67
|
+
def write_to_path(fp: pathlib.Path, content: Union[str, Dict[str, Any]], is_json: bool = False, mkdir: bool = False) -> None:
|
|
68
|
+
"""
|
|
69
|
+
Write content to a file path, optionally as JSON.
|
|
70
|
+
|
|
71
|
+
Args:
|
|
72
|
+
fp: File path to write to.
|
|
73
|
+
content: Content to write (string or dict for JSON).
|
|
74
|
+
is_json: If True, write as JSON.
|
|
75
|
+
mkdir: If True, create parent directories if they don't exist.
|
|
76
|
+
"""
|
|
77
|
+
if mkdir:
|
|
78
|
+
fp.parent.mkdir(parents=True, exist_ok=True)
|
|
79
|
+
|
|
68
80
|
with fp.open("w", encoding="utf-8") as f:
|
|
69
81
|
if is_json:
|
|
70
82
|
json.dump(content, f, indent=4)
|
|
@@ -74,6 +86,60 @@ def write_to_path(fp: pathlib.Path, content: Union[str, Dict[str, Any]], is_json
|
|
|
74
86
|
raise TypeError("Error writing to file. Please verify the file path and try again.")
|
|
75
87
|
|
|
76
88
|
|
|
89
|
+
def copy_file(src: pathlib.Path, dest: pathlib.Path, mkdir: bool = False) -> None:
|
|
90
|
+
"""
|
|
91
|
+
Copy a single file.
|
|
92
|
+
|
|
93
|
+
Args:
|
|
94
|
+
src: Source file path.
|
|
95
|
+
dest: Destination file path.
|
|
96
|
+
mkdir: If True, create parent directories if they don't exist.
|
|
97
|
+
|
|
98
|
+
Raises:
|
|
99
|
+
FileNotFoundError: If source doesn't exist.
|
|
100
|
+
OSError: If copy fails.
|
|
101
|
+
"""
|
|
102
|
+
if not src.exists():
|
|
103
|
+
raise FileNotFoundError(f"Source file not found: {src}")
|
|
104
|
+
if mkdir:
|
|
105
|
+
dest.parent.mkdir(parents=True, exist_ok=True)
|
|
106
|
+
shutil.copy(src, dest)
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
def copy_directory(src: pathlib.Path, dest: pathlib.Path, ignore_patterns: set[str] | None = None) -> None:
|
|
110
|
+
"""
|
|
111
|
+
Copy a directory tree.
|
|
112
|
+
|
|
113
|
+
Args:
|
|
114
|
+
src: Source directory path.
|
|
115
|
+
dest: Destination directory path.
|
|
116
|
+
ignore_patterns: Optional set of file/directory names to skip.
|
|
117
|
+
|
|
118
|
+
Raises:
|
|
119
|
+
FileNotFoundError: If source doesn't exist.
|
|
120
|
+
OSError: If copy fails.
|
|
121
|
+
"""
|
|
122
|
+
if not src.exists():
|
|
123
|
+
raise FileNotFoundError(f"Source directory not found: {src}")
|
|
124
|
+
|
|
125
|
+
def ignore_func(_: str, files: list[str]) -> list[str]:
|
|
126
|
+
if not ignore_patterns:
|
|
127
|
+
return []
|
|
128
|
+
return [f for f in files if f in ignore_patterns]
|
|
129
|
+
|
|
130
|
+
shutil.copytree(src, dest, ignore=ignore_func)
|
|
131
|
+
|
|
132
|
+
|
|
133
|
+
def remove_directory(path: pathlib.Path) -> None:
|
|
134
|
+
"""
|
|
135
|
+
Remove a directory and all its contents.
|
|
136
|
+
|
|
137
|
+
Does nothing if the directory doesn't exist.
|
|
138
|
+
"""
|
|
139
|
+
if path.exists():
|
|
140
|
+
shutil.rmtree(path)
|
|
141
|
+
|
|
142
|
+
|
|
77
143
|
# Visualization helper functions
|
|
78
144
|
def smooth_update(
|
|
79
145
|
live: Live, layout: Layout, sections_to_update: List[Tuple[str, Panel]], transition_delay: float = 0.05
|
weco-0.3.10/weco/setup.py
DELETED
|
@@ -1,367 +0,0 @@
|
|
|
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)
|
|
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
|
|
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
|