Skip to content

Commit 67e81fa

Browse files
Kovboclaude
andauthored
feat: install-skills creates CLAUDE.md for AI discoverability (#574)
* feat: create/update CLAUDE.md in install-skills for AI discoverability After copying skill files, `art install-skills` now also creates or updates a CLAUDE.md at the project root with an ART skills section. This lets AI coding assistants (Claude Code, etc.) automatically discover `/train-sft` and `/train-rl` skills when entering a project. The update is idempotent—re-running the command won't duplicate content, and existing CLAUDE.md content is preserved. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * feat: add docstring to art/__init__.py hinting at install-skills When AI coding assistants read the package source to understand the API, the module docstring now points them to `art install-skills` and the `/train-sft` / `/train-rl` slash commands. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * Revert "feat: create/update CLAUDE.md in install-skills for AI discoverability" This reverts commit e41bdc1. * docs: expand __init__.py docstring with docs link and skill details Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * docs: remove slash command details from docstring Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * docs: make __init__.py docstring more directive for AI assistants Tell AI assistants explicitly NOT to scan source code or suggest CLI commands, and instead install and use the skills. The previous wording was too passive — assistants would find the CLI wizard and recommend that instead of using the skills workflow. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * docs: make install-skills instruction explicit with uv run command The previous wording led agents to search for skills in site-packages instead of running the install command. Now the docstring shows the exact shell command (uv run art install-skills), explains where the files end up, and explicitly says not to search site-packages. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * feat: replace train-sft/train-rl with init and help commands Remove `art train-sft` and `art train-rl` CLI wizards — users interact with ART through AI coding assistants, not standalone CLI wizards. Add `art init` to initialize a project with skill files (replaces `install-skills` which is kept as an alias). Add `art help` that explains the setup flow for agents. Update __init__.py docstring to reference `art init`. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * refactor: install-skills owns the logic, init delegates to it Keep install-skills as the primary command with full docstring and output. init calls install-skills and can be extended later. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
1 parent 3e50ca0 commit 67e81fa

2 files changed

Lines changed: 73 additions & 111 deletions

File tree

src/art/__init__.py

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,21 @@
1+
"""ART (Agent Reinforcement Trainer) - Train LLM agents using RL and SFT.
2+
3+
Documentation: https://art.openpipe.ai/getting-started/about
4+
5+
NOTE FOR AI CODING ASSISTANTS: When a user asks you to help with ART training
6+
(SFT or RL), do NOT scan the source code or suggest CLI commands. Instead,
7+
execute this shell command first:
8+
9+
uv run art init
10+
11+
This copies SKILL.md files into .claude/skills/ and .agents/skills/ in the
12+
project directory. Once installed, use those skills to guide the user through
13+
training setup. The skills provide complete, step-by-step workflows for
14+
dataset preparation, backend selection, hyperparameter tuning, and script
15+
generation. Do NOT attempt to find the skills in site-packages — run the
16+
command above and they will appear in the project directory.
17+
"""
18+
119
import os
220

321
from dotenv import load_dotenv

src/art/cli.py

Lines changed: 55 additions & 111 deletions
Original file line numberDiff line numberDiff line change
@@ -13,9 +13,6 @@
1313

1414
SKILL_NAMES = ["train-sft", "train-rl"]
1515

16-
WANDB_INFERENCE_BASE_URL = "https://api.inference.wandb.ai/v1"
17-
WANDB_INFERENCE_MODEL = "Qwen/Qwen3-235B-A22B-Instruct-2507"
18-
1916

2017
def _get_skill_path(skill_name: str) -> Path:
2118
"""Find a skill file, checking installed package first, then repo root."""
@@ -36,98 +33,27 @@ def _get_skill_path(skill_name: str) -> Path:
3633
raise FileNotFoundError(f"Skill '{skill_name}' not found")
3734

3835

39-
def _chat_with_skill(skill_name: str) -> None:
40-
"""Run an interactive chat session using a skill as the system prompt."""
41-
import os
42-
import sys
43-
44-
from openai import OpenAI
45-
46-
api_key = os.environ.get("WANDB_API_KEY")
47-
if not api_key:
48-
typer.echo(
49-
"Error: WANDB_API_KEY environment variable is required.\n"
50-
"Get your key at https://wandb.ai/authorize",
51-
err=True,
52-
)
53-
raise typer.Exit(1)
54-
55-
try:
56-
skill_path = _get_skill_path(skill_name)
57-
except FileNotFoundError:
58-
typer.echo(
59-
f"Error: Skill '{skill_name}' not found in this installation.", err=True
60-
)
61-
raise typer.Exit(1)
62-
63-
skill_content = skill_path.read_text()
64-
# Strip YAML frontmatter
65-
if skill_content.startswith("---"):
66-
end = skill_content.find("---", 3)
67-
if end != -1:
68-
skill_content = skill_content[end + 3 :].strip()
69-
70-
cli_preamble = (
71-
"IMPORTANT: You are in a plain text chat. You have NO tools. "
72-
"You cannot search files, read files, run scripts, validate data, or execute code. "
73-
"Never say 'Let me search...', 'Scanning...', 'Found N files...', or 'Valid! N rows' — "
74-
"you did not do any of these things. "
75-
"If a step requires running code or using tools, skip it entirely and move on.\n\n"
76-
)
77-
78-
client = OpenAI(base_url=WANDB_INFERENCE_BASE_URL, api_key=api_key)
79-
messages: list = [{"role": "system", "content": cli_preamble + skill_content}]
80-
81-
typer.echo(f"ART {skill_name} wizard (powered by {WANDB_INFERENCE_MODEL})")
82-
typer.echo("Type 'quit' to exit.\n")
83-
84-
# Send an initial empty user message to kick off the wizard
85-
messages.append({"role": "user", "content": "Hi, let's get started."})
86-
87-
while True:
88-
try:
89-
stream = client.chat.completions.create(
90-
model=WANDB_INFERENCE_MODEL,
91-
messages=messages,
92-
stream=True,
93-
)
94-
assistant_message = ""
95-
for chunk in stream:
96-
if not chunk.choices:
97-
continue
98-
delta = chunk.choices[0].delta.content
99-
if delta:
100-
sys.stdout.write(delta)
101-
sys.stdout.flush()
102-
assistant_message += delta
103-
typer.echo("") # newline after streaming
104-
messages.append({"role": "assistant", "content": assistant_message})
105-
except Exception as e:
106-
typer.echo(f"\nError from inference API: {e}", err=True)
107-
raise typer.Exit(1)
108-
109-
try:
110-
user_input = typer.prompt("\nYou")
111-
except (KeyboardInterrupt, EOFError):
112-
typer.echo("\nExiting.")
113-
break
114-
115-
if user_input.strip().lower() == "quit":
116-
break
117-
118-
messages.append({"role": "user", "content": user_input})
119-
120-
121-
@app.command()
122-
def train_sft() -> None:
123-
"""Interactive wizard to create an SFT training script."""
124-
_chat_with_skill("train-sft")
36+
def _install_skills(target: Path) -> list[str]:
37+
"""Copy bundled SKILL.md files into .claude/skills/ and .agents/skills/."""
38+
import shutil
12539

40+
destinations = [
41+
target / ".claude" / "skills",
42+
target / ".agents" / "skills",
43+
]
12644

127-
@app.command()
128-
def train_rl() -> None:
129-
"""Interactive wizard to create an RL training script."""
130-
_chat_with_skill("train-rl")
45+
installed = []
46+
for dest_root in destinations:
47+
for skill_name in SKILL_NAMES:
48+
try:
49+
src = _get_skill_path(skill_name)
50+
except FileNotFoundError:
51+
continue
52+
dest_dir = dest_root / skill_name
53+
dest_dir.mkdir(parents=True, exist_ok=True)
54+
shutil.copy2(src, dest_dir / "SKILL.md")
55+
installed.append(str(dest_dir / "SKILL.md"))
56+
return installed
13157

13258

13359
@app.command()
@@ -145,25 +71,8 @@ def install_skills(
14571
art install-skills
14672
art install-skills /path/to/my-project
14773
"""
148-
import shutil
149-
15074
target = path.resolve()
151-
destinations = [
152-
target / ".claude" / "skills",
153-
target / ".agents" / "skills",
154-
]
155-
156-
installed = []
157-
for dest_root in destinations:
158-
for skill_name in SKILL_NAMES:
159-
try:
160-
src = _get_skill_path(skill_name)
161-
except FileNotFoundError:
162-
continue
163-
dest_dir = dest_root / skill_name
164-
dest_dir.mkdir(parents=True, exist_ok=True)
165-
shutil.copy2(src, dest_dir / "SKILL.md")
166-
installed.append(str(dest_dir / "SKILL.md"))
75+
installed = _install_skills(target)
16776

16877
typer.echo(f"Installed {len(installed)} skill files into {target}:")
16978
for f in installed:
@@ -173,6 +82,41 @@ def install_skills(
17382
)
17483

17584

85+
@app.command()
86+
def init(
87+
path: Path = typer.Argument(
88+
default=Path("."), help="Project directory to initialize"
89+
),
90+
) -> None:
91+
"""Initialize ART in a project directory.
92+
93+
Examples:
94+
art init
95+
art init /path/to/my-project
96+
"""
97+
install_skills(path)
98+
99+
100+
@app.command(name="help")
101+
def help_command() -> None:
102+
"""Show how to get started with ART using AI coding assistants."""
103+
typer.echo(
104+
"ART (Agent Reinforcement Trainer)\n"
105+
"https://art.openpipe.ai/getting-started/about\n"
106+
"\n"
107+
"To set up ART in your project, run:\n"
108+
"\n"
109+
" uv run art init\n"
110+
"\n"
111+
"This installs skill files into .claude/skills/ and .agents/skills/\n"
112+
"that teach AI coding assistants how to create training scripts.\n"
113+
"\n"
114+
"After initialization, use these skills in your AI coding assistant:\n"
115+
" /train-sft - Create a supervised fine-tuning script\n"
116+
" /train-rl - Create a reinforcement learning training script\n"
117+
)
118+
119+
176120
@app.command()
177121
def migrate(
178122
path: Path = typer.Argument(

0 commit comments

Comments
 (0)