llms-py 2.0.20__py3-none-any.whl → 3.0.18__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- llms/__init__.py +3 -1
- llms/db.py +359 -0
- llms/{ui/Analytics.mjs → extensions/analytics/ui/index.mjs} +254 -327
- llms/extensions/app/README.md +20 -0
- llms/extensions/app/__init__.py +588 -0
- llms/extensions/app/db.py +540 -0
- llms/{ui → extensions/app/ui}/Recents.mjs +99 -73
- llms/{ui/Sidebar.mjs → extensions/app/ui/index.mjs} +139 -68
- llms/extensions/app/ui/threadStore.mjs +440 -0
- llms/extensions/computer/README.md +96 -0
- llms/extensions/computer/__init__.py +59 -0
- llms/extensions/computer/base.py +80 -0
- llms/extensions/computer/bash.py +185 -0
- llms/extensions/computer/computer.py +523 -0
- llms/extensions/computer/edit.py +299 -0
- llms/extensions/computer/filesystem.py +542 -0
- llms/extensions/computer/platform.py +461 -0
- llms/extensions/computer/run.py +37 -0
- llms/extensions/core_tools/CALCULATOR.md +32 -0
- llms/extensions/core_tools/__init__.py +599 -0
- llms/extensions/core_tools/ui/codemirror/addon/edit/closebrackets.js +201 -0
- llms/extensions/core_tools/ui/codemirror/addon/edit/closetag.js +185 -0
- llms/extensions/core_tools/ui/codemirror/addon/edit/continuelist.js +101 -0
- llms/extensions/core_tools/ui/codemirror/addon/edit/matchbrackets.js +160 -0
- llms/extensions/core_tools/ui/codemirror/addon/edit/matchtags.js +66 -0
- llms/extensions/core_tools/ui/codemirror/addon/edit/trailingspace.js +27 -0
- llms/extensions/core_tools/ui/codemirror/addon/selection/active-line.js +72 -0
- llms/extensions/core_tools/ui/codemirror/addon/selection/mark-selection.js +119 -0
- llms/extensions/core_tools/ui/codemirror/addon/selection/selection-pointer.js +98 -0
- llms/extensions/core_tools/ui/codemirror/codemirror.css +344 -0
- llms/extensions/core_tools/ui/codemirror/codemirror.js +9884 -0
- llms/extensions/core_tools/ui/codemirror/doc/docs.css +225 -0
- llms/extensions/core_tools/ui/codemirror/doc/source_sans.woff +0 -0
- llms/extensions/core_tools/ui/codemirror/mode/clike/clike.js +942 -0
- llms/extensions/core_tools/ui/codemirror/mode/javascript/index.html +118 -0
- llms/extensions/core_tools/ui/codemirror/mode/javascript/javascript.js +962 -0
- llms/extensions/core_tools/ui/codemirror/mode/javascript/typescript.html +62 -0
- llms/extensions/core_tools/ui/codemirror/mode/python/python.js +402 -0
- llms/extensions/core_tools/ui/codemirror/theme/dracula.css +40 -0
- llms/extensions/core_tools/ui/codemirror/theme/mocha.css +135 -0
- llms/extensions/core_tools/ui/index.mjs +650 -0
- llms/extensions/gallery/README.md +61 -0
- llms/extensions/gallery/__init__.py +63 -0
- llms/extensions/gallery/db.py +243 -0
- llms/extensions/gallery/ui/index.mjs +482 -0
- llms/extensions/katex/README.md +39 -0
- llms/extensions/katex/__init__.py +6 -0
- llms/extensions/katex/ui/README.md +125 -0
- llms/extensions/katex/ui/contrib/auto-render.js +338 -0
- llms/extensions/katex/ui/contrib/auto-render.min.js +1 -0
- llms/extensions/katex/ui/contrib/auto-render.mjs +244 -0
- llms/extensions/katex/ui/contrib/copy-tex.js +127 -0
- llms/extensions/katex/ui/contrib/copy-tex.min.js +1 -0
- llms/extensions/katex/ui/contrib/copy-tex.mjs +105 -0
- llms/extensions/katex/ui/contrib/mathtex-script-type.js +109 -0
- llms/extensions/katex/ui/contrib/mathtex-script-type.min.js +1 -0
- llms/extensions/katex/ui/contrib/mathtex-script-type.mjs +24 -0
- llms/extensions/katex/ui/contrib/mhchem.js +3213 -0
- llms/extensions/katex/ui/contrib/mhchem.min.js +1 -0
- llms/extensions/katex/ui/contrib/mhchem.mjs +3109 -0
- llms/extensions/katex/ui/contrib/render-a11y-string.js +887 -0
- llms/extensions/katex/ui/contrib/render-a11y-string.min.js +1 -0
- llms/extensions/katex/ui/contrib/render-a11y-string.mjs +800 -0
- llms/extensions/katex/ui/fonts/KaTeX_AMS-Regular.ttf +0 -0
- llms/extensions/katex/ui/fonts/KaTeX_AMS-Regular.woff +0 -0
- llms/extensions/katex/ui/fonts/KaTeX_AMS-Regular.woff2 +0 -0
- llms/extensions/katex/ui/fonts/KaTeX_Caligraphic-Bold.ttf +0 -0
- llms/extensions/katex/ui/fonts/KaTeX_Caligraphic-Bold.woff +0 -0
- llms/extensions/katex/ui/fonts/KaTeX_Caligraphic-Bold.woff2 +0 -0
- llms/extensions/katex/ui/fonts/KaTeX_Caligraphic-Regular.ttf +0 -0
- llms/extensions/katex/ui/fonts/KaTeX_Caligraphic-Regular.woff +0 -0
- llms/extensions/katex/ui/fonts/KaTeX_Caligraphic-Regular.woff2 +0 -0
- llms/extensions/katex/ui/fonts/KaTeX_Fraktur-Bold.ttf +0 -0
- llms/extensions/katex/ui/fonts/KaTeX_Fraktur-Bold.woff +0 -0
- llms/extensions/katex/ui/fonts/KaTeX_Fraktur-Bold.woff2 +0 -0
- llms/extensions/katex/ui/fonts/KaTeX_Fraktur-Regular.ttf +0 -0
- llms/extensions/katex/ui/fonts/KaTeX_Fraktur-Regular.woff +0 -0
- llms/extensions/katex/ui/fonts/KaTeX_Fraktur-Regular.woff2 +0 -0
- llms/extensions/katex/ui/fonts/KaTeX_Main-Bold.ttf +0 -0
- llms/extensions/katex/ui/fonts/KaTeX_Main-Bold.woff +0 -0
- llms/extensions/katex/ui/fonts/KaTeX_Main-Bold.woff2 +0 -0
- llms/extensions/katex/ui/fonts/KaTeX_Main-BoldItalic.ttf +0 -0
- llms/extensions/katex/ui/fonts/KaTeX_Main-BoldItalic.woff +0 -0
- llms/extensions/katex/ui/fonts/KaTeX_Main-BoldItalic.woff2 +0 -0
- llms/extensions/katex/ui/fonts/KaTeX_Main-Italic.ttf +0 -0
- llms/extensions/katex/ui/fonts/KaTeX_Main-Italic.woff +0 -0
- llms/extensions/katex/ui/fonts/KaTeX_Main-Italic.woff2 +0 -0
- llms/extensions/katex/ui/fonts/KaTeX_Main-Regular.ttf +0 -0
- llms/extensions/katex/ui/fonts/KaTeX_Main-Regular.woff +0 -0
- llms/extensions/katex/ui/fonts/KaTeX_Main-Regular.woff2 +0 -0
- llms/extensions/katex/ui/fonts/KaTeX_Math-BoldItalic.ttf +0 -0
- llms/extensions/katex/ui/fonts/KaTeX_Math-BoldItalic.woff +0 -0
- llms/extensions/katex/ui/fonts/KaTeX_Math-BoldItalic.woff2 +0 -0
- llms/extensions/katex/ui/fonts/KaTeX_Math-Italic.ttf +0 -0
- llms/extensions/katex/ui/fonts/KaTeX_Math-Italic.woff +0 -0
- llms/extensions/katex/ui/fonts/KaTeX_Math-Italic.woff2 +0 -0
- llms/extensions/katex/ui/fonts/KaTeX_SansSerif-Bold.ttf +0 -0
- llms/extensions/katex/ui/fonts/KaTeX_SansSerif-Bold.woff +0 -0
- llms/extensions/katex/ui/fonts/KaTeX_SansSerif-Bold.woff2 +0 -0
- llms/extensions/katex/ui/fonts/KaTeX_SansSerif-Italic.ttf +0 -0
- llms/extensions/katex/ui/fonts/KaTeX_SansSerif-Italic.woff +0 -0
- llms/extensions/katex/ui/fonts/KaTeX_SansSerif-Italic.woff2 +0 -0
- llms/extensions/katex/ui/fonts/KaTeX_SansSerif-Regular.ttf +0 -0
- llms/extensions/katex/ui/fonts/KaTeX_SansSerif-Regular.woff +0 -0
- llms/extensions/katex/ui/fonts/KaTeX_SansSerif-Regular.woff2 +0 -0
- llms/extensions/katex/ui/fonts/KaTeX_Script-Regular.ttf +0 -0
- llms/extensions/katex/ui/fonts/KaTeX_Script-Regular.woff +0 -0
- llms/extensions/katex/ui/fonts/KaTeX_Script-Regular.woff2 +0 -0
- llms/extensions/katex/ui/fonts/KaTeX_Size1-Regular.ttf +0 -0
- llms/extensions/katex/ui/fonts/KaTeX_Size1-Regular.woff +0 -0
- llms/extensions/katex/ui/fonts/KaTeX_Size1-Regular.woff2 +0 -0
- llms/extensions/katex/ui/fonts/KaTeX_Size2-Regular.ttf +0 -0
- llms/extensions/katex/ui/fonts/KaTeX_Size2-Regular.woff +0 -0
- llms/extensions/katex/ui/fonts/KaTeX_Size2-Regular.woff2 +0 -0
- llms/extensions/katex/ui/fonts/KaTeX_Size3-Regular.ttf +0 -0
- llms/extensions/katex/ui/fonts/KaTeX_Size3-Regular.woff +0 -0
- llms/extensions/katex/ui/fonts/KaTeX_Size3-Regular.woff2 +0 -0
- llms/extensions/katex/ui/fonts/KaTeX_Size4-Regular.ttf +0 -0
- llms/extensions/katex/ui/fonts/KaTeX_Size4-Regular.woff +0 -0
- llms/extensions/katex/ui/fonts/KaTeX_Size4-Regular.woff2 +0 -0
- llms/extensions/katex/ui/fonts/KaTeX_Typewriter-Regular.ttf +0 -0
- llms/extensions/katex/ui/fonts/KaTeX_Typewriter-Regular.woff +0 -0
- llms/extensions/katex/ui/fonts/KaTeX_Typewriter-Regular.woff2 +0 -0
- llms/extensions/katex/ui/index.mjs +92 -0
- llms/extensions/katex/ui/katex-swap.css +1230 -0
- llms/extensions/katex/ui/katex-swap.min.css +1 -0
- llms/extensions/katex/ui/katex.css +1230 -0
- llms/extensions/katex/ui/katex.js +19080 -0
- llms/extensions/katex/ui/katex.min.css +1 -0
- llms/extensions/katex/ui/katex.min.js +1 -0
- llms/extensions/katex/ui/katex.min.mjs +1 -0
- llms/extensions/katex/ui/katex.mjs +18547 -0
- llms/extensions/providers/__init__.py +22 -0
- llms/extensions/providers/anthropic.py +260 -0
- llms/extensions/providers/cerebras.py +36 -0
- llms/extensions/providers/chutes.py +153 -0
- llms/extensions/providers/google.py +559 -0
- llms/extensions/providers/nvidia.py +103 -0
- llms/extensions/providers/openai.py +154 -0
- llms/extensions/providers/openrouter.py +74 -0
- llms/extensions/providers/zai.py +182 -0
- llms/extensions/skills/LICENSE +202 -0
- llms/extensions/skills/__init__.py +130 -0
- llms/extensions/skills/errors.py +25 -0
- llms/extensions/skills/models.py +39 -0
- llms/extensions/skills/parser.py +178 -0
- llms/extensions/skills/ui/index.mjs +376 -0
- llms/extensions/skills/ui/skills/create-plan/SKILL.md +74 -0
- llms/extensions/skills/validator.py +177 -0
- llms/extensions/system_prompts/README.md +22 -0
- llms/extensions/system_prompts/__init__.py +45 -0
- llms/extensions/system_prompts/ui/index.mjs +276 -0
- llms/extensions/system_prompts/ui/prompts.json +1067 -0
- llms/extensions/tools/__init__.py +67 -0
- llms/extensions/tools/ui/index.mjs +837 -0
- llms/index.html +36 -62
- llms/llms.json +180 -879
- llms/main.py +4009 -912
- llms/providers-extra.json +394 -0
- llms/providers.json +1 -0
- llms/ui/App.mjs +176 -8
- llms/ui/ai.mjs +156 -20
- llms/ui/app.css +3768 -321
- llms/ui/ctx.mjs +459 -0
- llms/ui/index.mjs +131 -0
- llms/ui/lib/chart.js +14 -0
- llms/ui/lib/charts.mjs +16 -0
- llms/ui/lib/color.js +14 -0
- llms/ui/lib/highlight.min.mjs +1243 -0
- llms/ui/lib/idb.min.mjs +8 -0
- llms/ui/lib/marked.min.mjs +8 -0
- llms/ui/lib/servicestack-client.mjs +1 -0
- llms/ui/lib/servicestack-vue.mjs +37 -0
- llms/ui/lib/vue-router.min.mjs +6 -0
- llms/ui/lib/vue.min.mjs +13 -0
- llms/ui/lib/vue.mjs +18530 -0
- llms/ui/markdown.mjs +25 -14
- llms/ui/modules/chat/ChatBody.mjs +1156 -0
- llms/ui/{SettingsDialog.mjs → modules/chat/SettingsDialog.mjs} +74 -74
- llms/ui/modules/chat/index.mjs +995 -0
- llms/ui/modules/icons.mjs +46 -0
- llms/ui/modules/layout.mjs +271 -0
- llms/ui/modules/model-selector.mjs +811 -0
- llms/ui/tailwind.input.css +560 -78
- llms/ui/typography.css +54 -36
- llms/ui/utils.mjs +221 -92
- llms_py-3.0.18.dist-info/METADATA +49 -0
- llms_py-3.0.18.dist-info/RECORD +194 -0
- {llms_py-2.0.20.dist-info → llms_py-3.0.18.dist-info}/WHEEL +1 -1
- {llms_py-2.0.20.dist-info → llms_py-3.0.18.dist-info}/licenses/LICENSE +1 -2
- llms/ui/Avatar.mjs +0 -28
- llms/ui/Brand.mjs +0 -34
- llms/ui/ChatPrompt.mjs +0 -443
- llms/ui/Main.mjs +0 -740
- llms/ui/ModelSelector.mjs +0 -60
- llms/ui/ProviderIcon.mjs +0 -29
- llms/ui/ProviderStatus.mjs +0 -105
- llms/ui/SignIn.mjs +0 -64
- llms/ui/SystemPromptEditor.mjs +0 -31
- llms/ui/SystemPromptSelector.mjs +0 -36
- llms/ui/Welcome.mjs +0 -8
- llms/ui/threadStore.mjs +0 -524
- llms/ui.json +0 -1069
- llms_py-2.0.20.dist-info/METADATA +0 -931
- llms_py-2.0.20.dist-info/RECORD +0 -36
- {llms_py-2.0.20.dist-info → llms_py-3.0.18.dist-info}/entry_points.txt +0 -0
- {llms_py-2.0.20.dist-info → llms_py-3.0.18.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,376 @@
|
|
|
1
|
+
import { ref, inject, computed } from "vue"
|
|
2
|
+
import { leftPart } from "@servicestack/client"
|
|
3
|
+
|
|
4
|
+
let ext
|
|
5
|
+
|
|
6
|
+
const SkillSelector = {
|
|
7
|
+
template: `
|
|
8
|
+
<div class="px-4 py-4 bg-gray-50 dark:bg-gray-800 border-b border-gray-200 dark:border-gray-700 max-h-[80vh] overflow-y-auto">
|
|
9
|
+
|
|
10
|
+
<!-- Global Controls -->
|
|
11
|
+
<div class="flex items-center justify-between mb-4">
|
|
12
|
+
<span class="text-xs font-bold uppercase text-gray-500 tracking-wider">Include Skills</span>
|
|
13
|
+
<div class="flex items-center gap-2">
|
|
14
|
+
<button type="button" v-if="!$ctx.tools?.isToolEnabled('skill')"
|
|
15
|
+
class="px-3 py-1 rounded-md text-xs font-medium border transition-colors select-none cursor-pointer bg-white dark:bg-gray-800 text-gray-600 dark:text-gray-400 border-gray-200 dark:border-gray-700 hover:border-gray-300 dark:hover:border-gray-600"
|
|
16
|
+
@click="$ctx.tools?.enableTool('skill')"
|
|
17
|
+
title="'skill' tool needs to be enabled to use Skills"
|
|
18
|
+
>
|
|
19
|
+
<span class="text-xs font-semibold text-red-700 dark:text-red-300">⚠️ Enable skill tool</span>
|
|
20
|
+
</button>
|
|
21
|
+
<button type="button" @click="$ctx.setPrefs({ onlySkills: null })"
|
|
22
|
+
class="px-3 py-1 rounded-md text-xs font-medium border transition-colors select-none"
|
|
23
|
+
:class="$prefs.onlySkills == null
|
|
24
|
+
? 'bg-green-100 dark:bg-green-900/40 text-green-800 dark:text-green-300 border-green-300 dark:border-green-800'
|
|
25
|
+
: 'cursor-pointer bg-white dark:bg-gray-800 text-gray-600 dark:text-gray-400 border-gray-200 dark:border-gray-700 hover:border-gray-300 dark:hover:border-gray-600'">
|
|
26
|
+
All Skills
|
|
27
|
+
</button>
|
|
28
|
+
<button type="button" @click="$ctx.setPrefs({ onlySkills:[] })"
|
|
29
|
+
class="px-3 py-1 rounded-md text-xs font-medium border transition-colors select-none"
|
|
30
|
+
:class="$prefs.onlySkills?.length === 0
|
|
31
|
+
? 'bg-fuchsia-100 dark:bg-fuchsia-900/40 text-fuchsia-800 dark:text-fuchsia-300 border-fuchsia-200 dark:border-fuchsia-800'
|
|
32
|
+
: 'cursor-pointer bg-white dark:bg-gray-800 text-gray-600 dark:text-gray-400 border-gray-200 dark:border-gray-700 hover:border-gray-300 dark:hover:border-gray-600'">
|
|
33
|
+
No Skills
|
|
34
|
+
</button>
|
|
35
|
+
</div>
|
|
36
|
+
</div>
|
|
37
|
+
|
|
38
|
+
<!-- Groups -->
|
|
39
|
+
<div class="space-y-3">
|
|
40
|
+
<div v-for="group in skillGroups" :key="group.name"
|
|
41
|
+
class="bg-white dark:bg-gray-900 rounded-lg border border-gray-200 dark:border-gray-700 overflow-hidden">
|
|
42
|
+
|
|
43
|
+
<!-- Group Header -->
|
|
44
|
+
<div class="flex items-center justify-between px-3 py-2 bg-gray-50/50 dark:bg-gray-800/50 cursor-pointer hover:bg-gray-100 dark:hover:bg-gray-800 transition-colors"
|
|
45
|
+
@click="toggleCollapse(group.name)">
|
|
46
|
+
|
|
47
|
+
<div class="flex items-center gap-2 min-w-0">
|
|
48
|
+
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="w-4 h-4 text-gray-400 transition-transform duration-200" :class="{ '-rotate-90': isCollapsed(group.name) }">
|
|
49
|
+
<path fill-rule="evenodd" d="M5.23 7.21a.75.75 0 011.06.02L10 11.168l3.71-3.938a.75.75 0 111.08 1.04l-4.25 4.5a.75.75 0 01-1.08 0l-4.25-4.5a.75.75 0 01.02-1.06z" clip-rule="evenodd" />
|
|
50
|
+
</svg>
|
|
51
|
+
<span class="font-semibold text-sm text-gray-700 dark:text-gray-200 truncate">
|
|
52
|
+
{{ group.name || 'Other Skills' }}
|
|
53
|
+
</span>
|
|
54
|
+
<span class="text-xs text-gray-400 font-mono">
|
|
55
|
+
{{ getActiveCount(group) }}/{{ group.skills.length }}
|
|
56
|
+
</span>
|
|
57
|
+
</div>
|
|
58
|
+
|
|
59
|
+
<div class="flex items-center gap-2" @click.stop>
|
|
60
|
+
<button @click="setGroupSkills(group, true)" type="button"
|
|
61
|
+
title="Include All in Group"
|
|
62
|
+
class="px-2 py-0.5 rounded text-xs font-medium border transition-colors select-none"
|
|
63
|
+
:class="getActiveCount(group) === group.skills.length
|
|
64
|
+
? 'bg-green-50 dark:bg-green-900/20 text-green-700 dark:text-green-300 border-green-300 dark:border-green-800 hover:bg-green-100 dark:hover:bg-green-900/40'
|
|
65
|
+
: 'bg-white dark:bg-gray-800 text-gray-600 dark:text-gray-400 border-gray-200 dark:border-gray-700 hover:border-gray-300 dark:hover:border-gray-600'">
|
|
66
|
+
all
|
|
67
|
+
</button>
|
|
68
|
+
<button @click="setGroupSkills(group, false)" type="button"
|
|
69
|
+
title="Include None in Group"
|
|
70
|
+
class="px-2 py-0.5 rounded text-xs font-medium border transition-colors select-none"
|
|
71
|
+
:class="getActiveCount(group) === 0
|
|
72
|
+
? 'bg-fuchsia-50 dark:bg-fuchsia-900/20 text-fuchsia-700 dark:text-fuchsia-300 border-fuchsia-200 dark:border-fuchsia-800 hover:bg-fuchsia-100 dark:hover:bg-fuchsia-900/40'
|
|
73
|
+
: 'bg-white dark:bg-gray-800 text-gray-600 dark:text-gray-400 border-gray-200 dark:border-gray-700 hover:border-gray-300 dark:hover:border-gray-600'">
|
|
74
|
+
none
|
|
75
|
+
</button>
|
|
76
|
+
</div>
|
|
77
|
+
</div>
|
|
78
|
+
|
|
79
|
+
<!-- Group Body -->
|
|
80
|
+
<div v-show="!isCollapsed(group.name)" class="p-3 bg-white dark:bg-gray-900 border-t border-gray-100 dark:border-gray-800">
|
|
81
|
+
<div class="flex flex-wrap gap-2">
|
|
82
|
+
<button v-for="skill in group.skills" :key="skill.name" type="button"
|
|
83
|
+
@click="toggleSkill(skill.name)"
|
|
84
|
+
:title="skill.description"
|
|
85
|
+
class="px-2.5 py-1 rounded-full text-xs font-medium border transition-colors select-none text-left truncate max-w-[200px]"
|
|
86
|
+
:class="isSkillActive(skill.name)
|
|
87
|
+
? 'bg-blue-100 dark:bg-blue-900/40 text-blue-800 dark:text-blue-300 border-blue-200 dark:border-blue-800'
|
|
88
|
+
: 'bg-gray-50 dark:bg-gray-800 text-gray-600 dark:text-gray-400 border-gray-200 dark:border-gray-700 hover:border-gray-300 dark:hover:border-gray-600'">
|
|
89
|
+
{{ skill.name }}
|
|
90
|
+
</button>
|
|
91
|
+
</div>
|
|
92
|
+
</div>
|
|
93
|
+
</div>
|
|
94
|
+
</div>
|
|
95
|
+
</div>
|
|
96
|
+
`,
|
|
97
|
+
setup() {
|
|
98
|
+
const ctx = inject('ctx')
|
|
99
|
+
const collapsedState = ref({})
|
|
100
|
+
|
|
101
|
+
const availableSkills = computed(() => Object.values(ctx.state.skills || {}))
|
|
102
|
+
|
|
103
|
+
const skillGroups = computed(() => {
|
|
104
|
+
const skills = availableSkills.value
|
|
105
|
+
const groupsMap = {}
|
|
106
|
+
const otherSkills = []
|
|
107
|
+
|
|
108
|
+
skills.forEach(skill => {
|
|
109
|
+
if (skill.group) {
|
|
110
|
+
if (!groupsMap[skill.group]) groupsMap[skill.group] = []
|
|
111
|
+
groupsMap[skill.group].push(skill)
|
|
112
|
+
} else {
|
|
113
|
+
otherSkills.push(skill)
|
|
114
|
+
}
|
|
115
|
+
})
|
|
116
|
+
|
|
117
|
+
const definedGroups = Object.entries(groupsMap).map(([name, skills]) => ({
|
|
118
|
+
name,
|
|
119
|
+
skills
|
|
120
|
+
}))
|
|
121
|
+
|
|
122
|
+
// Sort groups by name if needed, but for now rely on insertion order or backend order
|
|
123
|
+
definedGroups.sort((a, b) => a.name.localeCompare(b.name))
|
|
124
|
+
|
|
125
|
+
if (otherSkills.length > 0) {
|
|
126
|
+
definedGroups.push({ name: '', skills: otherSkills })
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
return definedGroups
|
|
130
|
+
})
|
|
131
|
+
|
|
132
|
+
function isSkillActive(name) {
|
|
133
|
+
const only = ctx.prefs.onlySkills
|
|
134
|
+
if (only == null) return true
|
|
135
|
+
if (Array.isArray(only)) {
|
|
136
|
+
return only.includes(name)
|
|
137
|
+
}
|
|
138
|
+
return false
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
function toggleSkill(name) {
|
|
142
|
+
let onlySkills = ctx.prefs.onlySkills
|
|
143
|
+
|
|
144
|
+
if (onlySkills == null) {
|
|
145
|
+
// If currently 'All', clicking a skill means we enter custom mode with all OTHER skills selected (deselecting clicked)
|
|
146
|
+
// Wait, logic in ToolSelector:
|
|
147
|
+
// if (onlyTools == null) { onlyTools = availableTools.value.map(t => t.function.name).filter(t => t !== name) }
|
|
148
|
+
// This means deselecting one tool switches to "custom" with all but that one.
|
|
149
|
+
|
|
150
|
+
onlySkills = availableSkills.value.map(s => s.name).filter(s => s !== name)
|
|
151
|
+
} else {
|
|
152
|
+
if (onlySkills.includes(name)) {
|
|
153
|
+
onlySkills = onlySkills.filter(s => s !== name)
|
|
154
|
+
} else {
|
|
155
|
+
onlySkills = [...onlySkills, name]
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
ctx.setPrefs({ onlySkills })
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
function toggleCollapse(groupName) {
|
|
163
|
+
const key = groupName || '_other_'
|
|
164
|
+
collapsedState.value[key] = !collapsedState.value[key]
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
function isCollapsed(groupName) {
|
|
168
|
+
const key = groupName || '_other_'
|
|
169
|
+
return !!collapsedState.value[key]
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
function setGroupSkills(group, enable) {
|
|
173
|
+
const groupSkillNames = group.skills.map(s => s.name)
|
|
174
|
+
let onlySkills = ctx.prefs.onlySkills
|
|
175
|
+
|
|
176
|
+
if (enable) {
|
|
177
|
+
if (onlySkills == null) return
|
|
178
|
+
const newSet = new Set(onlySkills)
|
|
179
|
+
groupSkillNames.forEach(n => newSet.add(n))
|
|
180
|
+
onlySkills = Array.from(newSet)
|
|
181
|
+
if (onlySkills.length === availableSkills.value.length) {
|
|
182
|
+
onlySkills = null
|
|
183
|
+
}
|
|
184
|
+
} else {
|
|
185
|
+
if (onlySkills == null) {
|
|
186
|
+
onlySkills = availableSkills.value
|
|
187
|
+
.map(s => s.name)
|
|
188
|
+
.filter(n => !groupSkillNames.includes(n))
|
|
189
|
+
} else {
|
|
190
|
+
onlySkills = onlySkills.filter(n => !groupSkillNames.includes(n))
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
ctx.setPrefs({ onlySkills })
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
function getActiveCount(group) {
|
|
198
|
+
const onlySkills = ctx.prefs.onlySkills
|
|
199
|
+
if (onlySkills == null) return group.skills.length
|
|
200
|
+
return group.skills.filter(s => onlySkills.includes(s.name)).length
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
return {
|
|
204
|
+
availableSkills,
|
|
205
|
+
skillGroups,
|
|
206
|
+
isSkillActive,
|
|
207
|
+
toggleSkill,
|
|
208
|
+
toggleCollapse,
|
|
209
|
+
isCollapsed,
|
|
210
|
+
setGroupSkills,
|
|
211
|
+
getActiveCount
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
function codeFragment(s) {
|
|
217
|
+
return "`" + s + "`"
|
|
218
|
+
}
|
|
219
|
+
function codeBlock(s) {
|
|
220
|
+
return "```\n" + s + "\n```\n"
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
const SkillInstructions = `
|
|
224
|
+
You have access to specialized skills that extend your capabilities with domain-specific knowledge, workflows, and tools.
|
|
225
|
+
Skills are modular packages containing instructions, scripts, references, and assets for particular tasks.
|
|
226
|
+
|
|
227
|
+
## Using Skills
|
|
228
|
+
|
|
229
|
+
Use the skill tool to read a skill's main instructions and guidance, e.g:
|
|
230
|
+
${codeBlock("skill({ name: \"skill-name\" })")}
|
|
231
|
+
|
|
232
|
+
To read a specific file within a skill (scripts, references, assets):
|
|
233
|
+
${codeBlock("skill({ name: \"skill-name\", file: \"relative/path/to/file\" })")}
|
|
234
|
+
|
|
235
|
+
Examples:
|
|
236
|
+
- ${codeFragment("skill({ name: \"create-plan\" })")} - Read the create-plan skill's SKILL.md instructions
|
|
237
|
+
- ${codeFragment("skill({ name: \"web-artifacts-builder\", file: \"scripts/init-artifact.sh\" })")} - Read a specific script
|
|
238
|
+
|
|
239
|
+
## When to Use Skills
|
|
240
|
+
|
|
241
|
+
You should read the appropriate skill BEFORE starting work on relevant tasks. Skills contain best practices, scripts, and reference materials that significantly improve output quality.
|
|
242
|
+
|
|
243
|
+
**Skill Selection Guidelines:**
|
|
244
|
+
- Match the task to available skill descriptions
|
|
245
|
+
- Multiple skills may be relevant - read all that apply
|
|
246
|
+
- Read the skill first, then follow its instructions
|
|
247
|
+
|
|
248
|
+
## Available Skills
|
|
249
|
+
$$AVAILABLE_SKILLS$$
|
|
250
|
+
|
|
251
|
+
## Important Notes
|
|
252
|
+
|
|
253
|
+
- Always read the skill BEFORE starting implementation
|
|
254
|
+
- Skills may contain scripts that can be executed directly without loading into context
|
|
255
|
+
- Multiple skills can and should be combined when tasks span multiple domains
|
|
256
|
+
- If a skill references additional files (references/, scripts/, assets/), read those as needed during execution
|
|
257
|
+
`
|
|
258
|
+
|
|
259
|
+
export default {
|
|
260
|
+
order: 15 - 100,
|
|
261
|
+
|
|
262
|
+
install(ctx) {
|
|
263
|
+
ext = ctx.scope("skills")
|
|
264
|
+
|
|
265
|
+
ctx.components({ SkillSelector })
|
|
266
|
+
|
|
267
|
+
const svg = (attrs, title) => `<svg ${attrs} xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">${title ? "<title>" + title + "</title>" : ''}<path fill="currentColor" d="M20 17a2 2 0 0 0 2-2V4a2 2 0 0 0-2-2H9.46c.35.61.54 1.3.54 2h10v11h-9v2m4-10v2H9v13H7v-6H5v6H3v-8H1.5V9a2 2 0 0 1 2-2zM8 4a2 2 0 0 1-2 2a2 2 0 0 1-2-2a2 2 0 0 1 2-2a2 2 0 0 1 2 2"/></svg>`
|
|
268
|
+
|
|
269
|
+
ctx.setTopIcons({
|
|
270
|
+
skills: {
|
|
271
|
+
component: {
|
|
272
|
+
template: svg([
|
|
273
|
+
`@click="$ctx.toggleTop('SkillSelector')"`,
|
|
274
|
+
`:class="!$tools?.isToolEnabled('skill') ? '' : $prefs.onlySkills == null ? 'text-green-600 dark:text-green-300' : $prefs.onlySkills.length ? 'text-blue-600! dark:text-blue-300!' : ''"`
|
|
275
|
+
].join(' ')),
|
|
276
|
+
},
|
|
277
|
+
isActive({ top }) {
|
|
278
|
+
return top === 'SkillSelector'
|
|
279
|
+
},
|
|
280
|
+
get title() {
|
|
281
|
+
return !ctx.tools?.isToolEnabled('skill')
|
|
282
|
+
? `skill tool not enabled`
|
|
283
|
+
: ctx.prefs.onlySkills == null
|
|
284
|
+
? `All Skills Included`
|
|
285
|
+
: ctx.prefs.onlySkills.length
|
|
286
|
+
? `${ctx.prefs.onlySkills.length} ${ctx.utils.pluralize('Skill', ctx.prefs.onlySkills.length)} Included`
|
|
287
|
+
: 'No Skills Included'
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
})
|
|
291
|
+
|
|
292
|
+
ctx.chatRequestFilters.push(({ request, thread, context }) => {
|
|
293
|
+
|
|
294
|
+
if (!ctx.tools?.isToolEnabled('skill')) {
|
|
295
|
+
console.log(`skills.chatRequestFilters: 'skill' tool is not enabled`)
|
|
296
|
+
return
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
const prefs = ctx.prefs
|
|
300
|
+
if (prefs.onlySkills != null) {
|
|
301
|
+
if (Array.isArray(prefs.onlySkills)) {
|
|
302
|
+
request.metadata.skills = prefs.onlySkills.length > 0
|
|
303
|
+
? prefs.onlySkills.join(',')
|
|
304
|
+
: 'none'
|
|
305
|
+
}
|
|
306
|
+
} else {
|
|
307
|
+
request.metadata.skills = 'all'
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
console.log('skills.chatRequestFilters', prefs.onlySkills, Object.keys(ctx.state.skills || {}))
|
|
311
|
+
const skills = ctx.state.skills
|
|
312
|
+
if (!skills) return
|
|
313
|
+
|
|
314
|
+
const includeSkills = []
|
|
315
|
+
for (const skill of Object.values(skills)) {
|
|
316
|
+
if (prefs.onlySkills == null || prefs.onlySkills.includes(skill.name)) {
|
|
317
|
+
includeSkills.push(skill)
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
if (!includeSkills.length) return
|
|
321
|
+
|
|
322
|
+
const sb = []
|
|
323
|
+
sb.push("<available_skills>")
|
|
324
|
+
for (const skill of includeSkills) {
|
|
325
|
+
sb.push(" <skill>")
|
|
326
|
+
sb.push(" <name>" + ctx.utils.encodeHtml(skill.name) + "</name>")
|
|
327
|
+
sb.push(" <description>" + ctx.utils.encodeHtml(skill.description) + "</description>")
|
|
328
|
+
sb.push(" <location>" + ctx.utils.encodeHtml(skill.location) + "</location>")
|
|
329
|
+
sb.push(" </skill>")
|
|
330
|
+
}
|
|
331
|
+
sb.push("</available_skills>")
|
|
332
|
+
|
|
333
|
+
const skillsPrompt = SkillInstructions.replace('$$AVAILABLE_SKILLS$$', sb.join('\n')).trim()
|
|
334
|
+
context.requiredSystemPrompts.push(skillsPrompt)
|
|
335
|
+
})
|
|
336
|
+
|
|
337
|
+
ctx.setThreadFooters({
|
|
338
|
+
skills: {
|
|
339
|
+
component: {
|
|
340
|
+
template: `
|
|
341
|
+
<div class="mt-2 w-full flex justify-center">
|
|
342
|
+
<button type="button" @click="$ctx.chat.sendUserMessage('proceed')"
|
|
343
|
+
class="px-3 py-1 rounded-md text-xs font-medium border border-gray-200 dark:border-gray-700 bg-white dark:bg-gray-800 text-gray-700 dark:text-gray-300 hover:bg-gray-50 dark:hover:bg-gray-700 transition-colors select-none">
|
|
344
|
+
proceed
|
|
345
|
+
</button>
|
|
346
|
+
</div>
|
|
347
|
+
`
|
|
348
|
+
},
|
|
349
|
+
show({ thread }) {
|
|
350
|
+
if (thread.messages.length < 2) return false
|
|
351
|
+
const msgRoles = thread.messages.map(m => m.role)
|
|
352
|
+
if (msgRoles[msgRoles.length - 1] != "assistant") return false
|
|
353
|
+
const hasSkillToolCall = thread.messages.some(m =>
|
|
354
|
+
m.tool_calls?.some(tc => tc.type == "function" && tc.function.name == "skill"))
|
|
355
|
+
const systemPrompt = thread.messages.find(m => m.role == "system")?.content.toLowerCase() || ''
|
|
356
|
+
const line1 = leftPart(systemPrompt.trim(), "\n")
|
|
357
|
+
const hasPlanSystemPrompt = line1.includes("plan") || systemPrompt.includes("# plan")
|
|
358
|
+
return hasSkillToolCall || hasPlanSystemPrompt
|
|
359
|
+
}
|
|
360
|
+
}
|
|
361
|
+
})
|
|
362
|
+
|
|
363
|
+
ctx.setState({
|
|
364
|
+
skills: {}
|
|
365
|
+
})
|
|
366
|
+
},
|
|
367
|
+
|
|
368
|
+
async load(ctx) {
|
|
369
|
+
const api = await ext.getJson('/')
|
|
370
|
+
if (api.response) {
|
|
371
|
+
ctx.setState({ skills: api.response })
|
|
372
|
+
} else {
|
|
373
|
+
ctx.setError(api.error)
|
|
374
|
+
}
|
|
375
|
+
}
|
|
376
|
+
}
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: create-plan
|
|
3
|
+
description: Create a concise plan. Use when a user explicitly asks for a plan related to a coding task.
|
|
4
|
+
metadata:
|
|
5
|
+
short-description: Create a plan
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
# Create Plan
|
|
9
|
+
|
|
10
|
+
## Goal
|
|
11
|
+
|
|
12
|
+
Turn a user prompt into a **single, actionable plan** delivered in the final assistant message.
|
|
13
|
+
|
|
14
|
+
## Minimal workflow
|
|
15
|
+
|
|
16
|
+
Throughout the entire workflow, operate in read-only mode. Do not write or update files.
|
|
17
|
+
|
|
18
|
+
1. **Scan context quickly**
|
|
19
|
+
- Read `README.md` and any obvious docs (`docs/`, `CONTRIBUTING.md`, `ARCHITECTURE.md`).
|
|
20
|
+
- Skim relevant files (the ones most likely touched).
|
|
21
|
+
- Identify constraints (language, frameworks, CI/test commands, deployment shape).
|
|
22
|
+
|
|
23
|
+
2. **Ask follow-ups only if blocking**
|
|
24
|
+
- Ask **at most 1–2 questions**.
|
|
25
|
+
- Only ask if you cannot responsibly plan without the answer; prefer multiple-choice.
|
|
26
|
+
- If unsure but not blocked, make a reasonable assumption and proceed.
|
|
27
|
+
|
|
28
|
+
3. **Create a plan using the template below**
|
|
29
|
+
- Start with **1 short paragraph** describing the intent and approach.
|
|
30
|
+
- Clearly call out what is **in scope** and what is **not in scope** in short.
|
|
31
|
+
- Then provide a **small checklist** of action items (default 6–10 items).
|
|
32
|
+
- Each checklist item should be a concrete action and, when helpful, mention files/commands.
|
|
33
|
+
- **Make items atomic and ordered**: discovery → changes → tests → rollout.
|
|
34
|
+
- **Verb-first**: “Add…”, “Refactor…”, “Verify…”, “Ship…”.
|
|
35
|
+
- Include at least one item for **tests/validation** and one for **edge cases/risk** when applicable.
|
|
36
|
+
- If there are unknowns, include a tiny **Open questions** section (max 3).
|
|
37
|
+
|
|
38
|
+
4. **Do not preface the plan with meta explanations; output only the plan as per template**
|
|
39
|
+
|
|
40
|
+
## Plan template (follow exactly)
|
|
41
|
+
|
|
42
|
+
```markdown
|
|
43
|
+
# Plan
|
|
44
|
+
|
|
45
|
+
<1–3 sentences: what we’re doing, why, and the high-level approach.>
|
|
46
|
+
|
|
47
|
+
## Scope
|
|
48
|
+
- In:
|
|
49
|
+
- Out:
|
|
50
|
+
|
|
51
|
+
## Action items
|
|
52
|
+
[ ] <Step 1>
|
|
53
|
+
[ ] <Step 2>
|
|
54
|
+
[ ] <Step 3>
|
|
55
|
+
[ ] <Step 4>
|
|
56
|
+
[ ] <Step 5>
|
|
57
|
+
[ ] <Step 6>
|
|
58
|
+
|
|
59
|
+
## Open questions
|
|
60
|
+
- <Question 1>
|
|
61
|
+
- <Question 2>
|
|
62
|
+
- <Question 3>
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
## Checklist item guidance
|
|
66
|
+
Good checklist items:
|
|
67
|
+
- Point to likely files/modules: src/..., app/..., services/...
|
|
68
|
+
- Name concrete validation: “Run npm test”, “Add unit tests for X”
|
|
69
|
+
- Include safe rollout when relevant: feature flag, migration plan, rollback note
|
|
70
|
+
|
|
71
|
+
Avoid:
|
|
72
|
+
- Vague steps (“handle backend”, “do auth”)
|
|
73
|
+
- Too many micro-steps
|
|
74
|
+
- Writing code snippets (keep the plan implementation-agnostic)
|
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
"""Skill validation logic."""
|
|
2
|
+
|
|
3
|
+
import unicodedata
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
from typing import Optional
|
|
6
|
+
|
|
7
|
+
from .errors import ParseError
|
|
8
|
+
from .parser import find_skill_md, parse_frontmatter
|
|
9
|
+
|
|
10
|
+
MAX_SKILL_NAME_LENGTH = 64
|
|
11
|
+
MAX_DESCRIPTION_LENGTH = 1024
|
|
12
|
+
MAX_COMPATIBILITY_LENGTH = 500
|
|
13
|
+
|
|
14
|
+
# Allowed frontmatter fields per Agent Skills Spec
|
|
15
|
+
ALLOWED_FIELDS = {
|
|
16
|
+
"name",
|
|
17
|
+
"description",
|
|
18
|
+
"license",
|
|
19
|
+
"allowed-tools",
|
|
20
|
+
"metadata",
|
|
21
|
+
"compatibility",
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def _validate_name(name: str, skill_dir: Path) -> list[str]:
|
|
26
|
+
"""Validate skill name format and directory match.
|
|
27
|
+
|
|
28
|
+
Skill names support i18n characters (Unicode letters) plus hyphens.
|
|
29
|
+
Names must be lowercase and cannot start/end with hyphens.
|
|
30
|
+
"""
|
|
31
|
+
errors = []
|
|
32
|
+
|
|
33
|
+
if not name or not isinstance(name, str) or not name.strip():
|
|
34
|
+
errors.append("Field 'name' must be a non-empty string")
|
|
35
|
+
return errors
|
|
36
|
+
|
|
37
|
+
name = unicodedata.normalize("NFKC", name.strip())
|
|
38
|
+
|
|
39
|
+
if len(name) > MAX_SKILL_NAME_LENGTH:
|
|
40
|
+
errors.append(
|
|
41
|
+
f"Skill name '{name}' exceeds {MAX_SKILL_NAME_LENGTH} character limit "
|
|
42
|
+
f"({len(name)} chars)"
|
|
43
|
+
)
|
|
44
|
+
|
|
45
|
+
if name != name.lower():
|
|
46
|
+
errors.append(f"Skill name '{name}' must be lowercase")
|
|
47
|
+
|
|
48
|
+
if name.startswith("-") or name.endswith("-"):
|
|
49
|
+
errors.append("Skill name cannot start or end with a hyphen")
|
|
50
|
+
|
|
51
|
+
if "--" in name:
|
|
52
|
+
errors.append("Skill name cannot contain consecutive hyphens")
|
|
53
|
+
|
|
54
|
+
if not all(c.isalnum() or c == "-" for c in name):
|
|
55
|
+
errors.append(
|
|
56
|
+
f"Skill name '{name}' contains invalid characters. "
|
|
57
|
+
"Only letters, digits, and hyphens are allowed."
|
|
58
|
+
)
|
|
59
|
+
|
|
60
|
+
if skill_dir:
|
|
61
|
+
dir_name = unicodedata.normalize("NFKC", skill_dir.name)
|
|
62
|
+
if dir_name != name:
|
|
63
|
+
errors.append(
|
|
64
|
+
f"Directory name '{skill_dir.name}' must match skill name '{name}'"
|
|
65
|
+
)
|
|
66
|
+
|
|
67
|
+
return errors
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
def _validate_description(description: str) -> list[str]:
|
|
71
|
+
"""Validate description format."""
|
|
72
|
+
errors = []
|
|
73
|
+
|
|
74
|
+
if not description or not isinstance(description, str) or not description.strip():
|
|
75
|
+
errors.append("Field 'description' must be a non-empty string")
|
|
76
|
+
return errors
|
|
77
|
+
|
|
78
|
+
if len(description) > MAX_DESCRIPTION_LENGTH:
|
|
79
|
+
errors.append(
|
|
80
|
+
f"Description exceeds {MAX_DESCRIPTION_LENGTH} character limit "
|
|
81
|
+
f"({len(description)} chars)"
|
|
82
|
+
)
|
|
83
|
+
|
|
84
|
+
return errors
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
def _validate_compatibility(compatibility: str) -> list[str]:
|
|
88
|
+
"""Validate compatibility format."""
|
|
89
|
+
errors = []
|
|
90
|
+
|
|
91
|
+
if not isinstance(compatibility, str):
|
|
92
|
+
errors.append("Field 'compatibility' must be a string")
|
|
93
|
+
return errors
|
|
94
|
+
|
|
95
|
+
if len(compatibility) > MAX_COMPATIBILITY_LENGTH:
|
|
96
|
+
errors.append(
|
|
97
|
+
f"Compatibility exceeds {MAX_COMPATIBILITY_LENGTH} character limit "
|
|
98
|
+
f"({len(compatibility)} chars)"
|
|
99
|
+
)
|
|
100
|
+
|
|
101
|
+
return errors
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
def _validate_metadata_fields(metadata: dict) -> list[str]:
|
|
105
|
+
"""Validate that only allowed fields are present."""
|
|
106
|
+
errors = []
|
|
107
|
+
|
|
108
|
+
extra_fields = set(metadata.keys()) - ALLOWED_FIELDS
|
|
109
|
+
if extra_fields:
|
|
110
|
+
errors.append(
|
|
111
|
+
f"Unexpected fields in frontmatter: {', '.join(sorted(extra_fields))}. "
|
|
112
|
+
f"Only {sorted(ALLOWED_FIELDS)} are allowed."
|
|
113
|
+
)
|
|
114
|
+
|
|
115
|
+
return errors
|
|
116
|
+
|
|
117
|
+
|
|
118
|
+
def validate_metadata(metadata: dict, skill_dir: Optional[Path] = None) -> list[str]:
|
|
119
|
+
"""Validate parsed skill metadata.
|
|
120
|
+
|
|
121
|
+
This is the core validation function that works on already-parsed metadata,
|
|
122
|
+
avoiding duplicate file I/O when called from the parser.
|
|
123
|
+
|
|
124
|
+
Args:
|
|
125
|
+
metadata: Parsed YAML frontmatter dictionary
|
|
126
|
+
skill_dir: Optional path to skill directory (for name-directory match check)
|
|
127
|
+
|
|
128
|
+
Returns:
|
|
129
|
+
List of validation error messages. Empty list means valid.
|
|
130
|
+
"""
|
|
131
|
+
errors = []
|
|
132
|
+
errors.extend(_validate_metadata_fields(metadata))
|
|
133
|
+
|
|
134
|
+
if "name" not in metadata:
|
|
135
|
+
errors.append("Missing required field in frontmatter: name")
|
|
136
|
+
else:
|
|
137
|
+
errors.extend(_validate_name(metadata["name"], skill_dir))
|
|
138
|
+
|
|
139
|
+
if "description" not in metadata:
|
|
140
|
+
errors.append("Missing required field in frontmatter: description")
|
|
141
|
+
else:
|
|
142
|
+
errors.extend(_validate_description(metadata["description"]))
|
|
143
|
+
|
|
144
|
+
if "compatibility" in metadata:
|
|
145
|
+
errors.extend(_validate_compatibility(metadata["compatibility"]))
|
|
146
|
+
|
|
147
|
+
return errors
|
|
148
|
+
|
|
149
|
+
|
|
150
|
+
def validate(skill_dir: Path) -> list[str]:
|
|
151
|
+
"""Validate a skill directory.
|
|
152
|
+
|
|
153
|
+
Args:
|
|
154
|
+
skill_dir: Path to the skill directory
|
|
155
|
+
|
|
156
|
+
Returns:
|
|
157
|
+
List of validation error messages. Empty list means valid.
|
|
158
|
+
"""
|
|
159
|
+
skill_dir = Path(skill_dir)
|
|
160
|
+
|
|
161
|
+
if not skill_dir.exists():
|
|
162
|
+
return [f"Path does not exist: {skill_dir}"]
|
|
163
|
+
|
|
164
|
+
if not skill_dir.is_dir():
|
|
165
|
+
return [f"Not a directory: {skill_dir}"]
|
|
166
|
+
|
|
167
|
+
skill_md = find_skill_md(skill_dir)
|
|
168
|
+
if skill_md is None:
|
|
169
|
+
return ["Missing required file: SKILL.md"]
|
|
170
|
+
|
|
171
|
+
try:
|
|
172
|
+
content = skill_md.read_text()
|
|
173
|
+
metadata, _ = parse_frontmatter(content)
|
|
174
|
+
except ParseError as e:
|
|
175
|
+
return [str(e)]
|
|
176
|
+
|
|
177
|
+
return validate_metadata(metadata, skill_dir)
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
# System Prompts Extension
|
|
2
|
+
|
|
3
|
+
This extension configures AI requests with a library of **over 200+** awesome curated system prompts that can be selected from the UI.
|
|
4
|
+
|
|
5
|
+
## Custom System Prompts
|
|
6
|
+
|
|
7
|
+
You can also maintain your own library of system prompts which can be maintained for all anonymous users at:
|
|
8
|
+
`~/.llms/user/default/system-prompts.json`
|
|
9
|
+
|
|
10
|
+
Or for signed in users at:
|
|
11
|
+
`~/.llms/user/<github-user>/system-prompts.json`
|
|
12
|
+
|
|
13
|
+
The JSON file should contain an array of Prompt objects, e.g:
|
|
14
|
+
|
|
15
|
+
```json
|
|
16
|
+
[
|
|
17
|
+
{
|
|
18
|
+
"name": "Helpful Assistant",
|
|
19
|
+
"prompt": "You are a helpful assistant."
|
|
20
|
+
}
|
|
21
|
+
]
|
|
22
|
+
```
|