Skip to content

Commit 26647c9

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, matching the /discover-and-add-mcps pattern for MCP servers. 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: 24 unit tests + 1 sanity resilience test Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent 76e9c07 commit 26647c9

File tree

10 files changed

+952
-2
lines changed

10 files changed

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