fix: adopt plural config dirs for agents, commands, and skills (#198)

This commit is contained in:
Bohdan Triapitsyn
2026-01-23 00:27:01 +02:00
committed by GitHub
parent 7c36418e23
commit 95e41940f8
7 changed files with 356 additions and 59 deletions
+79 -17
View File
@@ -5,9 +5,9 @@ import yaml from 'yaml';
import { parse as parseJsonc } from 'jsonc-parser';
const OPENCODE_CONFIG_DIR = path.join(os.homedir(), '.config', 'opencode');
const AGENT_DIR = path.join(OPENCODE_CONFIG_DIR, 'agent');
const COMMAND_DIR = path.join(OPENCODE_CONFIG_DIR, 'command');
const SKILL_DIR = path.join(OPENCODE_CONFIG_DIR, 'skill');
const AGENT_DIR = path.join(OPENCODE_CONFIG_DIR, 'agents');
const COMMAND_DIR = path.join(OPENCODE_CONFIG_DIR, 'commands');
const SKILL_DIR = path.join(OPENCODE_CONFIG_DIR, 'skills');
const CONFIG_FILE = path.join(OPENCODE_CONFIG_DIR, 'opencode.json');
const CUSTOM_CONFIG_FILE = process.env.OPENCODE_CONFIG
? path.resolve(process.env.OPENCODE_CONFIG)
@@ -51,10 +51,14 @@ function ensureDirs() {
* Ensure project-level agent directory exists
*/
function ensureProjectAgentDir(workingDirectory) {
const projectAgentDir = path.join(workingDirectory, '.opencode', 'agent');
const projectAgentDir = path.join(workingDirectory, '.opencode', 'agents');
if (!fs.existsSync(projectAgentDir)) {
fs.mkdirSync(projectAgentDir, { recursive: true });
}
const legacyProjectAgentDir = path.join(workingDirectory, '.opencode', 'agent');
if (!fs.existsSync(legacyProjectAgentDir)) {
fs.mkdirSync(legacyProjectAgentDir, { recursive: true });
}
return projectAgentDir;
}
@@ -62,14 +66,20 @@ function ensureProjectAgentDir(workingDirectory) {
* Get project-level agent path
*/
function getProjectAgentPath(workingDirectory, agentName) {
return path.join(workingDirectory, '.opencode', 'agent', `${agentName}.md`);
const pluralPath = path.join(workingDirectory, '.opencode', 'agents', `${agentName}.md`);
const legacyPath = path.join(workingDirectory, '.opencode', 'agent', `${agentName}.md`);
if (fs.existsSync(legacyPath) && !fs.existsSync(pluralPath)) return legacyPath;
return pluralPath;
}
/**
* Get user-level agent path
*/
function getUserAgentPath(agentName) {
return path.join(AGENT_DIR, `${agentName}.md`);
const pluralPath = path.join(AGENT_DIR, `${agentName}.md`);
const legacyPath = path.join(OPENCODE_CONFIG_DIR, 'agent', `${agentName}.md`);
if (fs.existsSync(legacyPath) && !fs.existsSync(pluralPath)) return legacyPath;
return pluralPath;
}
/**
@@ -173,10 +183,14 @@ function getAgentPermissionSource(agentName, workingDirectory) {
* Ensure project-level command directory exists
*/
function ensureProjectCommandDir(workingDirectory) {
const projectCommandDir = path.join(workingDirectory, '.opencode', 'command');
const projectCommandDir = path.join(workingDirectory, '.opencode', 'commands');
if (!fs.existsSync(projectCommandDir)) {
fs.mkdirSync(projectCommandDir, { recursive: true });
}
const legacyProjectCommandDir = path.join(workingDirectory, '.opencode', 'command');
if (!fs.existsSync(legacyProjectCommandDir)) {
fs.mkdirSync(legacyProjectCommandDir, { recursive: true });
}
return projectCommandDir;
}
@@ -184,14 +198,20 @@ function ensureProjectCommandDir(workingDirectory) {
* Get project-level command path
*/
function getProjectCommandPath(workingDirectory, commandName) {
return path.join(workingDirectory, '.opencode', 'command', `${commandName}.md`);
const pluralPath = path.join(workingDirectory, '.opencode', 'commands', `${commandName}.md`);
const legacyPath = path.join(workingDirectory, '.opencode', 'command', `${commandName}.md`);
if (fs.existsSync(legacyPath) && !fs.existsSync(pluralPath)) return legacyPath;
return pluralPath;
}
/**
* Get user-level command path
*/
function getUserCommandPath(commandName) {
return path.join(COMMAND_DIR, `${commandName}.md`);
const pluralPath = path.join(COMMAND_DIR, `${commandName}.md`);
const legacyPath = path.join(OPENCODE_CONFIG_DIR, 'command', `${commandName}.md`);
if (fs.existsSync(legacyPath) && !fs.existsSync(pluralPath)) return legacyPath;
return pluralPath;
}
/**
@@ -245,10 +265,14 @@ function getCommandWritePath(commandName, workingDirectory, requestedScope) {
* Ensure project-level skill directory exists
*/
function ensureProjectSkillDir(workingDirectory) {
const projectSkillDir = path.join(workingDirectory, '.opencode', 'skill');
const projectSkillDir = path.join(workingDirectory, '.opencode', 'skills');
if (!fs.existsSync(projectSkillDir)) {
fs.mkdirSync(projectSkillDir, { recursive: true });
}
const legacyProjectSkillDir = path.join(workingDirectory, '.opencode', 'skill');
if (!fs.existsSync(legacyProjectSkillDir)) {
fs.mkdirSync(legacyProjectSkillDir, { recursive: true });
}
return projectSkillDir;
}
@@ -256,28 +280,40 @@ function ensureProjectSkillDir(workingDirectory) {
* Get project-level skill directory path (.opencode/skill/{name}/)
*/
function getProjectSkillDir(workingDirectory, skillName) {
return path.join(workingDirectory, '.opencode', 'skill', skillName);
const pluralPath = path.join(workingDirectory, '.opencode', 'skills', skillName);
const legacyPath = path.join(workingDirectory, '.opencode', 'skill', skillName);
if (fs.existsSync(legacyPath) && !fs.existsSync(pluralPath)) return legacyPath;
return pluralPath;
}
/**
* Get project-level skill SKILL.md path
*/
function getProjectSkillPath(workingDirectory, skillName) {
return path.join(getProjectSkillDir(workingDirectory, skillName), 'SKILL.md');
const pluralPath = path.join(workingDirectory, '.opencode', 'skills', skillName, 'SKILL.md');
const legacyPath = path.join(workingDirectory, '.opencode', 'skill', skillName, 'SKILL.md');
if (fs.existsSync(legacyPath) && !fs.existsSync(pluralPath)) return legacyPath;
return pluralPath;
}
/**
* Get user-level skill directory path
*/
function getUserSkillDir(skillName) {
return path.join(SKILL_DIR, skillName);
const pluralPath = path.join(SKILL_DIR, skillName);
const legacyPath = path.join(OPENCODE_CONFIG_DIR, 'skill', skillName);
if (fs.existsSync(legacyPath) && !fs.existsSync(pluralPath)) return legacyPath;
return pluralPath;
}
/**
* Get user-level skill SKILL.md path
*/
function getUserSkillPath(skillName) {
return path.join(getUserSkillDir(skillName), 'SKILL.md');
const pluralPath = path.join(SKILL_DIR, skillName, 'SKILL.md');
const legacyPath = path.join(OPENCODE_CONFIG_DIR, 'skill', skillName, 'SKILL.md');
if (fs.existsSync(legacyPath) && !fs.existsSync(pluralPath)) return legacyPath;
return pluralPath;
}
/**
@@ -1463,9 +1499,9 @@ function discoverSkills(workingDirectory) {
}
};
// 1. Project level .opencode/skill/ (highest priority)
// 1. Project level .opencode/skills/ (highest priority)
if (workingDirectory) {
const projectSkillDir = path.join(workingDirectory, '.opencode', 'skill');
const projectSkillDir = path.join(workingDirectory, '.opencode', 'skills');
if (fs.existsSync(projectSkillDir)) {
const entries = fs.readdirSync(projectSkillDir, { withFileTypes: true });
for (const entry of entries) {
@@ -1477,6 +1513,19 @@ function discoverSkills(workingDirectory) {
}
}
}
const legacyProjectSkillDir = path.join(workingDirectory, '.opencode', 'skill');
if (fs.existsSync(legacyProjectSkillDir)) {
const entries = fs.readdirSync(legacyProjectSkillDir, { withFileTypes: true });
for (const entry of entries) {
if (entry.isDirectory()) {
const skillMdPath = path.join(legacyProjectSkillDir, entry.name, 'SKILL.md');
if (fs.existsSync(skillMdPath)) {
addSkill(entry.name, skillMdPath, SKILL_SCOPE.PROJECT, 'opencode');
}
}
}
}
// 2. Claude-compatible .claude/skills/
const claudeSkillDir = path.join(workingDirectory, '.claude', 'skills');
@@ -1493,7 +1542,7 @@ function discoverSkills(workingDirectory) {
}
}
// 3. User level ~/.config/opencode/skill/
// 3. User level ~/.config/opencode/skills/
if (fs.existsSync(SKILL_DIR)) {
const entries = fs.readdirSync(SKILL_DIR, { withFileTypes: true });
for (const entry of entries) {
@@ -1505,6 +1554,19 @@ function discoverSkills(workingDirectory) {
}
}
}
const legacyUserSkillDir = path.join(OPENCODE_CONFIG_DIR, 'skill');
if (fs.existsSync(legacyUserSkillDir)) {
const entries = fs.readdirSync(legacyUserSkillDir, { withFileTypes: true });
for (const entry of entries) {
if (entry.isDirectory()) {
const skillMdPath = path.join(legacyUserSkillDir, entry.name, 'SKILL.md');
if (fs.existsSync(skillMdPath)) {
addSkill(entry.name, skillMdPath, SKILL_SCOPE.USER, 'opencode');
}
}
}
}
return Array.from(skills.values());
}