{ "$schema": "https://ui.shadcn.com/schema/registry-item.json", "name": "agent-skill-editor", "title": "Agent Skill Editor", "description": "Controlled canonical Skill editor with instructions, bounded requirements and resource manifests, and replaceable SKILL.md preview", "dependencies": ["@wener/ai@^0.1.21", "daisyui", "lucide-react"], "registryDependencies": ["https://ui-components.wener.me/r/ai-config-editor.json"], "files": [ { "path": "registry/default/blocks/agent-skill-editor/agent-skill-editor-types.ts", "content": "import type { Skill } from '@wener/ai/agent/skill';\nimport type { ComponentPropsWithRef, ComponentType } from 'react';\nimport type {\n\tAiConfigEditorSlots,\n\tAiConfigSchema,\n\tAiConfigValidationMode,\n\tAiResourceEditorMessageOverrides,\n\tAiResourceEditorMessages,\n} from '../../ui/ai-config-editor';\n\nexport type AgentSkillEditorField =\n\t| 'id'\n\t| 'name'\n\t| 'description'\n\t| 'version'\n\t| 'instructions'\n\t| 'tags'\n\t| 'toolRequirements'\n\t| 'contextRequirements'\n\t| 'resources'\n\t| 'metadata'\n\t| 'extensions'\n\t| 'preview';\nexport type AgentSkillEditorSection =\n\t| 'identity'\n\t| 'instructions'\n\t| 'requirements'\n\t| 'resources'\n\t| 'extensions'\n\t| 'preview';\nexport type AgentSkillEditorMessages = AiResourceEditorMessages;\nexport type AgentSkillEditorMessageOverrides = AiResourceEditorMessageOverrides<\n\tAgentSkillEditorField,\n\tAgentSkillEditorSection\n>;\nexport type AgentSkillPreviewProps = { value: Skill; markdown: string };\nexport type AgentSkillEditorProps = Omit, 'onChange' | 'title'> & {\n\tvalue: Skill;\n\tonChange: (value: Skill) => void;\n\tonSubmit?: (value: Skill) => void;\n\tschema?: AiConfigSchema;\n\tvalidationMode?: AiConfigValidationMode;\n\tdisabled?: boolean;\n\treadOnly?: boolean;\n\tmessages?: AgentSkillEditorMessageOverrides;\n\tslots?: AiConfigEditorSlots;\n\tmaxToolRequirements?: number;\n\tmaxContextRequirements?: number;\n\tmaxResources?: number;\n\tpreview?: ComponentType;\n};\n", "type": "registry:lib", "target": "@components/blocks/agent-skill-editor/agent-skill-editor-types.ts" }, { "path": "registry/default/blocks/agent-skill-editor/skill-requirements-editor.tsx", "content": "'use client';\n\nimport type { SkillContextRequirement, SkillToolRequirement } from '@wener/ai/agent/skill';\nimport { Plus, Trash2 } from 'lucide-react';\nimport {\n\tAiConfigFieldGrid,\n\tAiConfigSelectField,\n\tAiConfigStringListField,\n\tAiConfigTextareaField,\n\tAiConfigTextField,\n\tAiConfigToggleField,\n\tJsonValueEditor,\n\tuseStableListEntries,\n} from '../../ui/ai-config-editor';\n\nconst contextKinds = ['agents', 'file', 'directory', 'environment', 'capability', 'service', 'other'].map((value) => ({\n\tvalue,\n\tlabel: value,\n}));\nexport type SkillRequirementsEditorProps = {\n\ttools: readonly SkillToolRequirement[];\n\tcontexts: readonly SkillContextRequirement[];\n\tonToolsChange: (value: SkillToolRequirement[]) => void;\n\tonContextsChange: (value: SkillContextRequirement[]) => void;\n\tdisabled?: boolean;\n\treadOnly?: boolean;\n\tmaxTools?: number;\n\tmaxContexts?: number;\n};\n\nexport function SkillRequirementsEditor({\n\ttools,\n\tcontexts,\n\tonToolsChange,\n\tonContextsChange,\n\tdisabled = false,\n\treadOnly = false,\n\tmaxTools = 64,\n\tmaxContexts = 64,\n}: SkillRequirementsEditorProps) {\n\tconst toolMax = bound(maxTools);\n\tconst contextMax = bound(maxContexts);\n\tconst toolEntries = useStableListEntries(tools, 'tool-requirement');\n\tconst contextEntries = useStableListEntries(contexts, 'context-requirement');\n\treturn (\n\t\t
\n\t\t\t
\n\t\t\t\t= toolMax}\n\t\t\t\t\tonAdd={() => onToolsChange([...tools, createToolRequirement(tools)])}\n\t\t\t\t/>\n\t\t\t\t
\n\t\t\t\t\t{tools.length ? (\n\t\t\t\t\t\ttoolEntries.map(({ item: tool, key }, index) => (\n\t\t\t\t\t\t\t
\n\t\t\t\t\t\t\t\t{tool.name}\n\t\t\t\t\t\t\t\t
\n\t\t\t\t\t\t\t\t\t onToolsChange(replace(tools, index, { ...tool, name }))}\n\t\t\t\t\t\t\t\t\t/>\n\t\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t\t\t\tonToolsChange(replace(tools, index, { ...tool, description: optional(description) }))\n\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t/>\n\t\t\t\t\t\t\t\t\t onToolsChange(replace(tools, index, { ...tool, capabilities }))}\n\t\t\t\t\t\t\t\t\t/>\n\t\t\t\t\t\t\t\t\t onToolsChange(replace(tools, index, { ...tool, required }))}\n\t\t\t\t\t\t\t\t\t/>\n\t\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t\t\t\tonToolsChange(\n\t\t\t\t\t\t\t\t\t\t\t\treplace(tools, index, {\n\t\t\t\t\t\t\t\t\t\t\t\t\t...tool,\n\t\t\t\t\t\t\t\t\t\t\t\t\tconfiguration: configuration as SkillToolRequirement['configuration'],\n\t\t\t\t\t\t\t\t\t\t\t\t}),\n\t\t\t\t\t\t\t\t\t\t\t)\n\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t/>\n\t\t\t\t\t\t\t\t\t{!readOnly ? (\n\t\t\t\t\t\t\t\t\t\t onToolsChange(tools.filter((_, itemIndex) => itemIndex !== index))}\n\t\t\t\t\t\t\t\t\t\t/>\n\t\t\t\t\t\t\t\t\t) : null}\n\t\t\t\t\t\t\t\t
\n\t\t\t\t\t\t\t
\n\t\t\t\t\t\t))\n\t\t\t\t\t) : (\n\t\t\t\t\t\t\n\t\t\t\t\t)}\n\t\t\t\t
\n\t\t\t
\n\t\t\t
\n\t\t\t\t= contextMax}\n\t\t\t\t\tonAdd={() => onContextsChange([...contexts, createContextRequirement(contexts)])}\n\t\t\t\t/>\n\t\t\t\t
\n\t\t\t\t\t{contexts.length ? (\n\t\t\t\t\t\tcontextEntries.map(({ item: context, key }, index) => (\n\t\t\t\t\t\t\t
\n\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t\t{context.kind}: {context.value}\n\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t
\n\t\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t\t\t\t\tonContextsChange(\n\t\t\t\t\t\t\t\t\t\t\t\t\treplace(contexts, index, { ...context, kind: kind as SkillContextRequirement['kind'] }),\n\t\t\t\t\t\t\t\t\t\t\t\t)\n\t\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t/>\n\t\t\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t\t\t\t\tonContextsChange(replace(contexts, index, { ...context, value: nextValue }))\n\t\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t/>\n\t\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t\t\t\tonContextsChange(replace(contexts, index, { ...context, description: optional(description) }))\n\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t/>\n\t\t\t\t\t\t\t\t\t onContextsChange(replace(contexts, index, { ...context, required }))}\n\t\t\t\t\t\t\t\t\t/>\n\t\t\t\t\t\t\t\t\t{!readOnly ? (\n\t\t\t\t\t\t\t\t\t\t onContextsChange(contexts.filter((_, itemIndex) => itemIndex !== index))}\n\t\t\t\t\t\t\t\t\t\t/>\n\t\t\t\t\t\t\t\t\t) : null}\n\t\t\t\t\t\t\t\t
\n\t\t\t\t\t\t\t
\n\t\t\t\t\t\t))\n\t\t\t\t\t) : (\n\t\t\t\t\t\t\n\t\t\t\t\t)}\n\t\t\t\t
\n\t\t\t
\n\t\t
\n\t);\n}\n\nfunction ListHeader({\n\ttitle,\n\tcount,\n\tmax,\n\tdisabled,\n\tonAdd,\n}: {\n\ttitle: string;\n\tcount: number;\n\tmax: number;\n\tdisabled: boolean;\n\tonAdd: () => void;\n}) {\n\treturn (\n\t\t
\n\t\t\t
\n\t\t\t\t

{title}

\n\t\t\t\t

\n\t\t\t\t\t{count} / {max}\n\t\t\t\t

\n\t\t\t
\n\t\t\t\n\t\t\t\t
\n\t);\n}\nfunction RemoveButton({ disabled, label, onClick }: { disabled: boolean; label: string; onClick: () => void }) {\n\treturn (\n\t\t
\n\t\t\t\n\t\t
\n\t);\n}\nfunction Empty({ label }: { label: string }) {\n\treturn
{label}
;\n}\nfunction replace(items: readonly T[], index: number, value: T): T[] {\n\treturn items.map((item, itemIndex) => (itemIndex === index ? value : item));\n}\nfunction createToolRequirement(items: readonly SkillToolRequirement[]): SkillToolRequirement {\n\tlet suffix = items.length + 1;\n\twhile (items.some((item) => item.name === `tool-${suffix}`)) suffix++;\n\treturn { name: `tool-${suffix}`, required: true };\n}\nfunction createContextRequirement(items: readonly SkillContextRequirement[]): SkillContextRequirement {\n\treturn { kind: 'file', value: `reference-${items.length + 1}.md`, required: true };\n}\nfunction optional(value: string): string | undefined {\n\treturn value.trim() ? value : undefined;\n}\nfunction bound(value: number): number {\n\treturn Number.isInteger(value) ? Math.max(1, Math.min(value, 256)) : 64;\n}\n", "type": "registry:component", "target": "@components/blocks/agent-skill-editor/skill-requirements-editor.tsx" }, { "path": "registry/default/blocks/agent-skill-editor/skill-resources-editor.tsx", "content": "'use client';\n\nimport type { SkillResource } from '@wener/ai/agent/skill';\nimport { Plus, Trash2 } from 'lucide-react';\nimport {\n\tAiConfigFieldGrid,\n\tAiConfigSelectField,\n\tAiConfigTextareaField,\n\tAiConfigTextField,\n\tAiConfigToggleField,\n\tuseStableListEntries,\n} from '../../ui/ai-config-editor';\n\nconst resourceTypes = ['reference', 'template', 'example', 'script', 'asset', 'schema', 'other'].map((value) => ({\n\tvalue,\n\tlabel: value,\n}));\nexport type SkillResourcesEditorProps = {\n\tvalue: readonly SkillResource[];\n\tonChange: (value: SkillResource[]) => void;\n\tdisabled?: boolean;\n\treadOnly?: boolean;\n\tmaxResources?: number;\n};\n\nexport function SkillResourcesEditor({\n\tvalue,\n\tonChange,\n\tdisabled = false,\n\treadOnly = false,\n\tmaxResources = 64,\n}: SkillResourcesEditorProps) {\n\tconst maximum = Number.isInteger(maxResources) ? Math.max(1, Math.min(maxResources, 256)) : 64;\n\tconst resourceEntries = useStableListEntries(value, 'skill-resource');\n\tconst update = (index: number, resource: SkillResource) =>\n\t\tonChange(value.map((current, itemIndex) => (itemIndex === index ? resource : current)));\n\treturn (\n\t\t
\n\t\t\t
\n\t\t\t\t

\n\t\t\t\t\t{value.length} / {maximum} 个资源 manifest\n\t\t\t\t

\n\t\t\t\t{!readOnly ? (\n\t\t\t\t\t= maximum}\n\t\t\t\t\t\tonClick={() => onChange([...value, createResource(value)])}\n\t\t\t\t\t>\n\t\t\t\t\t\t
\n\t\t\t
\n\t\t\t\t{value.length ? (\n\t\t\t\t\tresourceEntries.map(({ item: resource, key }, index) => (\n\t\t\t\t\t\t
\n\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t{resource.id}\n\t\t\t\t\t\t\t\t{resource.type}\n\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t
\n\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t\t update(index, { ...resource, id })}\n\t\t\t\t\t\t\t\t\t/>\n\t\t\t\t\t\t\t\t\t update(index, { ...resource, type: type as SkillResource['type'] })}\n\t\t\t\t\t\t\t\t\t/>\n\t\t\t\t\t\t\t\t\t update(index, { ...resource, path: optional(path) })}\n\t\t\t\t\t\t\t\t\t/>\n\t\t\t\t\t\t\t\t\t update(index, { ...resource, uri: optional(uri) })}\n\t\t\t\t\t\t\t\t\t/>\n\t\t\t\t\t\t\t\t\t update(index, { ...resource, mediaType: optional(mediaType) })}\n\t\t\t\t\t\t\t\t\t/>\n\t\t\t\t\t\t\t\t\t update(index, { ...resource, checksum: optional(checksum) })}\n\t\t\t\t\t\t\t\t\t/>\n\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t update(index, { ...resource, description: optional(description) })}\n\t\t\t\t\t\t\t\t/>\n\t\t\t\t\t\t\t\t update(index, { ...resource, required })}\n\t\t\t\t\t\t\t\t/>\n\t\t\t\t\t\t\t\t{!readOnly ? (\n\t\t\t\t\t\t\t\t\t
\n\t\t\t\t\t\t\t\t\t\t onChange(value.filter((_, itemIndex) => itemIndex !== index))}\n\t\t\t\t\t\t\t\t\t\t>\n\t\t\t\t\t\t\t\t\t\t\t
\n\t\t\t\t\t\t\t\t) : null}\n\t\t\t\t\t\t\t
\n\t\t\t\t\t\t
\n\t\t\t\t\t))\n\t\t\t\t) : (\n\t\t\t\t\t
暂无资源 manifest
\n\t\t\t\t)}\n\t\t\t
\n\t\t
\n\t);\n}\n\nfunction createResource(items: readonly SkillResource[]): SkillResource {\n\tlet suffix = items.length + 1;\n\twhile (items.some((item) => item.id === `resource-${suffix}`)) suffix++;\n\treturn { id: `resource-${suffix}`, type: 'reference', path: `references/resource-${suffix}.md` };\n}\nfunction optional(value: string): string | undefined {\n\treturn value.trim() ? value : undefined;\n}\n", "type": "registry:component", "target": "@components/blocks/agent-skill-editor/skill-resources-editor.tsx" }, { "path": "registry/default/blocks/agent-skill-editor/skill-markdown-preview.tsx", "content": "import type { Skill } from '@wener/ai/agent/skill';\nimport type { AgentSkillPreviewProps } from './agent-skill-editor-types';\n\nexport function formatSkillMarkdown(value: Skill): string {\n\tconst tags = value.tags?.length ? `\\ntags: [${value.tags.map((tag) => JSON.stringify(tag)).join(', ')}]` : '';\n\treturn `---\\nname: ${JSON.stringify(value.name)}\\ndescription: ${JSON.stringify(value.description)}\\nversion: ${JSON.stringify(value.version)}${tags}\\n---\\n\\n${value.instructions}`;\n}\n\nexport function SkillMarkdownPreview({ markdown }: AgentSkillPreviewProps) {\n\treturn (\n\t\t
\n\t\t\t
\n\t\t\t\t
\n\t\t\t\t\t

SKILL.md 预览

\n\t\t\t\t\t

仅展示 canonical 值;解析和文件写入由宿主 adapter 注入。

\n\t\t\t\t
\n\t\t\t\t只读预览\n\t\t\t
\n\t\t\t\n\t\t
\n\t);\n}\n", "type": "registry:component", "target": "@components/blocks/agent-skill-editor/skill-markdown-preview.tsx" }, { "path": "registry/default/blocks/agent-skill-editor/agent-skill-editor.tsx", "content": "'use client';\n\nimport { type Skill, SkillSchema } from '@wener/ai/agent/skill';\nimport type { ReactNode } from 'react';\nimport {\n\tAiConfigEditor,\n\tAiConfigFieldGrid,\n\tAiConfigFieldSlot,\n\tAiConfigFormSection,\n\tAiConfigStringListField,\n\tAiConfigTextareaField,\n\tAiConfigTextField,\n\taiConfigFieldError,\n\tJsonValueEditor,\n\tmergeResourceEditorMessages,\n\tuseAiConfigDraft,\n} from '../../ui/ai-config-editor';\nimport type {\n\tAgentSkillEditorField,\n\tAgentSkillEditorMessages,\n\tAgentSkillEditorProps,\n} from './agent-skill-editor-types';\nimport { formatSkillMarkdown, SkillMarkdownPreview } from './skill-markdown-preview';\nimport { SkillRequirementsEditor } from './skill-requirements-editor';\nimport { SkillResourcesEditor } from './skill-resources-editor';\n\nexport const defaultAgentSkill: Skill = SkillSchema.parse({\n\tname: 'new-skill',\n\tdescription: '描述此 Skill 的用途',\n\tinstructions: '在此编写执行指令。',\n\tversion: '1.0.0',\n});\nconst defaultMessages: AgentSkillEditorMessages = {\n\ttitle: 'Agent Skill 配置',\n\tdescription: '编辑 SKILL.md 对应内容、requirements、resource manifests 与扩展数据。',\n\tsections: {\n\t\tidentity: '身份',\n\t\tinstructions: 'Instructions',\n\t\trequirements: '工具与上下文要求',\n\t\tresources: '资源 Manifest',\n\t\textensions: 'Metadata 与 Extensions',\n\t\tpreview: 'SKILL.md 预览',\n\t},\n\tfields: {\n\t\tid: 'ID',\n\t\tname: '名称',\n\t\tdescription: '描述',\n\t\tversion: '版本',\n\t\tinstructions: 'Instructions',\n\t\ttags: '标签',\n\t\ttoolRequirements: '工具要求',\n\t\tcontextRequirements: '上下文要求',\n\t\tresources: '资源',\n\t\tmetadata: 'Metadata',\n\t\textensions: 'Extensions',\n\t\tpreview: 'SKILL.md 预览',\n\t},\n};\n\nexport function AgentSkillEditor({\n\tvalue,\n\tonChange,\n\tonSubmit,\n\tschema = SkillSchema,\n\tvalidationMode,\n\tdisabled = false,\n\treadOnly = false,\n\tmessages: overrides,\n\tslots,\n\tmaxToolRequirements = 64,\n\tmaxContextRequirements = 64,\n\tmaxResources = 64,\n\tpreview: Preview = SkillMarkdownPreview,\n\t...props\n}: AgentSkillEditorProps) {\n\tconst messages = mergeResourceEditorMessages(defaultMessages, overrides);\n\tconst controller = useAiConfigDraft({\n\t\tvalue,\n\t\tfallbackValue: defaultAgentSkill,\n\t\tschema,\n\t\tonChange,\n\t\tonSubmit,\n\t\tvalidationMode,\n\t});\n\tconst draft = controller.draft;\n\tconst patch = (next: Partial) => controller.setDraft({ ...draft, ...next });\n\tconst field = (name: AgentSkillEditorField, defaultField: ReactNode) => (\n\t\t\n\t);\n\treturn (\n\t\t\n\t\t\t\n\t\t\t\t\n\t\t\t\t\t{field(\n\t\t\t\t\t\t'id',\n\t\t\t\t\t\t patch({ id: optional(id) })}\n\t\t\t\t\t\t/>,\n\t\t\t\t\t)}\n\t\t\t\t\t{field(\n\t\t\t\t\t\t'name',\n\t\t\t\t\t\t patch({ name })}\n\t\t\t\t\t\t/>,\n\t\t\t\t\t)}\n\t\t\t\t\t{field(\n\t\t\t\t\t\t'version',\n\t\t\t\t\t\t patch({ version })}\n\t\t\t\t\t\t/>,\n\t\t\t\t\t)}\n\t\t\t\t\n\t\t\t\t{field(\n\t\t\t\t\t'description',\n\t\t\t\t\t patch({ description })}\n\t\t\t\t\t/>,\n\t\t\t\t)}\n\t\t\t\t{field(\n\t\t\t\t\t'tags',\n\t\t\t\t\t patch({ tags })}\n\t\t\t\t\t/>,\n\t\t\t\t)}\n\t\t\t\n\t\t\t\n\t\t\t\t{field(\n\t\t\t\t\t'instructions',\n\t\t\t\t\t patch({ instructions })}\n\t\t\t\t\t/>,\n\t\t\t\t)}\n\t\t\t\n\t\t\t\n\t\t\t\t{field(\n\t\t\t\t\t'toolRequirements',\n\t\t\t\t\t patch({ toolRequirements })}\n\t\t\t\t\t\tonContextsChange={(contextRequirements) => patch({ contextRequirements })}\n\t\t\t\t\t/>,\n\t\t\t\t)}\n\t\t\t\n\t\t\t\n\t\t\t\t{field(\n\t\t\t\t\t'resources',\n\t\t\t\t\t patch({ resources })}\n\t\t\t\t\t/>,\n\t\t\t\t)}\n\t\t\t\n\t\t\t\n\t\t\t\t
\n\t\t\t\t\t{field(\n\t\t\t\t\t\t'metadata',\n\t\t\t\t\t\t patch({ metadata: metadata as Skill['metadata'] })}\n\t\t\t\t\t\t/>,\n\t\t\t\t\t)}\n\t\t\t\t\t{field(\n\t\t\t\t\t\t'extensions',\n\t\t\t\t\t\t patch({ extensions: extensions as Skill['extensions'] })}\n\t\t\t\t\t\t/>,\n\t\t\t\t\t)}\n\t\t\t\t
\n\t\t\t
\n\t\t\t\n\t\t\t\t{field('preview', )}\n\t\t\t\n\t\t\n\t);\n}\n\nfunction optional(value: string): string | undefined {\n\treturn value.trim() ? value : undefined;\n}\n", "type": "registry:component", "target": "@components/blocks/agent-skill-editor/agent-skill-editor.tsx" }, { "path": "registry/default/blocks/agent-skill-editor/index.ts", "content": "export * from './agent-skill-editor';\nexport * from './agent-skill-editor-types';\nexport * from './skill-markdown-preview';\nexport * from './skill-requirements-editor';\nexport * from './skill-resources-editor';\n", "type": "registry:component", "target": "@components/blocks/agent-skill-editor/index.ts" } ], "type": "registry:block" }