name: openmates:add-app-skill
description: Scaffold a new skill in an existing app (BaseSkill, schemas, app.yml, i18n)
user-invocable: true
argument-hint: " "
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:
backend/apps/{appId}/app.yml— existing skills, embed types, categoriesbackend/apps/base_skill.py(lines 1-145) — BaseSkill interface- An existing skill in
backend/apps/{appId}/skills/— use as template 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 specify or create an inline spec 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
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
stage: development
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:
- Run or script at least two real skill requests that cover the main provider/result shapes.
- Create
frontend/packages/ui/src/components/embeds/{appId}/{SkillName}EmbedPreview.examples.tsnext to the preview component. - Export an array of flat preview props matching the preview component, with
query_translation_keyvalues undersettings.app_store_examples.{appId}.{skill_id_underscored}.<n>. - 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
BaseSkillorbackend/shared/ - All
execute()params must matchtool_schema.propertiesnames exactly - Use
logger = logging.getLogger(__name__)— neverprint() - Type hints on all function parameters and return values
- Pydantic models use
PascalCase— end request models withRequest, response withResponse - Do not add
stage; implemented skills are enabled by default unlessdefault_enabled: falseis explicitly needed - App-store examples are required for every new skill; do not ship a skill with an empty examples section