feat: Implement skill management functionality

- Added skill scope helpers and CRUD operations for skills in opencodeConfig.ts.
- Introduced API endpoints for skill management in main.tsx and index.js.
- Enhanced server-side logic to support skill discovery, creation, updating, and deletion.
- Implemented supporting file operations for skills, including reading, writing, and deleting files.
- Updated package.json to use the latest version of @opencode-ai/sdk.
This commit is contained in:
Bohdan Triapitsyn
2025-12-30 17:36:52 +02:00
parent f3a00bc8f4
commit 2c833cef40
25 changed files with 3587 additions and 15 deletions
@@ -1,7 +1,7 @@
import React from 'react';
import { RuntimeAPIContext } from '@/contexts/runtimeAPIContext';
import { RiArrowDownSLine, RiArrowRightSLine, RiFileEditLine, RiFileSearchLine, RiFileTextLine, RiFolder6Line, RiGitBranchLine, RiGlobalLine, RiListCheck3, RiMenuSearchLine, RiPencilLine, RiTerminalBoxLine, RiToolsLine } from '@remixicon/react';
import { RiArrowDownSLine, RiArrowRightSLine, RiBookLine, RiFileEditLine, RiFileSearchLine, RiFileTextLine, RiFolder6Line, RiGitBranchLine, RiGlobalLine, RiListCheck3, RiMenuSearchLine, RiPencilLine, RiTerminalBoxLine, RiToolsLine } from '@remixicon/react';
import { cn } from '@/lib/utils';
import { SimpleMarkdownRenderer } from '../../MarkdownRenderer';
import { getToolMetadata, getLanguageFromExtension, isImageFile, getImageMimeType } from '@/lib/toolHelpers';
@@ -82,6 +82,9 @@ export const getToolIcon = (toolName: string) => {
if (tool === 'todowrite' || tool === 'todoread') {
return <RiListCheck3 className={iconClass} />;
}
if (tool === 'skill') {
return <RiBookLine className={iconClass} />;
}
if (tool.startsWith('git')) {
return <RiGitBranchLine className={iconClass} />;
}
@@ -171,6 +174,10 @@ const getToolDescription = (part: ToolPartType, state: ToolStateUnion, isMobile:
return isMobile ? input.description.substring(0, 40) : input.description.substring(0, 80);
}
if (part.tool === 'skill' && input?.name && typeof input.name === 'string') {
return input.name;
}
const desc = input?.description || metadata?.description || ('title' in state && state.title) || '';
return typeof desc === 'string' ? desc : '';
};
@@ -654,6 +661,14 @@ const ToolExpandedContent: React.FC<ToolExpandedContentProps> = ({
);
}
if (part.tool === 'skill' && hasStringOutput) {
return renderScrollableBlock(
<div className="w-full min-w-0">
<SimpleMarkdownRenderer content={outputString} variant="tool" />
</div>
);
}
if ((part.tool === 'edit' || part.tool === 'multiedit') && ((!hasStringOutput && diffContent) || (outputString.trim().length === 0 || hasLspDiagnostics(outputString))) && diffContent) {
return renderScrollableBlock(
<DiffPreview diff={diffContent} syntaxTheme={syntaxTheme} input={input} />,