claude-code/components/skills/SkillsMenu.tsx

237 lines
27 KiB
TypeScript
Raw Permalink Normal View History

import { c as _c } from "react/compiler-runtime";
import capitalize from 'lodash-es/capitalize.js';
import * as React from 'react';
import { useMemo } from 'react';
import { type Command, type CommandBase, type CommandResultDisplay, getCommandName, type PromptCommand } from '../../commands.js';
import { Box, Text } from '../../ink.js';
import { estimateSkillFrontmatterTokens, getSkillsPath } from '../../skills/loadSkillsDir.js';
import { getDisplayPath } from '../../utils/file.js';
import { formatTokens } from '../../utils/format.js';
import { getSettingSourceName, type SettingSource } from '../../utils/settings/constants.js';
import { plural } from '../../utils/stringUtils.js';
import { ConfigurableShortcutHint } from '../ConfigurableShortcutHint.js';
import { Dialog } from '../design-system/Dialog.js';
// Skills are always PromptCommands with CommandBase properties
type SkillCommand = CommandBase & PromptCommand;
type SkillSource = SettingSource | 'plugin' | 'mcp';
type Props = {
onExit: (result?: string, options?: {
display?: CommandResultDisplay;
}) => void;
commands: Command[];
};
function getSourceTitle(source: SkillSource): string {
if (source === 'plugin') {
return 'Plugin skills';
}
if (source === 'mcp') {
return 'MCP skills';
}
return `${capitalize(getSettingSourceName(source))} skills`;
}
function getSourceSubtitle(source: SkillSource, skills: SkillCommand[]): string | undefined {
// MCP skills show server names; file-based skills show filesystem paths.
// Skill names are `<server>:<skill>`, not `mcp__<server>__…`.
if (source === 'mcp') {
const servers = [...new Set(skills.map(s => {
const idx = s.name.indexOf(':');
return idx > 0 ? s.name.slice(0, idx) : null;
}).filter((n): n is string => n != null))];
return servers.length > 0 ? servers.join(', ') : undefined;
}
const skillsPath = getDisplayPath(getSkillsPath(source, 'skills'));
const hasCommandsSkills = skills.some(s => s.loadedFrom === 'commands_DEPRECATED');
return hasCommandsSkills ? `${skillsPath}, ${getDisplayPath(getSkillsPath(source, 'commands'))}` : skillsPath;
}
export function SkillsMenu(t0) {
const $ = _c(35);
const {
onExit,
commands
} = t0;
let t1;
if ($[0] !== commands) {
t1 = commands.filter(_temp);
$[0] = commands;
$[1] = t1;
} else {
t1 = $[1];
}
const skills = t1;
let groups;
if ($[2] !== skills) {
groups = {
policySettings: [],
userSettings: [],
projectSettings: [],
localSettings: [],
flagSettings: [],
plugin: [],
mcp: []
};
for (const skill of skills) {
const source = skill.source as SkillSource;
if (source in groups) {
groups[source].push(skill);
}
}
for (const group of Object.values(groups)) {
group.sort(_temp2);
}
$[2] = skills;
$[3] = groups;
} else {
groups = $[3];
}
const skillsBySource = groups;
let t2;
if ($[4] !== onExit) {
t2 = () => {
onExit("Skills dialog dismissed", {
display: "system"
});
};
$[4] = onExit;
$[5] = t2;
} else {
t2 = $[5];
}
const handleCancel = t2;
if (skills.length === 0) {
let t3;
if ($[6] === Symbol.for("react.memo_cache_sentinel")) {
t3 = <Text dimColor={true}>Create skills in .claude/skills/ or ~/.claude/skills/</Text>;
$[6] = t3;
} else {
t3 = $[6];
}
let t4;
if ($[7] === Symbol.for("react.memo_cache_sentinel")) {
t4 = <Text dimColor={true} italic={true}><ConfigurableShortcutHint action="confirm:no" context="Confirmation" fallback="Esc" description="close" /></Text>;
$[7] = t4;
} else {
t4 = $[7];
}
let t5;
if ($[8] !== handleCancel) {
t5 = <Dialog title="Skills" subtitle="No skills found" onCancel={handleCancel} hideInputGuide={true}>{t3}{t4}</Dialog>;
$[8] = handleCancel;
$[9] = t5;
} else {
t5 = $[9];
}
return t5;
}
const renderSkill = _temp3;
let t3;
if ($[10] !== skillsBySource) {
t3 = source_0 => {
const groupSkills = skillsBySource[source_0];
if (groupSkills.length === 0) {
return null;
}
const title = getSourceTitle(source_0);
const subtitle = getSourceSubtitle(source_0, groupSkills);
return <Box flexDirection="column" key={source_0}><Box><Text bold={true} dimColor={true}>{title}</Text>{subtitle && <Text dimColor={true}> ({subtitle})</Text>}</Box>{groupSkills.map(skill_1 => renderSkill(skill_1))}</Box>;
};
$[10] = skillsBySource;
$[11] = t3;
} else {
t3 = $[11];
}
const renderSkillGroup = t3;
const t4 = skills.length;
let t5;
if ($[12] !== skills.length) {
t5 = plural(skills.length, "skill");
$[12] = skills.length;
$[13] = t5;
} else {
t5 = $[13];
}
const t6 = `${t4} ${t5}`;
let t7;
if ($[14] !== renderSkillGroup) {
t7 = renderSkillGroup("projectSettings");
$[14] = renderSkillGroup;
$[15] = t7;
} else {
t7 = $[15];
}
let t8;
if ($[16] !== renderSkillGroup) {
t8 = renderSkillGroup("userSettings");
$[16] = renderSkillGroup;
$[17] = t8;
} else {
t8 = $[17];
}
let t9;
if ($[18] !== renderSkillGroup) {
t9 = renderSkillGroup("policySettings");
$[18] = renderSkillGroup;
$[19] = t9;
} else {
t9 = $[19];
}
let t10;
if ($[20] !== renderSkillGroup) {
t10 = renderSkillGroup("plugin");
$[20] = renderSkillGroup;
$[21] = t10;
} else {
t10 = $[21];
}
let t11;
if ($[22] !== renderSkillGroup) {
t11 = renderSkillGroup("mcp");
$[22] = renderSkillGroup;
$[23] = t11;
} else {
t11 = $[23];
}
let t12;
if ($[24] !== t10 || $[25] !== t11 || $[26] !== t7 || $[27] !== t8 || $[28] !== t9) {
t12 = <Box flexDirection="column" gap={1}>{t7}{t8}{t9}{t10}{t11}</Box>;
$[24] = t10;
$[25] = t11;
$[26] = t7;
$[27] = t8;
$[28] = t9;
$[29] = t12;
} else {
t12 = $[29];
}
let t13;
if ($[30] === Symbol.for("react.memo_cache_sentinel")) {
t13 = <Text dimColor={true} italic={true}><ConfigurableShortcutHint action="confirm:no" context="Confirmation" fallback="Esc" description="close" /></Text>;
$[30] = t13;
} else {
t13 = $[30];
}
let t14;
if ($[31] !== handleCancel || $[32] !== t12 || $[33] !== t6) {
t14 = <Dialog title="Skills" subtitle={t6} onCancel={handleCancel} hideInputGuide={true}>{t12}{t13}</Dialog>;
$[31] = handleCancel;
$[32] = t12;
$[33] = t6;
$[34] = t14;
} else {
t14 = $[34];
}
return t14;
}
function _temp3(skill_0) {
const estimatedTokens = estimateSkillFrontmatterTokens(skill_0);
const tokenDisplay = `~${formatTokens(estimatedTokens)}`;
const pluginName = skill_0.source === "plugin" ? skill_0.pluginInfo?.pluginManifest.name : undefined;
return <Box key={`${skill_0.name}-${skill_0.source}`}><Text>{getCommandName(skill_0)}</Text><Text dimColor={true}>{pluginName ? ` · ${pluginName}` : ""} · {tokenDisplay} description tokens</Text></Box>;
}
function _temp2(a, b) {
return getCommandName(a).localeCompare(getCommandName(b));
}
function _temp(cmd) {
return cmd.type === "prompt" && (cmd.loadedFrom === "skills" || cmd.loadedFrom === "commands_DEPRECATED" || cmd.loadedFrom === "plugin" || cmd.loadedFrom === "mcp");
}
//# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJuYW1lcyI6WyJjYXBpdGFsaXplIiwiUmVhY3QiLCJ1c2VNZW1vIiwiQ29tbWFuZCIsIkNvbW1hbmRCYXNlIiwiQ29tbWFuZFJlc3VsdERpc3BsYXkiLCJnZXRDb21tYW5kTmFtZSIsIlByb21wdENvbW1hbmQiLCJCb3giLCJUZXh0IiwiZXN0aW1hdGVTa2lsbEZyb250bWF0dGVyVG9rZW5zIiwiZ2V0U2tpbGxzUGF0aCIsImdldERpc3BsYXlQYXRoIiwiZm9ybWF0VG9rZW5zIiwiZ2V0U2V0dGluZ1NvdXJjZU5hbWUiLCJTZXR0aW5nU291cmNlIiwicGx1cmFsIiwiQ29uZmlndXJhYmxlU2hvcnRjdXRIaW50IiwiRGlhbG9nIiwiU2tpbGxDb21tYW5kIiwiU2tpbGxTb3VyY2UiLCJQcm9wcyIsIm9uRXhpdCIsInJlc3VsdCIsIm9wdGlvbnMiLCJkaXNwbGF5IiwiY29tbWFuZHMiLCJnZXRTb3VyY2VUaXRsZSIsInNvdXJjZSIsImdldFNvdXJjZVN1YnRpdGxlIiwic2tpbGxzIiwic2VydmVycyIsIlNldCIsIm1hcCIsInMiLCJpZHgiLCJuYW1lIiwiaW5kZXhPZiIsInNsaWNlIiwiZmlsdGVyIiwibiIsImxlbmd0aCIsImpvaW4iLCJ1bmRlZmluZWQiLCJza2lsbHNQYXRoIiwiaGFzQ29tbWFuZHNTa2lsbHMiLCJzb21lIiwibG9hZGVkRnJvbSIsIlNraWxsc01lbnUiLCJ0MCIsIiQiLCJfYyIsInQxIiwiX3RlbXAiLCJncm91cHMiLCJwb2xpY3lTZXR0aW5ncyIsInVzZXJTZXR0aW5ncyIsInByb2plY3RTZXR0aW5ncyIsImxvY2FsU2V0dGluZ3MiLCJmbGFnU2V0dGluZ3MiLCJwbHVnaW4iLCJtY3AiLCJza2lsbCIsInB1c2giLCJncm91cCIsIk9iamVjdCIsInZhbHVlcyIsInNvcnQiLCJfdGVtcDIiLCJza2lsbHNCeVNvdXJjZSIsInQyIiwiaGFuZGxlQ2FuY2VsIiwidDMiLCJTeW1ib2wiLCJmb3IiLCJ0NCIsInQ1IiwicmVuZGVyU2tpbGwiLCJfdGVtcDMiLCJzb3VyY2VfMCIsImdyb3VwU2tpbGxzIiwidGl0bGUiLCJzdWJ0aXRsZSIsInNraWxsXzEiLCJyZW5kZXJTa2lsbEdyb3VwIiwidDYiLCJ0NyIsInQ4IiwidDkiLCJ0MTAiLCJ0MTEiLCJ0MTIiLCJ0MTMiLCJ0MTQiLCJza2lsbF8wIiwiZXN0aW1hdGVkVG9rZW5zIiwidG9rZW5EaXNwbGF5IiwicGx1Z2luTmFtZSIsInBsdWdpbkluZm8iLCJwbHVnaW5NYW5pZmVzdCIsImEiLCJiIiwibG9jYWxlQ29tcGFyZSIsImNtZCIsInR5cGUiXSwic291cmNlcyI6WyJTa2lsbHNNZW51LnRzeCJdLCJzb3VyY2VzQ29udGVudCI6WyJpbXBvcnQgY2FwaXRhbGl6ZSBmcm9tICdsb2Rhc2gtZXMvY2FwaXRhbGl6ZS5qcydcbmltcG9ydCAqIGFzIFJlYWN0IGZyb20gJ3JlYWN0J1xuaW1wb3J0IHsgdXNlTWVtbyB9IGZyb20gJ3JlYWN0J1xuaW1wb3J0IHtcbiAgdHlwZSBDb21tYW5kLFxuICB0eXBlIENvbW1hbmRCYXNlLFxuICB0eXBlIENvbW1hbmRSZXN1bHREaXNwbGF5LFxuICBnZXRDb21tYW5kTmFtZSxcbiAgdHlwZSBQcm9tcHRDb21tYW5kLFxufSBmcm9tICcuLi8uLi9jb21tYW5kcy5qcydcbmltcG9ydCB7IEJveCwgVGV4dCB9IGZyb20gJy4uLy4uL2luay5qcydcbmltcG9ydCB7XG4gIGVzdGltYXRlU2tpbGxGcm9udG1hdHRlclRva2VucyxcbiAgZ2V0U2tpbGxzUGF0aCxcbn0gZnJvbSAnLi4vLi4vc2tpbGxzL2xvYWRTa2lsbHNEaXIuanMnXG5pbXBvcnQgeyBnZXREaXNwbGF5UGF0aCB9IGZyb20gJy4uLy4uL3V0aWxzL2ZpbGUuanMnXG5pbXBvcnQgeyBmb3JtYXRUb2tlbnMgfSBmcm9tICcuLi8uLi91dGlscy9mb3JtYXQuanMnXG5pbXBvcnQge1xuICBnZXRTZXR0aW5nU291cmNlTmFtZSxcbiAgdHlwZSBTZXR0aW5nU291cmNlLFxufSBmcm9tICcuLi8uLi91dGlscy9zZXR0aW5ncy9jb25zdGFudHMuanMnXG5pbXBvcnQgeyBwbHVyYWwgfSBmcm9tICcuLi8uLi91dGlscy9zdHJpbmdVdGlscy5qcydcbmltcG9ydCB7IENvbmZpZ3VyYWJsZVNob3J0Y3V0SGludCB9IGZyb20gJy4uL0NvbmZpZ3VyYWJsZVNob3J0Y3V0SGludC5qcydcbmltcG9ydCB7IERpYWxvZyB9IGZyb20gJy4uL2Rlc2lnbi1zeXN0ZW0vRGlhbG9nLmpzJ1xuXG4vLyBTa2lsbHMgYXJlIGFsd2F5cyBQcm9tcHRDb21tYW5kcyB3aXRoIENvbW1hbmRCYXNlIHByb3BlcnRpZXNcbnR5cGUgU2tpbGxDb21tYW5kID0gQ29tbWFuZEJhc2UgJiBQcm9tcHRDb21tYW5kXG5cbnR5cGUgU2tpbGxTb3VyY2UgPSBTZXR0aW5nU291cmNlIHwgJ3BsdWdpbicgfCAnbWNwJ1xuXG50eXBlIFByb3BzID0ge1xuICBvbkV4aXQ6IChcbiAgICByZXN1bHQ/OiBzdHJpbmcsXG4gICAgb3B0aW9ucz86IHsgZGlzcGxheT86IENvbW1hbmRSZXN1bHREaXNwbGF5IH0sXG4gICkgPT4gdm9pZFxuICBjb21tYW5kczogQ29tbWFuZFtdXG59XG5cbmZ1bmN0aW9uIGdldFNvdXJjZVRpdGxlKHNvdXJjZTogU2tpbGxTb3VyY2UpOiBzdHJpbmcge1xuICBpZiAoc291cmNlID09PSAncGx1Z2luJykge1xuICAgIHJldHVybiAnUGx1Z2luIHNraWxscydcbiAgfVxuICBpZiAoc291cmNlID09PSAnbWNwJykge1xuICAgIHJldHVybiAnTUNQIHNraWxscydcbiAgfVxuICByZXR1cm4gYCR7Y2FwaXRhbGl6ZShnZXRTZXR0aW5nU291cmNlTmFtZShzb3VyY2UpKX0gc2tpbGxzYFxufVxuXG5mdW5jdGlvbiBnZXRTb3VyY2VTdWJ0aXRsZShcbiAgc291cmNlOiBTa2lsbFNvdXJjZSxcbiAgc2tpbGxzOiBTa2lsbENvbW1hbmRbXSxcbik6IHN0cmluZyB8IHVuZGVmaW5lZCB7XG4gIC8vIE1DUCBza2lsbHMgc2hvdyBzZXJ2ZXIgbmFtZXM7IGZpbGUtYmFzZWQgc2tpbGxzIHNob3cgZmlsZXN5c3RlbSBwYXRocy5cbiAgLy8gU2tpbGwgbmFtZXMgYXJlIGA8c2VydmVyPjo8c2tpbGw+YCwgbm90IGBtY3BfXzxzZXJ2ZXI+X1/igKZgLlxuICBpZiAoc291cmNlID09PSAnbWNwJykge1xuICAgIGNvbnN0IHNlcnZlcnMgPSBbXG4gICAgICAuLi5uZXcgU2V0KFxuICAgICAgICBza2lsbHNcbiAgICAgICAgICAubWFwKHMgPT4ge1xuICAgICAgICAgICAgY29uc3QgaWR4ID0gcy5uYW1lLmluZGV4T2YoJzonKVxuICAgICAgICAgICAgcmV0dXJuIGlkeCA+IDAgPyBzLm5hbWUuc2xpY2UoMCwgaWR4KSA6IG51bGxcbiAgICAgICAgICB9KVx