From 9bbfa3092d900e364cbe45aec8207ed021988993 Mon Sep 17 00:00:00 2001 From: salvacybersec Date: Mon, 6 Apr 2026 21:16:45 +0300 Subject: [PATCH] feat: add --install paperclip to build system MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Generates Paperclip-compatible agent output per persona: - agents//SOUL.md — identity, skills, escalation, full prompt - agents//hermes-config.yaml — model, provider, MCP, toolsets - agents//AGENTS.md — workspace overview with org connections - skills/ — 42 shared skills copied in SKILL.md + references format Usage: python3 build.py --install paperclip Output: generated/_paperclip/ (29 agents + 42 skills) Full platform matrix now: claude, antigravity, gemini, openclaw, paperclip, all Co-Authored-By: Claude Opus 4.6 (1M context) --- build.py | 141 ++++++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 139 insertions(+), 2 deletions(-) diff --git a/build.py b/build.py index ca2adb3..265672d 100755 --- a/build.py +++ b/build.py @@ -566,6 +566,141 @@ def install_gemini(output_dir: Path): return count +def install_paperclip(output_dir: Path, personas_dir: Path): + """Install personas as Paperclip agents (SOUL.md + hermes-config.yaml + AGENTS.md per agent).""" + pc_dir = output_dir / "_paperclip" + agents_dir = pc_dir / "agents" + skills_dir = pc_dir / "skills" + agents_dir.mkdir(parents=True, exist_ok=True) + skills_dir.mkdir(parents=True, exist_ok=True) + + # Build escalation graph for AGENTS.md org chart + flat_config = {} + escalation_graph = build_escalation_graph(personas_dir, flat_config) + + # Domain → toolset mapping for hermes-config + domain_toolsets = { + "cybersecurity": ["terminal", "file", "web", "code_execution"], + "intelligence": ["terminal", "file", "web"], + "military": ["terminal", "file", "web"], + "engineering": ["terminal", "file", "web", "code_execution"], + "law-economics": ["file", "web"], + "history": ["file", "web"], + "linguistics": ["file", "web"], + "academia": ["file", "web"], + } + + agent_count = 0 + skill_count = 0 + + for persona_dir in sorted(output_dir.iterdir()): + if not persona_dir.is_dir() or persona_dir.name.startswith("_"): + continue + + general_json = persona_dir / "general.json" + general_prompt = persona_dir / "general.prompt.md" + if not general_json.exists(): + continue + + data = json.loads(general_json.read_text(encoding="utf-8")) + codename = data.get("codename", persona_dir.name) + name = data.get("name", codename.title()) + role = data.get("role", "Specialist") + domain = data.get("domain", "general") + address_to = data.get("address_to", "") + tone = data.get("tone", "") + escalates_to = escalation_graph.get(persona_dir.name, []) + skills = data.get("skills", []) + + agent_dir = agents_dir / codename + agent_dir.mkdir(parents=True, exist_ok=True) + + # 1. SOUL.md — persona prompt adapted to Paperclip format + soul_lines = [ + f"# {name} — {role}\n", + f"## Kimlik", + f"- **Ad:** {name}", + f"- **Kod Adı:** {codename}", + f"- **Hitap:** {address_to}", + f"- **Domain:** {domain}", + f"- **Ton:** {tone}", + "", + ] + if escalates_to: + soul_lines.append(f"## İlişkiler") + soul_lines.append(f"- **Escalation:** {', '.join(escalates_to)}") + soul_lines.append("") + if skills: + soul_lines.append(f"## Skills") + for s in skills: + soul_lines.append(f"- {s}") + soul_lines.append("") + + # Append the full prompt body + if general_prompt.exists(): + soul_lines.append("## Detaylı Tanım\n") + soul_lines.append(general_prompt.read_text(encoding="utf-8")) + + (agent_dir / "SOUL.md").write_text("\n".join(soul_lines), encoding="utf-8") + + # 2. hermes-config.yaml + toolsets = domain_toolsets.get(domain, ["terminal", "file", "web"]) + hermes_config = { + "model": "qwen/qwen3.6-plus:free", + "provider": "openrouter", + "defaults": {"quiet": True, "reasoning_effort": "medium"}, + "mcp_servers": { + "web-search": { + "command": "npx", + "args": ["-y", "ddg-mcp-search"], + }, + }, + "skills": {"external_dirs": ["~/.hermes/skills"]}, + "toolsets": toolsets, + } + (agent_dir / "hermes-config.yaml").write_text( + yaml.dump(hermes_config, allow_unicode=True, default_flow_style=False), + encoding="utf-8", + ) + + # 3. AGENTS.md — workspace overview with org connections + agents_md_lines = [ + f"# {name} — Workspace\n", + f"- **Agent:** {name} ({role})", + f"- **Domain:** {domain}", + "", + ] + if escalates_to: + agents_md_lines.append("## Bağlantılar\n") + for target in escalates_to: + agents_md_lines.append(f"- → {target}") + agents_md_lines.append("") + + (agent_dir / "AGENTS.md").write_text("\n".join(agents_md_lines), encoding="utf-8") + agent_count += 1 + + # Copy shared skills as Paperclip skills (SKILL.md format already compatible) + shared_skills = personas_dir / "_shared" / "skills" + if shared_skills.exists(): + for skill_dir in sorted(shared_skills.iterdir()): + if not skill_dir.is_dir(): + continue + skill_md = skill_dir / "SKILL.md" + if skill_md.exists(): + dest = skills_dir / skill_dir.name + dest.mkdir(parents=True, exist_ok=True) + (dest / "SKILL.md").write_text(skill_md.read_text(encoding="utf-8"), encoding="utf-8") + # Copy references if exist + refs = skill_dir / "references" + if refs.is_dir(): + import shutil + shutil.copytree(refs, dest / "references", dirs_exist_ok=True) + skill_count += 1 + + print(f" Paperclip: {agent_count} agents + {skill_count} skills to {pc_dir}") + return agent_count + + def install_openclaw(output_dir: Path): """Install personas to OpenClaw format (IDENTITY.md + individual persona files).""" oc_dir = output_dir / "_openclaw" @@ -598,7 +733,7 @@ def install_openclaw(output_dir: Path): def main(): import argparse parser = argparse.ArgumentParser(description="Build persona library and optionally install to platforms.") - parser.add_argument("--install", choices=["claude", "antigravity", "gemini", "openclaw", "all"], + parser.add_argument("--install", choices=["claude", "antigravity", "gemini", "openclaw", "paperclip", "all"], help="Install generated personas to a target platform") args = parser.parse_args() @@ -641,7 +776,7 @@ def main(): # Platform installation if args.install: print(f"\n--- Installing to: {args.install} ---\n") - targets = ["claude", "antigravity", "gemini", "openclaw"] if args.install == "all" else [args.install] + targets = ["claude", "antigravity", "gemini", "openclaw", "paperclip"] if args.install == "all" else [args.install] for target in targets: if target == "claude": install_claude(output_dir) @@ -651,6 +786,8 @@ def main(): install_gemini(output_dir) elif target == "openclaw": install_openclaw(output_dir) + elif target == "paperclip": + install_paperclip(output_dir, personas_dir) print_summary(config, len(persona_dirs), total_variants, total_words)