fix: use opencode skills as source of truth

This commit is contained in:
Bohdan Triapitsyn
2026-05-17 14:51:25 +03:00
parent 8900b8f0f2
commit bbc297202e
20 changed files with 384 additions and 101 deletions
@@ -108,6 +108,10 @@ const SkillsInstalledPage: React.FC = () => {
switch (value) {
case 'project-opencode':
return t('settings.skills.location.option.projectOpencode.label');
case 'user-claude':
return t('settings.skills.location.option.userClaude.label');
case 'project-claude':
return t('settings.skills.location.option.projectClaude.label');
case 'user-agents':
return t('settings.skills.location.option.userAgents.label');
case 'project-agents':
@@ -121,6 +125,10 @@ const SkillsInstalledPage: React.FC = () => {
switch (value) {
case 'project-opencode':
return t('settings.skills.location.option.projectOpencode.description');
case 'user-claude':
return t('settings.skills.location.option.userClaude.description');
case 'project-claude':
return t('settings.skills.location.option.projectClaude.description');
case 'user-agents':
return t('settings.skills.location.option.userAgents.description');
case 'project-agents':
@@ -197,6 +205,7 @@ const SkillsInstalledPage: React.FC = () => {
instructions: instructions.trim() || undefined,
scope: isNewSkill ? draftScope : undefined,
source: isNewSkill ? draftSource : undefined,
targetPath: !isNewSkill ? selectedSkill?.path : undefined,
supportingFiles: isNewSkill && pendingFiles.length > 0 ? pendingFiles : undefined,
};
@@ -386,11 +395,6 @@ const SkillsInstalledPage: React.FC = () => {
<div className="min-w-0">
<h2 className="typography-ui-header font-semibold text-foreground truncate flex items-center gap-2">
{isNewSkill ? t('settings.skills.page.title.newSkill') : selectedSkillName}
{selectedSkill?.source === 'claude' && (
<span className="typography-micro font-normal bg-[var(--surface-muted)] text-muted-foreground px-1.5 py-0.5 rounded">
{t('settings.skills.page.badge.claudeCompatible')}
</span>
)}
</h2>
<p className="typography-meta text-muted-foreground truncate">
{selectedSkill
@@ -437,6 +437,12 @@ const SkillListItem: React.FC<SkillListItemProps> = ({
}) => {
const { t } = useI18n();
const isMobile = isMobileDeviceViaCSS();
const sourceLabel = skill.source === 'claude'
? t('settings.skills.sidebar.badge.claude')
: skill.source === 'agents'
? t('settings.skills.sidebar.badge.agents')
: t('settings.skills.sidebar.badge.opencode');
const badgeClassName = 'typography-micro text-muted-foreground bg-[var(--surface-muted)] px-1 rounded flex-shrink-0 leading-none pb-px border border-[var(--interactive-border)]/50';
return (
<div
className={cn(
@@ -458,19 +464,10 @@ const SkillListItem: React.FC<SkillListItemProps> = ({
<span className="typography-ui-label font-normal truncate text-foreground">
{skill.name}
</span>
<span className="typography-micro text-muted-foreground bg-muted px-1 rounded flex-shrink-0 leading-none pb-px border border-border/50">
<span className={badgeClassName}>
{skill.scope}
</span>
{skill.source === 'claude' && (
<span className="typography-micro text-muted-foreground bg-muted px-1 rounded flex-shrink-0 leading-none pb-px border border-border/50">
{t('settings.skills.sidebar.badge.claude')}
</span>
)}
{skill.source === 'agents' && (
<span className="typography-micro text-muted-foreground bg-muted px-1 rounded flex-shrink-0 leading-none pb-px border border-border/50">
{t('settings.skills.sidebar.badge.agents')}
</span>
)}
<span className={badgeClassName}>{sourceLabel}</span>
</div>
</button>
@@ -1,6 +1,6 @@
import type { SkillScope, SkillSource } from '@/stores/useSkillsStore';
export type SkillLocationValue = 'user-opencode' | 'project-opencode' | 'user-agents' | 'project-agents';
export type SkillLocationValue = 'user-opencode' | 'project-opencode' | 'user-claude' | 'project-claude' | 'user-agents' | 'project-agents';
export const SKILL_LOCATION_OPTIONS: Array<{
value: SkillLocationValue;
@@ -40,13 +40,17 @@ export const SKILL_LOCATION_OPTIONS: Array<{
];
export function locationValueFrom(scope: SkillScope, source: SkillSource): SkillLocationValue {
if (scope === 'project' && source === 'claude') return 'project-claude';
if (scope === 'project' && source === 'agents') return 'project-agents';
if (source === 'claude') return 'user-claude';
if (scope === 'project') return 'project-opencode';
if (source === 'agents') return 'user-agents';
return 'user-opencode';
}
export function locationPartsFrom(value: SkillLocationValue): { scope: SkillScope; source: SkillSource } {
if (value === 'user-claude') return { scope: 'user', source: 'claude' };
if (value === 'project-claude') return { scope: 'project', source: 'claude' };
const match = SKILL_LOCATION_OPTIONS.find((option) => option.value === value);
if (!match) {
return { scope: 'user', source: 'opencode' };
@@ -55,6 +59,8 @@ export function locationPartsFrom(value: SkillLocationValue): { scope: SkillScop
}
export function locationLabel(scope: SkillScope, source: SkillSource): string {
if (scope === 'user' && source === 'claude') return 'User / Claude';
if (scope === 'project' && source === 'claude') return 'Project / Claude';
const match = SKILL_LOCATION_OPTIONS.find((option) => option.scope === scope && option.source === source);
return match?.label || `${scope} / ${source}`;
}