feat: support skills in .agents directory and other provider config directories

This commit is contained in:
LaZzyMan 2026-03-09 10:14:47 +08:00
parent f3b56f5a31
commit ef772feea2
2 changed files with 51 additions and 22 deletions

View file

@ -504,17 +504,35 @@ Skill 3 content`);
}); });
}); });
describe('getSkillsBaseDir', () => { describe('getSkillsBaseDirs', () => {
it('should return project-level base dir', () => { it('should return all project-level base dirs', () => {
const baseDir = manager.getSkillsBaseDir('project'); const baseDirs = manager.getSkillsBaseDirs('project');
expect(baseDir).toBe(path.join('/test/project', '.qwen', 'skills')); expect(baseDirs).toHaveLength(5);
expect(baseDirs).toContain(path.join('/test/project', '.qwen', 'skills'));
expect(baseDirs).toContain(
path.join('/test/project', '.agent', 'skills'),
);
expect(baseDirs).toContain(
path.join('/test/project', '.cursor', 'skills'),
);
expect(baseDirs).toContain(
path.join('/test/project', '.codex', 'skills'),
);
expect(baseDirs).toContain(
path.join('/test/project', '.claude', 'skills'),
);
}); });
it('should return user-level base dir', () => { it('should return all user-level base dirs', () => {
const baseDir = manager.getSkillsBaseDir('user'); const baseDirs = manager.getSkillsBaseDirs('user');
expect(baseDir).toBe(path.join('/home/user', '.qwen', 'skills')); expect(baseDirs).toHaveLength(5);
expect(baseDirs).toContain(path.join('/home/user', '.qwen', 'skills'));
expect(baseDirs).toContain(path.join('/home/user', '.agent', 'skills'));
expect(baseDirs).toContain(path.join('/home/user', '.cursor', 'skills'));
expect(baseDirs).toContain(path.join('/home/user', '.codex', 'skills'));
expect(baseDirs).toContain(path.join('/home/user', '.claude', 'skills'));
}); });
}); });

View file

@ -25,6 +25,13 @@ import { normalizeContent } from '../utils/textUtils.js';
const debugLogger = createDebugLogger('SKILL_MANAGER'); const debugLogger = createDebugLogger('SKILL_MANAGER');
const QWEN_CONFIG_DIR = '.qwen'; const QWEN_CONFIG_DIR = '.qwen';
const PROVIDER_CONFIG_DIRS = [
'.qwen',
'.agent',
'.cursor',
'.codex',
'.claude',
];
const SKILLS_CONFIG_DIR = 'skills'; const SKILLS_CONFIG_DIR = 'skills';
const SKILL_MANIFEST_FILE = 'SKILL.md'; const SKILL_MANIFEST_FILE = 'SKILL.md';
@ -412,19 +419,18 @@ export class SkillManager {
* Gets the base directory for skills at a specific level. * Gets the base directory for skills at a specific level.
* *
* @param level - Storage level * @param level - Storage level
* @returns Absolute directory path * @returns Absolute directory paths
*/ */
getSkillsBaseDir(level: SkillLevel): string { getSkillsBaseDirs(level: SkillLevel): string[] {
const baseDir = const baseDirs =
level === 'project' level === 'project'
? path.join( ? PROVIDER_CONFIG_DIRS.map((v) =>
this.config.getProjectRoot(), path.join(this.config.getProjectRoot(), v, SKILLS_CONFIG_DIR),
QWEN_CONFIG_DIR,
SKILLS_CONFIG_DIR,
) )
: path.join(os.homedir(), QWEN_CONFIG_DIR, SKILLS_CONFIG_DIR); : PROVIDER_CONFIG_DIRS.map((v) =>
path.join(os.homedir(), v, SKILLS_CONFIG_DIR),
return baseDir; );
return baseDirs;
} }
/** /**
@ -461,9 +467,13 @@ export class SkillManager {
return skills; return skills;
} }
const baseDir = this.getSkillsBaseDir(level); const baseDirs = this.getSkillsBaseDirs(level);
debugLogger.debug(`Loading ${level} level skills from: ${baseDir}`); const skills: SkillConfig[] = [];
const skills = await this.loadSkillsFromDir(baseDir, level); for (let i = 0; i < baseDirs.length; i++) {
debugLogger.debug(`Loading ${level} level skills from: ${baseDirs[i]}`);
const skillsFromDir = await this.loadSkillsFromDir(baseDirs[i], level);
skills.push(...skillsFromDir);
}
debugLogger.debug(`Loaded ${skills.length} ${level} level skills`); debugLogger.debug(`Loaded ${skills.length} ${level} level skills`);
return skills; return skills;
} }
@ -583,7 +593,8 @@ export class SkillManager {
private updateWatchersFromCache(): void { private updateWatchersFromCache(): void {
const watchTargets = new Set<string>( const watchTargets = new Set<string>(
(['project', 'user'] as const) (['project', 'user'] as const)
.map((level) => this.getSkillsBaseDir(level)) .map((level) => this.getSkillsBaseDirs(level))
.reduce((acc, baseDirs) => acc.concat(baseDirs), [])
.filter((baseDir) => fsSync.existsSync(baseDir)), .filter((baseDir) => fsSync.existsSync(baseDir)),
); );
@ -639,7 +650,7 @@ export class SkillManager {
} }
private async ensureUserSkillsDir(): Promise<void> { private async ensureUserSkillsDir(): Promise<void> {
const baseDir = this.getSkillsBaseDir('user'); const baseDir = path.join(os.homedir(), QWEN_CONFIG_DIR, SKILLS_CONFIG_DIR);
try { try {
await fs.mkdir(baseDir, { recursive: true }); await fs.mkdir(baseDir, { recursive: true });
} catch (error) { } catch (error) {