#!/usr/bin/env python3 """ agentbuilder67/launch_paas.py — 多智能体 AgentBuilder 启动器 ============================================================= 用户输入一句话描述 → 6 个智能体协作 → 输出可部署的 agent YAML 配置 两种运行模式: 1. AgentPaaS API 模式: 启动 PaaS 服务,注册 builder agents,通过 REST API 执行 2. Claude Code CLI 模式: 利用 Claude CLI 作为 LLM 后端,本地执行多阶段流水线 用法: # 模式 1: PaaS API python launch_paas.py serve # 启动服务 python launch_paas.py register # 注册所有 builder agents python launch_paas.py build "做一个股票分析agent" # 通过 API 构建 # 模式 2: Claude Code CLI (推荐) python launch_paas.py claude-code "做一个能分析CSV数据的agent" python launch_paas.py claude-code --interactive # 交互模式 # 优化已部署的 agent python launch_paas.py optimize """ from __future__ import annotations import argparse import json import os import subprocess import sys import time from datetime import datetime from pathlib import Path from typing import Dict, List, Optional PROJECT_ROOT = Path(__file__).resolve().parent.parent.parent BUILDER_DIR = Path(__file__).resolve().parent sys.path.insert(0, str(PROJECT_ROOT)) PAAS_URL = os.environ.get("AGENTPAAS_URL", "http://127.0.0.1:8000") API_KEY = os.environ.get("AGENTPAAS_API_KEY", "") # ── Builder Agent 配置 ──────────────────────────────────── AGENT_CONFIGS = [ ("builder-orchestrator", "AgentBuilder 总指挥", BUILDER_DIR / "orchestrator.yml"), ("builder-analyst", "需求分析师", BUILDER_DIR / "agents" / "analyst.yml"), ("builder-retriever", "配置检索员", BUILDER_DIR / "agents" / "retriever.yml"), ("builder-prompter", "Prompt 工匠", BUILDER_DIR / "agents" / "prompter.yml"), ("builder-assembler", "配置组装师", BUILDER_DIR / "agents" / "assembler.yml"), ("builder-critic", "配置审查官", BUILDER_DIR / "agents" / "critic.yml"), ("builder-optimizer", "持续优化器", BUILDER_DIR / "agents" / "optimizer.yml"), ] # ── 运行参数 ────────────────────────────────────────────── MAX_REVISION_ROUNDS = 3 # Critic 不通过时最大修正轮数 MAX_TURNS_PER_PHASE = 20 # 每个 agent 最大工具调用轮数 PHASE_TIMEOUT = 300 # 每个 phase 超时 (5 分钟,builder 比 research 快) API_RETRY_MAX = 3 # API 失败最大重试次数 API_RETRY_BACKOFF = 15 # 重试间隔基数 (秒) PHASE_COOLDOWN = 5 # 阶段间冷却 (秒) CRITIC_PASS_SCORE = 7.0 # Critic 通过分数线 # ═══════════════════════════════════════════════════════════ # 1. PaaS API Helpers # ═══════════════════════════════════════════════════════════ def api_call(method: str, path: str, data: dict = None) -> dict: import urllib.request import urllib.error url = f"{PAAS_URL}/api/v1{path}" body = json.dumps(data).encode("utf-8") if data else None req = urllib.request.Request(url, data=body, method=method) req.add_header("Content-Type", "application/json") if API_KEY: req.add_header("Authorization", f"Bearer {API_KEY}") try: with urllib.request.urlopen(req, timeout=600) as resp: return json.loads(resp.read()) except urllib.error.HTTPError as e: body = e.read().decode() print(f" API Error {e.code}: {body[:500]}") return {"error": body} except urllib.error.URLError as e: print(f" Connection error: {e}") return {"error": str(e)} def wait_for_server(timeout: int = 30) -> bool: import urllib.request for _ in range(timeout): try: req = urllib.request.Request(f"{PAAS_URL}/health") with urllib.request.urlopen(req, timeout=2): return True except Exception: time.sleep(1) return False # ═══════════════════════════════════════════════════════════ # 2. 命令: serve — 启动 AgentPaaS 服务 # ═══════════════════════════════════════════════════════════ def cmd_serve(args): print("Starting AgentPaaS server...") proc = subprocess.Popen( [sys.executable, "-m", "agentpaas", "serve", "--port", str(args.port), "--dev"], cwd=str(PROJECT_ROOT), stdout=subprocess.PIPE, stderr=subprocess.PIPE, ) print(f" PID: {proc.pid}") print(f" URL: http://127.0.0.1:{args.port}") if not wait_for_server(30): print(" ERROR: Server failed to start within 30s") proc.kill() return None print(" ✓ Server ready") return proc # ═══════════════════════════════════════════════════════════ # 3. 命令: register — 注册所有 builder agents # ═══════════════════════════════════════════════════════════ def cmd_register(args): import yaml print(f"\nRegistering {len(AGENT_CONFIGS)} builder agents to AgentPaaS...") agent_ids = {} for agent_id, name, yml_path in AGENT_CONFIGS: with open(yml_path, "r", encoding="utf-8") as f: config = yaml.safe_load(f) result = api_call("POST", "/agents", { "name": name, "description": config.get("description", ""), "config": config, "tags": ["agentbuilder", config.get("type", "react")], "environment": "production", }) if "agent_id" in result: agent_ids[agent_id] = result["agent_id"] print(f" ✓ {name:20s} → {result['agent_id']} (v{result['version']})") else: print(f" ✗ {name:20s} → {result.get('error', 'Unknown error')}") mapping_path = BUILDER_DIR / ".paas_agent_ids.json" mapping_path.write_text(json.dumps(agent_ids, indent=2), encoding="utf-8") print(f"\n Agent IDs saved to {mapping_path}") return agent_ids # ═══════════════════════════════════════════════════════════ # 4. 命令: build — 通过 PaaS API 构建 agent # ═══════════════════════════════════════════════════════════ def cmd_build(args): mapping_path = BUILDER_DIR / ".paas_agent_ids.json" if not mapping_path.exists(): print("ERROR: No agent ID mapping. Run 'register' first.") return agent_ids = json.loads(mapping_path.read_text()) orchestrator_id = agent_ids.get("builder-orchestrator") if not orchestrator_id: print("ERROR: Orchestrator not registered.") return user_desc = args.description print(f"\n{'═'*60}") print(f" AgentBuilder — building agent from description") print(f" Input: {user_desc[:80]}...") print(f"{'═'*60}\n") result = api_call("POST", f"/agents/{orchestrator_id}/run", { "input": user_desc, "parameters": {}, "context": {"sub_agent_ids": agent_ids}, }) if "error" not in result: print(f"\n ✓ Build completed") print(f" Run ID: {result.get('run_id')}") output = result.get("output", "") print(f"\n{output}") else: print(f"\n ✗ Build failed: {result['error'][:500]}") # ═══════════════════════════════════════════════════════════ # 5. Claude Code CLI — 核心多阶段流水线 # ═══════════════════════════════════════════════════════════ def run_claude_phase(phase_dir: Path, phase_name: str, desc: str, prompt_content: str) -> str: """执行单个 Claude CLI 阶段,带重试。""" phase_dir.mkdir(parents=True, exist_ok=True) (phase_dir / "prompt.md").write_text(prompt_content, encoding="utf-8") print(f"\n{'━'*60}") print(f" Phase: {phase_name} — {desc}") print(f" Timeout: {PHASE_TIMEOUT}s | Max turns: {MAX_TURNS_PER_PHASE}") print(f"{'━'*60}") cmd = [ "claude", "-p", "--model", "claude-sonnet-4-20250514", "--max-turns", str(MAX_TURNS_PER_PHASE), "--allowedTools", "Read,Glob,Grep", ] for attempt in range(1, API_RETRY_MAX + 1): t0 = time.time() label = f"[attempt {attempt}/{API_RETRY_MAX}]" try: print(f" 🚀 {label} Running claude CLI ...") result = subprocess.run( cmd, input=prompt_content, capture_output=True, text=True, timeout=PHASE_TIMEOUT, cwd=str(phase_dir), ) elapsed = time.time() - t0 output = result.stdout or "" stderr = result.stderr or "" # 检测 API 错误 is_api_error = False for err_pattern in ["Request timed out", "overloaded_error", "529", "rate_limit", "500 Internal"]: if err_pattern in output or err_pattern in stderr: is_api_error = True break if is_api_error and attempt < API_RETRY_MAX: wait = API_RETRY_BACKOFF * (2 ** (attempt - 1)) print(f" ⚠ {label} API error after {elapsed:.0f}s. Retrying in {wait}s ...") time.sleep(wait) continue (phase_dir / "output.md").write_text(output, encoding="utf-8") print(f" ✓ {label} Done in {elapsed:.0f}s | {len(output)} chars") return output except subprocess.TimeoutExpired: elapsed = time.time() - t0 msg = f"[TIMEOUT] Phase {phase_name} timed out after {elapsed:.0f}s" print(f" ✗ {msg}") (phase_dir / "output.md").write_text(msg, encoding="utf-8") return msg except FileNotFoundError: print(" ✗ 'claude' CLI not found. Is Claude Code installed?") return "[ERROR] claude CLI not found" return f"[FAILED] All {API_RETRY_MAX} attempts failed" def load_reference_docs() -> str: """加载 schema 参考和示例文档。""" parts = [] schema_path = BUILDER_DIR / "prompts" / "schema_reference.md" examples_path = BUILDER_DIR / "prompts" / "examples.md" if schema_path.exists(): parts.append(schema_path.read_text(encoding="utf-8")) if examples_path.exists(): parts.append(examples_path.read_text(encoding="utf-8")) return "\n\n---\n\n".join(parts) def load_agent_prompt(agent_name: str) -> str: """从 YAML 配置中读取 agent 的 systemPrompt。""" import yaml yml_path = BUILDER_DIR / "agents" / f"{agent_name}.yml" if not yml_path.exists(): return "" with open(yml_path, "r", encoding="utf-8") as f: config = yaml.safe_load(f) return config.get("systemPrompt", "") def build_phase_prompt(agent_name: str, user_desc: str, context: Dict = None, revision: str = "") -> str: """构建阶段 prompt。""" system_prompt = load_agent_prompt(agent_name) reference = load_reference_docs() parts = [ f"# Agent Configuration Builder — {agent_name}", f"\n## System Instructions\n{system_prompt}", f"\n## User Description\n{user_desc}", ] if context: parts.append("\n## Previous Phase Outputs") for key, value in context.items(): parts.append(f"\n### {key}\n{str(value)[:4000]}") if revision: parts.append(f"\n## Revision Instructions (from Critic)\n{revision}") parts.append(f"\n## Reference: YAML Schema & Examples\n{reference[:6000]}") parts.append(""" ## Output Instructions - Output ONLY the result in the specified format (JSON or YAML) - Do not wrap output in markdown code blocks unless it is YAML - Be precise and concise """) return "\n".join(parts) def parse_critic_output(output: str) -> Dict: """从 Critic 输出中提取 JSON 评分。""" # 尝试直接解析 try: return json.loads(output) except (json.JSONDecodeError, TypeError): pass # 尝试从 markdown 代码块中提取 import re json_match = re.search(r'```(?:json)?\s*\n(.*?)\n```', output, re.DOTALL) if json_match: try: return json.loads(json_match.group(1)) except (json.JSONDecodeError, TypeError): pass # 尝试找 { } 块 brace_match = re.search(r'\{[^{}]*"overall_score"[^{}]*\}', output, re.DOTALL) if brace_match: try: return json.loads(brace_match.group(0)) except (json.JSONDecodeError, TypeError): pass return {"overall_score": 0, "verdict": "unknown", "parse_error": True} def extract_yaml_from_output(output: str) -> str: """从 Assembler 输出中提取 YAML 配置。""" import re # 尝试从 ```yaml 代码块中提取 yaml_match = re.search(r'```ya?ml\s*\n(.*?)\n```', output, re.DOTALL) if yaml_match: return yaml_match.group(1).strip() # 尝试找以 agentId: 开头的块 agent_match = re.search(r'(agentId:.*)', output, re.DOTALL) if agent_match: return agent_match.group(1).strip() return output.strip() def cmd_claude_code(args): """ 通过 Claude Code CLI 执行多智能体构建流水线。 Layer 1 (并行): Analyst + Retriever + Prompter Layer 2 (串行): Assembler → Critic Loop: 如果 Critic 不通过,修正后重新走 Layer 2 """ import yaml user_desc = args.description if not user_desc: print("ERROR: Please provide a description. Example:") print(' python launch_paas.py claude-code "做一个能分析股票K线的agent"') return # 检查 claude CLI try: r = subprocess.run(["claude", "--version"], capture_output=True, text=True, timeout=10) print(f" Claude CLI: {r.stdout.strip()}") except FileNotFoundError: print(" ERROR: 'claude' not found. Install Claude Code CLI.") return # 创建工作区 ts = datetime.now().strftime("%Y%m%d_%H%M%S") workspace = BUILDER_DIR / "workspace" / f"build_{ts}" workspace.mkdir(parents=True, exist_ok=True) # 保存输入 (workspace / "user_input.txt").write_text(user_desc, encoding="utf-8") print(f""" ╔══════════════════════════════════════════════════════════════╗ ║ AgentBuilder — Multi-Agent Configuration Generator ║ ║ Model: Claude Sonnet 4 ║ ║ Max revision rounds: {MAX_REVISION_ROUNDS} ║ ╚══════════════════════════════════════════════════════════════╝ Input: {user_desc} Workspace: {workspace} """) # ═══ Layer 1: 并行分析 ═══════════════════════════════ print(f"\n{'═'*60}") print(f" LAYER 1 — Parallel Analysis (Analyst + Retriever + Prompter)") print(f"{'═'*60}") # Layer 1 的三个阶段按顺序执行(CLI 模式下不方便真正并行,但各自独立) # Analyst analyst_output = run_claude_phase( workspace / "01_analyst", "analyst", "需求分析", build_phase_prompt("analyst", user_desc), ) time.sleep(PHASE_COOLDOWN) # Retriever retriever_output = run_claude_phase( workspace / "02_retriever", "retriever", "配置检索", build_phase_prompt("retriever", user_desc, context={"analyst": analyst_output}), ) time.sleep(PHASE_COOLDOWN) # Prompter prompter_output = run_claude_phase( workspace / "03_prompter", "prompter", "Prompt 撰写", build_phase_prompt("prompter", user_desc, context={"analyst": analyst_output}), ) time.sleep(PHASE_COOLDOWN) # ═══ Layer 2: 组装 + 审查(可迭代)═══════════════════ assembler_context = { "analyst_output": analyst_output, "retriever_output": retriever_output, "prompter_output": prompter_output, } final_config = None final_score = 0 revision_instructions = "" for round_num in range(1, MAX_REVISION_ROUNDS + 1): print(f"\n{'═'*60}") print(f" LAYER 2 — Assembly + Review (Round {round_num}/{MAX_REVISION_ROUNDS})") print(f"{'═'*60}") # Assembler assembler_output = run_claude_phase( workspace / f"04_assembler_r{round_num}", "assembler", f"配置组装 (Round {round_num})", build_phase_prompt("assembler", user_desc, context=assembler_context, revision=revision_instructions), ) time.sleep(PHASE_COOLDOWN) # 提取 YAML generated_yaml = extract_yaml_from_output(assembler_output) (workspace / f"04_assembler_r{round_num}" / "generated.yml").write_text( generated_yaml, encoding="utf-8" ) # Lint 检查 from tools.config_lint import lint_config try: lint_result = lint_config(generated_yaml) (workspace / f"04_assembler_r{round_num}" / "lint_result.json").write_text( json.dumps(lint_result, indent=2, ensure_ascii=False), encoding="utf-8" ) print(f" Lint: valid={lint_result['valid']}, score={lint_result['score']}, " f"errors={len(lint_result['errors'])}, warnings={len(lint_result['warnings'])}") except Exception as e: lint_result = {"valid": False, "errors": [str(e)], "warnings": [], "score": 0} print(f" Lint error: {e}") # Critic critic_context = { "user_description": user_desc, "generated_config": generated_yaml, "lint_result": json.dumps(lint_result, ensure_ascii=False), } critic_output = run_claude_phase( workspace / f"05_critic_r{round_num}", "critic", f"配置审查 (Round {round_num})", build_phase_prompt("critic", user_desc, context=critic_context), ) # 解析 Critic 评分 critic_result = parse_critic_output(critic_output) (workspace / f"05_critic_r{round_num}" / "scores.json").write_text( json.dumps(critic_result, indent=2, ensure_ascii=False), encoding="utf-8" ) overall_score = critic_result.get("overall_score", 0) verdict = critic_result.get("verdict", "unknown") print(f"\n 📊 Critic: score={overall_score}/10, verdict={verdict}") if overall_score >= CRITIC_PASS_SCORE or verdict == "approve": print(f" ✅ Approved! Score {overall_score} >= {CRITIC_PASS_SCORE}") final_config = generated_yaml final_score = overall_score break elif round_num < MAX_REVISION_ROUNDS: # 提取修改指令 revision_instructions = "" rev_inst = critic_result.get("revision_instructions", {}) if isinstance(rev_inst, dict): for target, instruction in rev_inst.items(): if instruction: revision_instructions += f"\n[{target}]: {instruction}" critical = critic_result.get("critical_issues", []) if critical: revision_instructions += "\n\nCritical issues:\n" + "\n".join( f"- {issue}" for issue in critical ) suggestions = critic_result.get("suggestions", []) if suggestions: revision_instructions += "\n\nSuggestions:\n" + "\n".join( f"- {s}" for s in suggestions ) print(f" ⚠ Score {overall_score} < {CRITIC_PASS_SCORE}, revising...") print(f" Revision notes: {revision_instructions[:200]}...") time.sleep(PHASE_COOLDOWN) else: print(f" ❌ Max rounds reached. Using best result (score={overall_score})") final_config = generated_yaml final_score = overall_score # ═══ 输出最终结果 ═══════════════════════════════════ if final_config: output_path = workspace / "final_agent.yml" output_path.write_text(final_config, encoding="utf-8") print(f"\n{'═'*60}") print(f" ✅ AgentBuilder Complete!") print(f"{'═'*60}") print(f" Score: {final_score}/10") print(f" Output: {output_path}") print(f" Workspace: {workspace}") print(f"\n Generated config:") print(f"{'─'*60}") print(final_config[:2000]) if len(final_config) > 2000: print(f" ... ({len(final_config)} chars total)") print(f"{'─'*60}") # 尝试解析并展示摘要 try: config = yaml.safe_load(final_config) if isinstance(config, dict): print(f"\n Summary:") print(f" Agent ID: {config.get('agentId', '?')}") print(f" Name: {config.get('name', '?')}") print(f" Type: {config.get('type', '?')}") print(f" Model: {config.get('model', {}).get('provider', '?')}" f"/{config.get('model', {}).get('name', '?')}") tools = config.get('mcp', {}).get('localTools', []) print(f" Tools: {', '.join(tools)}") except Exception: pass else: print(f"\n ❌ Build failed. Check workspace: {workspace}") # ═══════════════════════════════════════════════════════════ # 6. 命令: interactive — 交互式构建 # ═══════════════════════════════════════════════════════════ def cmd_interactive(args): """交互式 AgentBuilder:对话式构建 + 实时预览。""" print(f""" ╔══════════════════════════════════════════════════════════════╗ ║ AgentBuilder Interactive Mode ║ ║ 输入你想要的 agent 描述,我来生成配置 ║ ║ 输入 quit 退出 | 输入 save 保存当前配置 ║ ╚══════════════════════════════════════════════════════════════╝ """) current_config = None while True: try: user_input = input("\n🔧 描述你的 agent > ").strip() except (EOFError, KeyboardInterrupt): print("\nBye!") break if not user_input: continue if user_input.lower() == "quit": break if user_input.lower() == "save" and current_config: save_path = BUILDER_DIR / "workspace" / f"agent_{datetime.now().strftime('%H%M%S')}.yml" save_path.parent.mkdir(parents=True, exist_ok=True) save_path.write_text(current_config, encoding="utf-8") print(f" ✓ Saved to {save_path}") continue # 构建 agent(复用 claude-code 逻辑) args.description = user_input cmd_claude_code(args) # ═══════════════════════════════════════════════════════════ # 7. 命令: optimize — 优化已部署的 agent # ═══════════════════════════════════════════════════════════ def cmd_optimize(args): """通过 Claude CLI 运行 Optimizer agent。""" agent_id = args.agent_id print(f"\n{'═'*60}") print(f" AgentBuilder Optimizer") print(f" Target: {agent_id}") print(f"{'═'*60}\n") ts = datetime.now().strftime("%Y%m%d_%H%M%S") workspace = BUILDER_DIR / "workspace" / f"optimize_{ts}" workspace.mkdir(parents=True, exist_ok=True) optimizer_prompt = load_agent_prompt("optimizer") prompt = f"""# Agent Optimizer ## System Instructions {optimizer_prompt} ## Target Agent agent_id: {agent_id} ## Task 1. 调用 get_run_stats 获取运行数据 2. 调用 get_user_feedback 获取用户反馈 3. 调用 get_current_config 获取当前配置 4. 分析数据,找出优化点 5. 生成优化补丁 ## Environment - AgentPaaS API: {PAAS_URL} - API Key: {'set' if API_KEY else 'not set'} 输出优化建议 JSON。 """ output = run_claude_phase( workspace / "optimizer", "optimizer", f"优化 {agent_id}", prompt, ) print(f"\n Optimizer output:") print(output[:2000]) # ═══════════════════════════════════════════════════════════ # 8. 命令: all — 一键启动全流程 # ═══════════════════════════════════════════════════════════ def cmd_all(args): """一键: 启动服务 + 注册 agents + 构建。""" global API_KEY proc = cmd_serve(args) if not proc: return try: # 创建租户 result = subprocess.run( [sys.executable, "-m", "agentpaas", "create-tenant", "--name", "builder-lab"], capture_output=True, text=True, cwd=str(PROJECT_ROOT), ) print(result.stdout) for line in result.stdout.splitlines(): if line.startswith("API Key:"): API_KEY = line.split(":", 1)[1].strip() os.environ["AGENTPAAS_API_KEY"] = API_KEY cmd_register(args) cmd_build(args) finally: print("\nShutting down server...") proc.terminate() proc.wait(timeout=5) # ═══════════════════════════════════════════════════════════ # CLI Entry Point # ═══════════════════════════════════════════════════════════ def _apply_overrides(args): global MAX_REVISION_ROUNDS, MAX_TURNS_PER_PHASE, PHASE_TIMEOUT if hasattr(args, "max_rounds") and args.max_rounds: MAX_REVISION_ROUNDS = args.max_rounds if hasattr(args, "max_turns") and args.max_turns: MAX_TURNS_PER_PHASE = args.max_turns if hasattr(args, "timeout") and args.timeout: PHASE_TIMEOUT = args.timeout def main(): parser = argparse.ArgumentParser( description="AgentBuilder — Multi-agent Configuration Generator", formatter_class=argparse.RawDescriptionHelpFormatter, epilog=""" Examples: python launch_paas.py claude-code "做一个能分析CSV数据的agent" python launch_paas.py claude-code "帮我做一个能上网搜索的调研助手" python launch_paas.py claude-code --interactive python launch_paas.py optimize agent-abc123 python launch_paas.py all --port 8000 -d "股票K线分析agent" """) sub = parser.add_subparsers(dest="command") # serve serve_p = sub.add_parser("serve", help="Start AgentPaaS server") serve_p.add_argument("--port", type=int, default=8000) # register sub.add_parser("register", help="Register builder agents to PaaS") # build (via PaaS API) build_p = sub.add_parser("build", help="Build agent via PaaS API") build_p.add_argument("description", type=str, help="Agent description") # claude-code (via CLI) cc_p = sub.add_parser("claude-code", help="Build agent via Claude Code CLI") cc_p.add_argument("description", type=str, nargs="?", default="", help="Agent description") cc_p.add_argument("--interactive", "-i", action="store_true", help="Interactive mode") cc_p.add_argument("--max-rounds", type=int, default=None, help=f"Max revision rounds (default: {MAX_REVISION_ROUNDS})") cc_p.add_argument("--max-turns", type=int, default=None, help=f"Max turns per phase (default: {MAX_TURNS_PER_PHASE})") cc_p.add_argument("--timeout", type=int, default=None, help=f"Phase timeout in seconds (default: {PHASE_TIMEOUT})") # optimize opt_p = sub.add_parser("optimize", help="Optimize a deployed agent") opt_p.add_argument("agent_id", type=str, help="Target agent ID") # all all_p = sub.add_parser("all", help="Serve + register + build (one-shot)") all_p.add_argument("--port", type=int, default=8000) all_p.add_argument("-d", "--description", type=str, required=True, help="Agent description") args = parser.parse_args() # 应用参数覆盖 _apply_overrides(args) commands = { "serve": cmd_serve, "register": cmd_register, "build": cmd_build, "claude-code": lambda a: cmd_interactive(a) if getattr(a, "interactive", False) else cmd_claude_code(a), "optimize": cmd_optimize, "all": cmd_all, } if args.command in commands: commands[args.command](args) else: parser.print_help() if __name__ == "__main__": main()