CrazyBoyM 85f44c358a Complete rewrite: original educational content only
- Remove all reverse-engineered Claude Code source code
- Replace with 100% original educational content from mini-claude-code
- Add clear disclaimer: independent project, not affiliated with Anthropic
- 5 progressive agent implementations (v0-v4, ~1100 lines total)
- Include agent-builder skill for teaching agent construction
- Bilingual documentation (EN + ZH)

This repository now focuses purely on teaching how modern AI agents work
through original, from-scratch implementations.

Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-12-31 07:01:42 +08:00

280 lines
9.7 KiB
Python

#!/usr/bin/env python3
"""
Agent Scaffold Script - Create a new agent project with best practices.
Usage:
python init_agent.py <agent-name> [--level 0-4] [--path <output-dir>]
Examples:
python init_agent.py my-agent # Level 1 (4 tools)
python init_agent.py my-agent --level 0 # Minimal (bash only)
python init_agent.py my-agent --level 2 # With TodoWrite
python init_agent.py my-agent --path ./bots # Custom output directory
"""
import argparse
import sys
from pathlib import Path
# Agent templates for each level
TEMPLATES = {
0: '''#!/usr/bin/env python3
"""
Level 0 Agent - Bash is All You Need (~50 lines)
Core insight: One tool (bash) can do everything.
Subagents via self-recursion: python {name}.py "subtask"
"""
from anthropic import Anthropic
from dotenv import load_dotenv
import subprocess
import os
load_dotenv()
client = Anthropic(
api_key=os.getenv("ANTHROPIC_API_KEY"),
base_url=os.getenv("ANTHROPIC_BASE_URL")
)
MODEL = os.getenv("MODEL_NAME", "claude-sonnet-4-20250514")
SYSTEM = """You are a coding agent. Use bash for everything:
- Read: cat, grep, find, ls
- Write: echo 'content' > file
- Subagent: python {name}.py "subtask"
"""
TOOL = [{{
"name": "bash",
"description": "Execute shell command",
"input_schema": {{"type": "object", "properties": {{"command": {{"type": "string"}}}}, "required": ["command"]}}
}}]
def run(prompt, history=[]):
history.append({{"role": "user", "content": prompt}})
while True:
r = client.messages.create(model=MODEL, system=SYSTEM, messages=history, tools=TOOL, max_tokens=8000)
history.append({{"role": "assistant", "content": r.content}})
if r.stop_reason != "tool_use":
return "".join(b.text for b in r.content if hasattr(b, "text"))
results = []
for b in r.content:
if b.type == "tool_use":
print(f"> {{b.input['command']}}")
try:
out = subprocess.run(b.input["command"], shell=True, capture_output=True, text=True, timeout=60)
output = (out.stdout + out.stderr).strip() or "(empty)"
except Exception as e:
output = f"Error: {{e}}"
results.append({{"type": "tool_result", "tool_use_id": b.id, "content": output[:50000]}})
history.append({{"role": "user", "content": results}})
if __name__ == "__main__":
h = []
print("{name} - Level 0 Agent\\nType 'q' to quit.\\n")
while (q := input(">> ").strip()) not in ("q", "quit", ""):
print(run(q, h), "\\n")
''',
1: '''#!/usr/bin/env python3
"""
Level 1 Agent - Model as Agent (~200 lines)
Core insight: 4 tools cover 90% of coding tasks.
The model IS the agent. Code just runs the loop.
"""
from anthropic import Anthropic
from dotenv import load_dotenv
from pathlib import Path
import subprocess
import os
load_dotenv()
client = Anthropic(
api_key=os.getenv("ANTHROPIC_API_KEY"),
base_url=os.getenv("ANTHROPIC_BASE_URL")
)
MODEL = os.getenv("MODEL_NAME", "claude-sonnet-4-20250514")
WORKDIR = Path.cwd()
SYSTEM = f"""You are a coding agent at {{WORKDIR}}.
Rules:
- Prefer tools over prose. Act, don't just explain.
- Never invent file paths. Use ls/find first if unsure.
- Make minimal changes. Don't over-engineer.
- After finishing, summarize what changed."""
TOOLS = [
{{"name": "bash", "description": "Run shell command",
"input_schema": {{"type": "object", "properties": {{"command": {{"type": "string"}}}}, "required": ["command"]}}}},
{{"name": "read_file", "description": "Read file contents",
"input_schema": {{"type": "object", "properties": {{"path": {{"type": "string"}}}}, "required": ["path"]}}}},
{{"name": "write_file", "description": "Write content to file",
"input_schema": {{"type": "object", "properties": {{"path": {{"type": "string"}}, "content": {{"type": "string"}}}}, "required": ["path", "content"]}}}},
{{"name": "edit_file", "description": "Replace exact text in file",
"input_schema": {{"type": "object", "properties": {{"path": {{"type": "string"}}, "old_text": {{"type": "string"}}, "new_text": {{"type": "string"}}}}, "required": ["path", "old_text", "new_text"]}}}},
]
def safe_path(p: str) -> Path:
"""Prevent path escape attacks."""
path = (WORKDIR / p).resolve()
if not path.is_relative_to(WORKDIR):
raise ValueError(f"Path escapes workspace: {{p}}")
return path
def execute(name: str, args: dict) -> str:
"""Execute a tool and return result."""
if name == "bash":
dangerous = ["rm -rf /", "sudo", "shutdown", "> /dev/"]
if any(d in args["command"] for d in dangerous):
return "Error: Dangerous command blocked"
try:
r = subprocess.run(args["command"], shell=True, cwd=WORKDIR, capture_output=True, text=True, timeout=60)
return (r.stdout + r.stderr).strip()[:50000] or "(empty)"
except subprocess.TimeoutExpired:
return "Error: Timeout (60s)"
except Exception as e:
return f"Error: {{e}}"
if name == "read_file":
try:
return safe_path(args["path"]).read_text()[:50000]
except Exception as e:
return f"Error: {{e}}"
if name == "write_file":
try:
p = safe_path(args["path"])
p.parent.mkdir(parents=True, exist_ok=True)
p.write_text(args["content"])
return f"Wrote {{len(args['content'])}} bytes to {{args['path']}}"
except Exception as e:
return f"Error: {{e}}"
if name == "edit_file":
try:
p = safe_path(args["path"])
content = p.read_text()
if args["old_text"] not in content:
return f"Error: Text not found in {{args['path']}}"
p.write_text(content.replace(args["old_text"], args["new_text"], 1))
return f"Edited {{args['path']}}"
except Exception as e:
return f"Error: {{e}}"
return f"Unknown tool: {{name}}"
def agent(prompt: str, history: list = None) -> str:
"""Run the agent loop."""
if history is None:
history = []
history.append({{"role": "user", "content": prompt}})
while True:
response = client.messages.create(
model=MODEL, system=SYSTEM, messages=history, tools=TOOLS, max_tokens=8000
)
history.append({{"role": "assistant", "content": response.content}})
if response.stop_reason != "tool_use":
return "".join(b.text for b in response.content if hasattr(b, "text"))
results = []
for block in response.content:
if block.type == "tool_use":
print(f"> {{block.name}}: {{str(block.input)[:100]}}")
output = execute(block.name, block.input)
print(f" {{output[:100]}}...")
results.append({{"type": "tool_result", "tool_use_id": block.id, "content": output}})
history.append({{"role": "user", "content": results}})
if __name__ == "__main__":
print(f"{name} - Level 1 Agent at {{WORKDIR}}")
print("Type 'q' to quit.\\n")
h = []
while True:
try:
query = input(">> ").strip()
except (EOFError, KeyboardInterrupt):
break
if query in ("q", "quit", "exit", ""):
break
print(agent(query, h), "\\n")
''',
}
ENV_TEMPLATE = '''# API Configuration
ANTHROPIC_API_KEY=sk-xxx
ANTHROPIC_BASE_URL=https://api.anthropic.com
MODEL_NAME=claude-sonnet-4-20250514
'''
def create_agent(name: str, level: int, output_dir: Path):
"""Create a new agent project."""
# Validate level
if level not in TEMPLATES and level not in (2, 3, 4):
print(f"Error: Level {level} not yet implemented in scaffold.")
print("Available levels: 0 (minimal), 1 (4 tools)")
print("For levels 2-4, copy from mini-claude-code repository.")
sys.exit(1)
# Create output directory
agent_dir = output_dir / name
agent_dir.mkdir(parents=True, exist_ok=True)
# Write agent file
agent_file = agent_dir / f"{name}.py"
template = TEMPLATES.get(level, TEMPLATES[1])
agent_file.write_text(template.format(name=name))
print(f"Created: {agent_file}")
# Write .env.example
env_file = agent_dir / ".env.example"
env_file.write_text(ENV_TEMPLATE)
print(f"Created: {env_file}")
# Write .gitignore
gitignore = agent_dir / ".gitignore"
gitignore.write_text(".env\n__pycache__/\n*.pyc\n")
print(f"Created: {gitignore}")
print(f"\nAgent '{name}' created at {agent_dir}")
print(f"\nNext steps:")
print(f" 1. cd {agent_dir}")
print(f" 2. cp .env.example .env")
print(f" 3. Edit .env with your API key")
print(f" 4. pip install anthropic python-dotenv")
print(f" 5. python {name}.py")
def main():
parser = argparse.ArgumentParser(
description="Scaffold a new AI coding agent project",
formatter_class=argparse.RawDescriptionHelpFormatter,
epilog="""
Levels:
0 Minimal (~50 lines) - Single bash tool, self-recursion for subagents
1 Basic (~200 lines) - 4 core tools: bash, read, write, edit
2 Todo (~300 lines) - + TodoWrite for structured planning
3 Subagent (~450) - + Task tool for context isolation
4 Skills (~550) - + Skill tool for domain expertise
"""
)
parser.add_argument("name", help="Name of the agent to create")
parser.add_argument("--level", type=int, default=1, choices=[0, 1, 2, 3, 4],
help="Complexity level (default: 1)")
parser.add_argument("--path", type=Path, default=Path.cwd(),
help="Output directory (default: current directory)")
args = parser.parse_args()
create_agent(args.name, args.level, args.path)
if __name__ == "__main__":
main()