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
@@ -14,6 +14,17 @@ import { downloadClawdHubSkill, fetchClawdHubSkillInfo } from './api.js';
const SKILL_NAME_PATTERN = /^[a-z0-9][a-z0-9-]*[a-z0-9]$|^[a-z0-9]$/;
function normalizeUserSkillDir(userSkillDir) {
if (!userSkillDir) return null;
const legacySkillDir = path.join(os.homedir(), '.config', 'opencode', 'skill');
const pluralSkillDir = path.join(os.homedir(), '.config', 'opencode', 'skills');
if (userSkillDir === legacySkillDir) {
if (fs.existsSync(legacySkillDir) && !fs.existsSync(pluralSkillDir)) return legacySkillDir;
return pluralSkillDir;
}
return userSkillDir;
}
function validateSkillName(skillName) {
if (typeof skillName !== 'string') return false;
if (skillName.length < 1 || skillName.length > 64) return false;
@@ -41,7 +52,7 @@ function getTargetSkillDir({ scope, workingDirectory, userSkillDir, skillName })
throw new Error('workingDirectory is required for project installs');
}
return path.join(workingDirectory, '.opencode', 'skill', skillName);
return path.join(workingDirectory, '.opencode', 'skills', skillName);
}
/**
@@ -71,6 +82,11 @@ export async function installSkillsFromClawdHub({
return { ok: false, error: { kind: 'unknown', message: 'userSkillDir is required' } };
}
const normalizedUserSkillDir = normalizeUserSkillDir(userSkillDir);
if (normalizedUserSkillDir) {
userSkillDir = normalizedUserSkillDir;
}
if (scope === 'project' && !workingDirectory) {
return { ok: false, error: { kind: 'invalidSource', message: 'Project installs require a directory parameter' } };
}
@@ -7,6 +7,17 @@ import { parseSkillRepoSource } from './source.js';
const SKILL_NAME_PATTERN = /^[a-z0-9][a-z0-9-]*[a-z0-9]$|^[a-z0-9]$/;
function normalizeUserSkillDir(userSkillDir) {
if (!userSkillDir) return null;
const legacySkillDir = path.join(os.homedir(), '.config', 'opencode', 'skill');
const pluralSkillDir = path.join(os.homedir(), '.config', 'opencode', 'skills');
if (userSkillDir === legacySkillDir) {
if (fs.existsSync(legacySkillDir) && !fs.existsSync(pluralSkillDir)) return legacySkillDir;
return pluralSkillDir;
}
return userSkillDir;
}
function validateSkillName(skillName) {
if (typeof skillName !== 'string') return false;
if (skillName.length < 1 || skillName.length > 64) return false;
@@ -103,7 +114,7 @@ function getTargetSkillDir({ scope, workingDirectory, userSkillDir, skillName })
throw new Error('workingDirectory is required for project installs');
}
return path.join(workingDirectory, '.opencode', 'skill', skillName);
return path.join(workingDirectory, '.opencode', 'skills', skillName);
}
export async function installSkillsFromRepository({
@@ -123,14 +134,19 @@ export async function installSkillsFromRepository({
return { ok: false, error: gitCheck.error };
}
if (scope !== 'user' && scope !== 'project') {
return { ok: false, error: { kind: 'invalidSource', message: 'Invalid scope' } };
const normalizedUserSkillDir = normalizeUserSkillDir(userSkillDir);
if (normalizedUserSkillDir) {
userSkillDir = normalizedUserSkillDir;
}
if (!userSkillDir) {
return { ok: false, error: { kind: 'unknown', message: 'userSkillDir is required' } };
}
if (scope !== 'user' && scope !== 'project') {
return { ok: false, error: { kind: 'invalidSource', message: 'Invalid scope' } };
}
if (scope === 'project' && !workingDirectory) {
return { ok: false, error: { kind: 'invalidSource', message: 'Project installs require a directory parameter' } };
}