fix(providers): guard invalid skill command namespaces

Claude plugin ids come from local settings and installed plugin metadata.

Invalid ids such as empty strings or @ should not become command namespaces.

Skip plugin folders when no safe plugin name can be derived.

This prevents malformed slash commands like /:command from reaching the UI.

Add regression coverage for empty and @ plugin ids.

Keyboard selection in the slash menu should match mouse selection.

Only skills are inserted into the composer because they are provider invocations.

Built-in and custom commands execute directly and close the menu on success or failure.
This commit is contained in:
Haileyesus
2026-05-11 18:58:55 +03:00
parent 053e43447a
commit aabf331e91
3 changed files with 85 additions and 26 deletions

View File

@@ -20,9 +20,14 @@ import {
const getClaudeHomePath = (): string => path.join(os.homedir(), '.claude');
const getClaudePluginName = (pluginId: string): string => {
const [pluginName] = pluginId.split('@');
return pluginName || pluginId;
const getClaudePluginName = (pluginId: string): string | null => {
const normalizedPluginId = pluginId.trim();
if (!normalizedPluginId || normalizedPluginId === '@') {
return null;
}
const [pluginName] = normalizedPluginId.split('@');
return readOptionalString(pluginName) ?? null;
};
const stripMarkdownExtension = (filename: string): string =>
@@ -52,7 +57,7 @@ const listChildDirectories = async (directoryPath: string): Promise<string[]> =>
const readClaudePluginName = async (
installPath: string,
pluginId: string,
): Promise<string> => {
): Promise<string | null> => {
try {
const pluginConfig = await readJsonConfig(
path.join(installPath, '.claude-plugin', 'plugin.json'),
@@ -142,6 +147,10 @@ export class ClaudeSkillsProvider extends SkillsProvider {
visitedPluginFolders.add(pluginFolderKey);
const pluginName = await readClaudePluginName(pluginFolder, pluginId);
if (!pluginName) {
continue;
}
const commandsPath = path.join(pluginFolder, 'commands');
if (await pathExistsAsDirectory(commandsPath)) {
skills.push(