""" ⚒️ FORGE — Federated Open Registry for Generative Executables v2.2 — Gradio 6.9 native theme · CRUD · MCP · SKILL.md · Claude Skills """ import json from pathlib import Path import gradio as gr from fastapi import FastAPI, HTTPException, Request from fastapi.responses import JSONResponse, Response import skill_registry as registry from mcp_server import register_mcp_routes # ─── FastAPI + MCP ──────────────────────────────────────────────── api = FastAPI(title="FORGE API", version="2.2.0") register_mcp_routes(api) def jresp(data, status=200): return JSONResponse(content=data, status_code=status) @api.get("/api/v1/manifest") async def api_manifest(): return jresp(registry.get_manifest()) @api.get("/api/v1/skills") async def api_list(tag: str | None = None, q: str | None = None): return jresp({"skills": registry.list_skills(tag=tag, query=q), "count": len(registry.list_skills(tag=tag, query=q))}) @api.get("/api/v1/skills/{skill_id}") async def api_get(skill_id: str): s = registry.get_skill(skill_id) if not s: raise HTTPException(404, f"'{skill_id}' not found") return jresp(s) @api.get("/api/v1/skills/{skill_id}/code") async def api_code(skill_id: str): d = registry.get_skill_code(skill_id) if not d: raise HTTPException(404) return jresp(d) @api.get("/api/v1/skills/{skill_id}/download") async def api_download(skill_id: str): d = registry.get_skill_code(skill_id) if not d: raise HTTPException(404) code = f'"""\nFORGE Skill: {skill_id} v{d["version"]}\nhttps://huggingface.co/spaces/Chris4K/agent-forge\n"""\n\n{d["code"]}\n' return Response(code, media_type="text/x-python", headers={"Content-Disposition": f'attachment; filename="{skill_id}.py"'}) @api.post("/api/v1/skills") async def api_publish(request: Request): try: skill = await request.json() except Exception: raise HTTPException(400, "Invalid JSON") ok, msg = registry.publish_skill(skill) return jresp({"ok": ok, "message": msg}, 201 if ok else 400) @api.put("/api/v1/skills/{skill_id}") async def api_update(skill_id: str, request: Request): try: updates = await request.json() except Exception: raise HTTPException(400, "Invalid JSON") ok, msg = registry.update_skill(skill_id, updates) return jresp({"ok": ok, "message": msg}, 200 if ok else 404) @api.delete("/api/v1/skills/{skill_id}") async def api_delete(skill_id: str): ok, msg = registry.delete_skill(skill_id) return jresp({"ok": ok, "message": msg}, 200 if ok else 404) @api.get("/api/v1/search") async def api_search(q: str): skills = registry.list_skills(query=q) return jresp({"query": q, "skills": skills, "count": len(skills)}) @api.get("/api/v1/tags") async def api_tags(): return jresp({"tags": registry.get_all_tags()}) @api.get("/api/v1/stats") async def api_stats(): return jresp(registry.get_stats()) # ─── Gradio 6 Theme ─────────────────────────────────────────────── # All dark palette values set via the theme system — no CSS class hacks class ForgeTheme(gr.themes.Base): def __init__(self): super().__init__( primary_hue=gr.themes.colors.Color( c50="#fff3e0", c100="#ffe0b2", c200="#ffcc80", c300="#ffb74d", c400="#ffa726", c500="#ff9500", c600="#ff6b00", c700="#e65100", c800="#bf360c", c900="#8b1a00", c950="#5a0e00", ), neutral_hue=gr.themes.colors.Color( c50="#f0f0f8", c100="#d8d8e8", c200="#b0b0c8", c300="#8888a8", c400="#606080", c500="#404060", c600="#2a2a45", c700="#1e1e35", c800="#141425", c900="#0e0e1a", c950="#0a0a0f", ), font=gr.themes.GoogleFont("Space Mono"), font_mono=gr.themes.GoogleFont("Space Mono"), ) # Body self.body_background_fill = "#0a0a0f" self.body_background_fill_dark = "#0a0a0f" self.body_text_color = "#e8e8f0" self.body_text_color_dark = "#e8e8f0" self.body_text_color_subdued = "#6b6b8a" self.body_text_color_subdued_dark = "#6b6b8a" # Blocks/panels self.block_background_fill = "#111118" self.block_background_fill_dark = "#111118" self.block_border_color = "#1e1e2e" self.block_border_color_dark = "#1e1e2e" self.block_border_width = "1px" self.block_title_text_color = "#ff6b00" self.block_title_text_color_dark = "#ff6b00" self.block_label_text_color = "#6b6b8a" self.block_label_text_color_dark = "#6b6b8a" self.block_label_background_fill = "#111118" self.block_label_background_fill_dark = "#111118" # Background fills (secondary panels) self.background_fill_primary = "#0a0a0f" self.background_fill_primary_dark = "#0a0a0f" self.background_fill_secondary = "#111118" self.background_fill_secondary_dark = "#111118" # Inputs self.input_background_fill = "#111118" self.input_background_fill_dark = "#111118" self.input_background_fill_focus = "#16162a" self.input_background_fill_focus_dark = "#16162a" self.input_border_color = "#2a2a45" self.input_border_color_dark = "#2a2a45" self.input_border_color_focus = "#ff6b00" self.input_border_color_focus_dark = "#ff6b00" self.input_placeholder_color = "#4a4a6a" self.input_placeholder_color_dark = "#4a4a6a" # Borders self.border_color_primary = "#1e1e2e" self.border_color_primary_dark = "#1e1e2e" self.border_color_accent = "#ff6b00" self.border_color_accent_dark = "#ff6b00" # Buttons self.button_primary_background_fill = "#ff6b00" self.button_primary_background_fill_dark = "#ff6b00" self.button_primary_background_fill_hover = "#ff9500" self.button_primary_background_fill_hover_dark = "#ff9500" self.button_primary_text_color = "#000000" self.button_primary_text_color_dark = "#000000" self.button_secondary_background_fill = "#1e1e2e" self.button_secondary_background_fill_dark = "#1e1e2e" self.button_secondary_background_fill_hover = "#2a2a45" self.button_secondary_background_fill_hover_dark = "#2a2a45" self.button_secondary_text_color = "#e8e8f0" self.button_secondary_text_color_dark = "#e8e8f0" self.button_secondary_border_color = "#2a2a45" self.button_secondary_border_color_dark = "#2a2a45" # Code blocks self.code_background_fill = "#0d0d1a" self.code_background_fill_dark = "#0d0d1a" # Shadow self.block_shadow = "none" self.block_shadow_dark = "none" # ─── Custom HTML-only CSS (skill cards, stat bar, forge branding) ─ # Only targets our own HTML components, not Gradio internals. CUSTOM_CSS = """ /* Forge brand */ .forge-header { text-align: center; padding: 2.2rem 1rem 1.2rem; border-bottom: 1px solid #1e1e2e; background: linear-gradient(180deg, #0f0f1a 0%, #0a0a0f 100%); margin-bottom: 0.5rem; } .forge-logo { font-family: 'Space Mono', monospace; font-size: 3rem; font-weight: 700; background: linear-gradient(135deg, #ff6b00, #ff9500); -webkit-background-clip: text; -webkit-text-fill-color: transparent; background-clip: text; line-height: 1; letter-spacing: -2px; } .forge-sub { font-family: 'Space Mono', monospace; font-size: 0.62rem; color: #4a4a6a; letter-spacing: 0.32em; text-transform: uppercase; margin-top: 0.4rem; } /* Stats row */ .stat-bar { display: flex; justify-content: center; gap: 2.5rem; padding: 0.8rem 1rem; border: 1px solid #1e1e2e; border-radius: 8px; background: #111118; margin: 0.8rem 0 1rem; } .stat-num { font-family: 'Space Mono', monospace; font-size: 1.7rem; color: #ff6b00; font-weight: 700; line-height: 1; } .stat-lbl { font-size: 0.58rem; color: #4a4a6a; text-transform: uppercase; letter-spacing: 0.18em; margin-top: 3px; } /* Skill cards */ .skill-card { background: #111118; border: 1px solid #1e1e2e; border-radius: 8px; padding: 1rem 1.2rem; margin-bottom: 0.55rem; transition: border-color 0.15s, transform 0.1s; cursor: default; } .skill-card:hover { border-color: #ff6b00; transform: translateX(3px); } .sn { font-family: 'Space Mono', monospace; font-size: 0.88rem; font-weight: 700; color: #ff6b00; } .sd { font-size: 0.8rem; color: #6b6b8a; line-height: 1.5; margin-top: 0.3rem; } /* Tags + runtime badges */ .tag { display: inline-block; background: #16162a; border: 1px solid #2a2a50; color: #8b5cf6; font-family: 'Space Mono', monospace; font-size: 0.58rem; padding: 1px 7px; border-radius: 20px; margin: 2px 2px 0; } .rt { display:inline-block; font-family:'Space Mono',monospace; font-size:0.58rem; padding:1px 6px; border-radius:3px; margin-left:6px; background:#0f2018; color:#00ff88; border:1px solid #00ff8825; } .rt.node { background:#121a0a; color:#84cc16; border-color:#84cc1625; } .rt.ins { background:#12122a; color:#8b5cf6; border-color:#8b5cf625; } .rt.cld { background:#1e1208; color:#ff9500; border-color:#ff950025; } /* API / MCP boxes */ .api-row { background: #111118; border-left: 3px solid #ff6b00; padding: 0.52rem 1rem; margin: 0.32rem 0; border-radius: 0 6px 6px 0; font-family: 'Space Mono', monospace; font-size: 0.7rem; color: #c0c0d8; } .m-get { color: #00ff88; font-weight: 700; } .m-post { color: #ff9500; font-weight: 700; } .m-put { color: #60a5fa; font-weight: 700; } .m-del { color: #ff4444; font-weight: 700; } .mcp-box { background: #100a20; border: 1px solid #5b21b640; border-radius: 8px; padding: 0.75rem 1rem; margin: 0.45rem 0; font-family: 'Space Mono', monospace; font-size: 0.7rem; color: #c4b5fd; } .mcp-box code { color: #a78bfa; } .codebox { background: #0d0d1a; border: 1px solid #1e1e2e; border-radius: 6px; padding: 0.9rem 1rem; font-family: 'Space Mono', monospace; font-size: 0.68rem; color: #00ff88; white-space: pre; overflow-x: auto; line-height: 1.55; } .sec { font-family: 'Space Mono', monospace; color: #8b5cf6; font-size: 0.68rem; font-weight: 700; text-transform: uppercase; letter-spacing: 0.15em; margin: 1.1rem 0 0.45rem; } .info-note { font-family: 'Space Mono', monospace; font-size: 0.68rem; color: #4a4a6a; padding: 0.4rem 0; } """ PUBLISH_TEMPLATE = """{ "id": "my_skill", "name": "My Skill", "version": "1.0.0", "description": "What this skill does for an agent.", "author": "Chris4K", "tags": ["utility"], "runtime": "python", "dependencies": [], "env_required": [], "bins": [], "schema": { "input": { "text": "str" }, "output": { "result": "str" } }, "code": "def execute(text: str) -> dict:\\n return {'result': text.upper()}\\n" }""" SKILL_MD_TEMPLATE = """--- name: my-instructions-skill version: 1.0.0 description: Teach an agent how to do something step-by-step. author: Chris4K tags: [instructions, utility] runtime: instructions --- # My Instructions Skill ## Purpose Describe what capability this adds to an agent. ## When to use Use this skill when the user asks you to... ## Steps 1. First, do this 2. Then do that 3. Return the result ## Examples Input: "example" Output: "expected" """ # ─── HTML helpers ───────────────────────────────────────────────── def _rt(rt): rt = (rt or "python").lower() c = "node" if rt in ("node","nodejs","shell") else "ins" if rt=="instructions" else "cld" if "claude" in rt else "" return f'{rt}' def fmt_card(s): tags = "".join(f'{t}' for t in s.get("tags", [])) return f"""
⚙ {s['name']}{_rt(s.get('runtime','python'))} ↓{s.get('downloads',0)} · v{s['version']}
by {s['author']} · {s['id']}
{s['description'][:170]}{'…' if len(s['description'])>170 else ''}
{tags}
""" def render_list(skills): if not skills: return '
No skills found.
' return "".join(fmt_card(s) for s in skills) def make_header(): st = registry.get_stats() return f"""
Federated Open Registry for Generative Executables
Python · Node · SKILL.md · MCP · Claude Skills
{st['total_skills']}
Skills
{st['total_downloads']}
Downloads
{st['total_tags']}
Tags
MCP Live
""" # ─── Tab handlers ───────────────────────────────────────────────── def do_browse(q, tag): return render_list(registry.list_skills( tag=None if tag == "all" else tag, query=q.strip() or None )) def load_detail(skill_id): empty4 = ("", "", "", "") blank = '
Enter a skill ID above, then click 📂 Load.
' if not skill_id.strip(): return blank, *empty4 s = registry.get_skill(skill_id.strip()) if not s: return f'
Skill \'{skill_id}\' not found.
', *empty4 tags = " ".join(f'{t}' for t in s.get("tags", [])) env_w = f'
⚠ env vars needed: {", ".join(s["env_required"])}
' if s.get("env_required") else "" bins_i = f'
🔧 system bins: {", ".join(s["bins"])}
' if s.get("bins") else "" deps = ", ".join(s.get("dependencies", [])) or "none" meta = f"""
⚙ {s['name']}{_rt(s.get('runtime','python'))}
v{s['version']} · by {s['author']} · ↓{s.get('downloads',0)} downloads
{s['description']}
{tags}
{env_w}{bins_i}
Schema
{json.dumps(s.get("schema", {}), indent=2)}
Dependencies
{deps}
""" code = s.get("code", "# no code") instr = s.get("instructions", "_No SKILL.md instructions for this skill._") usage = f"""# ⚒️ Quick Start: {s['id']} # ───────────────────────────────────────────────── import requests, types def bootstrap_forge(url="https://huggingface.co/spaces/Chris4K/agent-forge"): r = requests.get(f"{{url}}/api/v1/skills/forge_client/code") m = types.ModuleType("forge_client") exec(r.json()["code"], m.__dict__) return m.ForgeClient(url) forge = bootstrap_forge() skill = forge.load("{s['id']}") result = skill.execute() # ← fill in params from schema above print(result) """ edit_j = json.dumps( {k: v for k, v in s.items() if k not in ("created_at","updated_at","downloads")}, indent=2, ensure_ascii=False ) return meta, code, instr, usage, edit_j def do_update(sid, ej): if not sid.strip(): return "⚠ Load a skill first" try: u = json.loads(ej) except json.JSONDecodeError as e: return f"❌ JSON error: {e}" ok, msg = registry.update_skill(sid.strip(), u) return ("✅ " if ok else "❌ ") + msg def do_delete(sid): if not sid.strip(): return "⚠ Load a skill first" ok, msg = registry.delete_skill(sid.strip()) return ("✅ " if ok else "❌ ") + msg def publish_json(jstr): try: s = json.loads(jstr) except json.JSONDecodeError as e: return f"❌ Invalid JSON: {e}" ok, msg = registry.publish_skill(s) return ("✅ " if ok else "❌ ") + msg def publish_md(mstr): s, err = registry.parse_skill_md(mstr) if err: return f"❌ {err}" ok, msg = registry.publish_skill(s) return ("✅ Published '" + s["id"] + "' — " if ok else "❌ ") + msg # ─── Build UI ───────────────────────────────────────────────────── def build_app(): theme = ForgeTheme() with gr.Blocks(title="⚒️ FORGE — Skill Artifactory", theme=theme) as demo: # Inject only our custom component CSS via ") # Header + stats bar gr.HTML(make_header()) with gr.Tabs(): # ── BROWSE ──────────────────────────────────────────── with gr.Tab("🔍 Browse"): with gr.Row(): search_in = gr.Textbox( placeholder="search name, tags, description…", label="Search", scale=3, container=True ) tag_dd = gr.Dropdown( choices=["all"] + registry.get_all_tags(), value="all", label="Tag", scale=1 ) srch_btn = gr.Button("🔍 Search", variant="primary") skills_out = gr.HTML(render_list(registry.list_skills())) srch_btn.click(do_browse, [search_in, tag_dd], skills_out) search_in.submit(do_browse, [search_in, tag_dd], skills_out) tag_dd.change(do_browse, [search_in, tag_dd], skills_out) # ── CRUD ───────────────────────────────────────────── with gr.Tab("⚙ Skill CRUD"): gr.HTML('
Load any skill by ID to view, edit, or delete it.
') sid_in = gr.Textbox( placeholder="e.g. calculator web_search claude_chat", label="Skill ID" ) with gr.Row(): load_btn = gr.Button("📂 Load", variant="primary") update_btn = gr.Button("💾 Save Changes", variant="secondary") delete_btn = gr.Button("🗑 Delete", variant="secondary") crud_msg = gr.Textbox(label="Status", interactive=False, max_lines=1) detail_meta = gr.HTML('
No skill loaded.
') with gr.Tabs(): with gr.Tab("📄 Code"): d_code = gr.Code(language="python", label="Executable code") with gr.Tab("📝 SKILL.md"): d_md = gr.Markdown() with gr.Tab("🤖 Usage"): d_use = gr.Code(language="python", label="Agent bootstrap") with gr.Tab("✏️ Edit JSON"): d_edit = gr.Code(language="json", label="Edit → Save Changes") load_btn.click(load_detail, sid_in, [detail_meta, d_code, d_md, d_use, d_edit]) sid_in.submit(load_detail, sid_in, [detail_meta, d_code, d_md, d_use, d_edit]) update_btn.click(do_update, [sid_in, d_edit], crud_msg) delete_btn.click(do_delete, sid_in, crud_msg) # ── PUBLISH ─────────────────────────────────────────── with gr.Tab("📦 Publish"): with gr.Tabs(): with gr.Tab("🐍 Python / JSON"): gr.HTML('
Must include def execute(...) → dict
') pj_in = gr.Code(value=PUBLISH_TEMPLATE, language="json", label="Skill JSON", lines=20) pj_btn = gr.Button("⚒ Publish JSON Skill", variant="primary") pj_out = gr.Textbox(label="Result", interactive=False) pj_btn.click(publish_json, pj_in, pj_out) with gr.Tab("📝 SKILL.md"): gr.HTML('
YAML frontmatter + markdown body. Compatible with ClawHub / OpenClaw.
') pm_in = gr.Code(value=SKILL_MD_TEMPLATE, language="markdown", label="SKILL.md", lines=20) pm_btn = gr.Button("⚒ Publish SKILL.md", variant="primary") pm_out = gr.Textbox(label="Result", interactive=False) pm_btn.click(publish_md, pm_in, pm_out) # ── MCP ─────────────────────────────────────────────── with gr.Tab("🔌 MCP"): gr.HTML(f"""
⚒️ FORGE MCP Server

Connect Claude Desktop, Claude API, or any MCP client directly to FORGE.

SSE Endpoint — Claude Desktop / mcp-remote
https://huggingface.co/spaces/Chris4K/agent-forge/mcp/sse
JSON-RPC Endpoint — direct API
https://huggingface.co/spaces/Chris4K/agent-forge/mcp
Claude Desktop Config
{{ "mcpServers": {{ "forge": {{ "command": "npx", "args": ["-y", "mcp-remote", "https://huggingface.co/spaces/Chris4K/agent-forge/mcp/sse"] }} }} }}
Anthropic API with MCP
import anthropic client = anthropic.Anthropic() response = client.beta.messages.create( model="claude-opus-4-6", max_tokens=1024, mcp_servers=[{{ "type": "url", "url": "https://huggingface.co/spaces/Chris4K/agent-forge/mcp/sse", "name": "forge" }}], messages=[{{"role": "user", "content": "List all skills in FORGE, load the calculator, compute 2**32"}}] )
Available MCP Tools
forge_list_skills · forge_get_skill · forge_get_code · forge_search · forge_publish_skill · forge_get_stats
""") # ── API ─────────────────────────────────────────────── with gr.Tab("📡 API"): gr.HTML(f"""
REST API v2

Base: https://huggingface.co/spaces/Chris4K/agent-forge

GET /api/v1/skills             List skills   ?tag= ?q=
GET /api/v1/skills/{{id}}         Get skill + code
GET /api/v1/skills/{{id}}/code    Minimal hot-load payload
GET /api/v1/skills/{{id}}/download  Download as .py
POST /api/v1/skills             Publish new skill
PUT /api/v1/skills/{{id}}         Update skill
DEL /api/v1/skills/{{id}}         Delete skill
GET /api/v1/search?q=           Full-text search
GET /api/v1/manifest            All skills + code (offline)
GET /mcp/sse                    MCP SSE stream
POST /mcp                        MCP JSON-RPC
Agent Bootstrap
import requests, types def bootstrap_forge(url="https://huggingface.co/spaces/Chris4K/agent-forge"): r = requests.get(f"{{url}}/api/v1/skills/forge_client/code") m = types.ModuleType("forge_client") exec(r.json()["code"], m.__dict__) return m.ForgeClient(url) forge = bootstrap_forge() calc = forge.load("calculator") # pure Python math search = forge.load("web_search") # DuckDuckGo, no key needed memory = forge.load("memory_store") # KV store with TTL fetch = forge.load("http_fetch") # web page scraper claude = forge.load("claude_chat") # needs ANTHROPIC_API_KEY print(calc.execute(expression="2**32")) print(search.execute(query="AI news today", max_results=3)) memory.execute(action="set", key="goal", value="build JARVIS", ttl=3600) reply = claude.execute(prompt="Explain MCP in one paragraph")
""") # ── HOWTO ───────────────────────────────────────────── with gr.Tab("📖 How-To"): p = Path(__file__).parent / "HOWTO.md" gr.Markdown(p.read_text(encoding="utf-8") if p.exists() else "HOWTO.md not found") gr.HTML('
⚒️ FORGE v2.2 · ki-fusion-labs.de · Chris4K · MIT
') return demo # ─── Mount ──────────────────────────────────────────────────────── demo = build_app() app = gr.mount_gradio_app(api, demo, path="/") if __name__ == "__main__": demo.launch(server_name="0.0.0.0", server_port=7860, ssr_mode=False)