diff --git a/src/tools/skill/constants.ts b/src/tools/skill/constants.ts
new file mode 100644
index 0000000..538dc09
--- /dev/null
+++ b/src/tools/skill/constants.ts
@@ -0,0 +1,8 @@
+export const TOOL_NAME = "skill" as const
+
+export const TOOL_DESCRIPTION_NO_SKILLS = "Load a skill to get detailed instructions for a specific task. No skills are currently available."
+
+export const TOOL_DESCRIPTION_PREFIX = `Load a skill to get detailed instructions for a specific task.
+
+Skills provide specialized knowledge and step-by-step guidance.
+Use this when a task matches an available skill's description.`
diff --git a/src/tools/skill/index.ts b/src/tools/skill/index.ts
new file mode 100644
index 0000000..3c32b1c
--- /dev/null
+++ b/src/tools/skill/index.ts
@@ -0,0 +1,3 @@
+export * from "./constants"
+export * from "./types"
+export { skill, createSkillTool } from "./tools"
diff --git a/src/tools/skill/tools.ts b/src/tools/skill/tools.ts
new file mode 100644
index 0000000..da6f217
--- /dev/null
+++ b/src/tools/skill/tools.ts
@@ -0,0 +1,79 @@
+import { dirname } from "node:path"
+import { readFileSync } from "node:fs"
+import { tool, type ToolDefinition } from "@opencode-ai/plugin"
+import { TOOL_DESCRIPTION_NO_SKILLS, TOOL_DESCRIPTION_PREFIX } from "./constants"
+import type { SkillArgs, SkillInfo, SkillLoadOptions } from "./types"
+import { discoverSkills, getSkillByName, type LoadedSkill } from "../../features/opencode-skill-loader"
+import { parseFrontmatter } from "../../shared/frontmatter"
+
+function loadedSkillToInfo(skill: LoadedSkill): SkillInfo {
+ return {
+ name: skill.name,
+ description: skill.definition.description || "",
+ location: skill.path,
+ scope: skill.scope,
+ license: skill.license,
+ compatibility: skill.compatibility,
+ metadata: skill.metadata,
+ allowedTools: skill.allowedTools,
+ }
+}
+
+function formatSkillsXml(skills: SkillInfo[]): string {
+ if (skills.length === 0) return ""
+
+ const skillsXml = skills.map(skill => {
+ const lines = [
+ " ",
+ ` ${skill.name}`,
+ ` ${skill.description}`,
+ ]
+ if (skill.compatibility) {
+ lines.push(` ${skill.compatibility}`)
+ }
+ lines.push(" ")
+ return lines.join("\n")
+ }).join("\n")
+
+ return `\n\n\n${skillsXml}\n`
+}
+
+export function createSkillTool(options: SkillLoadOptions = {}): ToolDefinition {
+ const skills = discoverSkills({ includeClaudeCodePaths: !options.opencodeOnly })
+ const skillInfos = skills.map(loadedSkillToInfo)
+
+ const description = skillInfos.length === 0
+ ? TOOL_DESCRIPTION_NO_SKILLS
+ : TOOL_DESCRIPTION_PREFIX + formatSkillsXml(skillInfos)
+
+ return tool({
+ description,
+ args: {
+ name: tool.schema.string().describe("The skill identifier from available_skills (e.g., 'code-review')"),
+ },
+ async execute(args: SkillArgs) {
+ const skill = getSkillByName(args.name, { includeClaudeCodePaths: !options.opencodeOnly })
+
+ if (!skill) {
+ const available = skills.map(s => s.name).join(", ")
+ throw new Error(`Skill "${args.name}" not found. Available skills: ${available || "none"}`)
+ }
+
+ const content = readFileSync(skill.path, "utf-8")
+ const { body } = parseFrontmatter(content)
+ const dir = dirname(skill.path)
+
+ const output = [
+ `## Skill: ${skill.name}`,
+ "",
+ `**Base directory**: ${dir}`,
+ "",
+ body.trim(),
+ ].join("\n")
+
+ return output
+ },
+ })
+}
+
+export const skill = createSkillTool()
diff --git a/src/tools/skill/types.ts b/src/tools/skill/types.ts
new file mode 100644
index 0000000..9534086
--- /dev/null
+++ b/src/tools/skill/types.ts
@@ -0,0 +1,19 @@
+export interface SkillArgs {
+ name: string
+}
+
+export interface SkillInfo {
+ name: string
+ description: string
+ location: string
+ scope: "opencode-project" | "project" | "opencode" | "user"
+ license?: string
+ compatibility?: string
+ metadata?: Record
+ allowedTools?: string[]
+}
+
+export interface SkillLoadOptions {
+ /** When true, only load from OpenCode paths (.opencode/skill/, ~/.config/opencode/skill/) */
+ opencodeOnly?: boolean
+}