- name
- add-app-skill
- description
- Scaffold a new skill in an existing app (BaseSkill, schemas, app.yml, i18n)
- user-invocable
- true
- argument-hint
- <appId> <skillId> <SkillClassName>
## 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:
```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
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 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
Ver no GitHub