Skip to main content

14days-build-claude-code-cli

Build a Claude Code-style AI agent CLI from scratch in Python with tool calling, file editing, bash execution, and MCP integration

インストールへ移動

ソース情報

リポジトリ
reason-machines/claude-code-skills
ソースの最終更新活動
2026年6月6日 00:42
検出された SKILL.md の言語
英語
スター
4
フォーク
1

インストール方法

デフォルトでは、最初にソースを確認する Prompt が選択されています。直接コマンドに切り替えるか、ローカルコピーをダウンロードすることもできます。

ソースファイルを確認

インストールを決める前に、SKILL.md と SkillsMP に表示されている付属ファイルをお読みください。

SKILL.md を表示中

SKILL.md
ソースの指示 · 読み取り専用プレビュー
name
14days-build-claude-code-cli
description
Build a Claude Code-style AI agent CLI from scratch in Python with tool calling, file editing, bash execution, and MCP integration
triggers
["how do I build an AI code agent CLI","teach me to create a tool-calling agent","implement file editing tools for my agent","add bash execution to my AI assistant","create an agent harness with permissions","build a code agent with session memory","implement MCP protocol in my agent","add subagents and skills to my CLI"]
# 14days-build-claude-code-cli > Skill by [ara.so](https://ara.so) — Claude Code Skills collection. A 14-day tutorial project teaching you to build a production-style AI code agent CLI (`agent-code`) in Python. Learn the harness architecture: tool calling, file operations, bash execution, permissions, session memory, hooks, skills, subagents, worktree isolation, and MCP integration. ## What This Project Does This is an educational implementation of a Claude Code-style agent harness. You'll build `agent-code`, a CLI that: - Accepts one-shot prompts or enters an interactive REPL - Calls real LLMs (default: DeepSeek via Anthropic-compatible endpoint) - Uses tool calling (`tool_use` / `tool_result`) with file operations, web search, and bash - Implements safety: read-before-edit, diff preview, permission system - Manages sessions, project memory, and context compression - Supports slash commands, hooks, skills, subagents, and MCP tools **Core philosophy**: The model handles reasoning; the harness handles context, tools, permissions, execution, state, and feedback loops. ## Installation Each day is a standalone package. To follow the tutorial from Day 1: ```bash # Start your own project mkdir my-agent-code && cd my-agent-code uv init uv add anthropic typer rich aiofiles # Follow docs/day-01-hello-agent.md ``` To run a reference snapshot: ```bash git clone https://github.com/bozhouDev/14days-build-claude-code-cli cd 14days-build-claude-code-cli/packages/day-08-interactive-shell-plan-mode uv sync uv run agent-code "list files in this project" ``` **Web tutorial**: Visit https://buildcc.dev or run locally: ```bash cd agent-code-learn npm install && npm run dev # Open http://localhost:3000 ``` ## Configuration Set environment variables for the LLM provider: ```bash # Default: DeepSeek with Anthropic-compatible API export ANTHROPIC_AUTH_TOKEN="sk-your-deepseek-key" export ANTHROPIC_BASE_URL="https://api.deepseek.com/anthropic" # Or use official Claude export ANTHROPIC_AUTH_TOKEN="sk-ant-..." export ANTHROPIC_BASE_URL="https://api.anthropic.com" ``` Override model in CLI: ```bash uv run agent-code --model deepseek-v4-flash "your task" uv run agent-code --model claude-3-7-sonnet-20250219 "your task" ``` ## 14-Day Learning Path | Day | Topic | Key Harness Concepts | |-----|-------|---------------------| | 1 | Hello Agent | CLI, REPL, MockProvider, minimal Agent Loop | | 2 | Real Model + Tool Calling | AnthropicProvider, `tool_use` / `tool_result` | | 3 | File + Web Tools | cwd boundary, file read/search, web tools | | 4 | Safe Edit | read-before-edit, string replacement, diff preview | | 5 | Bash + Permission | command execution, permission system, background tasks | | 6 | Session + Memory | session JSONL, project memory, memdir | | 7 | Slash + Hooks | slash commands, hooks, cron `/loop` | | 8 | Interactive Shell + Plan Mode | interactive shell, TodoWrite, plan approval | | 9 | Skills | load domain knowledge and workflows on demand | | 10 | Subagents | delegate tasks to specialized subagents | | 11 | Context Compact | long context compression, cost tracking | | 12 | Agent Coordinator | lightweight multi-agent orchestration | | 13 | Worktree + Final Demo | worktree isolation, end-to-end code tasks | | 14 | MCP + ToolSearch | MCP client, tool discovery, ecosystem extension | ## Key Commands ### Basic Usage ```bash # One-shot prompt uv run agent-code "create a hello.py file" # Interactive REPL uv run agent-code # Specify model uv run agent-code --model deepseek-v4-flash "analyze this codebase" # Resume session uv run agent-code --session my-session-id ``` ### Interactive REPL Commands (Day 7+) ``` > /help # List all slash commands > /quit # Exit REPL > /new # Start new session > /save my-session # Save current session > /load my-session # Load previous session > /clear # Clear context > /tools # List available tools > /loop 5m "task" # Run task every 5 minutes ``` ### Plan Mode (Day 8+) ```bash # Enable plan mode for multi-step tasks uv run agent-code --plan-mode "refactor auth module" # Agent creates todo list, you approve each step ``` ## Core Architecture Examples ### Day 1-2: Minimal Agent Loop with Tool Calling ```python # src/agent_code/providers/anthropic_provider.py from anthropic import Anthropic from typing import List, Dict class AnthropicProvider: def __init__(self, model: str = "deepseek-v4-flash"): self.client = Anthropic() self.model = model def chat(self, messages: List[Dict], tools: List[Dict] = None) -> Dict: response = self.client.messages.create( model=self.model, messages=messages, tools=tools or [], max_tokens=8192 ) return response.model_dump() # src/agent_code/core/agent.py class Agent: def __init__(self, provider, tool_registry): self.provider = provider self.tools = tool_registry self.messages = [] def run(self, user_input: str): self.messages.append({"role": "user", "content": user_input}) while True: response = self.provider.chat( self.messages, self.tools.get_tool_schemas() ) if response["stop_reason"] == "end_turn": break # Handle tool calls for block in response["content"]: if block["type"] == "tool_use": result = self.tools.execute( block["name"], block["input"] ) self.messages.append({ "role": "user", "content": [{ "type": "tool_result", "tool_use_id": block["id"], "content": result }] }) ``` ### Day 3: File Tools with Safety ```python # src/agent_code/tools/file_tools.py import os from pathlib import Path class FileTools: def __init__(self, workspace_root: Path): self.workspace = workspace_root def _check_path(self, path: str) -> Path: """Ensure path is within workspace.""" full_path = (self.workspace / path).resolve() if not str(full_path).startswith(str(self.workspace)): raise ValueError(f"Path {path} outside workspace") return full_path def read_file(self, path: str) -> str: """Read file contents safely.""" file_path = self._check_path(path) return file_path.read_text() def list_files(self, pattern: str = "*") -> List[str]: """List files matching pattern.""" return [ str(p.relative_to(self.workspace)) for p in self.workspace.rglob(pattern) if p.is_file() ] ``` ### Day 4: Safe Edit with Diff Preview ```python # src/agent_code/tools/edit_tools.py from difflib import unified_diff class EditTools: def __init__(self, file_tools): self.files = file_tools def edit_file(self, path: str, old_text: str, new_text: str) -> str: """Replace text with read-before-edit validation.""" # 1. Read current content current = self.files.read_file(path) # 2. Validate old_text exists if old_text not in current: raise ValueError(f"old_text not found in {path}") # 3. Generate diff preview updated = current.replace(old_text, new_text, 1) diff = "\n".join(unified_diff( current.splitlines(), updated.splitlines(), fromfile=path, tofile=path, lineterm="" )) # 4. Apply change self.files._check_path(path).write_text(updated) return f"Applied:\n{diff}" ``` ### Day 5: Bash Tool with Permissions ```python # src/agent_code/tools/bash_tools.py import subprocess from typing import Set DANGEROUS_COMMANDS = {"rm", "sudo", "chmod", "mv"} class BashTools: def __init__(self, permission_callback): self.ask_permission = permission_callback self.allowed_commands: Set[str] = set() def execute_bash(self, command: str) -> str: """Execute bash command with permission check.""" first_word = command.split()[0] if first_word in DANGEROUS_COMMANDS: if first_word not in self.allowed_commands: if not self.ask_permission(f"Allow: {command}?"): return "Permission denied" self.allowed_commands.add(first_word) result = subprocess.run( command, shell=True, capture_output=True, text=True, timeout=30 ) return result.stdout + result.stderr ``` ### Day 6: Session Persistence ```python # src/agent_code/core/session.py import json from pathlib import Path from datetime import datetime class Session: def __init__(self, session_dir: Path): self.dir = session_dir self.dir.mkdir(parents=True, exist_ok=True) def save(self, session_id: str, messages: List[Dict]): """Save session to JSONL.""" file = self.dir / f"{session_id}.jsonl" with file.open("w") as f: for msg in messages: f.write(json.dumps(msg) + "\n") def load(self, session_id: str) -> List[Dict]: """Load session from JSONL.""" file = self.dir / f"{session_id}.jsonl" if not file.exists(): return [] messages = [] with file.open() as f: for line in f: messages.append(json.loads(line)) return messages def list_sessions(self) -> List[str]: """List all saved sessions.""" return [ p.stem for p in self.dir.glob("*.jsonl") ] ``` ### Day 7: Slash Commands ```python # src/agent_code/cli/slash_commands.py from typing import Callable, Dict class SlashRouter: def __init__(self): self.commands: Dict[str, Callable] = {} def register(self, name: str, handler: Callable): self.commands[name] = handler def handle(self, user_input: str) -> bool: """Return True if handled as slash command.""" if not user_input.startswith("/"): return False parts = user_input[1:].split(maxsplit=1) cmd = parts[0] args = parts[1] if len(parts) > 1 else "" if cmd in self.commands: self.commands[cmd](args) return True return False # Usage in REPL slash = SlashRouter() slash.register("help", lambda _: print("Available: /help, /quit, /save")) slash.register("save", lambda args: session.save(args, agent.messages)) while True: user_input = input("> ") if slash.handle(user_input): continue agent.run(user_input) ``` ### Day 8: Plan Mode with TodoWrite ```python # src/agent_code/tools/todo_tools.py class TodoTools: def __init__(self): self.todos = [] self.current_index = 0 def write_todos(self, tasks: List[str]) -> str: """Agent writes plan as todo list.""" self.todos = tasks self.current_index = 0 return f"Created plan with {len(tasks)} steps" def get_next_todo(self) -> str: """Get next unapproved task.""" if self.current_index >= len(self.todos): return "No more tasks" return self.todos[self.current_index] def approve_todo(self): """User approves current task.""" self.current_index += 1 # In agent loop (plan mode enabled) def run_with_plan_mode(agent, user_input): # 1. Agent creates plan agent.run(f"Create a todo list for: {user_input}") # 2. Execute each todo with approval while True: todo = todo_tools.get_next_todo() if todo == "No more tasks": break print(f"\nNext task: {todo}") if input("Approve? [y/n]: ").lower() != "y": break todo_tools.approve_todo() agent.run(f"Execute approved task: {todo}") ``` ## Common Patterns ### Tool Registry Pattern ```python class ToolRegistry: def __init__(self):
GitHubで見る
この SKILL.md は非常に大きいため、SkillsMP では最初のセクションだけを表示しています。 GitHubで見る