diff --git a/README.md b/README.md index 27eb6e5e..ec94c1ad 100644 --- a/README.md +++ b/README.md @@ -30,7 +30,7 @@ A game built entirely by Claude Code using these agents, skills, and pipelines. ## Installation -Requires [Claude Code](https://docs.anthropic.com/en/docs/claude-code) installed and working (`claude --version` should print a version number). Codex CLI is also supported: the installer mirrors toolkit skills into `~/.codex/skills` so Codex can use the same skill library (`codex --version` should print a version number if you want Codex support too). +Requires [Claude Code](https://docs.anthropic.com/en/docs/claude-code) installed and working (`claude --version` should print a version number). Codex CLI is also supported: the installer mirrors toolkit skills into `~/.codex/skills` and toolkit agents into `~/.codex/agents` so Codex can use the same skill and agent library (`codex --version` should print a version number if you want Codex support too). ```bash git clone https://github.com/notque/claude-code-toolkit.git ~/claude-code-toolkit @@ -38,7 +38,7 @@ cd ~/claude-code-toolkit ./install.sh --symlink ``` -The installer links agents, skills, hooks, commands, and scripts into `~/.claude/`, where Claude Code loads extensions from. It also mirrors skills into `~/.codex/skills` for Codex. Use `--symlink` to get updates via `git pull`, or run without it for a stable copy. +The installer links agents, skills, hooks, commands, and scripts into `~/.claude/`, where Claude Code loads extensions from. It also mirrors skills into `~/.codex/skills` and agents into `~/.codex/agents` for Codex. Use `--symlink` to get updates via `git pull`, or run without it for a stable copy. Verify the install with: diff --git a/docs/QUICKSTART.md b/docs/QUICKSTART.md index 0156aa2d..bb85bf03 100644 --- a/docs/QUICKSTART.md +++ b/docs/QUICKSTART.md @@ -13,7 +13,7 @@ cd ~/claude-code-toolkit ./install.sh ``` -Claude Code is the primary runtime. If you also use Codex CLI, the same install mirrors toolkit skills into `~/.codex/skills`. +Claude Code is the primary runtime. If you also use Codex CLI, the same install mirrors toolkit skills into `~/.codex/skills` and toolkit agents into `~/.codex/agents`. Command entry points: - Claude Code: `/do` @@ -35,7 +35,7 @@ python3 ~/.claude/scripts/install-doctor.py check python3 ~/.claude/scripts/install-doctor.py inventory ``` -If Codex should pick up newly added skills after a `git pull`, rerun `./install.sh --symlink`. +If Codex should pick up newly added skills or agents after a `git pull`, rerun `./install.sh --symlink`. --- diff --git a/docs/start-here.md b/docs/start-here.md index 26a8b7c3..2761f19f 100644 --- a/docs/start-here.md +++ b/docs/start-here.md @@ -12,7 +12,7 @@ claude --version If that prints a version number, you're good. If not, install Claude Code first and come back. -Optional: if you also use Codex CLI, run `codex --version`. The toolkit mirrors its skills into `~/.codex/skills`, but Claude Code is still the full runtime for hooks, agents, commands, and scripts. +Optional: if you also use Codex CLI, run `codex --version`. The toolkit mirrors its skills into `~/.codex/skills` and its agents into `~/.codex/agents`, so Codex sessions can Read the same domain expertise Claude Code dispatches. Claude Code is still the full runtime for hooks, commands, and scripts. Command entry points: - Claude Code: `/do` @@ -36,7 +36,7 @@ cd claude-code-toolkit The installer asks one question -- symlink or copy -- then sets everything up. Pick symlink if you want updates via `git pull`, copy if you want a stable snapshot. Either works fine. -What just happened: the installer linked agents, skills, hooks, commands, and scripts into `~/.claude/`, which is where Claude Code looks for extensions. It also mirrored skills into `~/.codex/skills` for Codex and configured hooks in your settings so they activate automatically. +What just happened: the installer linked agents, skills, hooks, commands, and scripts into `~/.claude/`, which is where Claude Code looks for extensions. It also mirrored skills into `~/.codex/skills` and agents into `~/.codex/agents` for Codex, and configured hooks in your settings so they activate automatically. ## Verify It @@ -47,7 +47,7 @@ python3 ~/.claude/scripts/install-doctor.py check python3 ~/.claude/scripts/install-doctor.py inventory ``` -`check` verifies the install layout, settings, hook paths, learning DB access, and Codex skill mirror. `inventory` shows what Claude and Codex can currently see. If you pull new toolkit changes later and want Codex to pick up new skills, rerun `./install.sh`. +`check` verifies the install layout, settings, hook paths, learning DB access, and both Codex skill and agent mirrors. `inventory` shows what Claude and Codex can currently see. If you pull new toolkit changes later and want Codex to pick up new skills or agents, rerun `./install.sh`. ## Your First Commands diff --git a/hooks/sync-to-user-claude.py b/hooks/sync-to-user-claude.py old mode 100644 new mode 100755 index 5c85093e..2ad7c6af --- a/hooks/sync-to-user-claude.py +++ b/hooks/sync-to-user-claude.py @@ -502,8 +502,10 @@ def main(): if voice_count > 0: synced.append(f"private-voices({voice_count})") - # Sync skills to ~/.codex/skills/ for OpenAI Codex CLI. - # Codex only supports skills (no agents, hooks, or scripts). + # Sync skills and agents to ~/.codex/ for OpenAI Codex CLI. + # Codex natively supports skills; agents are mirrored as reference + # material so Codex sessions can Read the same domain expertise that + # Claude Code sessions dispatch via subagent_type. codex_skills_dst = Path.home() / ".codex" / "skills" codex_sources = [("skills", repo_root / "skills")] codex_count = 0 @@ -555,6 +557,39 @@ def main(): total = sum(1 for _ in codex_skills_dst.rglob("*") if _.is_file()) synced.append(f".codex/skills({total} current)") + # Sync agents to ~/.codex/agents/ — parallel mirror to skills. + # Agents carry domain expertise (Go, Python, K8s, TypeScript, etc.) + # and their reference subdirectories. Codex can Read them even though + # it has no native subagent_type dispatch. + codex_agents_dst = Path.home() / ".codex" / "agents" + codex_agent_sources = [("agents", repo_root / "agents")] + private_agents_dir = repo_root / "private-agents" + if private_agents_dir.is_dir(): + codex_agent_sources.append(("private-agents", private_agents_dir)) + codex_agent_count = 0 + for label, src in codex_agent_sources: + if not src.is_dir(): + continue + try: + codex_agents_dst.mkdir(parents=True, exist_ok=True) + for item in src.rglob("*"): + if item.is_file(): + rel = item.relative_to(src) + target = codex_agents_dst / rel + target.parent.mkdir(parents=True, exist_ok=True) + if target.exists() and filecmp.cmp(item, target, shallow=False): + continue + shutil.copy2(item, target) + codex_agent_count += 1 + except Exception as e: + errors.append(f"codex-{label}: {e}") + # No stale cleanup for Codex agents — additive only, same rationale as skills. + if codex_agent_count > 0: + synced.append(f".codex/agents({codex_agent_count} updated)") + elif codex_agents_dst.is_dir(): + total = sum(1 for _ in codex_agents_dst.rglob("*") if _.is_file()) + synced.append(f".codex/agents({total} current)") + # Output for hook feedback if synced: print(f"[sync] Updated ~/.claude: {', '.join(synced)}") diff --git a/install.sh b/install.sh index 62facfb4..7ef0d161 100755 --- a/install.sh +++ b/install.sh @@ -33,6 +33,7 @@ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" CLAUDE_DIR="${HOME}/.claude" CODEX_DIR="${HOME}/.codex" CODEX_SKILLS_DIR="${CODEX_DIR}/skills" +CODEX_AGENTS_DIR="${CODEX_DIR}/agents" echo -e "${BLUE}╔════════════════════════════════════════════════════════════════╗${NC}" echo -e "${BLUE}║ Claude Code Toolkit - Installation Script ║${NC}" @@ -282,6 +283,42 @@ os.rename(tmp, dst) echo " No ~/.codex/skills mirror found. Nothing to clean." fi + echo "" + echo -e "${YELLOW}Cleaning Codex agents mirror...${NC}" + if [ -d "$CODEX_AGENTS_DIR" ]; then + for item in "${SCRIPT_DIR}/agents/"*; do + [ -e "$item" ] || continue + target="${CODEX_AGENTS_DIR}/$(basename "$item")" + if [ -L "$target" ] || [ -e "$target" ]; then + if [ "$DRY_RUN" = true ]; then + echo -e "${BLUE} Would remove Codex entry: ${target}${NC}" + else + rm -rf "$target" + echo -e "${GREEN} ✓ Removed Codex entry: ${target}${NC}" + fi + REMOVED+=("Codex agent $(basename "$item")") + fi + done + + if [ -d "${SCRIPT_DIR}/private-agents" ]; then + for item in "${SCRIPT_DIR}/private-agents/"*; do + [ -e "$item" ] || continue + target="${CODEX_AGENTS_DIR}/$(basename "$item")" + if [ -L "$target" ] || [ -e "$target" ]; then + if [ "$DRY_RUN" = true ]; then + echo -e "${BLUE} Would remove Codex entry: ${target}${NC}" + else + rm -rf "$target" + echo -e "${GREEN} ✓ Removed Codex entry: ${target}${NC}" + fi + REMOVED+=("Codex agent $(basename "$item")") + fi + done + fi + else + echo " No ~/.codex/agents mirror found. Nothing to clean." + fi + # Phase 4: Remove install manifest echo "" echo -e "${YELLOW}Cleaning up manifest...${NC}" @@ -408,6 +445,15 @@ else fi echo -e "${GREEN}✓ ${CODEX_SKILLS_DIR} ready${NC}" +echo "" +echo -e "${YELLOW}Setting up ~/.codex agents directory...${NC}" +if [ "$DRY_RUN" = true ]; then + echo -e "${BLUE} Would create: ${CODEX_AGENTS_DIR}${NC}" +else + mkdir -p "${CODEX_AGENTS_DIR}" +fi +echo -e "${GREEN}✓ ${CODEX_AGENTS_DIR} ready${NC}" + # Install components echo "" echo -e "${YELLOW}Installing components (mode: ${MODE})...${NC}" @@ -572,6 +618,25 @@ if [ -d "${SCRIPT_DIR}/private-skills" ]; then done fi +echo "" +echo -e "${YELLOW}Syncing Codex agents mirror...${NC}" +CODEX_AGENT_COUNT=0 +for item in "${SCRIPT_DIR}/agents/"*; do + [ -e "$item" ] || continue + target="${CODEX_AGENTS_DIR}/$(basename "$item")" + sync_codex_entry "$item" "$target" + CODEX_AGENT_COUNT=$((CODEX_AGENT_COUNT + 1)) +done + +if [ -d "${SCRIPT_DIR}/private-agents" ]; then + for item in "${SCRIPT_DIR}/private-agents/"*; do + [ -e "$item" ] || continue + target="${CODEX_AGENTS_DIR}/$(basename "$item")" + sync_codex_entry "$item" "$target" + CODEX_AGENT_COUNT=$((CODEX_AGENT_COUNT + 1)) + done +fi + # Set up local overlay echo "" echo -e "${YELLOW}Setting up local overlay...${NC}" @@ -751,6 +816,7 @@ echo "Installed components:" echo " • Agents: ${AGENT_COUNT} specialized domain experts" echo " • Skills: ${SKILL_COUNT} workflow methodologies (${INVOCABLE_COUNT} user-invocable)" echo " • Codex skills: ${CODEX_ENTRY_COUNT} mirrored entries in ~/.codex/skills" +echo " • Codex agents: ${CODEX_AGENT_COUNT} mirrored entries in ~/.codex/agents" echo " • Hooks: ${HOOK_COUNT} automation hooks" echo " • Commands: ${COMMAND_COUNT} slash commands" echo " • Scripts: ${SCRIPT_COUNT} utility scripts" diff --git a/scripts/install-doctor.py b/scripts/install-doctor.py index 6e0b8e28..d21db450 100755 --- a/scripts/install-doctor.py +++ b/scripts/install-doctor.py @@ -203,6 +203,55 @@ def check_codex_skills() -> dict: } +def check_codex_agents() -> dict: + """Check that toolkit agents are mirrored into ~/.codex/agents.""" + codex_agents_dir = CODEX_DIR / "agents" + repo_root = get_toolkit_repo_root() + + if repo_root is None: + return { + "name": "codex_agents", + "label": "~/.codex/agents mirror", + "passed": codex_agents_dir.is_dir(), + "detail": str(codex_agents_dir) + if codex_agents_dir.is_dir() + else "Codex agents mirror not found. Run install.sh from the toolkit repo.", + } + + expected_entries = [item.name for item in sorted((repo_root / "agents").iterdir())] + + private_agents_dir = repo_root / "private-agents" + if private_agents_dir.is_dir(): + for agent_item in sorted(private_agents_dir.iterdir()): + expected_entries.append(agent_item.name) + + expected_entries = list(dict.fromkeys(expected_entries)) + + if not codex_agents_dir.is_dir(): + return { + "name": "codex_agents", + "label": "~/.codex/agents mirror", + "passed": False, + "detail": "Directory not found. Run install.sh to mirror toolkit agents for Codex.", + } + + missing = [entry for entry in expected_entries if not (codex_agents_dir / entry).exists()] + if missing: + return { + "name": "codex_agents", + "label": "~/.codex/agents mirror", + "passed": False, + "detail": f"{len(expected_entries) - len(missing)}/{len(expected_entries)} entries present; missing: {', '.join(missing[:5])}", + } + + return { + "name": "codex_agents", + "label": "~/.codex/agents mirror", + "passed": True, + "detail": f"All {len(expected_entries)} toolkit entries mirrored", + } + + def check_hook_files() -> list[dict]: """Check that hooks referenced in settings.json actually exist.""" settings_file = CLAUDE_DIR / "settings.json" @@ -567,6 +616,12 @@ def inventory() -> dict: else: counts["codex_skills"] = 0 + codex_agents_dir = CODEX_DIR / "agents" + if codex_agents_dir.is_dir(): + counts["codex_agents"] = len([f for f in codex_agents_dir.glob("*.md") if f.name != "README.md"]) + else: + counts["codex_agents"] = 0 + # Count MCP servers from registry mcp_results = check_mcp_servers() mcp_total = sum(1 for r in mcp_results if r["name"].startswith("mcp_") and r["name"] != "mcp_registry") @@ -585,6 +640,7 @@ def run_all_checks() -> list[dict]: results.append(check_claude_dir()) results.extend(check_components_installed()) results.append(check_codex_skills()) + results.append(check_codex_agents()) results.append(check_settings_json()) results.extend(check_hook_files()) results.append(check_python_version()) @@ -638,7 +694,10 @@ def main(): print("\n Installed Components:\n") print(f" Agents: {counts.get('agents', 0)}") print(f" Skills: {counts.get('skills', 0)} ({counts.get('skills_invocable', 0)} user-invocable)") - print(f" Codex: {counts.get('codex_skills', 0)} skills available") + print( + f" Codex: {counts.get('codex_skills', 0)} skills available, " + f"{counts.get('codex_agents', 0)} agents available" + ) print(f" Hooks: {counts.get('hooks', 0)}") print(f" Commands: {counts.get('commands', 0)}") print(f" Scripts: {counts.get('scripts', 0)}")