mirror of
https://github.com/siteboon/claudecodeui.git
synced 2026-06-23 02:05:45 +08:00
feat(skills): add provider skill management
Users need one settings surface to discover and install skills without manually navigating provider-specific directories. Add provider-backed global skill installation for Claude, Codex, Gemini, and Cursor, while keeping OpenCode read-only because it reuses other providers' skill locations. Add a responsive Skills settings tab with scoped discovery, search, refresh controls, markdown and folder uploads, upload feedback, and overflow-safe layouts. Validate bundled skill files and paths before writing them, preserve scripts and assets, and cover provider discovery and installation behavior with tests.
This commit is contained in:
@@ -12,6 +12,7 @@ import type {
|
||||
ProviderModelsDefinition,
|
||||
ProviderMcpServer,
|
||||
ProviderSessionActiveModelChange,
|
||||
ProviderSkillCreateInput,
|
||||
UpsertProviderMcpServerInput,
|
||||
} from '@/shared/types.js';
|
||||
|
||||
@@ -101,6 +102,15 @@ export interface IProviderSkills {
|
||||
* Lists all skills visible to this provider for the optional workspace.
|
||||
*/
|
||||
listSkills(options?: ProviderSkillListOptions): Promise<ProviderSkill[]>;
|
||||
|
||||
/**
|
||||
* Writes one or more global user-scoped skills for this provider.
|
||||
*
|
||||
* Implementations should install the supplied markdown entries into the
|
||||
* provider's writable user skill folder and return the normalized skill
|
||||
* records that were written.
|
||||
*/
|
||||
addSkills(input: ProviderSkillCreateInput): Promise<ProviderSkill[]>;
|
||||
}
|
||||
|
||||
// ---------------------------
|
||||
|
||||
@@ -320,6 +320,47 @@ export type ProviderSkillListOptions = {
|
||||
workspacePath?: string;
|
||||
};
|
||||
|
||||
/**
|
||||
* One supporting file bundled with an uploaded provider skill.
|
||||
*
|
||||
* `relativePath` is resolved below the installed skill directory and must never
|
||||
* be absolute or contain traversal segments. Text files may use `utf8`; binary
|
||||
* scripts and assets should use `base64` so JSON transport does not corrupt
|
||||
* their bytes.
|
||||
*/
|
||||
export type ProviderSkillCreateFile = {
|
||||
relativePath: string;
|
||||
content: string;
|
||||
encoding: 'utf8' | 'base64';
|
||||
};
|
||||
|
||||
/**
|
||||
* One skill markdown payload submitted for provider-managed installation.
|
||||
*
|
||||
* `content` is the raw markdown body that will be written to `SKILL.md`.
|
||||
* `directoryName` lets callers control the target folder name explicitly when
|
||||
* they want stable filesystem paths that differ from the markdown front matter
|
||||
* `name` field. `fileName` is optional upload metadata used only as a final
|
||||
* fallback when no directory name or front matter name is present. `files`
|
||||
* carries scripts, references, and other files from a complete skill folder.
|
||||
*/
|
||||
export type ProviderSkillCreateEntry = {
|
||||
content: string;
|
||||
directoryName?: string;
|
||||
fileName?: string;
|
||||
files?: ProviderSkillCreateFile[];
|
||||
};
|
||||
|
||||
/**
|
||||
* Shared input accepted by provider skill creation operations.
|
||||
*
|
||||
* The service layer batches multiple skill definitions in one request. Each
|
||||
* entry can contain only markdown or a complete skill folder.
|
||||
*/
|
||||
export type ProviderSkillCreateInput = {
|
||||
entries: ProviderSkillCreateEntry[];
|
||||
};
|
||||
|
||||
/**
|
||||
* Normalized skill record returned by provider skill adapters.
|
||||
*
|
||||
|
||||
@@ -957,9 +957,25 @@ export async function readProviderSkillMarkdownDefinition(
|
||||
skillPath: string,
|
||||
): Promise<{ name: string; description: string }> {
|
||||
const content = await readFile(skillPath, 'utf8');
|
||||
return readProviderSkillMarkdownDefinitionFromContent(
|
||||
content,
|
||||
path.basename(path.dirname(skillPath)),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads the `name` and `description` fields from raw skill markdown content.
|
||||
*
|
||||
* This keeps filesystem discovery and newly uploaded skill creation aligned on
|
||||
* the same front matter parsing rules. `fallbackName` is used when the markdown
|
||||
* omits a `name` field so callers still get a stable, non-empty skill id.
|
||||
*/
|
||||
export function readProviderSkillMarkdownDefinitionFromContent(
|
||||
content: string,
|
||||
fallbackName: string,
|
||||
): { name: string; description: string } {
|
||||
const parsed = parseFrontMatter(content);
|
||||
const data = readObjectRecord(parsed.data) ?? {};
|
||||
const fallbackName = path.basename(path.dirname(skillPath));
|
||||
|
||||
return {
|
||||
name: readOptionalString(data.name) ?? fallbackName,
|
||||
|
||||
Reference in New Issue
Block a user