Guide for creating effective skills. Use when users want to create or update a skill that extends Claude with specialized knowledge, workflows, or tool integrations.
Disclaimer: This is a living template.
This document formalizes how to design and implement OpenCode skills in this repo so they are:
The intended long-term workflow:
Key constraint: skills must be able to self-bootstrap their local state and credentials, because “pulling a skill” should not require manual repo surgery.
.opencode/skill/<skill-name>/ primarily anchored by SKILL.md.A good skill clearly separates these.
Collocated scaffold (no src/):
.opencode/skill/<skill-name>/
├── SKILL.md # Required. Human-readable + copy/paste commands.
├── .skill.config.example # Tracked. Declares required env vars.
├── .skill.config # Gitignored. Actual credentials.
├── load-env.ts # Tracked. Validates env vars; exports config.
├── client.ts # Tracked. Request helper (fetch wrapper).
├── first-call.ts # Tracked. Minimal “does auth work?” check.
├── openapi.json # Optional. Tracked API spec when available.
├── <thing>.example.json # Optional. Tracked template for local state.
├── <thing>.json # Optional. Gitignored generated local state.
└── scripts/ # Optional. Tracked reusable scripts.
├── bootstrap.ts # Optional. Creates/validates local overlay.
└── <action>.ts # Optional. Deterministic helpers.
Notes:
.skill.config* and load-env.ts.*.example.* template and treat the real file as local overlay.scripts/ so they stay collocated and discoverable.bun (repo convention).^[a-z0-9]+(-[a-z0-9]+)*$.SKILL.md frontmatter name matches folder name.description is one line and task-oriented.Tracked files must never contain:
If it’s sensitive, it goes in the local overlay (.skill.config, *.json, OS keychain, Bitwarden).
Every credentialed skill needs:
.skill.config.opencode/skill/<skill-name>/.skill.config (gitignored)..skill.config.example..env to avoid collisions with repo-root .env files.Recommended SKILL.md snippet:
# Always run commands from the skill folder
cd .opencode/skill/<skill-name>
# Load credentials
source .skill.config
If you expect multiple users or frequent rotation, document a Bitwarden-based setup.
Rule of thumb:
.skill.config: fastest and simplest.A self-building skill can:
telegram-chats.json, torrent-sources.json).*.example.json template.thing.example.json (safe defaults)thing.json (customized per environment)This pattern is already used by:
telegram-chats.example.json → telegram-chats.jsontorrent-sources.example.json → torrent-sources.jsonIf the skill is expected to run end-to-end often, include a bootstrap script (tracked) that ensures the local overlay exists.
Preferred location: .opencode/skill/<skill-name>/scripts/bootstrap.ts.
Example responsibilities for scripts/bootstrap.ts:
.skill.config missing, print clear “Blocked” instructions (don’t guess secrets).*.json missing, copy from *.example.json.You can invoke it with:
bun .opencode/skill/<skill-name>/scripts/bootstrap.ts
Skills should improve after real usage:
SKILL.md immediately.*.ts) to make it deterministic.Keep the updates portable:
Use the following order and headings. This keeps skills consistent across the repo.
---
name: <skill-name>
description: <one-line description>
---
.skill.config lives..skill.config.exampleKeep it short and declarative:
# <Skill Name>
# Base URL (include scheme)
SKILL_URL=
# Token or key
SKILL_TOKEN=
load-env.ts (Template)Use this pattern for strict validation and ergonomic usage in scripts.
// .opencode/skill/<skill-name>/load-env.ts
import { config as loadDotenv } from "dotenv";
import { resolve } from "node:path";
loadDotenv({ path: resolve(import.meta.dir, ".skill.config") });
function requireEnv(name: string): string {
const value = process.env[name];
if (!value) throw new Error(`Missing required env var: ${name}`);
return value;
}
export const SKILL_URL = requireEnv("SKILL_URL");
export const SKILL_TOKEN = requireEnv("SKILL_TOKEN");
Notes:
.skill.config with secrets; only create .skill.config from .skill.config.example with blanks.first-call.ts (Template)This should be the smallest possible auth/health check.
import { SKILL_TOKEN, SKILL_URL } from "./load-env";
const res = await fetch(`${SKILL_URL}/health`, {
headers: { Authorization: `Bearer ${SKILL_TOKEN}` },
});
if (!res.ok) {
throw new Error(`Auth check failed: ${res.status} ${await res.text()}`);
}
console.log("OK");
When running OpenCode in Docker, keep the same split:
Do not design a skill that requires editing tracked files to run.
Before calling a skill “done”:
SKILL.md contains a working “Credential Check” and “First-Time Setup”.*.example.* tracked template.first-call.ts or curl).Search for places (restaurants, cafes, etc.) via Google Places API proxy on localhost.
Interact with GitHub using the `gh` CLI. Use `gh issue`, `gh pr`, `gh run`, and `gh api` for issues, PRs, CI runs, and advanced queries.
Create or update AgentSkills. Use when designing, structuring, or packaging skills with scripts, references, and assets.
Start voice calls via the OpenClaw voice-call plugin.
Notion API for creating and managing pages, databases, and blocks.
Gemini CLI for one-shot Q&A, summaries, and generation.
Category:developer