add-app-skill

SkillDev tools

Scaffold a new skill in an existing app (BaseSkill, schemas, app.yml, i18n)

Available today. Use it from your connected AI after setup.

Connect ahel once, and every AI you use reads what you have installed.

Then ask your AI: use the add-app-skill skill

What this skill tells your AI

The instructions your AI receives, as published by glowingkitty/openmates in .agents/skills/add-app-skill/SKILL.md and read by ahel’s review.

Arguments

Parse $ARGUMENTS into three parts:

  • appId — app directory name (e.g., web, news, travel)
  • skillId — kebab-case skill identifier (e.g., deep-research)
  • SkillClassName — PascalCase class name (e.g., DeepResearchSkill)

If any are missing, ask the user before proceeding.

Instructions

You are adding a new skill to an existing app microservice. This touches backend Python code, YAML config, and i18n.

Step 1: Understand the Target App

Read these files to understand the app's patterns:

  1. backend/apps/{appId}/app.yml — existing skills, embed types, categories
  2. backend/apps/base_skill.py (lines 1-145) — BaseSkill interface
  3. An existing skill in backend/apps/{appId}/skills/ — use as template
  4. backend/shared/python_schemas/app_metadata_schemas.py — AppYAML schema (for valid field names)

Step 1b: Create Skill Behavior Spec

Before scaffolding a new app skill, run create-plan or create an inline Plan using docs/contributing/guides/spec-driven-development.md.

New app skills usually require a full spec because they define user-facing AI behavior, tool contracts, provider behavior, and often embed behavior. The spec must include:

  • Sample user prompts
  • Expected tool input parameters
  • Expected skill output shape
  • No-results behavior
  • Provider-error behavior
  • Permission, privacy, and API-key behavior
  • Embed preview/fullscreen behavior if the skill produces embeds
  • App-store examples that double as executable examples

The spec or inline contract must also define the phase gate for this skill:

  1. Implement and test the skill through OpenMates CLI against the dev server first, using real prompts or CLI contract tests that exercise the backend skill without browser state. This proof must use real CLI commands against the real dev API/WebSocket path; mocked OpenMates API calls, mocked SDK clients, stubbed servers, direct function calls, and fixture replay do not satisfy the gate.
  2. Implement and test npm SDK and pip SDK parity locally against the dev server for the same callable behavior when the skill is exposed programmatically. After local CLI and SDK evidence is green, reproduce or wire the same coverage into GitHub Actions for CI/daily tests.
  3. Implement web app surfaces, embeds, and app-store examples only after CLI and SDK parity are green.
  4. Run deployed Playwright visual smoke for larger web/UI surfaces in both laptop and mobile viewports, fixing and redeploying any objective rendering, error, loading, or responsiveness issue, then ask the user to confirm the deployed dev web behavior works and looks correct before starting any Apple parity work. Use Firecrawl only as a recorded fallback when Playwright is impractical or blocked. Playwright specs alone are not enough.
  5. Start Apple parity only after CLI, SDK, web, and user-confirmation evidence are complete, or after the spec records an explicit waiver/blocker.

Step 2: Create the Skill File

Create backend/apps/{appId}/skills/{skill_file}.py where skill_file is skillId with hyphens replaced by underscores.

Follow this structure exactly:

"""
{SkillClassName} — {brief description}.

Architecture: docs/architecture/app_skills.md
"""
import logging
from typing import Dict, Any, Optional
from pydantic import BaseModel, Field

from backend.apps.base_skill import BaseSkill

logger = logging.getLogger(__name__)


class {SkillName}Request(BaseModel):
    """{description}."""
    # Define input fields from tool_schema


class {SkillName}Response(BaseModel):
    """{description}."""
    success: bool = Field(default=False)
    # Define output fields


class {SkillClassName}(BaseSkill):
    """
    {Description of what this skill does}.
    """

    async def execute(
        self,
        # Skill-specific params (must match tool_schema properties)
        secrets_manager=None,
        cache_service=None,
        encryption_service=None,
        directus_service=None,
        user_id: Optional[str] = None,
        chat_id: Optional[str] = None,
        **kwargs
    ) -> {SkillName}Response:
        """Execute the skill."""
        try:
            # Implementation here
            return {SkillName}Response(success=True)
        except Exception as e:
            logger.error(f"{SkillClassName} error: {e}", exc_info=True)
            return {SkillName}Response(success=False, error=str(e))

Step 3: Register in app.yml

Add to the skills: list in backend/apps/{appId}/app.yml:

  - id: {skillId}
    name_translation_key: {appId}.{skill_id_underscored}
    description_translation_key: {appId}.{skill_id_underscored}.description
    icon_image: {icon}.svg
    preprocessor_hint: >
      Natural language description for AI model routing
    providers:
      - name: OpenMates
        no_api_key: true
    class_path: backend.apps.{appId}.skills.{skill_file}.{SkillClassName}
    tool_schema:
      type: object
      properties:
        # Define input parameters
      required:
        # List required params

If the skill produces embeds, also add an embed_types: entry.

Step 4: Add i18n Entries

Add skill name and description to frontend/packages/ui/src/i18n/sources/skills.yml (all 20 locales).

Then rebuild:

cd frontend/packages/ui && npm run build:translations

Step 5: Add App-Store Examples

Every new app skill must include app-store examples so the skill details page can show realistic preview cards.

If the skill produces embeds:

  1. Run or script at least two real CLI skill requests that cover the main provider/result shapes.
  2. Create frontend/packages/ui/src/components/embeds/{appId}/{SkillName}EmbedPreview.examples.ts next to the preview component.
  3. Export an array of flat preview props matching the preview component, with query_translation_key values under settings.app_store_examples.{appId}.{skill_id_underscored}.<n>.
  4. Add those query labels to frontend/packages/ui/src/i18n/sources/settings/app_store_examples.yml.

If the skill does not produce embeds, add equivalent user-facing examples in backend/apps/{appId}/app.yml using the existing example_entries or example_translation_keys pattern for that app.

Step 6: Create Test Script (Optional)

If scripts/test_skills/ exists, create test_{skill_id_underscored}.py following the pattern of other test scripts in that directory.

Step 7: Check for Embed Need

Ask the user: "Does this skill produce embeds that need a frontend component?"

If yes, suggest running /add-embed-type {appId} {skillId} {SkillName} next.

Rules

  • Skills must NOT import from other skills — shared logic goes to BaseSkill or backend/shared/
  • All execute() params must match tool_schema.properties names exactly
  • Use logger = logging.getLogger(__name__) — never print()
  • Type hints on all function parameters and return values
  • Pydantic models use PascalCase — end request models with Request, response with Response
  • Do not add stage; implemented skills are enabled by default unless default_enabled: false is explicitly needed
  • App-store examples are required for every new skill; do not ship a skill with an empty examples section

Signals

GitHub stars
46
Forks
3
Last commit
Sep 2026
Advanced
Catalog kind
skill
Gateway key
add-app-skill
Source
github.com/glowingkitty/openmates