mirror of
https://github.com/siteboon/claudecodeui.git
synced 2026-05-16 17:16:19 +00:00
208 lines
8.1 KiB
TypeScript
208 lines
8.1 KiB
TypeScript
import assert from 'node:assert/strict';
|
|
import fs from 'node:fs/promises';
|
|
import os from 'node:os';
|
|
import path from 'node:path';
|
|
import test from 'node:test';
|
|
|
|
import { llmSkillsService } from '@/modules/llm/services/skills.service.js';
|
|
|
|
const patchHomeDir = (nextHomeDir: string) => {
|
|
const original = os.homedir;
|
|
(os as any).homedir = () => nextHomeDir;
|
|
return () => {
|
|
(os as any).homedir = original;
|
|
};
|
|
};
|
|
|
|
const createSkill = async (
|
|
rootSkillsDirectory: string,
|
|
directoryName: string,
|
|
metadata: {
|
|
name: string;
|
|
description: string;
|
|
},
|
|
) => {
|
|
const skillDirectory = path.join(rootSkillsDirectory, directoryName);
|
|
await fs.mkdir(skillDirectory, { recursive: true });
|
|
await fs.writeFile(
|
|
path.join(skillDirectory, 'SKILL.md'),
|
|
`---\nname: ${metadata.name}\ndescription: ${metadata.description}\n---\n\n# ${metadata.name}\n`,
|
|
'utf8',
|
|
);
|
|
};
|
|
|
|
/**
|
|
* This test covers Claude skills fetching from user/project/plugin locations and plugin namespace invocation.
|
|
*/
|
|
test('llmSkillsService lists claude user/project/plugin skills with proper invocation names', { concurrency: false }, async () => {
|
|
const tempRoot = await fs.mkdtemp(path.join(os.tmpdir(), 'llm-skills-claude-'));
|
|
const workspacePath = path.join(tempRoot, 'workspace');
|
|
const pluginInstallPath = path.join(tempRoot, 'plugin-install');
|
|
await fs.mkdir(workspacePath, { recursive: true });
|
|
|
|
const restoreHomeDir = patchHomeDir(tempRoot);
|
|
try {
|
|
await createSkill(path.join(tempRoot, '.claude', 'skills'), 'user-helper', {
|
|
name: 'user-helper',
|
|
description: 'User skill description',
|
|
});
|
|
await createSkill(path.join(workspacePath, '.claude', 'skills'), 'project-helper', {
|
|
name: 'project-helper',
|
|
description: 'Project skill description',
|
|
});
|
|
await createSkill(path.join(pluginInstallPath, 'skills'), 'plugin-helper', {
|
|
name: 'plugin-helper',
|
|
description: 'Plugin skill description',
|
|
});
|
|
|
|
await fs.mkdir(path.join(tempRoot, '.claude', 'plugins'), { recursive: true });
|
|
await fs.writeFile(
|
|
path.join(tempRoot, '.claude', 'settings.json'),
|
|
JSON.stringify({
|
|
enabledPlugins: {
|
|
'example-skills@anthropic-agent-skills': true,
|
|
},
|
|
}),
|
|
'utf8',
|
|
);
|
|
await fs.writeFile(
|
|
path.join(tempRoot, '.claude', 'plugins', 'installed_plugins.json'),
|
|
JSON.stringify({
|
|
version: 2,
|
|
plugins: {
|
|
'example-skills@anthropic-agent-skills': [
|
|
{
|
|
installPath: pluginInstallPath,
|
|
},
|
|
],
|
|
},
|
|
}),
|
|
'utf8',
|
|
);
|
|
|
|
const skills = await llmSkillsService.listProviderSkills('claude', { workspacePath });
|
|
assert.ok(skills.some((skill) => skill.scope === 'user' && skill.invocation === '/user-helper'));
|
|
assert.ok(skills.some((skill) => skill.scope === 'project' && skill.invocation === '/project-helper'));
|
|
assert.ok(skills.some((skill) => skill.scope === 'plugin' && skill.invocation === '/example-skills:plugin-helper'));
|
|
} finally {
|
|
restoreHomeDir();
|
|
await fs.rm(tempRoot, { recursive: true, force: true });
|
|
}
|
|
});
|
|
|
|
/**
|
|
* This test covers Codex skills discovery across repo/user/system locations and `$` invocation prefix.
|
|
*/
|
|
test('llmSkillsService lists codex skills from repo/user/system locations with dollar invocation', { concurrency: false }, async () => {
|
|
const tempRoot = await fs.mkdtemp(path.join(os.tmpdir(), 'llm-skills-codex-'));
|
|
const repoRoot = path.join(tempRoot, 'repo');
|
|
const workspacePath = path.join(repoRoot, 'packages', 'app');
|
|
await fs.mkdir(workspacePath, { recursive: true });
|
|
await fs.mkdir(path.join(repoRoot, '.git'), { recursive: true });
|
|
|
|
const restoreHomeDir = patchHomeDir(tempRoot);
|
|
try {
|
|
await createSkill(path.join(workspacePath, '.agents', 'skills'), 'cwd-skill', {
|
|
name: 'cwd-skill',
|
|
description: 'cwd skill',
|
|
});
|
|
await createSkill(path.join(workspacePath, '..', '.agents', 'skills'), 'parent-skill', {
|
|
name: 'parent-skill',
|
|
description: 'parent skill',
|
|
});
|
|
await createSkill(path.join(repoRoot, '.agents', 'skills'), 'repo-root-skill', {
|
|
name: 'repo-root-skill',
|
|
description: 'repo root skill',
|
|
});
|
|
await createSkill(path.join(tempRoot, '.agents', 'skills'), 'user-skill', {
|
|
name: 'user-skill',
|
|
description: 'user skill',
|
|
});
|
|
await createSkill(path.join(tempRoot, '.codex', 'skills', '.system'), 'system-skill', {
|
|
name: 'system-skill',
|
|
description: 'system skill',
|
|
});
|
|
|
|
const skills = await llmSkillsService.listProviderSkills('codex', { workspacePath });
|
|
assert.ok(skills.some((skill) => skill.name === 'cwd-skill' && skill.invocation === '$cwd-skill'));
|
|
assert.ok(skills.some((skill) => skill.name === 'parent-skill' && skill.invocation === '$parent-skill'));
|
|
assert.ok(skills.some((skill) => skill.name === 'repo-root-skill' && skill.invocation === '$repo-root-skill'));
|
|
assert.ok(skills.some((skill) => skill.name === 'user-skill' && skill.invocation === '$user-skill'));
|
|
assert.ok(skills.some((skill) => skill.name === 'system-skill' && skill.invocation === '$system-skill'));
|
|
} finally {
|
|
restoreHomeDir();
|
|
await fs.rm(tempRoot, { recursive: true, force: true });
|
|
}
|
|
});
|
|
|
|
/**
|
|
* This test covers Gemini skill fetch locations and slash-based invocation format.
|
|
*/
|
|
test('llmSkillsService lists gemini skills from documented directories', { concurrency: false }, async () => {
|
|
const tempRoot = await fs.mkdtemp(path.join(os.tmpdir(), 'llm-skills-gemini-'));
|
|
const workspacePath = path.join(tempRoot, 'workspace');
|
|
await fs.mkdir(workspacePath, { recursive: true });
|
|
|
|
const restoreHomeDir = patchHomeDir(tempRoot);
|
|
try {
|
|
await createSkill(path.join(tempRoot, '.gemini', 'skills'), 'home-gemini', {
|
|
name: 'home-gemini',
|
|
description: 'home gemini skill',
|
|
});
|
|
await createSkill(path.join(tempRoot, '.agents', 'skills'), 'home-agents', {
|
|
name: 'home-agents',
|
|
description: 'home agents skill',
|
|
});
|
|
await createSkill(path.join(workspacePath, '.gemini', 'skills'), 'project-gemini', {
|
|
name: 'project-gemini',
|
|
description: 'project gemini skill',
|
|
});
|
|
await createSkill(path.join(workspacePath, '.agents', 'skills'), 'project-agents', {
|
|
name: 'project-agents',
|
|
description: 'project agents skill',
|
|
});
|
|
|
|
const skills = await llmSkillsService.listProviderSkills('gemini', { workspacePath });
|
|
assert.ok(skills.some((skill) => skill.invocation === '/home-gemini'));
|
|
assert.ok(skills.some((skill) => skill.invocation === '/home-agents'));
|
|
assert.ok(skills.some((skill) => skill.invocation === '/project-gemini'));
|
|
assert.ok(skills.some((skill) => skill.invocation === '/project-agents'));
|
|
} finally {
|
|
restoreHomeDir();
|
|
await fs.rm(tempRoot, { recursive: true, force: true });
|
|
}
|
|
});
|
|
|
|
/**
|
|
* This test covers Cursor skill fetch locations and slash-based invocation format.
|
|
*/
|
|
test('llmSkillsService lists cursor skills from documented directories', { concurrency: false }, async () => {
|
|
const tempRoot = await fs.mkdtemp(path.join(os.tmpdir(), 'llm-skills-cursor-'));
|
|
const workspacePath = path.join(tempRoot, 'workspace');
|
|
await fs.mkdir(workspacePath, { recursive: true });
|
|
|
|
const restoreHomeDir = patchHomeDir(tempRoot);
|
|
try {
|
|
await createSkill(path.join(workspacePath, '.agents', 'skills'), 'project-agents', {
|
|
name: 'project-agents',
|
|
description: 'project agents skill',
|
|
});
|
|
await createSkill(path.join(workspacePath, '.cursor', 'skills'), 'project-cursor', {
|
|
name: 'project-cursor',
|
|
description: 'project cursor skill',
|
|
});
|
|
await createSkill(path.join(tempRoot, '.cursor', 'skills'), 'user-cursor', {
|
|
name: 'user-cursor',
|
|
description: 'user cursor skill',
|
|
});
|
|
|
|
const skills = await llmSkillsService.listProviderSkills('cursor', { workspacePath });
|
|
assert.ok(skills.some((skill) => skill.invocation === '/project-agents'));
|
|
assert.ok(skills.some((skill) => skill.invocation === '/project-cursor'));
|
|
assert.ok(skills.some((skill) => skill.invocation === '/user-cursor'));
|
|
} finally {
|
|
restoreHomeDir();
|
|
await fs.rm(tempRoot, { recursive: true, force: true });
|
|
}
|
|
});
|