skill-forge / app.py
WhySoCodius's picture
Skill Forge: static browser app + Python/MCP port for Agent Skills
4fa5831 verified
Raw
History Blame Contribute Delete
4.46 kB
"""
Skill Forge — a Gradio app + MCP server for authoring Agent Skills.
Tabs: validate a SKILL.md, score its `description` for trigger reliability, and
scaffold a new skill. The same three functions are exposed as MCP tools
(`skill_validate`, `skill_lint_description`, `skill_scaffold`) so an agent can
call them while it writes skills for itself.
Run locally: python app.py (UI on :7860, MCP at /gradio_api/mcp/sse)
"""
import gradio as gr
from skillforge import validate_skill, lint_description, scaffold_skill, package_skill
EXAMPLE = """---
name: changelog-writer
description: Drafts a release changelog from merged PRs. Use when the user asks to \
"write the changelog", "summarize what shipped", or prep release notes for a tag.
---
# Changelog Writer
## When to use this skill
- The user is cutting a release and wants notes grouped by type.
## Instructions
1. Collect merged PRs since the last tag.
2. Group as Features / Fixes / Internal and write one line each.
"""
def skill_validate(skill_md: str) -> str:
"""Validate a SKILL.md against the Agent Skill format.
Args:
skill_md: Full text of a SKILL.md file (YAML frontmatter + Markdown body).
Returns:
A Markdown report listing errors, warnings, and info.
"""
return validate_skill(skill_md).as_markdown()
def skill_lint_description(description: str) -> str:
"""Score a skill `description` for how reliably an agent will trigger on it.
Args:
description: The frontmatter `description` string on its own.
Returns:
A Markdown report ending with 'Trigger score: N/100'.
"""
return lint_description(description).as_markdown()
def skill_scaffold(name: str, description: str, when_to_use: str = "") -> str:
"""Generate a ready-to-edit SKILL.md for a new Agent Skill.
Args:
name: kebab-case skill name, e.g. 'pdf-form-filler'.
description: What the skill does and when to use it.
when_to_use: Optional extra trigger examples.
Returns:
The full text of a SKILL.md file.
"""
return scaffold_skill(name, description, when_to_use)
def _package(skill_md: str):
data = package_skill(skill_md)
path = "skill.zip"
with open(path, "wb") as f:
f.write(data)
return path
with gr.Blocks(title="Skill Forge") as demo:
gr.Markdown(
"# 🛠️ Skill Forge\n"
"Validate, lint, and scaffold **Agent Skills** (`SKILL.md`). "
"Also an MCP server — point your agent at `/gradio_api/mcp/sse`."
)
with gr.Tab("Validate"):
md_in = gr.Code(value=EXAMPLE, language="yaml", label="SKILL.md", lines=20)
with gr.Row():
v_btn = gr.Button("Validate", variant="primary")
z_btn = gr.Button("Package as .zip")
v_out = gr.Markdown()
z_out = gr.File(label="Packaged skill", visible=True)
v_btn.click(skill_validate, md_in, v_out, api_name="skill_validate")
z_btn.click(_package, md_in, z_out)
with gr.Tab("Lint description"):
d_in = gr.Textbox(
label="Frontmatter `description`", lines=4,
value=('Drafts a release changelog from merged PRs. Use when the user '
'asks to "write the changelog" or prep release notes.'))
d_btn = gr.Button("Score it", variant="primary")
d_out = gr.Markdown()
d_btn.click(skill_lint_description, d_in, d_out,
api_name="skill_lint_description")
with gr.Tab("Scaffold"):
s_name = gr.Textbox(label="name (kebab-case)", value="terraform-plan-reviewer")
s_desc = gr.Textbox(label="description", lines=3,
value="Reviews a Terraform plan and summarizes drift and risk.")
s_when = gr.Textbox(label="when to use (optional)",
value='the user runs "terraform plan" and wants a risk summary')
s_btn = gr.Button("Generate SKILL.md", variant="primary")
s_out = gr.Code(language="yaml", label="SKILL.md")
s_btn.click(skill_scaffold, [s_name, s_desc, s_when], s_out,
api_name="skill_scaffold")
gr.Markdown(
"---\nRules checked: frontmatter parses as YAML · `name` kebab-case ≤64 · "
"`description` present ≤1024 with a trigger cue · non-empty structured body · "
"relative bundled-file links. MIT licensed."
)
if __name__ == "__main__":
demo.launch(mcp_server=True)