Follow Discord
Sweep 22 Oct 2026 · 21:04Z Build v2.1.309 528 read Stable v2.1.308 Latest v2.1.309 Next v2.1.309 Feeds RSS JSON llms.txt llms-full.txt Unofficial
One change · claude-docs

Create custom skills changedskills/how-to

Nearest release: v2.1.284, published 16 hours after upstream edited the page. Shown because the two are within 24 hours of each other. Nothing here says the release caused the edit.

Upstream edited this page at 28 Sep 2026 00:41 UTC, give or take a minute or two: the time comes from Anthropic’s own sitemap rather than from a commit. This site recorded the change at 20 Oct 2026 18:07 UTC.

Upstream edited
Recorded here
Lines+119added
Lines−488removed
From line 2 where the diff opens
First seen 14 Aug 2026 this site's first read of the page
Recorded edits7to this page, all time

The whole hunk

from line 2, old and new numbered
/
lines

The two sides of this change are more than 400 edits apart, too far apart to line up, so this is the differ's own diff of it and the words inside a line are not marked.

from line 2
22 
33> Create a custom skill for Claude: write the SKILL.md file, add resources and scripts, package the skill, and test it.
44 
5export const Piece = ({id, children}) => <div className="pe-piece" data-piece={id}>{children}</div>;
6 
7export const PluginExplorer = ({children, variant}) => {
8 const SKILL_PIECES = [{
9 id: 'skillmd',
10 required: 'Required',
11 name: 'SKILL.md',
12 path: 'brand-guidelines/SKILL.md',
13 lines: [{
14 depth: 0,
15 kind: 'file',
16 text: 'SKILL.md'
17 }],
18 href: 'https://agentskills.io/specification',
19 linkText: 'Go to the Agent Skills specification'
20 }, {
21 id: 'references',
22 name: 'Reference file',
23 path: 'brand-guidelines/references/voice-and-tone.md',
24 lines: [{
25 depth: 0,
26 kind: 'folder',
27 text: 'references/'
28 }, {
29 depth: 1,
30 kind: 'file',
31 text: 'voice-and-tone.md'
32 }],
33 href: 'https://agentskills.io/specification',
34 linkText: 'Go to the Agent Skills specification'
35 }, {
36 id: 'assets',
37 name: 'Asset',
38 path: 'brand-guidelines/assets/slide-template.md',
39 lines: [{
40 depth: 0,
41 kind: 'folder',
42 text: 'assets/'
43 }, {
44 depth: 1,
45 kind: 'file',
46 text: 'slide-template.md'
47 }],
48 href: 'https://agentskills.io/specification',
49 linkText: 'Go to the Agent Skills specification'
50 }, {
51 id: 'scripts',
52 name: 'Script',
53 path: 'brand-guidelines/scripts/check_contrast.py',
54 lines: [{
55 depth: 0,
56 kind: 'folder',
57 text: 'scripts/'
58 }, {
59 depth: 1,
60 kind: 'file',
61 text: 'check_contrast.py'
62 }],
63 href: 'https://code.claude.com/docs/en/skills',
64 linkText: 'Go to Skills in Claude Code'
65 }];
66 const PLUGIN_PIECES = [{
67 id: 'manifest',
68 required: 'Required',
69 name: 'Manifest',
70 path: '.claude-plugin/plugin.json',
71 lines: [{
72 depth: 0,
73 kind: 'folder',
74 text: '.claude-plugin/'
75 }, {
76 depth: 1,
77 kind: 'file',
78 text: 'plugin.json'
79 }],
80 href: '/plugins/build#write-the-manifest',
81 linkText: 'Go to Write the manifest'
82 }, {
83 id: 'skills',
84 name: 'Skill',
85 path: 'skills/file-expense/SKILL.md',
86 lines: [{
87 depth: 0,
88 kind: 'folder',
89 text: 'skills/'
90 }, {
91 depth: 1,
92 kind: 'folder',
93 text: 'file-expense/'
94 }, {
95 depth: 2,
96 kind: 'file',
97 text: 'SKILL.md'
98 }],
99 href: '/skills/how-to',
100 linkText: 'Go to Create custom skills'
101 }, {
102 id: 'references',
103 name: 'Skill reference file',
104 path: 'skills/file-expense/references/categories.md',
105 lines: [{
106 depth: 2,
107 kind: 'folder',
108 text: 'references/'
109 }, {
110 depth: 3,
111 kind: 'file',
112 text: 'categories.md'
113 }],
114 href: '/skills/how-to#add-resources',
115 linkText: 'Go to Add resources'
116 }, {
117 id: 'scripts',
118 name: 'Skill script',
119 path: 'skills/file-expense/scripts/total.py',
120 lines: [{
121 depth: 2,
122 kind: 'folder',
123 text: 'scripts/'
124 }, {
125 depth: 3,
126 kind: 'file',
127 text: 'total.py'
128 }],
129 href: '/skills/how-to#add-scripts',
130 linkText: 'Go to Add scripts'
131 }, {
132 id: 'commands',
133 name: 'Command',
134 path: 'commands/summarize.md',
135 lines: [{
136 depth: 0,
137 kind: 'folder',
138 text: 'commands/'
139 }, {
140 depth: 1,
141 kind: 'file',
142 text: 'summarize.md'
143 }],
144 href: '/plugins/platform-support#compare-component-support-by-app',
145 linkText: 'Go to component support by app'
146 }, {
147 id: 'mcp',
148 name: 'MCP connector',
149 path: '.mcp.json',
150 lines: [{
151 depth: 0,
152 kind: 'file',
153 text: '.mcp.json'
154 }],
155 href: '/plugins/build#bundle-an-mcp-connector-with-its-skill',
156 linkText: 'Go to Bundle an MCP connector with its skill'
157 }, {
158 id: 'readme',
159 required: 'Required to publish',
160 name: 'README',
161 path: 'README.md',
162 lines: [{
163 depth: 0,
164 kind: 'file',
165 text: 'README.md'
166 }],
167 href: '/plugins/pre-submission-checklist#readme-and-license',
168 linkText: 'Go to README and license checks'
169 }, {
170 id: 'license',
171 required: 'Required to publish',
172 name: 'License',
173 path: 'LICENSE',
174 lines: [{
175 depth: 0,
176 kind: 'file',
177 text: 'LICENSE'
178 }],
179 href: '/plugins/pre-submission-checklist#readme-and-license',
180 linkText: 'Go to README and license checks'
181 }];
182 const isSkill = variant === 'skill';
183 const PIECES = isSkill ? SKILL_PIECES : PLUGIN_PIECES;
184 const rootLabel = isSkill ? 'brand-guidelines/' : 'expense-reports/';
185 const title = isSkill ? 'What goes in the skill folder' : 'What goes in the plugin folder';
186 const treeCaption = isSkill ? 'Skill folder' : 'Plugin folder';
187 const [selectedId, setSelectedId] = useState(isSkill ? 'skillmd' : 'manifest');
188 const [isFullscreen, setIsFullscreen] = useState(false);
189 const rootRef = useRef(null);
190 useEffect(() => {
191 const onFsChange = () => setIsFullscreen(!!document.fullscreenElement);
192 document.addEventListener('fullscreenchange', onFsChange);
193 return () => document.removeEventListener('fullscreenchange', onFsChange);
194 }, []);
195 const toggleFullscreen = () => {
196 if (!rootRef.current) return;
197 if (document.fullscreenElement) document.exitFullscreen(); else rootRef.current.requestFullscreen().catch(() => {});
198 };
199 const selected = PIECES.find(p => p.id === selectedId) || PIECES[0];
200 const onTreeKeyDown = e => {
201 const keys = ['ArrowDown', 'ArrowUp', 'Home', 'End'];
202 if (keys.indexOf(e.key) === -1) return;
203 const i = PIECES.findIndex(p => p.id === selectedId);
204 let next = i;
205 if (e.key === 'ArrowDown') next = Math.min(PIECES.length - 1, i + 1);
206 if (e.key === 'ArrowUp') next = Math.max(0, i - 1);
207 if (e.key === 'Home') next = 0;
208 if (e.key === 'End') next = PIECES.length - 1;
209 e.preventDefault();
210 if (next === i) return;
211 const id = PIECES[next].id;
212 setSelectedId(id);
213 const el = document.getElementById('pe-node-' + id);
214 if (el) el.focus();
215 };
216 const FolderIcon = () => <svg className="pe-icon" width="15" height="15" viewBox="0 0 16 16" fill="none" stroke="currentColor" strokeWidth="1.3" strokeLinejoin="round" aria-hidden="true">
217 <path d="M1.5 4.5a1 1 0 0 1 1-1h3.2l1.3 1.5h6a1 1 0 0 1 1 1V12a1 1 0 0 1-1 1h-10.5a1 1 0 0 1-1-1z" />
218 </svg>;
219 const FileIcon = () => <svg className="pe-icon" width="15" height="15" viewBox="0 0 16 16" fill="none" stroke="currentColor" strokeWidth="1.3" strokeLinejoin="round" aria-hidden="true">
220 <path d="M4 1.5h5.5L13 5v9.5H4z" />
221 <path d="M9.5 1.5V5H13" />
222 </svg>;
223 return <div ref={rootRef} className={isFullscreen ? 'pe-root pe-fullscreen not-prose' : 'pe-root not-prose'} data-selected={selected.id}>
224 <style>{`
225 .pe-root {
226 --pe-mono: var(--font-mono, ui-monospace, SFMono-Regular, Menlo, monospace);
227 --pe-accent: #D97757;
228 --pe-accent-text: #A8502F;
229 --pe-accent-bg: rgba(217,119,87,0.10);
230 --pe-bg: #FFFFFF;
231 --pe-surface: #FAFAF7;
232 --pe-hover: #F0EEE6;
233 --pe-border: #E8E6DC;
234 --pe-text: #141413;
235 --pe-text-2: #3D3D3A;
236 --pe-text-3: #5E5D59;
237 font-family: inherit;
238 background: var(--pe-bg);
239 color: var(--pe-text);
240 border: 1px solid var(--pe-border);
241 border-radius: 12px;
242 margin: 1.5rem 0;
243 overflow: hidden;
244 box-sizing: border-box;
245 }
246 .dark .pe-root {
247 --pe-accent-text: #EBA98F;
248 --pe-accent-bg: rgba(217,119,87,0.18);
249 --pe-bg: #1A1918;
250 --pe-surface: #232221;
251 --pe-hover: #2E2D2B;
252 --pe-border: #3A3936;
253 --pe-text: #F1EFE9;
254 --pe-text-2: #D6D4CA;
255 --pe-text-3: #B8B5AD;
256 }
257 .pe-root *, .pe-root *::before, .pe-root *::after { box-sizing: border-box; }
258 .pe-head { display: flex; align-items: flex-start; gap: 12px; padding: 18px 24px 16px; border-bottom: 1px solid var(--pe-border); }
259 .pe-head-text { flex: 1; min-width: 0; }
260 .pe-fs-btn { flex-shrink: 0; width: 32px; height: 32px; display: inline-flex; align-items: center; justify-content: center; border: 1px solid var(--pe-border); border-radius: 6px; background: var(--pe-surface); color: var(--pe-text-2); font-size: 15px; line-height: 1; cursor: pointer; }
261 .pe-fs-btn:hover { background: var(--pe-hover); }
262 .pe-fs-btn:focus-visible { outline: 2px solid var(--pe-accent); outline-offset: 2px; }
263 .pe-fullscreen { border-radius: 0; height: 100vh; display: flex; flex-direction: column; overflow: auto; }
264 .pe-fullscreen .pe-body { flex: 1; }
265 .pe-title { font-size: 19px; font-weight: 600; line-height: 1.3; color: var(--pe-text); margin: 0; }
266 .pe-sub { font-size: 15px; line-height: 1.5; color: var(--pe-text-3); margin: 4px 0 0; }
267 .pe-sub code { font-family: var(--pe-mono); font-size: 0.88em; padding: 1px 5px; border-radius: 4px; background: var(--pe-surface); border: 1px solid var(--pe-border); }
268 .pe-body { display: flex; align-items: stretch; }
269 .pe-tree-pane { width: 270px; flex-shrink: 0; background: var(--pe-surface); border-right: 1px solid var(--pe-border); padding: 16px 0 12px; }
270 .pe-panel { flex: 1; min-width: 0; padding: 16px 24px 24px; }
271 .pe-caption { font-size: 13px; font-weight: 600; color: var(--pe-text-3); margin: 0 0 10px; }
272 .pe-tree-pane .pe-caption { padding: 0 16px; }
273 .pe-rootline { display: flex; align-items: center; gap: 7px; padding: 3px 16px; font-family: var(--pe-mono); font-size: 13.5px; color: var(--pe-text-3); }
274 .pe-node {
275 display: block; width: 100%; margin: 0; padding: 3px 16px 3px 30px; text-align: left; cursor: pointer;
276 background: transparent; color: var(--pe-text-2);
277 border: none; border-left: 3px solid transparent;
278 font-family: var(--pe-mono); font-size: 13.5px; line-height: 1.4;
279 }
280 .pe-node:hover { background: var(--pe-hover); }
281 .pe-node:focus-visible { outline: 2px solid var(--pe-accent); outline-offset: -2px; }
282 .pe-node[aria-pressed="true"] { background: var(--pe-accent-bg); border-left-color: var(--pe-accent); color: var(--pe-accent-text); font-weight: 600; }
283 .pe-line { display: flex; align-items: center; gap: 7px; padding: 2px 0; }
284 .pe-line span { overflow-wrap: anywhere; }
285 .pe-line > span:not(.pe-req) { white-space: nowrap; flex-shrink: 0; }
286 .pe-req { flex-shrink: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; margin-left: 8px; padding: 0 6px; border-radius: 999px; font-size: 11px; line-height: 18px; font-family: var(--pe-sans, inherit); letter-spacing: .02em; color: var(--pe-accent-text); border: 1px solid var(--pe-border); background: var(--pe-surface); white-space: nowrap; }
287 .pe-piece { display: none; font-size: 16px; line-height: 1.6; color: var(--pe-text-2); }
288 ${PIECES.map(p => '.pe-root[data-selected="' + p.id + '"] .pe-piece[data-piece="' + p.id + '"]').join(',\n ')} { display: block; }
289 .pe-piece p { margin: 0 0 10px; }
290 .pe-piece p:last-child { margin-bottom: 0; }
291 .pe-piece ul { list-style: disc; padding-left: 1.25em; margin: 0 0 10px; }
292 .pe-piece li { margin: 2px 0; }
293 .pe-piece code { font-family: var(--pe-mono); font-size: 0.88em; padding: 1px 5px; border-radius: 4px; background: var(--pe-surface); border: 1px solid var(--pe-border); }
294 .pe-piece .code-block { margin: 12px 0 0; }
295 .pe-piece pre code { padding: 0; border: none; background: none; }
296 .pe-piece a { color: var(--pe-accent-text); }
297 .pe-line-compact { display: none; }
298 .pe-icon { flex-shrink: 0; }
299 .pe-name { font-size: 22px; font-weight: 600; line-height: 1.25; letter-spacing: -0.2px; color: var(--pe-text); margin: 0; }
300 .pe-path { font-family: var(--pe-mono); font-size: 13.5px; color: var(--pe-accent-text); margin: 4px 0 0; overflow-wrap: anywhere; }
301 .pe-block { margin: 20px 0 0; }
302 .pe-link {
303 display: inline-block; margin: 24px 0 0; padding: 8px 14px; border-radius: 8px;
304 font-size: 14.5px; font-weight: 600; text-decoration: none;
305 color: var(--pe-accent-text); background: var(--pe-accent-bg); border: 1px solid var(--pe-accent);
306 }
307 .pe-link:hover { filter: brightness(0.97); }
308 .pe-link:focus-visible { outline: 2px solid var(--pe-accent); outline-offset: 2px; }
309 @media (max-width: 700px) {
310 .pe-head { padding: 16px 16px 14px; }
311 .pe-body { flex-direction: column; }
312 .pe-tree-pane { width: 100%; border-right: none; border-bottom: 1px solid var(--pe-border); }
313 .pe-line-tree { display: none; }
314 .pe-line-compact { display: flex; }
315 .pe-panel { padding: 16px 16px 20px; }
316 }
317 `}</style>
318 
319 <div className="pe-head">
320 <div className="pe-head-text">
321 <div className="pe-title">{title}</div>
322 {isSkill ? <div className="pe-sub">This example skill, <code>brand-guidelines</code>, has one of each kind of file a skill can carry. Select a file to read what it's for and see a minimal example.</div> : <div className="pe-sub">This example plugin, <code>expense-reports</code>, has one of each file that chat, Cowork, and Claude Code all load, plus the README and license the directory requires. Select a file to read what it’s for and see a minimal example.</div>}
323 </div>
324 <button type="button" className="pe-fs-btn" onClick={toggleFullscreen} aria-label={isFullscreen ? 'Exit fullscreen' : 'Fullscreen'} title={isFullscreen ? 'Exit fullscreen' : 'Fullscreen'}>
325 {isFullscreen ? '⤡' : '⛶'}
326 </button>
327 </div>
328 
329 <div className="pe-body">
330 <div className="pe-tree-pane">
331 <div className="pe-caption" id="pe-tree-caption">{treeCaption}</div>
332 <div role="group" aria-labelledby="pe-tree-caption" onKeyDown={onTreeKeyDown}>
333 <div className="pe-rootline"><FolderIcon /><span>{rootLabel}</span></div>
334 {PIECES.map(p => <button key={p.id} id={'pe-node-' + p.id} type="button" className="pe-node" aria-pressed={p.id === selected.id} aria-label={p.name + ', ' + p.path} onClick={() => setSelectedId(p.id)}>
335 {p.lines.map((line, i) => <span key={i} className="pe-line pe-line-tree" style={{
336 paddingLeft: line.depth * 18 + 'px'
337 }}>
338 {line.kind === 'folder' ? <FolderIcon /> : <FileIcon />}
339 <span>{line.text}</span>
340 {p.required && i === p.lines.length - 1 ? <span className="pe-req" title={p.required}>Required</span> : null}
341 </span>)}
342 <span className="pe-line pe-line-compact">
343 <FileIcon />
344 <span>{p.path}</span>
345 {p.required ? <span className="pe-req" title={p.required}>Required</span> : null}
346 </span>
347 </button>)}
348 </div>
349 </div>
350 
351 <div className="pe-panel" role="region" aria-labelledby="pe-panel-caption" aria-live="polite" aria-atomic="true">
352 <div className="pe-caption" id="pe-panel-caption">Selected file</div>
353 <div className="pe-name">{selected.name}{selected.required ? <span className="pe-req">{selected.required}</span> : null}</div>
354 <div className="pe-path">{selected.path}</div>
355 
356 <div className="pe-block">{children}</div>
357 
358 <a className="pe-link" href={selected.href}>{selected.linkText}</a>
359 </div>
360 </div>
361 </div>;
362};
363 
3645A custom skill is a folder with a `SKILL.md` file of instructions, and optionally scripts and reference files, that Claude loads when a task matches the skill's description. This guide is for anyone writing a skill of their own. It explains how to create, structure, and test one.
3656 
3667If you already know what the skill should do, start with the [directory structure](#directory-structure). If you're not sure a skill is the right tool, read [Decide what skill to create](#decide-what-skill-to-create) first.
3678 
368<Note>
369 Skills follow the [Agent Skills specification](https://agentskills.io/specification). See the specification for more in-depth information.
370</Note>
9> [!NOTE]
10>
11> Skills follow the [Agent Skills specification](https://agentskills.io/specification). See the specification for more in-depth information.
37112 
37213## Decide what skill to create
37314 
from line 26
38526 
38627A skill is a folder named after the skill. The only required file is `SKILL.md`; the other folders are optional and hold material that `SKILL.md` points Claude to. Select a file in the explorer to see what goes in it and what Claude does with it.
38728 
388<PluginExplorer variant="skill">
389 <Piece id="skillmd">
390 <p>The one required file. Its frontmatter names and describes the skill, and Claude reads the <code>description</code> to decide when the skill applies. The body holds the instructions Claude follows once it does.</p>
391 <p>In this example, the skill applies Acme's brand guidelines and tells Claude where the supporting files are:</p>
392 
393 ```markdown SKILL.md theme={null}
394 ---
395 name: brand-guidelines
396 description: Apply Acme Corp brand guidelines to presentations and documents, including official colors, fonts, and logo usage.
397 ---
398 Use these guidelines whenever you produce a document or deck for Acme.
399 
400 1. Use the colors and fonts in the sections below.
401 2. For wording, follow `references/voice-and-tone.md`.
402 3. For slides, start from `assets/slide-template.md`.
403 4. Before you finish, run `python3 ${CLAUDE_SKILL_DIR}/scripts/check_contrast.py` on any color pairs you chose.
404 ```
405 </Piece>
406 
407 <Piece id="references">
408 <p>Longer background that Claude reads only when a step calls for it, so it doesn't crowd <code>SKILL.md</code>. Mention the file by path at the step where Claude needs it.</p>
409 <p>In this example, the file holds the writing rules the instructions point to in step 2:</p>
410 
411 ```markdown references/voice-and-tone.md theme={null}
412 # Voice and tone
413 - Write in the second person and the present tense.
414 - Prefer short sentences. Avoid exclamation marks.
415 - Product names are always capitalized: Acme Cloud, Acme Sync.
416 ```
417 </Piece>
418 
419 <Piece id="assets">
420 <p>Templates, images, and data files that Claude copies or fills in rather than reads for guidance.</p>
421 <p>In this example, the asset is the slide outline that step 3 tells Claude to start from:</p>
422 
423 ```markdown assets/slide-template.md theme={null}
424 # [Deck title]
425 ## Agenda
426 ## [Section 1]
427 ## [Section 2]
428 ## Next steps
429 ```
430 </Piece>
431 
432 <Piece id="scripts">
433 <p>Code that Claude runs while following the skill, for work that is more reliable as a program than as instructions. Reference it from `SKILL.md` with `${CLAUDE_SKILL_DIR}` so the path resolves wherever the skill is installed.</p>
434 <p>In this example, the script checks that a text and background color pair has enough contrast:</p>
435 
436 ```python scripts/check_contrast.py theme={null}
437 import sys
438 
439 def luminance(hex_color):
440 r, g, b = (int(hex_color[i:i+2], 16) / 255 for i in (1, 3, 5))
441 f = lambda c: c / 12.92 if c <= 0.03928 else ((c + 0.055) / 1.055) ** 2.4
442 return 0.2126 * f(r) + 0.7152 * f(g) + 0.0722 * f(b)
443 
444 fg, bg = sys.argv[1], sys.argv[2]
445 l1, l2 = sorted((luminance(fg), luminance(bg)), reverse=True)
446 ratio = (l1 + 0.05) / (l2 + 0.05)
447 print(f"{ratio:.2f}", "OK" if ratio >= 4.5 else "LOW")
448 ```
449 </Piece>
450</PluginExplorer>
29What goes in the skill folder
30 
31This example skill, `brand-guidelines`, has one of each kind of file a skill can carry. Select a file to read what it's for and see a minimal example.
32 
33Skill folder
34 
35brand-guidelines/
36 
37Selected file
38 
39The one required file. Its frontmatter names and describes the skill, and Claude reads the `description` to decide when the skill applies. The body holds the instructions Claude follows once it does.In this example, the skill applies Acme's brand guidelines and tells Claude where the supporting files are:
40 
41```markdown SKILL.md theme={null}
42---
43name: brand-guidelines
44description: Apply Acme Corp brand guidelines to presentations and documents, including official colors, fonts, and logo usage.
45---
46Use these guidelines whenever you produce a document or deck for Acme.
47 
481. Use the colors and fonts in the sections below.
492. For wording, follow `references/voice-and-tone.md`.
503. For slides, start from `assets/slide-template.md`.
514. Before you finish, run `python3 ${CLAUDE_SKILL_DIR}/scripts/check_contrast.py` on any color pairs you chose.
52```
53 
54Longer background that Claude reads only when a step calls for it, so it doesn't crowd `SKILL.md`. Mention the file by path at the step where Claude needs it.In this example, the file holds the writing rules the instructions point to in step 2:
55 
56```markdown references/voice-and-tone.md theme={null}
57# Voice and tone
58- Write in the second person and the present tense.
59- Prefer short sentences. Avoid exclamation marks.
60- Product names are always capitalized: Acme Cloud, Acme Sync.
61```
62 
63Templates, images, and data files that Claude copies or fills in rather than reads for guidance.In this example, the asset is the slide outline that step 3 tells Claude to start from:
64 
65```markdown assets/slide-template.md theme={null}
66# [Deck title]
67## Agenda
68## [Section 1]
69## [Section 2]
70## Next steps
71```
72 
73Code that Claude runs while following the skill, for work that is more reliable as a program than as instructions. Reference it from `SKILL.md` with `${CLAUDE_SKILL_DIR}` so the path resolves wherever the skill is installed.In this example, the script checks that a text and background color pair has enough contrast:
74 
75```python scripts/check_contrast.py theme={null}
76import sys
77 
78def luminance(hex_color):
79 r, g, b = (int(hex_color[i:i+2], 16) / 255 for i in (1, 3, 5))
80 f = lambda c: c / 12.92 if c <= 0.03928 else ((c + 0.055) / 1.055) ** 2.4
81 return 0.2126 * f(r) + 0.7152 * f(g) + 0.0722 * f(b)
82 
83fg, bg = sys.argv[1], sys.argv[2]
84l1, l2 = sorted((luminance(fg), luminance(bg)), reverse=True)
85ratio = (l1 + 0.05) / (l2 + 0.05)
86print(f"{ratio:.2f}", "OK" if ratio >= 4.5 else "LOW")
87```
45188 
45289As a plain tree, the same skill looks like this. The directory name must match the `name` field in your `SKILL.md`, and everything except `SKILL.md` is optional:
45390 
from line 212
575212 └── scripts/
576213```
577214 
578<Steps>
579 <Step title="Check the directory name">
580 Make sure the directory name matches the `name` field in `SKILL.md`.
581 </Step>
582 
583 <Step title="Zip the directory from its parent folder">
584 Where the `zip` command is available, such as on macOS and Linux, run it from the folder that contains the skill directory, so the directory becomes the top level of the archive:
585 
586 ```bash theme={null}
587 zip -r my-skill.zip my-skill/
588 ```
589 
590 If you use another tool to create the ZIP, compress the skill folder itself rather than the files inside it.
591 </Step>
592 
593 <Step title="Confirm the structure">
594 List the archive and check that every entry starts with `my-skill/`:
595 
596 ```bash theme={null}
597 unzip -l my-skill.zip
598 ```
599 
600 If `SKILL.md` appears without the `my-skill/` prefix, you zipped the contents instead of the folder; zip again from the parent folder.
601 </Step>
602</Steps>
215**1. Check the directory name**
216 
217Make sure the directory name matches the `name` field in `SKILL.md`.
218 
219**2. Zip the directory from its parent folder**
220 
221Where the `zip` command is available, such as on macOS and Linux, run it from the folder that contains the skill directory, so the directory becomes the top level of the archive:
222 
223```bash theme={null}
224zip -r my-skill.zip my-skill/
225```
226 
227If you use another tool to create the ZIP, compress the skill folder itself rather than the files inside it.
228 
229**3. Confirm the structure**
230 
231List the archive and check that every entry starts with `my-skill/`:
232 
233```bash theme={null}
234unzip -l my-skill.zip
235```
236 
237If `SKILL.md` appears without the `my-skill/` prefix, you zipped the contents instead of the folder; zip again from the parent folder.
603238 
604239To check the skill's contents rather than the archive shape, [validate it before uploading](#before-uploading) with `skills-ref validate` or `claude plugin validate`, or ask Claude to review the folder against the [Agent Skills specification](https://agentskills.io/specification).
605240 
from line 246
611246 
612247Before you upload the ZIP, check the skill's files:
613248 
614<Steps>
615 <Step title="Review SKILL.md">
616 Review `SKILL.md` for clarity.
617 </Step>
618 
619 <Step title="Check the description">
620 Verify the description accurately reflects when Claude should use the skill.
621 </Step>
622 
623 <Step title="Check referenced files">
624 Check that all referenced files exist.
625 </Step>
626 
627 <Step title="Validate the skill">
628 Check the frontmatter against the Agent Skills specification with the [`skills-ref` reference tool](https://github.com/agentskills/agentskills/tree/main/skills-ref). It isn't preinstalled: clone that repository and install it into a Python virtual environment as its README describes, which puts `skills-ref` on your `PATH` while the environment is active. Then, from the folder that contains the skill directory, run:
629 
630 ```bash theme={null}
631 skills-ref validate ./my-skill
632 ```
633 
634 A skill that passes prints `Valid skill: ./my-skill`. Otherwise the command lists each problem, such as a `name` that doesn't match the directory or a frontmatter field the specification doesn't define.
635 
636 If the skill is inside a plugin folder, running `claude plugin validate ./my-plugin` in your terminal also parses each skill's frontmatter: it reports a `SKILL.md` whose frontmatter doesn't parse, and prints `✔ Validation passed` when the plugin passes.
637 </Step>
638</Steps>
249**1. Review SKILL.md**
250 
251Review `SKILL.md` for clarity.
252 
253**2. Check the description**
254 
255Verify the description accurately reflects when Claude should use the skill.
256 
257**3. Check referenced files**
258 
259Check that all referenced files exist.
260 
261**4. Validate the skill**
262 
263Check the frontmatter against the Agent Skills specification with the [`skills-ref` reference tool](https://github.com/agentskills/agentskills/tree/main/skills-ref). It isn't preinstalled: clone that repository and install it into a Python virtual environment as its README describes, which puts `skills-ref` on your `PATH` while the environment is active. Then, from the folder that contains the skill directory, run:
264 
265```bash theme={null}
266skills-ref validate ./my-skill
267```
268 
269A skill that passes prints `Valid skill: ./my-skill`. Otherwise the command lists each problem, such as a `name` that doesn't match the directory or a frontmatter field the specification doesn't define.
270 
271If the skill is inside a plugin folder, running `claude plugin validate ./my-plugin` in your terminal also parses each skill's frontmatter: it reports a `SKILL.md` whose frontmatter doesn't parse, and prints `✔ Validation passed` when the plugin passes.
639272 
640273### In Claude Code
641274 
from line 278
645278 
646279After you upload, confirm that Claude loads the skill when it should:
647280 
648<Steps>
649 <Step title="Turn the skill on">
650 Go to [**Customize > Skills**](https://claude.ai/customize/skills) in claude.ai or the desktop app and turn the skill on.
651 </Step>
652 
653 <Step title="Try prompts that should trigger it">
654 Send prompts that should trigger the skill, and review Claude's thinking to confirm it's loading the skill.
655 </Step>
656 
657 <Step title="Iterate on the description">
658 Iterate on the description if Claude isn't using it when expected.
659 </Step>
660</Steps>
281**1. Turn the skill on**
282 
283Go to [**Customize > Skills**](https://claude.ai/customize/skills) in claude.ai or the desktop app and turn the skill on.
284 
285**2. Try prompts that should trigger it**
286 
287Send prompts that should trigger the skill, and review Claude's thinking to confirm it's loading the skill.
288 
289**3. Iterate on the description**
290 
291Iterate on the description if Claude isn't using it when expected.
661292 
662293### Measure whether the skill improves the output
663294 
664295 
Feedback