From a8b7c9d08b34c8f96e5978558cd0e1d4c1d5d00d Mon Sep 17 00:00:00 2001 From: Evan Hu Date: Sat, 21 Mar 2026 02:46:04 +0900 Subject: [PATCH] feat: comprehensive registry improvements MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Community docs: - CODE_OF_CONDUCT.md (Contributor Covenant v2.1) - SECURITY.md (vulnerability reporting policy) - CHANGELOG.md (initial release notes) - CODEOWNERS (per-type review ownership) GitHub config: - Issue templates: bug-report, pricing-correction, documentation - FUNDING.yml (GitHub Sponsors) Validation enhancements: - Cross-reference check: hand [[requires]] → integration existence - Routing alias collisions as warnings (errors with --strict) - --strict flag to promote warnings to errors - --type filter to validate single content type - CI: add taplo format check and lychee link check jobs Developer experience: - Makefile with validate, fmt, and scaffold targets - Scaffold templates for all 5 content types - .pre-commit-config.yaml (trailing whitespace, TOML check, validate) - docs/content-guide.md (naming, descriptions, prompts, decision guide) Content quality: - schema.toml: add last_verified field for model pricing - CONTRIBUTING.md: add pricing verification guide with source links --- .github/FUNDING.yml | 1 + .github/ISSUE_TEMPLATE/bug-report.yml | 49 ++++++++ .github/ISSUE_TEMPLATE/documentation.yml | 23 ++++ .github/ISSUE_TEMPLATE/pricing-correction.yml | 44 +++++++ .github/workflows/validate.yml | 27 +++++ .pre-commit-config.yaml | 16 +++ CHANGELOG.md | 23 ++++ CODEOWNERS | 12 ++ CODE_OF_CONDUCT.md | 29 +++++ CONTRIBUTING.md | 25 ++++ Makefile | 105 ++++++++++++++++ SECURITY.md | 34 ++++++ docs/content-guide.md | 50 ++++++++ schema.toml | 1 + scripts/validate.py | 113 +++++++++++++++--- templates/HAND.toml | 33 +++++ templates/agent.toml | 32 +++++ templates/integration.toml | 29 +++++ templates/provider.toml | 22 ++++ templates/skill.toml | 20 ++++ 20 files changed, 670 insertions(+), 18 deletions(-) create mode 100644 .github/FUNDING.yml create mode 100644 .github/ISSUE_TEMPLATE/bug-report.yml create mode 100644 .github/ISSUE_TEMPLATE/documentation.yml create mode 100644 .github/ISSUE_TEMPLATE/pricing-correction.yml create mode 100644 .pre-commit-config.yaml create mode 100644 CHANGELOG.md create mode 100644 CODEOWNERS create mode 100644 CODE_OF_CONDUCT.md create mode 100644 Makefile create mode 100644 SECURITY.md create mode 100644 docs/content-guide.md create mode 100644 templates/HAND.toml create mode 100644 templates/agent.toml create mode 100644 templates/integration.toml create mode 100644 templates/provider.toml create mode 100644 templates/skill.toml diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml new file mode 100644 index 0000000..3ed0602 --- /dev/null +++ b/.github/FUNDING.yml @@ -0,0 +1 @@ +github: [librefang] diff --git a/.github/ISSUE_TEMPLATE/bug-report.yml b/.github/ISSUE_TEMPLATE/bug-report.yml new file mode 100644 index 0000000..da43c9f --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug-report.yml @@ -0,0 +1,49 @@ +name: Bug Report +description: Report an issue with existing registry content (wrong data, broken config, etc.) +labels: ["bug"] +body: + - type: dropdown + id: content_type + attributes: + label: Content Type + description: What type of registry content has the issue? + options: + - Provider + - Agent + - Hand + - Integration (MCP server) + - Skill + - Plugin + validations: + required: true + + - type: input + id: content_name + attributes: + label: Content Name + description: The name or ID of the affected content. + placeholder: e.g. openai, code-review-agent, github + validations: + required: true + + - type: textarea + id: whats_wrong + attributes: + label: What's Wrong + description: Describe the issue you found. + placeholder: | + e.g. The integration command listed in the registry fails to install, + or the agent config references a model that doesn't exist. + validations: + required: true + + - type: textarea + id: expected_vs_actual + attributes: + label: Expected vs Actual + description: What did you expect, and what actually happened? + placeholder: | + Expected: The provider config should list gpt-4o as a supported model. + Actual: gpt-4o is missing from the models array. + validations: + required: true diff --git a/.github/ISSUE_TEMPLATE/documentation.yml b/.github/ISSUE_TEMPLATE/documentation.yml new file mode 100644 index 0000000..2c4caa9 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/documentation.yml @@ -0,0 +1,23 @@ +name: Documentation Issue +description: Report incorrect, incomplete, or missing documentation +labels: ["documentation"] +body: + - type: input + id: which_document + attributes: + label: Which Document + description: The file path or page where the issue exists. + placeholder: e.g. README.md, docs/contributing.md, providers/openai.toml + validations: + required: true + + - type: textarea + id: whats_wrong + attributes: + label: What's Wrong or Missing + description: Describe the documentation issue. + placeholder: | + e.g. The contributing guide doesn't explain how to add a new provider, + or the README setup instructions are outdated. + validations: + required: true diff --git a/.github/ISSUE_TEMPLATE/pricing-correction.yml b/.github/ISSUE_TEMPLATE/pricing-correction.yml new file mode 100644 index 0000000..c738a3d --- /dev/null +++ b/.github/ISSUE_TEMPLATE/pricing-correction.yml @@ -0,0 +1,44 @@ +name: Pricing Correction +description: Report outdated or incorrect model pricing +labels: ["pricing"] +body: + - type: input + id: provider + attributes: + label: Provider Name + description: Which provider's pricing needs correction? + placeholder: e.g. openai, anthropic, groq + validations: + required: true + + - type: input + id: model_id + attributes: + label: Model ID + description: The canonical model identifier. + placeholder: e.g. gpt-4o, claude-sonnet-4-20250514 + validations: + required: true + + - type: input + id: current_pricing + attributes: + label: Current Pricing in Registry + description: What does the registry currently show? + placeholder: "e.g. Input: $5.00 / 1M tokens, Output: $15.00 / 1M tokens" + validations: + required: true + + - type: textarea + id: correct_pricing + attributes: + label: Correct Pricing (with source) + description: | + Provide the correct pricing and a link to the official source + so we can verify the update. + placeholder: | + Input: $2.50 / 1M tokens + Output: $10.00 / 1M tokens + Source: https://openai.com/pricing + validations: + required: true diff --git a/.github/workflows/validate.yml b/.github/workflows/validate.yml index 98a277d..d78ae86 100644 --- a/.github/workflows/validate.yml +++ b/.github/workflows/validate.yml @@ -18,3 +18,30 @@ jobs: - name: Validate registry run: python scripts/validate.py + + toml-format-check: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v5 + + - name: Install taplo + run: | + curl -fsSL https://github.com/tamasfe/taplo/releases/latest/download/taplo-full-linux-x86_64.gz \ + | gunzip > /usr/local/bin/taplo + chmod +x /usr/local/bin/taplo + + - name: Check TOML formatting + continue-on-error: true + run: taplo fmt --check **/*.toml + + markdown-link-check: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v5 + + - name: Check markdown links + uses: lycheeverse/lychee-action@v2 + with: + args: --verbose --no-progress '**/*.md' + fail: false + continue-on-error: true diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml new file mode 100644 index 0000000..830b5ee --- /dev/null +++ b/.pre-commit-config.yaml @@ -0,0 +1,16 @@ +repos: + - repo: https://github.com/pre-commit/pre-commit-hooks + rev: v5.0.0 + hooks: + - id: trailing-whitespace + - id: end-of-file-fixer + - id: check-toml + + - repo: local + hooks: + - id: validate-registry + name: Validate registry TOML files + entry: python3 scripts/validate.py + language: system + pass_filenames: false + always_run: true diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..9f4d119 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,23 @@ +# Changelog + +All notable changes to this project will be documented in this file. + +The format is based on [Keep a Changelog](https://keepachangelog.com/). + +## [Unreleased] + +## [1.0.0] - 2026-03-21 + +### Added + +- 46 LLM providers with 220+ model definitions +- 33 agent definitions across diverse domains +- 14 hand definitions (communication, content, data, development, productivity) +- 25 MCP integration templates +- 2 skill examples (prompt-only and Python) +- 1 plugin example (echo-memory) +- Validation script with per-type and cross-type checks +- CI workflow for automated validation +- README documentation for every directory and subdirectory +- CONTRIBUTING guide with per-type examples and checklists +- Schema reference (schema.toml) diff --git a/CODEOWNERS b/CODEOWNERS new file mode 100644 index 0000000..b4155da --- /dev/null +++ b/CODEOWNERS @@ -0,0 +1,12 @@ +# LibreFang Registry — Code Owners +# Each line maps a path pattern to the team responsible for review. + +providers/ @librefang/models +agents/ @librefang/agents +hands/ @librefang/agents +integrations/ @librefang/integrations +skills/ @librefang/agents +plugins/ @librefang/agents +scripts/ @librefang/core +.github/ @librefang/core +*.md @librefang/docs diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md new file mode 100644 index 0000000..520ebd2 --- /dev/null +++ b/CODE_OF_CONDUCT.md @@ -0,0 +1,29 @@ +# Code of Conduct + +## Our Pledge + +We pledge to make participation in our community a harassment-free experience for everyone, regardless of age, body size, disability, ethnicity, gender identity and expression, level of experience, nationality, personal appearance, race, religion, or sexual identity and orientation. + +## Our Standards + +Examples of behavior that contributes to a positive environment: + +- Using welcoming and inclusive language +- Being respectful of differing viewpoints and experiences +- Gracefully accepting constructive criticism +- Focusing on what is best for the community + +Examples of unacceptable behavior: + +- Trolling, insulting/derogatory comments, and personal or political attacks +- Public or private harassment +- Publishing others' private information without explicit permission +- Other conduct which could reasonably be considered inappropriate + +## Enforcement + +Instances of unacceptable behavior may be reported to **team@librefang.dev**. All complaints will be reviewed and investigated promptly and fairly. + +## Attribution + +This Code of Conduct is adapted from the [Contributor Covenant](https://www.contributor-covenant.org/), version 2.1. diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index c11c34b..9f41a34 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -222,6 +222,31 @@ aliases = ["short-name"] - [ ] `context_window` and `max_output_tokens` are positive integers - [ ] Boolean capability fields are correct - [ ] Pricing verified from official source +- [ ] `last_verified` date included (ISO format, e.g. `2025-03-15`) + +### Pricing Verification + +**Always verify pricing from official sources before submitting.** Model pricing changes frequently and stale data leads to incorrect cost tracking for users. + +When adding or updating model pricing: + +1. Check the provider's official pricing page (see links below) +2. Record the exact `input_cost_per_m` and `output_cost_per_m` values in USD per million tokens +3. Include the `last_verified` field with today's date in ISO format (`YYYY-MM-DD`) +4. If a model is subscription-based (e.g. GitHub Copilot) or has no public per-token pricing, note this in your PR description + +Common official pricing pages: + +- **OpenAI**: https://openai.com/pricing +- **Anthropic**: https://docs.anthropic.com/en/docs/about-claude/models +- **Google Gemini**: https://ai.google.dev/pricing +- **DeepSeek**: https://platform.deepseek.com/api-docs/pricing +- **Mistral**: https://mistral.ai/technology/#pricing +- **Groq**: https://wow.groq.com/ +- **xAI**: https://docs.x.ai/docs +- **Together**: https://www.together.ai/pricing +- **Fireworks**: https://fireworks.ai/pricing +- **OpenRouter**: https://openrouter.ai/models (per-model pricing listed) ## Where to Find Model Information diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..73e1a63 --- /dev/null +++ b/Makefile @@ -0,0 +1,105 @@ +# LibreFang Registry — Development Tooling +# Usage: make help + +.DEFAULT_GOAL := help +SHELL := /bin/bash + +TEMPLATES_DIR := templates +AGENTS_DIR := agents +HANDS_DIR := hands +INTEGRATIONS_DIR := integrations +SKILLS_DIR := skills +PROVIDERS_DIR := providers + +# ── Validation ──────────────────────────────────────────────────────────────── + +.PHONY: validate +validate: ## Run registry validation + python3 scripts/validate.py + +.PHONY: validate-strict +validate-strict: ## Run validation treating warnings as errors + python3 scripts/validate.py --strict + +# ── Formatting ──────────────────────────────────────────────────────────────── + +.PHONY: fmt +fmt: ## Format all TOML files with taplo (skips if not installed) + @if command -v taplo >/dev/null 2>&1; then \ + echo "Formatting TOML files..."; \ + find . -name '*.toml' -not -path './.git/*' -exec taplo fmt {} +; \ + echo "Done."; \ + else \ + echo "taplo not found — skipping TOML formatting."; \ + echo "Install: cargo install taplo-cli or brew install taplo"; \ + fi + +.PHONY: fmt-check +fmt-check: ## Check TOML formatting without modifying + @if command -v taplo >/dev/null 2>&1; then \ + taplo fmt --check $$(find . -name '*.toml' -not -path './.git/*'); \ + else \ + echo "taplo not found — skipping format check."; \ + echo "Install: cargo install taplo-cli or brew install taplo"; \ + fi + +# ── Scaffolding ─────────────────────────────────────────────────────────────── + +.PHONY: new-agent +new-agent: ## Scaffold a new agent (usage: make new-agent NAME=my-agent) + @if [ -z "$(NAME)" ]; then echo "ERROR: NAME is required. Usage: make new-agent NAME=my-agent"; exit 1; fi + @if [ -d "$(AGENTS_DIR)/$(NAME)" ]; then echo "ERROR: Agent '$(NAME)' already exists."; exit 1; fi + @mkdir -p "$(AGENTS_DIR)/$(NAME)" + @sed 's/{{NAME}}/$(NAME)/g' "$(TEMPLATES_DIR)/agent.toml" > "$(AGENTS_DIR)/$(NAME)/agent.toml" + @echo "Created $(AGENTS_DIR)/$(NAME)/agent.toml" + @echo "Next: edit the agent.toml to fill in TODO placeholders." + +.PHONY: new-hand +new-hand: ## Scaffold a new hand (usage: make new-hand NAME=my-hand) + @if [ -z "$(NAME)" ]; then echo "ERROR: NAME is required. Usage: make new-hand NAME=my-hand"; exit 1; fi + @if [ -d "$(HANDS_DIR)/$(NAME)" ]; then echo "ERROR: Hand '$(NAME)' already exists."; exit 1; fi + @mkdir -p "$(HANDS_DIR)/$(NAME)" + @sed 's/{{NAME}}/$(NAME)/g' "$(TEMPLATES_DIR)/HAND.toml" > "$(HANDS_DIR)/$(NAME)/HAND.toml" + @echo "Created $(HANDS_DIR)/$(NAME)/HAND.toml" + @echo "Next: edit the HAND.toml to fill in TODO placeholders." + +.PHONY: new-integration +new-integration: ## Scaffold a new integration (usage: make new-integration NAME=my-service) + @if [ -z "$(NAME)" ]; then echo "ERROR: NAME is required. Usage: make new-integration NAME=my-service"; exit 1; fi + @if [ -f "$(INTEGRATIONS_DIR)/$(NAME).toml" ]; then echo "ERROR: Integration '$(NAME)' already exists."; exit 1; fi + @sed 's/{{NAME}}/$(NAME)/g' "$(TEMPLATES_DIR)/integration.toml" > "$(INTEGRATIONS_DIR)/$(NAME).toml" + @echo "Created $(INTEGRATIONS_DIR)/$(NAME).toml" + @echo "Next: edit the file to fill in TODO placeholders." + +.PHONY: new-skill +new-skill: ## Scaffold a new skill (usage: make new-skill NAME=my-skill) + @if [ -z "$(NAME)" ]; then echo "ERROR: NAME is required. Usage: make new-skill NAME=my-skill"; exit 1; fi + @if [ -d "$(SKILLS_DIR)/$(NAME)" ]; then echo "ERROR: Skill '$(NAME)' already exists."; exit 1; fi + @mkdir -p "$(SKILLS_DIR)/$(NAME)" + @sed 's/{{NAME}}/$(NAME)/g' "$(TEMPLATES_DIR)/skill.toml" > "$(SKILLS_DIR)/$(NAME)/skill.toml" + @echo "Created $(SKILLS_DIR)/$(NAME)/skill.toml" + @echo "Next: edit the skill.toml to fill in TODO placeholders." + +.PHONY: new-provider +new-provider: ## Scaffold a new provider (usage: make new-provider NAME=my-provider) + @if [ -z "$(NAME)" ]; then echo "ERROR: NAME is required. Usage: make new-provider NAME=my-provider"; exit 1; fi + @if [ -f "$(PROVIDERS_DIR)/$(NAME).toml" ]; then echo "ERROR: Provider '$(NAME)' already exists."; exit 1; fi + @sed 's/{{NAME}}/$(NAME)/g' "$(TEMPLATES_DIR)/provider.toml" > "$(PROVIDERS_DIR)/$(NAME).toml" + @echo "Created $(PROVIDERS_DIR)/$(NAME).toml" + @echo "Next: edit the file to fill in TODO placeholders." + +# ── Help ────────────────────────────────────────────────────────────────────── + +.PHONY: help +help: ## Show this help + @echo "LibreFang Registry — Available targets:" + @echo "" + @grep -E '^[a-zA-Z_-]+:.*?## .*$$' $(MAKEFILE_LIST) | \ + awk 'BEGIN {FS = ":.*?## "}; {printf " \033[36m%-20s\033[0m %s\n", $$1, $$2}' + @echo "" + @echo "Scaffold examples:" + @echo " make new-agent NAME=my-agent" + @echo " make new-hand NAME=my-hand" + @echo " make new-integration NAME=my-service" + @echo " make new-skill NAME=my-skill" + @echo " make new-provider NAME=my-provider" diff --git a/SECURITY.md b/SECURITY.md new file mode 100644 index 0000000..7bf2894 --- /dev/null +++ b/SECURITY.md @@ -0,0 +1,34 @@ +# Security Policy + +## Scope + +This repository contains TOML content definitions (agents, hands, integrations, skills, plugins, providers). Security concerns include: + +- Malicious content in system prompts or descriptions +- Command injection in integration transport commands +- Integration URLs pointing to phishing or malicious sites +- Credential exposure in TOML files + +## Reporting a Vulnerability + +**Do NOT open a public issue for security vulnerabilities.** + +Email **security@librefang.dev** with: + +1. Description of the vulnerability +2. Affected file(s) and content type +3. Steps to reproduce or exploit +4. Suggested fix (if any) + +## Response Timeline + +- **Acknowledgment**: within 48 hours +- **Assessment**: within 5 business days +- **Fix**: dependent on severity, typically within 2 weeks + +## Supported Versions + +| Version | Supported | +|---------|-----------| +| main branch | Yes | +| Other branches | No | diff --git a/docs/content-guide.md b/docs/content-guide.md new file mode 100644 index 0000000..ddee788 --- /dev/null +++ b/docs/content-guide.md @@ -0,0 +1,50 @@ +# Content Guide + +Guidelines for contributing content to the LibreFang Registry. + +## Naming Conventions + +- Use **lowercase, hyphenated** names: `my-agent`, `web-scraper`, `code-reviewer`. +- Agent/hand/skill directory names must match their `name`/`id` field in the TOML. +- Provider and integration filenames must match their `id` field. + +## Writing Descriptions + +- Keep descriptions to **1-2 sentences**. Lead with a verb. +- Good: "Analyzes pull requests and suggests improvements." +- Bad: "This is an agent that can be used to analyze pull requests." + +## System Prompts (Agents & Hands) + +- Start with a clear role statement: "You are X, responsible for Y." +- Include specific instructions on behavior, not vague aspirations. +- Define what the agent should **not** do (scope boundaries). +- List tools it should use and when. +- Aim for **100-500 words** for agents, up to 1000 for complex hands. +- Avoid repeating information already in the TOML metadata. + +## Agent vs Hand vs Skill + +| Type | Use When | +|------|----------| +| **Agent** | Conversational, general-purpose, stateless Q&A or analysis. | +| **Hand** | Multi-step workflow requiring tools (shell, files, APIs). Has settings, dashboard, requirements. | +| **Skill** | Single focused task with defined inputs/outputs. Prompt-only or a short script. | + +- If it needs `shell_exec` or external tools, it is probably a **hand**. +- If it is a reusable prompt template with parameters, it is a **skill**. +- If it is a conversational assistant for a domain, it is an **agent**. + +## Provider Entries + +- Include all models the provider offers that support chat completions. +- Use accurate `input_cost_per_m` / `output_cost_per_m` (USD per million tokens). +- Set `tier` honestly: `frontier` is reserved for the most capable models. +- Always include `context_window` and `max_output_tokens` from official docs. + +## General Tips + +- Run `make validate` before submitting. +- Run `make fmt` if you have taplo installed to keep TOML formatting consistent. +- Check `schema.toml` for the full field reference. +- Test scaffold output: `make new-agent NAME=test-agent`, verify, then delete. diff --git a/schema.toml b/schema.toml index 3dad453..3d5b0ac 100644 --- a/schema.toml +++ b/schema.toml @@ -27,6 +27,7 @@ context_window = 128000 # Required: maximum input tokens max_output_tokens = 16384 # Required: maximum output tokens input_cost_per_m = 2.50 # Required: USD per million input tokens (0.0 for free/local) output_cost_per_m = 10.0 # Required: USD per million output tokens (0.0 for free/local) +last_verified = "2025-03-15" # Optional: ISO date when pricing was last verified against official sources supports_tools = true # Optional: tool/function calling support (default: false) supports_vision = true # Optional: vision/image input support (default: false) supports_streaming = true # Optional: streaming response support (default: true) diff --git a/scripts/validate.py b/scripts/validate.py index 20b694b..047fbb7 100755 --- a/scripts/validate.py +++ b/scripts/validate.py @@ -3,6 +3,9 @@ Usage: python scripts/validate.py + python scripts/validate.py --strict + python scripts/validate.py --type providers + python scripts/validate.py --strict --type agents Validates: - Provider TOML files (required fields, valid tiers, non-negative costs, no duplicates) @@ -11,11 +14,13 @@ Validates: - Integration TOML files (required fields: id, name, [transport]) - Skill TOML files (required fields: [skill].name, [runtime].type) - aliases.toml parsing + - Cross-reference: hand [[requires]] integration references + - Routing alias collision detection (WARNING, ERROR with --strict) Exit code 0 on success, 1 on any validation error. """ -import os +import argparse import sys from pathlib import Path @@ -42,6 +47,8 @@ REQUIRED_MODEL_FIELDS = { "input_cost_per_m", "output_cost_per_m", } +VALID_CONTENT_TYPES = {"providers", "agents", "hands", "integrations", "skills", "plugins", "aliases"} + def load_toml(filepath: Path) -> tuple[dict | None, str | None]: """Load a TOML file, return (data, error).""" @@ -126,8 +133,14 @@ def validate_agent_file(filepath: Path) -> list[str]: return errors -def validate_hand_file(filepath: Path) -> list[str]: - """Validate a HAND.toml file.""" +def validate_hand_file(filepath: Path, integration_ids: set[str]) -> list[str]: + """Validate a HAND.toml file. + + Args: + filepath: Path to the HAND.toml file. + integration_ids: Set of known integration IDs (filenames without .toml + from the integrations/ directory) for cross-reference validation. + """ data, err = load_toml(filepath) if err: return [err] @@ -152,6 +165,19 @@ def validate_hand_file(filepath: Path) -> list[str]: if "agent" not in data: errors.append(f"{rel}: Missing [agent] section") + # Cross-reference: check that [[requires]] with requirement_type = "integration" + # reference existing integration TOML files + requires_list = data.get("requires", []) + if isinstance(requires_list, list): + for req in requires_list: + if isinstance(req, dict) and req.get("requirement_type") == "integration": + req_key = req.get("key", "") + if req_key and req_key not in integration_ids: + errors.append( + f"{rel}: [[requires]] references integration '{req_key}' " + f"but no integrations/{req_key}.toml exists" + ) + return errors @@ -233,16 +259,52 @@ def validate_aliases_file(filepath: Path) -> list[str]: return errors +def parse_args() -> argparse.Namespace: + """Parse command-line arguments.""" + parser = argparse.ArgumentParser( + description="Validate all TOML files in the LibreFang registry." + ) + parser.add_argument( + "--strict", + action="store_true", + help="Treat all warnings as errors.", + ) + parser.add_argument( + "--type", + choices=sorted(VALID_CONTENT_TYPES), + dest="content_type", + default=None, + help="Validate only the specified content type (e.g. providers, agents, hands).", + ) + return parser.parse_args() + + +def should_validate(content_type: str, filter_type: str | None) -> bool: + """Check whether a content type should be validated given the --type filter.""" + if filter_type is None: + return True + return content_type == filter_type + + def main(): + args = parse_args() + script_dir = Path(__file__).resolve().parent root = script_dir.parent all_errors = [] stats = {} + # --- Collect integration IDs for cross-reference validation --- + integrations_dir = root / "integrations" + integration_ids: set[str] = set() + if integrations_dir.is_dir(): + for fp in integrations_dir.glob("*.toml"): + integration_ids.add(fp.stem) + # --- Providers --- providers_dir = root / "providers" - if providers_dir.is_dir(): + if providers_dir.is_dir() and should_validate("providers", args.content_type): toml_files = sorted(providers_dir.glob("*.toml")) total_models = 0 global_model_ids = {} @@ -273,7 +335,7 @@ def main(): # --- Agents --- agents_dir = root / "agents" - if agents_dir.is_dir(): + if agents_dir.is_dir() and should_validate("agents", args.content_type): agent_dirs = sorted([d for d in agents_dir.iterdir() if d.is_dir() and not d.name.startswith(".")]) for d in agent_dirs: agent_toml = d / "agent.toml" @@ -285,19 +347,18 @@ def main(): # --- Hands --- hands_dir = root / "hands" - if hands_dir.is_dir(): + if hands_dir.is_dir() and should_validate("hands", args.content_type): hand_dirs = sorted([d for d in hands_dir.iterdir() if d.is_dir() and not d.name.startswith(".")]) for d in hand_dirs: hand_toml = d / "HAND.toml" if hand_toml.exists(): - all_errors.extend(validate_hand_file(hand_toml)) + all_errors.extend(validate_hand_file(hand_toml, integration_ids)) else: all_errors.append(f"hands/{d.name}: Missing HAND.toml") stats["hands"] = len(hand_dirs) # --- Integrations --- - integrations_dir = root / "integrations" - if integrations_dir.is_dir(): + if integrations_dir.is_dir() and should_validate("integrations", args.content_type): int_files = sorted(integrations_dir.glob("*.toml")) for fp in int_files: all_errors.extend(validate_integration_file(fp)) @@ -305,7 +366,7 @@ def main(): # --- Skills --- skills_dir = root / "skills" - if skills_dir.is_dir(): + if skills_dir.is_dir() and should_validate("skills", args.content_type): skill_dirs = sorted([d for d in skills_dir.iterdir() if d.is_dir() and not d.name.startswith(".")]) for d in skill_dirs: skill_toml = d / "skill.toml" @@ -317,7 +378,7 @@ def main(): # --- Plugins --- plugins_dir = root / "plugins" - if plugins_dir.is_dir(): + if plugins_dir.is_dir() and should_validate("plugins", args.content_type): plugin_dirs = sorted([d for d in plugins_dir.iterdir() if d.is_dir() and not d.name.startswith(".")]) for d in plugin_dirs: plugin_toml = d / "plugin.toml" @@ -342,14 +403,18 @@ def main(): stats["plugins"] = len(plugin_dirs) # --- Aliases --- - all_errors.extend(validate_aliases_file(root / "aliases.toml")) + if should_validate("aliases", args.content_type): + all_errors.extend(validate_aliases_file(root / "aliases.toml")) - # --- Cross-type: duplicate routing aliases --- + # --- Cross-type: duplicate routing aliases (now treated as ERRORS) --- + # Only run this cross-type check when no --type filter is active, + # or when filtering to agents or hands specifically. all_aliases = {} # alias -> (type, name) - warnings = [] + alias_collisions = [] + run_alias_check = args.content_type is None or args.content_type in ("agents", "hands") # Collect agent routing aliases - if agents_dir.is_dir(): + if run_alias_check and agents_dir.is_dir(): for d in sorted([d for d in agents_dir.iterdir() if d.is_dir() and not d.name.startswith(".")]): data, _ = load_toml(d / "agent.toml") if data: @@ -358,12 +423,12 @@ def main(): key = alias.lower() if key in all_aliases: prev_type, prev_name = all_aliases[key] - warnings.append(f"Routing alias '{alias}' used by both {prev_type}/{prev_name} and agent/{d.name}") + alias_collisions.append(f"Routing alias '{alias}' used by both {prev_type}/{prev_name} and agent/{d.name}") else: all_aliases[key] = ("agent", d.name) # Collect hand routing aliases - if hands_dir.is_dir(): + if run_alias_check and hands_dir.is_dir(): for d in sorted([d for d in hands_dir.iterdir() if d.is_dir() and not d.name.startswith(".")]): data, _ = load_toml(d / "HAND.toml") if data: @@ -372,13 +437,25 @@ def main(): key = alias.lower() if key in all_aliases: prev_type, prev_name = all_aliases[key] - warnings.append(f"Routing alias '{alias}' used by both {prev_type}/{prev_name} and hand/{d.name}") + alias_collisions.append(f"Routing alias '{alias}' used by both {prev_type}/{prev_name} and hand/{d.name}") else: all_aliases[key] = ("hand", d.name) + # --- Collect warnings --- + warnings = list(alias_collisions) + + # --- In --strict mode, promote warnings to errors --- + if args.strict and warnings: + all_errors.extend(warnings) + warnings = [] + # --- Report --- print("=" * 60) print("LibreFang Registry Validation") + if args.content_type: + print(f" Filter: --type {args.content_type}") + if args.strict: + print(" Mode: --strict (warnings are errors)") print("=" * 60) print() diff --git a/templates/HAND.toml b/templates/HAND.toml new file mode 100644 index 0000000..3f7d001 --- /dev/null +++ b/templates/HAND.toml @@ -0,0 +1,33 @@ +id = "{{NAME}}" +name = "TODO: Display Name" +description = "TODO: One-sentence description of what this hand does." +category = "productivity" # communication | content | data | development | devops | finance | productivity | research | social +icon = "TODO" +tools = ["shell_exec", "file_read", "file_write", "file_list", "memory_store", "memory_recall"] + +[routing] +aliases = ["TODO: activation phrases"] +weak_aliases = ["TODO: keyword hints"] + +[agent] +name = "{{NAME}}-hand" +description = "TODO: Agent description" +module = "builtin:chat" +provider = "default" +model = "default" +max_tokens = 8192 +temperature = 0.4 +max_iterations = 30 +system_prompt = """TODO: Write a detailed system prompt for the hand's agent. + +Define: +- The hand's purpose and capabilities +- Step-by-step workflow it follows +- Tools it should use and how +- Output format and reporting +""" + +[metadata] +frequency = "on-demand" # continuous | on-demand | scheduled +token_consumption = "medium" # low | medium | high +default_active = true diff --git a/templates/agent.toml b/templates/agent.toml new file mode 100644 index 0000000..63d0aff --- /dev/null +++ b/templates/agent.toml @@ -0,0 +1,32 @@ +name = "{{NAME}}" +version = "0.1.0" +description = "TODO: One-sentence description of what this agent does." +author = "TODO" +module = "builtin:chat" + +[metadata.routing] +aliases = ["TODO: exact match phrases"] +weak_aliases = ["TODO: partial match keywords"] + +[model] +provider = "default" +model = "default" +max_tokens = 4096 +temperature = 0.7 +system_prompt = """TODO: Write a clear system prompt that defines: +- Who the agent is and its role +- What it should and should not do +- How it should format responses +- Any domain-specific instructions + +Keep it focused and under 500 words.""" + +[resources] +max_llm_tokens_per_hour = 100000 + +[capabilities] +tools = ["web_search", "web_fetch", "memory_store", "memory_recall"] +network = ["*"] +memory_read = ["*"] +memory_write = ["self.*"] +agent_spawn = false diff --git a/templates/integration.toml b/templates/integration.toml new file mode 100644 index 0000000..bdd7625 --- /dev/null +++ b/templates/integration.toml @@ -0,0 +1,29 @@ +id = "{{NAME}}" +name = "TODO: Service Display Name" +description = "TODO: One-sentence description of this integration." +category = "devtools" # devtools | communication | storage | monitoring | data | productivity +icon = "TODO" +tags = ["TODO"] + +[transport] +type = "stdio" +command = "npx" +args = ["-y", "TODO: @scope/mcp-server-package"] + +[[required_env]] +name = "TODO_API_KEY" +label = "TODO: API Key" +help = "TODO: How to obtain this key" +is_secret = true +get_url = "https://example.com/settings" + +[health_check] +interval_secs = 60 +unhealthy_threshold = 3 + +setup_instructions = """ +TODO: Step-by-step setup instructions. +1. Go to ... +2. Create an API key with ... +3. Paste the key above. +""" diff --git a/templates/provider.toml b/templates/provider.toml new file mode 100644 index 0000000..3785931 --- /dev/null +++ b/templates/provider.toml @@ -0,0 +1,22 @@ +# TODO: Provider Name — https://example.com +# Models: 1 + +[provider] +id = "{{NAME}}" +display_name = "TODO: Provider Display Name" +api_key_env = "TODO_API_KEY" +base_url = "https://api.example.com/v1" +key_required = true + +[[models]] +id = "TODO-model-id" +display_name = "TODO: Model Display Name" +tier = "balanced" # frontier | smart | balanced | fast | local +context_window = 128000 +max_output_tokens = 4096 +input_cost_per_m = 0.0 +output_cost_per_m = 0.0 +supports_tools = true +supports_vision = false +supports_streaming = true +aliases = [] diff --git a/templates/skill.toml b/templates/skill.toml new file mode 100644 index 0000000..ff3ca2f --- /dev/null +++ b/templates/skill.toml @@ -0,0 +1,20 @@ +[skill] +name = "{{NAME}}" +version = "0.1.0" +description = "TODO: One-sentence description of what this skill does." +author = "TODO" +tags = ["TODO"] + +[runtime] +type = "promptonly" + +[input] +topic = { type = "string", description = "TODO: Describe this input parameter", required = true } + +[prompt] +template = """TODO: Write the prompt template for this skill. + +Use {{topic}} to reference input parameters. + +Be specific about the desired output format and constraints. +"""