--- name: 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: 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 `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: ```python """ {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`: ```yaml - 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: ```bash 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 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}.`. 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