Skip to content

Commit d6c62db

Browse files
kulvirgitclaude
andcommitted
feat: auto-discover skills/commands from Claude Code, Codex, and Gemini configs
Adds external skill discovery with both auto and manual modes: Auto mode (opt-in): Set experimental.auto_skill_discovery: true in config to auto-load external skills at startup. Manual mode (always available): Run /discover-skills to scan, list, and selectively add skills on demand — works regardless of config setting. Uses the skill_discover tool which directly registers skills into runtime state (no config flag dependency). Sources scanned: - .claude/commands/**/*.md (Claude Code commands) - .codex/skills/**/SKILL.md (Codex CLI skills) - .gemini/skills/**/SKILL.md (Gemini CLI skills) - .gemini/commands/**/*.toml (Gemini commands, {{args}} → $ARGUMENTS) Security: - Rejects symlinks (lstat check) - Rejects __proto__/constructor/prototype names - Rejects path traversal (.. in names) - Existing skills never overwritten Tests: 21 unit tests + 1 sanity resilience test Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent 76e9c07 commit d6c62db

10 files changed

Lines changed: 913 additions & 2 deletions

File tree

Lines changed: 115 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,115 @@
1+
// altimate_change start — skill discovery tool for on-demand external skill scanning
2+
import z from "zod"
3+
import path from "path"
4+
import { Tool } from "../../tool/tool"
5+
import { discoverExternalSkills } from "../../skill/discover-external"
6+
import { Instance } from "../../project/instance"
7+
import { Skill } from "../../skill/skill"
8+
9+
export const SkillDiscoverTool = Tool.define("skill_discover", {
10+
description:
11+
"Discover skills and commands from external AI tool configs (Claude Code, Codex CLI, Gemini CLI). Lists what's available and which are already loaded.",
12+
parameters: z.object({
13+
action: z
14+
.enum(["list", "add"])
15+
.describe('"list" to show discovered skills, "add" to load them into the current session'),
16+
skills: z
17+
.array(z.string())
18+
.optional()
19+
.describe('When action is "add", which skills to load. Omit to add all discovered skills.'),
20+
}),
21+
async execute(args) {
22+
const { skills: discovered, sources } = await discoverExternalSkills(Instance.worktree)
23+
24+
if (discovered.length === 0) {
25+
return {
26+
title: "Skill Discovery",
27+
metadata: { action: args.action, found: 0 },
28+
output:
29+
"No external skills or commands found.\n\n" +
30+
"Searched for:\n" +
31+
"- .claude/commands/**/*.md (Claude Code commands)\n" +
32+
"- .codex/skills/**/SKILL.md (Codex CLI skills)\n" +
33+
"- .gemini/skills/**/SKILL.md (Gemini CLI skills)\n" +
34+
"- .gemini/commands/**/*.toml (Gemini CLI commands)\n\n" +
35+
"These are searched in both the project directory and home directory.",
36+
}
37+
}
38+
39+
// Get currently loaded skills to show which are new
40+
const existing = await Skill.all()
41+
const existingNames = new Set(existing.map((s) => s.name))
42+
43+
const newSkills = discovered.filter((s) => !existingNames.has(s.name))
44+
const alreadyLoaded = discovered.filter((s) => existingNames.has(s.name))
45+
46+
if (args.action === "list") {
47+
const lines: string[] = [
48+
`Found ${discovered.length} external skill(s) from ${sources.join(", ")}:`,
49+
"",
50+
]
51+
52+
if (newSkills.length > 0) {
53+
lines.push(`**New** (${newSkills.length}):`)
54+
for (const s of newSkills) {
55+
lines.push(`- \`${s.name}\` — ${s.description || "(no description)"} (${s.location})`)
56+
}
57+
lines.push("")
58+
}
59+
60+
if (alreadyLoaded.length > 0) {
61+
lines.push(`**Already loaded** (${alreadyLoaded.length}):`)
62+
for (const s of alreadyLoaded) {
63+
lines.push(`- \`${s.name}\``)
64+
}
65+
lines.push("")
66+
}
67+
68+
if (newSkills.length > 0) {
69+
lines.push(
70+
'To add these skills to the current session, call this tool again with `action: "add"`.',
71+
)
72+
}
73+
74+
return {
75+
title: `Skill Discovery: ${discovered.length} found (${newSkills.length} new)`,
76+
metadata: { action: "list", found: discovered.length, new: newSkills.length },
77+
output: lines.join("\n"),
78+
}
79+
}
80+
81+
// action === "add"
82+
const toAdd = args.skills
83+
? discovered.filter((s) => args.skills!.includes(s.name) && !existingNames.has(s.name))
84+
: newSkills
85+
86+
if (toAdd.length === 0) {
87+
return {
88+
title: "Skill Discovery: nothing to add",
89+
metadata: { action: "add", added: 0 },
90+
output: "All discovered skills are already loaded in the current session.",
91+
}
92+
}
93+
94+
// Directly register skills into the runtime state — works regardless of
95+
// auto_skill_discovery config setting. This is the on-demand path.
96+
const currentState = await Skill.state()
97+
for (const skill of toAdd) {
98+
if (!currentState.skills[skill.name]) {
99+
currentState.skills[skill.name] = skill
100+
currentState.dirs.push(path.dirname(skill.location))
101+
}
102+
}
103+
104+
return {
105+
title: `Skill Discovery: ${toAdd.length} skill(s) added`,
106+
metadata: { action: "add", added: toAdd.length, names: toAdd.map((s) => s.name) },
107+
output:
108+
`Added ${toAdd.length} skill(s) to the current session:\n` +
109+
toAdd.map((s) => `- \`/${s.name}\` — ${s.description || "(no description)"}`).join("\n") +
110+
"\n\nTo enable auto-discovery at startup, set `experimental.auto_skill_discovery: true` in your config." +
111+
"\n\nYou can now use these skills by name (e.g., `/" + toAdd[0].name + "`).",
112+
}
113+
},
114+
})
115+
// altimate_change end

packages/opencode/src/command/index.ts

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import PROMPT_FEEDBACK from "./template/feedback.txt"
1212
import PROMPT_CONFIGURE_CLAUDE from "./template/configure-claude.txt"
1313
import PROMPT_CONFIGURE_CODEX from "./template/configure-codex.txt"
1414
import PROMPT_DISCOVER_MCPS from "./template/discover-and-add-mcps.txt"
15+
import PROMPT_DISCOVER_SKILLS from "./template/discover-skills.txt"
1516
// altimate_change end
1617
import { MCP } from "../mcp"
1718
import { Skill } from "../skill"
@@ -72,6 +73,7 @@ export namespace Command {
7273
CONFIGURE_CLAUDE: "configure-claude",
7374
CONFIGURE_CODEX: "configure-codex",
7475
DISCOVER_MCPS: "discover-and-add-mcps",
76+
DISCOVER_SKILLS: "discover-skills",
7577
// altimate_change end
7678
} as const
7779

@@ -144,6 +146,15 @@ export namespace Command {
144146
},
145147
hints: hints(PROMPT_DISCOVER_MCPS),
146148
},
149+
[Default.DISCOVER_SKILLS]: {
150+
name: Default.DISCOVER_SKILLS,
151+
description: "discover skills/commands from Claude Code, Codex, and Gemini configs",
152+
source: "command",
153+
get template() {
154+
return PROMPT_DISCOVER_SKILLS
155+
},
156+
hints: hints(PROMPT_DISCOVER_SKILLS),
157+
},
147158
// altimate_change end
148159
}
149160

@@ -206,7 +217,7 @@ export namespace Command {
206217
get template() {
207218
return skill.content
208219
},
209-
hints: [],
220+
hints: hints(skill.content),
210221
}
211222
}
212223
} catch (e) {
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
Discover skills and commands from other AI tools (Claude Code, Codex CLI, Gemini CLI) and load them into the current session.
2+
3+
## Instructions
4+
5+
1. First, call the `skill_discover` tool with `action: "list"` to see what's available.
6+
7+
2. Show the user the results — which skills are new and which are already loaded.
8+
9+
3. If there are new skills, ask the user which ones they want to add.
10+
11+
4. Call `skill_discover` with `action: "add"` and the selected `skills` array (or omit to add all).
12+
13+
5. Report what was added and how to use them (e.g., `/skill-name`).
14+
15+
If $ARGUMENTS contains specific skill names, filter to just those.

packages/opencode/src/config/config.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1300,6 +1300,12 @@ export namespace Config {
13001300
.default(true)
13011301
.describe("Auto-discover MCP servers from VS Code, Claude Code, Copilot, and Gemini configs at startup. Set to false to disable."),
13021302
// altimate_change end
1303+
// altimate_change start - auto skill/command discovery toggle
1304+
auto_skill_discovery: z
1305+
.boolean()
1306+
.default(false)
1307+
.describe("Auto-discover skills and commands from Claude Code, Codex, and Gemini configs at startup. Opt-in — set to true to enable."),
1308+
// altimate_change end
13031309
})
13041310
.optional(),
13051311
})

0 commit comments

Comments
 (0)