diff --git a/course/units/en/_toctree.yml b/course/units/en/_toctree.yml index a604e03e..f7b1d05d 100644 --- a/course/units/en/_toctree.yml +++ b/course/units/en/_toctree.yml @@ -32,7 +32,27 @@ - title: "2. Using Skills with Coding Agents" sections: - local: unit2/introduction - title: Skills Across Agent Platforms + title: Using Skills with Claude Code + - local: unit2/skills-in-claude-code + title: Skills in Claude Code + - local: unit2/hands-on-debugging-activation + title: "Hands-on: Did Your Skill Fire?" + - local: unit2/inside-the-box + title: "Inside the Box: Load, Select, Compose" + - local: unit2/the-real-sdk + title: The Real SDK + - local: unit2/hands-on-build-space + title: "Hands-on: Build the Space" + - local: unit2/hands-on-iterate + title: "Hands-on: Fix It Mid-Session" + - local: unit2/skill-creator-install + title: "The skill-creator Skill: Install and Tour" + - local: unit2/skill-creator-loop + title: "The skill-creator Loop" + - local: unit2/hands-on-optimize-description + title: "Hands-on: Optimize the Description" + - local: unit2/publish-and-conclusion + title: Publish and Conclusion - title: "3. Sharing Skills" sections: diff --git a/course/units/en/unit2/assets/baseline.png b/course/units/en/unit2/assets/baseline.png new file mode 100644 index 00000000..7244ad0e Binary files /dev/null and b/course/units/en/unit2/assets/baseline.png differ diff --git a/course/units/en/unit2/assets/brutalist.png b/course/units/en/unit2/assets/brutalist.png new file mode 100644 index 00000000..66b1ad89 Binary files /dev/null and b/course/units/en/unit2/assets/brutalist.png differ diff --git a/course/units/en/unit2/assets/hfbrand.png b/course/units/en/unit2/assets/hfbrand.png new file mode 100644 index 00000000..46802240 Binary files /dev/null and b/course/units/en/unit2/assets/hfbrand.png differ diff --git a/course/units/en/unit2/hands-on-build-space.mdx b/course/units/en/unit2/hands-on-build-space.mdx new file mode 100644 index 00000000..39784bb1 --- /dev/null +++ b/course/units/en/unit2/hands-on-build-space.mdx @@ -0,0 +1,93 @@ +# Hands-on: Build the Space + +You fine-tuned a model in Unit 1. Time to give it a demo people can actually click on. + +## The Setup + +Clone the starter and install the brand skill: + +```bash +git clone https://github.com/huggingface/skills-course-unit2-starter +cd skills-course-unit2-starter +cp -r skills/hf-brand .claude/skills/ +``` + +> [!NOTE] +> If you didn't complete the Unit 1 fine-tune (it requires HF Pro), that's fine — the scaffold has a mock `predict()` function. You can swap in your real model later, or use any Hub model. + +## What the Brand Skill Does + +Open `.claude/skills/hf-brand/SKILL.md`. It's about 60 lines encoding Hugging Face's visual identity: + +- Primary yellow `#FFD21E`, orange `#FF9D00` hover +- The 🤗 emoji as the brand mark +- Source Sans Pro font, soft rounded corners +- Warm, welcoming copy — "Try it out!" not "ENTER TEXT" +- The Gradio 6 theme config, pre-written + +It's a **brand guideline in skill form**. Claude reads it and builds accordingly. + +## Build It + +Open Claude Code in the starter directory: + +```bash +claude +``` + +Ask: + +``` +Build a Gradio demo for the sentiment classifier in app_scaffold.py. +Keep the mock predict() function — just build a proper UI around it. +Three example inputs. Make it ready to deploy as a Space. +``` + +You didn't say "use the HF brand skill." Claude matched the request ("Gradio", "Space") to the skill's description and loaded it. + +## What You'll See + +With the skill loaded, the app Claude builds looks like this: + +![HF-branded demo](./assets/hfbrand.png) + +🤗 in the title. Yellow button with dark text. Soft rounded cards. Copy like *"Paste any review and see what the model thinks."* + +
+What it looks like without the skill + +Same prompt, same model, no brand skill: + +![Baseline demo](./assets/baseline.png) + +It works. It's fine. It also looks like every other Gradio app on the Hub — generic blue, random emoji, default theme. + +
+ +## Run It + +```bash +pip install gradio +python app.py +``` + +Open `http://localhost:7860`. Click around. + +## Try a Different Aesthetic + +The starter also includes `skills/brutalist/` — monochrome, monospace, hard edges, ALL CAPS labels. Swap it in: + +```bash +rm -r .claude/skills/hf-brand +cp -r skills/brutalist .claude/skills/ +``` + +`/reload-plugins` in Claude Code, then ask for a rebuild. Same app, different personality: + +![Brutalist demo](./assets/brutalist.png) + +`SENTIMENT CLASSIFIER` in heavy black caps. `PASTE A REVIEW. RECEIVE A VERDICT.` Zero rounded corners. + +Same 60-odd lines of skill. Completely different output. + +Next: what to do when it's *almost* right. diff --git a/course/units/en/unit2/hands-on-debugging-activation.mdx b/course/units/en/unit2/hands-on-debugging-activation.mdx new file mode 100644 index 00000000..2447b456 --- /dev/null +++ b/course/units/en/unit2/hands-on-debugging-activation.mdx @@ -0,0 +1,92 @@ +# Hands-on: Did Your Skill Fire? + +You built `dataset-publisher` in Unit 1. Let's find out if it works. + +## Step 1: Install It + +Copy your skill into Claude Code's skills directory: + +```bash +mkdir -p ~/.claude/skills/dataset-publisher +cp path/to/your/unit1/dataset-publisher/SKILL.md ~/.claude/skills/dataset-publisher/ +``` + +If you have the helper script and template from Unit 1, copy those too — they go in the same directory. + +## Step 2: Ask Without Naming It + +Open Claude Code in an empty directory: + +```bash +mkdir ~/skill-test && cd ~/skill-test +claude +``` + +Now ask something your skill *should* handle — but don't say the skill's name: + +``` +I want to share about 20 Python coding questions and answers with my team +in a structured way. What's a good approach? +``` + +Watch the response. + +## Step 3: Diagnose + +Three things can happen. + +### It activated + +You'll see Claude follow your skill's workflow — initialize repo, define structure, add data, write card. The response references the specific steps you wrote. **This is success.** Your description was clear enough. + +### It didn't activate + +Generic advice. "You could use a CSV file, or a shared doc, or..." No mention of the Hub, no structured workflow. + +The problem is almost always the description. Unit 1 gave you: + +```yaml +description: Create, populate, and publish datasets on the Hugging Face Hub +``` + +That's a *definition*, not a *trigger*. It says what the skill does, not when to use it. Claude reads "share Q&A with my team" and doesn't see a match. + +Rewrite it to include trigger phrases: + +```yaml +description: Create and publish datasets to the Hugging Face Hub. Use when + the user wants to share structured data, publish a dataset, upload examples + for training, or create a dataset card. Triggers on "dataset", "publish to + Hub", "share training data", "Q&A pairs". +``` + +Save the file. In Claude Code, reload: + +``` +/reload-plugins +``` + +Ask again. Different response. + +### You can't tell + +Ask directly: + +``` +Which skills did you consider or load for that last response? +``` + +Claude will tell you. If `dataset-publisher` isn't mentioned, it didn't fire. + +You can also run `/context` to see what's loaded in the context window. + +## What You Just Learned + +The skill you wrote in Unit 1 was *correct* — the format was right, the instructions were good. But it probably didn't activate on a natural question, because the description was written for humans reading a catalog, not for an agent matching intent. + +This is the single most common reason skills don't work. And now you know how to fix it. + +> [!TIP] +> **A good description has two parts:** what the skill does (one sentence) and when to use it (trigger phrases, situations, keywords). Unit 1 taught the first part. The second part is what makes it fire. + +Next: build something with a skill that's already tuned to activate well. diff --git a/course/units/en/unit2/hands-on-iterate.mdx b/course/units/en/unit2/hands-on-iterate.mdx new file mode 100644 index 00000000..8f13fe5f --- /dev/null +++ b/course/units/en/unit2/hands-on-iterate.mdx @@ -0,0 +1,84 @@ +# Hands-on: Fix It Mid-Session + +The Space is close. But something's off. + +## The Annoyance + +Look at what Claude generated. Somewhere in there — probably at the bottom — there's something like: + +```python +gr.Markdown( + "⚠️ **Note:** This demo uses keyword-based mock inference for " + "illustration purposes. In a production system, replace this with " + "a real NLP model (e.g., a fine-tuned BERT or RoBERTa classifier)." +) +``` + +You didn't ask for that. Claude adds disclaimers by default because it's being careful. Sometimes you want that. Right now you don't. + +You could say "remove the disclaimer." That fixes it once. Next time you ask Claude to edit the app, the disclaimer might come back. + +## Write the Skill — Right Now + +Don't close Claude Code. In a second terminal: + +```bash +mkdir -p .claude/skills/no-fluff +``` + +Create `.claude/skills/no-fluff/SKILL.md`: + +```markdown +--- +name: no-fluff +description: Strip disclaimers and filler from demos. Use when building + Gradio apps, demo Spaces, or example code. +--- + +# No Fluff + +When generating demo code: + +- No "⚠️ Note: this is a demo" footers +- No "In production, you would..." disclaimers +- No TODO placeholders unless asked +- Trust the user knows it's a demo + +The user is a developer. They understand the difference between a demo +and production. Let the code speak. +``` + +Twelve lines. Thirty seconds. + +## Reload and Continue + +Back in Claude Code — the *same* session, same conversation: + +``` +/reload-plugins +``` + +Then: + +``` +Regenerate app.py. Same requirements. +``` + +The disclaimer is gone. And it stays gone, because the skill loads every time Claude touches this project. + +## What Just Happened + +This is how skills actually get written. Not in advance, from a spec. When Claude does the annoying thing for the third time and you decide you're done explaining. + +The pattern: + +1. Claude does X +2. You say "don't do X" +3. Next session, Claude does X again +4. You write a skill: "don't do X" +5. It stops + +> [!TIP] +> Skills you write this way tend to be small and specific. That's good. A skill that fixes one thing reliably is more useful than a skill that tries to fix everything and sometimes misfires. You'll accumulate many small ones over time. + +Now let's ship it. diff --git a/course/units/en/unit2/hands-on-optimize-description.mdx b/course/units/en/unit2/hands-on-optimize-description.mdx new file mode 100644 index 00000000..22d38281 --- /dev/null +++ b/course/units/en/unit2/hands-on-optimize-description.mdx @@ -0,0 +1,65 @@ +# Hands-on: Optimize the Description + +Your skill works when it's loaded. The question is whether Claude loads it when it should. + +## Why This Step Exists + +The `description` field in your frontmatter is the only thing Claude sees when deciding whether to consult a skill. Not the body, not the scripts — just the description. If it's vague, the skill never fires. If it's too broad, it fires when it shouldn't. + +Unit 1 gave you descriptions like *"Create, populate, and publish datasets on the Hugging Face Hub"* — accurate but unhelpful for matching. A real user asking "help me share some Q&A with my team" won't trigger that. The description says what the skill *is*, not when to *use* it. + +skill-creator has an automated loop to fix this. + +## Generate Trigger Queries + +In Claude Code: + +``` +Let's optimize the description for triggering. +``` + +Claude generates 20 eval queries — half should trigger, half shouldn't. The tricky part: the should-not-trigger cases need to be *near-misses*, not obviously irrelevant. "Write a fibonacci function" as a negative for a PDF skill is too easy. "Show me the text on page 3" (simple enough to handle without a skill) is a real test. + +## Review the Queries + +Claude opens an HTML form in your browser. You can edit queries, flip should/shouldn't, add or remove. When you're done, click **Export Eval Set** — it downloads `eval_set.json` to `~/Downloads/`. + +This step matters. Bad eval queries → bad optimized description. Spend a minute here. + +## Run the Loop + +```bash +python -m scripts.run_loop \ + --eval-set ~/Downloads/eval_set.json \ + --skill-path ./your-skill \ + --model claude-sonnet-4-6 \ + --max-iterations 5 \ + --verbose +``` + +This runs in the background. What it does: + +1. Split your eval set 60/40 into train and held-out test +2. Evaluate the current description — runs each query 3 times, measures trigger rate +3. Ask Claude to rewrite the description based on what failed +4. Re-evaluate the new description on both train *and* test +5. Repeat up to 5 times +6. Pick the best by **test** score, not train — to avoid overfitting + +It opens an HTML report when done. You'll see something like: + +``` +Iteration 0 (original): train 65% test 60% +Iteration 1: train 80% test 75% +Iteration 2: train 85% test 80% ← best +Iteration 3: train 90% test 75% (overfit — discarded) +``` + +## Apply It + +The output JSON has a `best_description` field. Claude updates your `SKILL.md` frontmatter with it. Show the before/after. + +> [!TIP] +> **What a good optimized description looks like:** It's pushier than you'd expect. Not just "helps with X" but "Use this whenever the user mentions X, Y, or Z, even if they don't explicitly ask." Claude undertriggers by default — the description has to compensate. + +Your skill now fires when it should. Time to package it. diff --git a/course/units/en/unit2/inside-the-box.mdx b/course/units/en/unit2/inside-the-box.mdx new file mode 100644 index 00000000..4b064a08 --- /dev/null +++ b/course/units/en/unit2/inside-the-box.mdx @@ -0,0 +1,59 @@ +# Inside the Box + +What Claude Code does with your skills folder — stripped to the essentials. + +## Three Functions + +### 1. Load + +Walk `skills/`, find every `SKILL.md`, parse the frontmatter: + +```python +@dataclass +class Skill: + name: str + description: str + body: str +``` + +The `name` and `description` come from YAML frontmatter. The `body` is everything after the second `---`. That's all a skill is at this layer — two strings and a blob of markdown. + +### 2. Select + +The part that matters. You have N skills. The user asked something. Which ones are relevant? + +The simplest approach: show Claude the descriptions and the user's message, ask which match. Haiku is fast and cheap enough to make this a routing call — you don't need the big model for *selection*, only for the actual response. + +```python +def select_skills(user_message: str) -> list[str]: + # show descriptions + message to a cheap model + # get back a list of names + ... +``` + +### 3. Compose + +Take the selected skills' full bodies, concatenate, use as the system prompt: + +```python +def system_prompt_for(skills, selected_names): + chosen = [s for s in skills if s.name in selected_names] + return "\n\n---\n\n".join(s.body for s in chosen) +``` + +That's it. Load, select, compose. The agent loop is: user message → select → compose system prompt → generate. + +## Progressive Disclosure, Revisited + +Unit 1 described progressive disclosure conceptually: the agent reads descriptions, loads full content only when relevant. + +You just saw it as code. **Select** reads descriptions. **Compose** loads full bodies — but only for the selected ones. A 2000-token skill that wasn't selected costs nothing. + +## What Claude Code Does Differently + +The real thing is more sophisticated — skill descriptions are always in context so selection is implicit, there's caching, there's conflict resolution when two skills claim the same name. But the shape is the same. Three functions. + +> [!NOTE] +> The starter's `skill_loader.py` implements load and compose. You implement select. That's the TODO. + +Next: the starter. diff --git a/course/units/en/unit2/introduction.mdx b/course/units/en/unit2/introduction.mdx index 8f54d774..8ca802a2 100644 --- a/course/units/en/unit2/introduction.mdx +++ b/course/units/en/unit2/introduction.mdx @@ -1,3 +1,36 @@ -# Using Skills with Coding Agents +# Using Skills with Claude Code -*Coming soon.* This unit will cover how skills work across different agent platforms, with deep dives into Claude Code, Codex, and Gemini CLI integration. +In Unit 1, you wrote a skill. You installed a few more. You ran them. + +This unit is about the part that comes after — actually *living* with skills in your daily work. The difference between "I did a course exercise once" and "I reach for skills when Claude keeps getting something wrong" comes down to a few things Unit 1 didn't have time for: + +- Knowing whether your skill actually activated +- Fixing it when it didn't +- Writing new ones mid-task, not in advance + +You'll do all of that in Claude Code, and you'll ship a Gradio Space at the end. + +## What You'll Build + +A demo Space for the model you fine-tuned in Unit 1 — built inside Claude Code, styled by a skill, deployed to the Hub. + +Along the way you'll debug a skill that *should* activate but doesn't, write a tiny one mid-conversation to fix an annoyance, and see the difference between a skill you wrote and a skill that works. + +## Prerequisites + +- Completion of Unit 1 (you should have a `dataset-publisher` skill you wrote) +- Claude Code installed — see the box below if you haven't +- A Hugging Face account + +> [!TIP] +> **Installing Claude Code:** Follow the [official guide](https://docs.anthropic.com/en/docs/claude-code/getting-started). Install via npm, run `claude` in a project directory, authenticate through console.anthropic.com. Takes about two minutes. + +## Unit Overview + +1. **Skills in Claude Code** — Where they live. How they load. What a slash command actually is. +2. **Hands-on: Did Your Skill Fire?** — Drop your Unit 1 skill in. Ask without naming it. Debug. +3. **Hands-on: Build the Space** — Gradio demo, styled by a brand skill you install. +4. **Hands-on: Fix It Mid-Session** — Something's off. Write 10 lines. Reload. Continue. +5. **Publish** — Push to the Hub. + +Let's start with the mechanics. diff --git a/course/units/en/unit2/publish-and-conclusion.mdx b/course/units/en/unit2/publish-and-conclusion.mdx new file mode 100644 index 00000000..1dd17b08 --- /dev/null +++ b/course/units/en/unit2/publish-and-conclusion.mdx @@ -0,0 +1,54 @@ +# Publish and Conclusion + +## Deploy to the Hub + +Your app is ready. Push it as a Space: + +```bash +pip install huggingface_hub +huggingface-cli login +``` + +Create the Space and push: + +```bash +huggingface-cli repo create your-model-demo --type space --space_sdk gradio +git init +git remote add origin https://huggingface.co/spaces/your-username/your-model-demo +git add app.py requirements.txt +git commit -m "Initial demo" +git push origin main +``` + +A minute later it's live at `huggingface.co/spaces/your-username/your-model-demo`. + +## Share Your Skills + +The skills you wrote (`no-fluff`, your improved `dataset-publisher`) live in your `.claude/skills/`. They're useful to you. They might be useful to someone else. + +Unit 3 covers publishing skills properly — to the Hub, to Vercel's skills.sh, as a plugin marketplace. For now, commit them alongside your Space so they're versioned: + +```bash +git add .claude/skills/ +git commit -m "Add project skills" +git push +``` + +Anyone who clones your repo and opens Claude Code gets them. + +## What You Learned + +| | | +|---|---| +| **Install** | Manual (`~/.claude/skills/`), project-local (`.claude/skills/`), or plugin. Manual is simplest. | +| **Activation** | The `description` is everything. Write triggers, not definitions. | +| **Debug** | Ask "which skills did you load?" Use `/context`. Rewrite, `/reload-plugins`, retry. | +| **Iterate** | Write skills mid-session when Claude annoys you. Small and specific beats big and vague. | + +## What's Next + +**Unit 3** covers sharing skills at scale — publishing to marketplaces, versioning, what makes a skill someone else would actually install. + +**Unit 4** is the deep dive: using the `cuda-kernels` skill to build architecture-aware GPU code with Claude. + +Your skills directory is going to fill up. That's the point. diff --git a/course/units/en/unit2/skill-creator-install.mdx b/course/units/en/unit2/skill-creator-install.mdx new file mode 100644 index 00000000..ade438de --- /dev/null +++ b/course/units/en/unit2/skill-creator-install.mdx @@ -0,0 +1,72 @@ +# Install and Tour + +## Install + +Add the Anthropic skills marketplace (if you haven't already) and install `skill-creator`: + +``` +/plugin marketplace add anthropics/skills +/plugin install skill-creator@anthropics/skills +``` + +Or manually — clone and copy: + +```bash +git clone https://github.com/anthropics/skills +cp -r skills/skills/skill-creator ~/.claude/skills/ +``` + +Verify: + +``` +/help +``` + +You should see `skill-creator` in the list. + +## What You Got + +The skill is a full toolkit, not just a SKILL.md: + +``` +skill-creator/ +├── SKILL.md — the instructions Claude follows +├── scripts/ +│ ├── aggregate_benchmark.py — rolls test results into pass rates + timing +│ ├── run_loop.py — automates description optimization +│ ├── package_skill.py — bundles your skill into a .skill file +│ └── ... +├── eval-viewer/ +│ └── generate_review.py — the browser viewer for test results +├── agents/ +│ ├── grader.md — how subagents grade outputs +│ ├── comparator.md — blind A/B comparison +│ └── analyzer.md — why did one version win +└── references/ + └── schemas.md — JSON shapes for evals.json, grading.json +``` + +This is progressive disclosure from Unit 1, in practice. The SKILL.md is the router. The scripts execute. The agents/ files load only when a grader subagent needs instructions. The reference docs load only when Claude needs to look up a schema. + +## Read the SKILL.md + +Open it: + +```bash +cat ~/.claude/skills/skill-creator/SKILL.md +``` + +It's about 500 lines. You don't need to read it all now, but skim the structure — it's the workflow you'll follow for the rest of this unit: + +1. Capture intent → what should the skill do, when should it trigger +2. Draft → write SKILL.md + test prompts +3. Run → spawn with-skill and baseline subagents in parallel +4. Review → browser viewer, leave feedback +5. Improve → rewrite based on feedback, repeat +6. Optimize description → automated loop to fix triggering +7. Package → `.skill` file + +> [!NOTE] +> The skill is written as instructions *to Claude*, not to you. When you tell Claude "I want to make a skill for X," it reads this SKILL.md and follows the workflow. You're steering, it's driving. + +Next: what the loop looks like before you do it. diff --git a/course/units/en/unit2/skill-creator-loop.mdx b/course/units/en/unit2/skill-creator-loop.mdx new file mode 100644 index 00000000..9bdb4aff --- /dev/null +++ b/course/units/en/unit2/skill-creator-loop.mdx @@ -0,0 +1,70 @@ +# The Loop + +Before you run it, see what one iteration looks like. + +## You Say + +``` +I want a skill for writing commit messages in my team's style. We use emoji +prefixes, the ticket number goes at the start in brackets, and there's a +Reviewed-by footer. +``` + +## skill-creator Does + +**Captures intent.** Asks a few follow-ups — which emoji for which change type? Example ticket format? Who counts as a reviewer? + +**Drafts the skill.** Writes `commit-style/SKILL.md` with your conventions encoded. Shows it to you. + +**Writes test cases.** Two or three realistic diffs, saves them to `evals/evals.json`: + +```json +{ + "skill_name": "commit-style", + "evals": [ + {"id": 1, "prompt": "Write a commit for this diff: ...", "expected_output": "..."}, + {"id": 2, "prompt": "...", "expected_output": "..."} + ] +} +``` + +**Runs everything in parallel.** For each test case, spawns two subagents in the same turn — one *with* the skill, one *without*. All at once, so you're not waiting for one batch to finish before the next starts. + +**While you wait, drafts assertions.** "Does the commit start with `[TICKET-NNN]`?" "Is there a valid emoji?" "Is there a Reviewed-by footer?" Saves these to `eval_metadata.json`. + +**Grades and aggregates.** When the runs finish, a grader subagent checks each assertion. Then: + +```bash +python -m scripts.aggregate_benchmark commit-style-workspace/iteration-1 --skill-name commit-style +``` + +This produces `benchmark.json` — pass rates, timing, tokens, with-skill vs baseline, mean ± stddev. + +**Opens the viewer in your browser:** + +```bash +python eval-viewer/generate_review.py commit-style-workspace/iteration-1 \ + --skill-name "commit-style" \ + --benchmark commit-style-workspace/iteration-1/benchmark.json +``` + +## You See + +Two tabs: + +**Outputs** — click through each test case. Prompt, with-skill output, baseline output. A textbox for feedback that auto-saves as you type. + +**Benchmark** — the numbers. Pass rate per assertion, time per run, token usage. With-skill vs baseline side by side. + +You leave feedback: *"Test 2's emoji is wrong — it used 🔧 for a bug fix, should be 🐛."* Click Submit. + +## skill-creator Reads Your Feedback + +Opens `feedback.json`, sees your note about test 2, rewrites the skill's emoji table to be clearer about fix vs chore. Reruns into `iteration-2/`. Opens the viewer again — this time with a "previous output" section so you can see the delta. + +Repeat until you're happy. + +> [!TIP] +> Skills with objective outputs (file formats, code generation, structured text) benefit from assertions. Skills with subjective outputs (writing style, design) are better reviewed qualitatively — don't force assertions onto taste. + +Now do it yourself. diff --git a/course/units/en/unit2/skills-in-claude-code.mdx b/course/units/en/unit2/skills-in-claude-code.mdx new file mode 100644 index 00000000..069646dc --- /dev/null +++ b/course/units/en/unit2/skills-in-claude-code.mdx @@ -0,0 +1,86 @@ +# Skills in Claude Code + +Unit 1 showed you `/plugin install` and moved on. Here's what's actually happening. + +## Three Ways to Install a Skill + +### Manual (start here) + +Drop a directory into Claude Code's skills folder. That's it. + +```bash +mkdir -p ~/.claude/skills/my-skill +# put your SKILL.md in there +``` + +Next time you run `claude`, the skill is available. The `name` in your frontmatter becomes a slash command — if your SKILL.md says `name: dataset-publisher`, you now have `/dataset-publisher`. + +This is the simplest path, and it's what you'll use most while learning. + +### Project-local + +Same thing, but in your repo instead of your home directory: + +```bash +mkdir -p .claude/skills/my-skill +``` + +These skills are versioned with your code. Anyone who clones the repo and opens Claude Code gets them. Good for team conventions — "this is how we write tests here." + +### Plugin marketplace + +For skills someone else maintains and distributes: + +```bash +/plugin marketplace add huggingface/skills +/plugin install hugging-face-model-trainer@huggingface/skills +``` + +Plugin skills are namespaced — they show up as `/huggingface:model-trainer`, not `/model-trainer`. This avoids collisions when you have many installed. + +> [!NOTE] +> Unit 1 started with plugins because it was demonstrating HF's published skills. For your own skills, the manual path is simpler. You can always package them as a plugin later — that's Unit 3. + +## How Activation Works + +When you open Claude Code, it reads every installed skill's `description` field. Not the whole SKILL.md — just the description. Those descriptions sit in context. + +When you ask something, Claude matches your request against those descriptions and loads the relevant skills' full content. That's progressive disclosure — the thing Unit 1 described conceptually. + +**The description is the entire selection mechanism.** Not one input. The only input. + +This means the difference between a skill that activates reliably and one that never fires is usually a single line of frontmatter: + +```yaml +# Won't activate — too vague +description: A helper for datasets + +# Will activate — says when +description: Create and publish datasets to the HF Hub. Use when the user mentions + publishing a dataset, uploading data to the Hub, or writing a dataset card. +``` + +You'll debug exactly this in the next section. + +## Slash Commands vs Auto-Activation + +Two ways a skill gets used: + +- **Auto:** You ask about datasets. Claude matches the description. Loads the skill. You never said its name. +- **Explicit:** You type `/dataset-publisher`. The skill loads no matter what. Useful when auto-matching misses, or when you want to force a specific workflow. + +Most of the time you want auto. Slash commands are the fallback. + +## Seeing What Loaded + +There's no `--verbose` flag that shows which skills fired. Instead: + +- `/context` shows what's in the context window, including skill content +- Just ask: *"Which skills did you load for that last response?"* — Claude will tell you +- If the skill has a distinctive workflow ("Step 1: Initialize the repository..."), you'll see it in the response + +## Skills vs MCP — One Paragraph + +You'll see both mentioned in Claude Code docs and it's easy to confuse them. **Skills are instructions** — markdown that goes into context and tells Claude *how* to do something. **MCP servers are tools** — external programs that give Claude new *capabilities*, like "query this database" or "post to Slack." A skill might say "here's how to use our internal deploy tool." An MCP server *is* the deploy tool. Skills need a `.md` file. MCP needs a server process and `.mcp.json` config. + +Now let's use your Unit 1 skill and see if it actually works. diff --git a/course/units/en/unit2/starter/README.md b/course/units/en/unit2/starter/README.md new file mode 100644 index 00000000..00233a87 --- /dev/null +++ b/course/units/en/unit2/starter/README.md @@ -0,0 +1,32 @@ +# Unit 2 Starter — Claude Code + +## What's here + +``` +skills/ + hf-brand/SKILL.md — HuggingFace visual identity for Gradio + brutalist/SKILL.md — alternate aesthetic (monochrome, hard edges) + no-fluff/SKILL.md — strips disclaimers and filler +app_scaffold.py — minimal Gradio shell with mock inference +``` + +## Setup + +Copy the skills you want into your project's `.claude/skills/`: + +```bash +mkdir -p .claude/skills +cp -r skills/hf-brand .claude/skills/ +``` + +Or into `~/.claude/skills/` to use them everywhere. + +## Gradio 6 notes + +Things Claude sometimes gets wrong with Gradio 6.x: + +1. **`theme` and `css` go on `Blocks()`**, not `launch()`. `gr.Blocks(theme=theme, css=css)` is correct; passing them to `launch()` raises a TypeError. +2. **Fonts need `gr.themes.GoogleFont("Name")`**, not bare strings. `font=["Source Sans Pro"]` will crash; `font=[gr.themes.GoogleFont("Source Sans Pro")]` works. +3. **Nested quotes in placeholders** — if Claude writes `placeholder="e.g. "hello""`, that's a syntax error. Use single quotes outside or escape. + +The `hf-brand` skill already encodes #1 and #2. diff --git a/course/units/en/unit2/starter/app_scaffold.py b/course/units/en/unit2/starter/app_scaffold.py new file mode 100644 index 00000000..49d72943 --- /dev/null +++ b/course/units/en/unit2/starter/app_scaffold.py @@ -0,0 +1,35 @@ +""" +Starter scaffold for the Unit 2 Space. + +You fine-tuned a model in Unit 1. This scaffold gives you a Gradio shell +to demo it. The `predict()` function is a mock — swap it for your model. + +Ask Claude Code to fill this out. With the hf-brand skill installed, +it'll style it to match the Hub. +""" + +import gradio as gr + + +def predict(text: str) -> dict[str, float]: + """Mock inference. Replace with your fine-tuned model.""" + words = text.lower().split() + pos = sum(1 for w in words if w in {"good", "great", "love", "excellent"}) + neg = sum(1 for w in words if w in {"bad", "terrible", "hate", "awful"}) + total = max(pos + neg, 1) + return { + "positive": pos / total if total else 0.33, + "negative": neg / total if total else 0.33, + "neutral": 1 - (pos + neg) / max(len(words), 1), + } + + +with gr.Blocks() as demo: + gr.Markdown("# Model Demo") + inp = gr.Textbox(label="Input") + out = gr.Label(label="Prediction") + inp.submit(predict, inp, out) + + +if __name__ == "__main__": + demo.launch() diff --git a/course/units/en/unit2/starter/skills/brutalist/SKILL.md b/course/units/en/unit2/starter/skills/brutalist/SKILL.md new file mode 100644 index 00000000..6a1c6553 --- /dev/null +++ b/course/units/en/unit2/starter/skills/brutalist/SKILL.md @@ -0,0 +1,52 @@ +--- +name: brutalist +description: Build Gradio interfaces in a stark brutalist style — monochrome, monospace, hard edges, no decoration. Use when the user asks for "minimal", "brutalist", "no-frills", "terminal-style", or explicitly rejects friendly/soft UI. +--- + +# Brutalist Gradio + +## Core principles + +- **Monochrome only.** Black `#000`, white `#fff`, one mid-gray `#888`. No accent colors. +- **Monospace everywhere.** JetBrains Mono or Consolas. +- **Hard edges.** Zero border-radius. 2px solid black borders, or none. +- **No decoration.** No icons, no emoji, no shadows, no gradients. + +## Copy + +- Labels are COMMANDS: "ENTER TEXT" not "Your text here" +- No exclamation marks. No friendly microcopy. +- Descriptions are one sentence, declarative, period at end. + +## Gradio theme (Gradio 6.x) + +```python +import gradio as gr + +theme = gr.themes.Monochrome( + font_mono=[gr.themes.GoogleFont("JetBrains Mono"), "Consolas", "monospace"], + radius_size=gr.themes.sizes.radius_none, +).set( + button_primary_background_fill="#000000", + button_primary_text_color="#ffffff", + border_color_primary="#000000", + block_border_width="2px", +) + +css = """ +.gradio-container { font-family: "JetBrains Mono", monospace !important; max-width: 720px; margin: auto; } +label { text-transform: uppercase; letter-spacing: 0.05em; font-weight: 700; } +h1 { font-size: 2.5rem; font-weight: 900; text-transform: uppercase; } +* { border-radius: 0 !important; } +""" + +with gr.Blocks(theme=theme, css=css) as demo: + ... +demo.launch() +``` + +## Layout + +- Single column, max 720px +- Input above output, always. No side-by-side. +- Generous vertical whitespace diff --git a/course/units/en/unit2/starter/skills/hf-brand/SKILL.md b/course/units/en/unit2/starter/skills/hf-brand/SKILL.md new file mode 100644 index 00000000..7a7a741f --- /dev/null +++ b/course/units/en/unit2/starter/skills/hf-brand/SKILL.md @@ -0,0 +1,64 @@ +--- +name: hf-brand +description: Apply Hugging Face visual identity to Gradio apps. Use when building demos, Spaces, or any Gradio UI that should feel native on the Hub. Triggers on "Gradio", "demo", "Space", "Hub UI", "make it look like HuggingFace". +--- + +# Hugging Face Brand — Gradio + +When building Gradio demos, apply HF's visual identity. + +## Colors (from huggingface.co/brand) + +- Primary yellow: `#FFD21E` — buttons, highlights +- Secondary orange: `#FF9D00` — hover states, accents +- Text gray: `#6B7280` — secondary text + +Backgrounds stay white or `#f9fafb`. Let the yellow do the work. + +## Typography + +- Font: Source Sans Pro (via `gr.themes.GoogleFont`) +- Headings: bold, sentence case — NOT uppercase +- Body: 16px, comfortable line-height + +## The 🤗 + +Use the hugging face emoji in the title. It's the brand mark. + +## Shape & tone + +- Soft rounded corners (8-12px radius) +- Warm, welcoming copy: "Try it out!" not "ENTER TEXT" +- Gentle emoji are fine. Exclamation points are fine. +- Explain what's happening — HF users like transparency. + +## Gradio theme (Gradio 6.x) + +```python +import gradio as gr + +theme = gr.themes.Soft( + primary_hue=gr.themes.Color( + c50="#fffbeb", c100="#fef3c7", c200="#fde68a", c300="#fcd34d", + c400="#fbbf24", c500="#FFD21E", c600="#FF9D00", c700="#d97706", + c800="#b45309", c900="#92400e", c950="#78350f", + ), + font=[gr.themes.GoogleFont("Source Sans Pro"), "system-ui", "sans-serif"], + radius_size=gr.themes.sizes.radius_md, +).set( + button_primary_background_fill="#FFD21E", + button_primary_text_color="#111827", + button_primary_background_fill_hover="#FF9D00", +) + +# Pass theme to Blocks(), not launch(): +with gr.Blocks(theme=theme) as demo: + ... +demo.launch() +``` + +## Layout + +- Max-width ~860px, centered +- Breathing room between sections +- Examples as inviting cards, not a dropdown diff --git a/course/units/en/unit2/starter/skills/no-fluff/SKILL.md b/course/units/en/unit2/starter/skills/no-fluff/SKILL.md new file mode 100644 index 00000000..3ebbba7a --- /dev/null +++ b/course/units/en/unit2/starter/skills/no-fluff/SKILL.md @@ -0,0 +1,40 @@ +--- +name: no-fluff +description: Strip disclaimers, warnings, and filler from generated code and UI. Use when building demos or writing code — keeps output focused on what matters. +--- + +# No Fluff + +When generating code or UI copy: + +## Don't add + +- "⚠️ Note: this is a demo" footers +- "For production use, consider..." disclaimers +- Excessive inline comments explaining obvious code +- "TODO" or "FIXME" placeholders unless the user asked +- Error handling for conditions that can't happen in the demo context + +## Do + +- Trust the user knows it's a demo +- Let the code speak for itself +- One-line docstrings if any +- Comments only where the logic is non-obvious + +## Example + +Before: +```python +# ⚠️ WARNING: This is a mock function for demonstration purposes only. +# In a production environment, you would replace this with a real model. +def predict(text): + # TODO: implement real inference + return {"positive": 0.8} # mock result +``` + +After: +```python +def predict(text): + return {"positive": 0.8} +``` diff --git a/course/units/en/unit2/the-real-sdk.mdx b/course/units/en/unit2/the-real-sdk.mdx new file mode 100644 index 00000000..e08e0d37 --- /dev/null +++ b/course/units/en/unit2/the-real-sdk.mdx @@ -0,0 +1,74 @@ +# The Real SDK + +You built skill loading by hand. Here's how the Agent SDK does it — same `skills/` directory, zero loader code. + +## Native Skill Discovery + +The Claude Agent SDK reads `.claude/skills/` automatically, if you tell it to: + +```python +import asyncio +from claude_agent_sdk import query, ClaudeAgentOptions + +async def main(): + options = ClaudeAgentOptions( + cwd="/path/to/your/project", # dir containing .claude/skills/ + setting_sources=["project"], # REQUIRED — see the gotcha below + allowed_tools=["Skill", "Read", "Bash"], + ) + async for message in query( + prompt="Build me a Gradio demo for my sentiment classifier", + options=options, + ): + print(message) + +asyncio.run(main()) +``` + +Same directory layout your hand-rolled agent reads: + +``` +your-project/ +└── .claude/ + └── skills/ + ├── hf-brand/SKILL.md + └── domain-qwen/SKILL.md +``` + +No `skill_loader.py`. No `select_skills()`. The SDK handles discovery, selection, and progressive disclosure. + +## The Gotcha + +> [!WARNING] +> **`setting_sources` defaults to `None`, which loads nothing.** You'll put SKILL.md on disk, run your code, and wonder why no skills fire. This is the most common issue in the SDK docs' troubleshooting section. Always set `setting_sources=["project"]` (or `["user", "project"]` to include `~/.claude/skills/` too). + +Also: `"Skill"` must be in `allowed_tools`. That's the tool Claude uses to invoke a skill. Not in the allowlist → it can't. + +## Plugin Packaging + +Want your skills portable — not tied to a `.claude/` dir in one project? Package as a plugin: + +```python +options = ClaudeAgentOptions( + plugins=[{"type": "local", "path": "/abs/path/to/my-plugin"}], + allowed_tools=["Skill", "Read", "Bash"], +) +``` + +``` +my-plugin/ +├── .claude-plugin/plugin.json +└── skills/ + └── hf-brand/SKILL.md +``` + +This is the same plugin format Claude Code reads. One skill bundle, works in both. + +## Why You Built It By Hand First + +Your `skill_loader.py` is the SDK's skill-loading code — simplified, visible. When `setting_sources` doesn't do what you expect, you know which three steps to check: did it find the files, did it match the descriptions, did it compose the prompt. You've written all three. + +> [!TIP] +> **Canonical docs:** [platform.claude.com/docs/en/api/agent-sdk/skills](https://platform.claude.com/docs/en/api/agent-sdk/skills). Full Python and TypeScript examples, plus the troubleshooting section you'll want bookmarked. + +Next: deploy.