
Slidev Multi Agent
- 16 installs
- 82 repo stars
- Updated August 2, 2026
- aaaaqwq/claude-code-skills
slidev-multi-agent is a Claude Code skill that scaffolds, edits, themes, builds, and exports Slidev presentations through a bundled script-first workflow.
About
slidev-multi-agent creates, edits, themes, builds, and exports Slidev presentations using a script-first workflow with bundled reference docs. A developer uses it when authoring or customizing Slidev decks, themes, or exports from an agent. It provides deterministic scripts and local references so slide work runs the same across Codex, Claude Code, and OpenClaw.
- Script-first workflow to create, edit, theme, build, and export Slidev decks
- Bundles slidev-init, dev, build, export, and theme-eject/scaffold shell scripts
- Ships local reference docs for Slidev syntax, layouts, theming, and hosting
Slidev Multi Agent by the numbers
- 16 all-time installs (skills.sh)
- Ranked #452 of 687 Office & Documents skills by installs in the Skillselion catalog
- Data as of Aug 3, 2026 (Skillselion catalog sync)
slidev-multi-agent capabilities & compatibility
- Capabilities
- slide authoring · presentation export · theme scaffold
- Use cases
- presentations · documentation
- Pricing
- Free
What slidev-multi-agent says it does
Create, edit, theme, build, and export Slidev presentations using a script-first workflow with detailed local references.
Prefer `scripts/*` as the execution layer before ad-hoc CLI commands.
npx skills add https://github.com/aaaaqwq/claude-code-skills --skill slidev-multi-agentAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 16 |
|---|---|
| repo stars | ★ 82 |
| Last updated | August 2, 2026 |
| Repository | aaaaqwq/claude-code-skills ↗ |
What it does
Scaffold, edit, theme, and export a Slidev presentation deck from an agent using bundled scripts.
Who is it for?
Building and exporting Slidev markdown presentation decks and themes
Skip if: General document editing or non-Slidev slide tools
When should I use this skill?
Working on Slidev decks, themes, layouts, exports, or hosting
What you get
A built and exported Slidev presentation with a customized theme.
- A Slidev deck (slides.md)
- A built SPA or exported assets
- A scaffolded or ejected theme
By the numbers
- 7 bundled slidev shell scripts
- 4 core workflows: create, edit, theme, build/export
Files
Slidev Multi-Agent Skill
Use this skill for Slidev presentation work across Codex, Claude Code, and OpenClaw.
Required workflow
1. Read references/index.md first. 2. Load only the specific reference files needed for the current task. 3. Prefer scripts/* as the execution layer before ad-hoc CLI commands. 4. Fall back to direct Slidev CLI only when no script covers the workflow.
Core workflows
Create a deck
1. Run scripts/slidev-init.sh [dir]. 2. Confirm slides.md exists. 3. Start preview with scripts/slidev-dev.sh [entry].
Edit slides
1. Use references/slidev/core-syntax.md and references/slidev/layout.md. 2. Update slide structure, frontmatter, layouts, and content. 3. Preview changes with scripts/slidev-dev.sh.
Customize themes
1. Use references/slidev/theme-addon.md, references/slidev/write-theme.md, and references/slidev/directory-structure.md. 2. For theme extraction from active deck, run scripts/slidev-theme-eject.sh. 3. For new theme scaffold, run scripts/slidev-theme-scaffold.sh [theme-name].
Build and export
1. Use references/slidev/hosting.md and references/slidev/exporting.md. 2. Build SPA with scripts/slidev-build.sh. 3. Export assets with scripts/slidev-export.sh.
Reference loading rules
- For syntax and authoring:
references/slidev/core-syntax.md - For CLI behavior:
references/slidev/cli.md - For theming and addons:
references/slidev/theme-addon.md,references/slidev/write-theme.md - For deck structure and custom files:
references/slidev/directory-structure.md - For exports and hosting:
references/slidev/exporting.md,references/slidev/hosting.md - For platform-specific skill behavior:
- Codex:
references/platforms/codex-skills.md - Claude Code:
references/platforms/claude-skills.md - OpenClaw:
references/platforms/openclaw-skills.md
Notes
- Keep operations deterministic and script-first.
- Do not assume platform-specific metadata files are required.
- This skill is intentionally a single shared source of truth.
Summary
<!-- What does this PR do and why? -->
Changes
-
Type
- [ ] Bug fix
- [ ] New feature
- [ ] Refactor
- [ ] Documentation
- [ ] CI/Build
Testing
- [ ] Tested locally
- [ ] Existing tests pass
Related Issues
<!-- Fixes #123 -->
Checklist
- [ ] Code follows project conventions
- [ ] No hardcoded secrets or credentials
- [ ] Self-review completed
MIT License
Copyright (c) 2026
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
{
"name": "slidev-multi-agent",
"version": "1.0.0",
"private": true,
"type": "module",
"scripts": {
"sync:references": "node scripts/sync-references.mjs",
"check:shell": "bash -n scripts/*.sh"
},
"packageManager": "pnpm@10.28.0"
}
<div align="center">
███████╗██╗ ██╗██████╗ ███████╗██╗ ██╗
██╔════╝██║ ██║██╔══██╗██╔════╝██║ ██║
███████╗██║ ██║██║ ██║█████╗ ██║ ██║
╚════██║██║ ██║██║ ██║██╔══╝ ╚██╗ ██╔╝
███████║███████╗██║██████╔╝███████╗ ╚████╔╝
╚══════╝╚══════╝╚═╝╚═════╝ ╚══════╝ ╚═══╝
A G E N T S K I L LGive your AI agent the power to create stunning presentations.
Create, edit, theme, build, and export Slidev decks — from any AI agent platform.
---
    
</div>
---
Why?
AI agents are great at writing code — but when you ask them to make a Slidev presentation, they fumble through CLI docs, forget syntax, and produce broken builds.
This skill fixes that. It gives your agent structured knowledge and battle-tested scripts so it can go from blank file to deployed deck without hand-holding.
---
What it does
| Capability | Script | Formats |
|---|---|---|
| Scaffold a new deck | slidev-init.sh | — |
| Live-preview edits | slidev-dev.sh | — |
| Build for production | slidev-build.sh | Static SPA |
| Export presentations | slidev-export.sh | PDF, PPTX, PNG, Markdown |
| Eject a theme | slidev-theme-eject.sh | — |
| Scaffold a custom theme | slidev-theme-scaffold.sh | — |
All scripts handle edge cases, resolve the Slidev CLI automatically, and work around known CLI bugs — so the agent doesn't have to.
---
Architecture
slidev-agent-skill/
│
├── SKILL.md # Orchestration — agent reads this first
│
├── references/
│ ├── index.md # Task-to-reference routing table
│ ├── slidev/ # Official Slidev docs (auto-synced)
│ │ ├── core-syntax.md
│ │ ├── cli.md
│ │ ├── layout.md
│ │ ├── theme-addon.md
│ │ ├── write-theme.md
│ │ ├── directory-structure.md
│ │ ├── exporting.md
│ │ ├── hosting.md
│ │ └── work-with-ai.md
│ └── platforms/ # Platform-specific skill guides
│ ├── claude-skills.md
│ ├── codex-skills.md
│ └── openclaw-skills.md
│
└── scripts/ # Deterministic execution layer
├── _slidev_common.sh # Shared CLI resolution
├── slidev-init.sh
├── slidev-dev.sh
├── slidev-build.sh
├── slidev-export.sh
├── slidev-theme-eject.sh
├── slidev-theme-scaffold.sh
└── sync-references.mjs # Auto-sync docs from sli.devThe skill follows a strict 4-layer model:
1. Orchestration (SKILL.md) — routing logic and workflow rules 2. Documentation (references/) — official docs, loaded on-demand per task 3. Execution (scripts/) — deterministic shell scripts wrapping the Slidev CLI 4. Configuration (package.json) — minimal, no heavy dependencies
---
Install
Claude Code
# Clone into your skills directory
git clone https://github.com/6missedcalls/slidev-agent-skill.git \
~/.claude/skills/slidev-agent-skillCodex / OpenAI Agents
git clone https://github.com/6missedcalls/slidev-agent-skill.git \
~/.agents/skills/slidev-agent-skillOpenClaw
git clone https://github.com/6missedcalls/slidev-agent-skill.git \
skills/slidev-agent-skillNo agent-specific packaging required. Works as-is across all platforms.
Prerequisites
bashnode>= 18 andnpmplaywright-chromium— required for exports (auto-installable via scripts)
---
Quick start
# Create a new deck
./scripts/slidev-init.sh my-talk
cd my-talk
# Start the dev server
../scripts/slidev-dev.sh slides.md --port 3030
# Build for production
../scripts/slidev-build.sh slides.md --out dist
# Export to PDF
../scripts/slidev-export.sh slides.md --format pdf --output my-talk.pdf---
Script reference
slidev-init.sh [dir] [--no-install]
Scaffolds a new Slidev project with slides.md, package.json, and dependencies.
slidev-dev.sh [entry] [--port N] [--base /x/] [--theme name]
Starts the live-reload dev server for iterative editing.
slidev-build.sh [entry] [--out dir] [--base /x/] [--without-notes]
Builds a deployable static SPA.
slidev-export.sh [entry] [--format pdf|pptx|png|md] [--output file] [--with-clicks] [--range ...] [--dark] [--install-playwright]
Exports the deck. Supports PDF, PowerPoint, PNG, and Markdown formats.
slidev-theme-eject.sh [entry] [--dir theme] [--theme name]
Ejects the active theme into a local directory for full customization.
slidev-theme-scaffold.sh [theme-name]
Scaffolds a brand-new theme package with layouts, styles, and setup files.
All scripts support --help. Run any script without arguments for usage info.---
How agents use this
The agent workflow is intentionally script-first and reference-routed:
1. Agent reads SKILL.md
2. Agent checks references/index.md for task routing
3. Agent loads ONLY the reference files needed
4. Agent executes via scripts/* (not raw CLI)
5. Agent falls back to direct Slidev CLI only when neededThis keeps the agent's context window efficient — it never loads documentation it doesn't need.
Reference routing
| Task | References loaded |
|---|---|
| Create a deck | core-syntax.md, cli.md |
| Edit content and layouts | core-syntax.md, layout.md, directory-structure.md |
| Theme customization | theme-addon.md, write-theme.md, directory-structure.md |
| Build and deploy | hosting.md, cli.md |
| Export | exporting.md, cli.md |
---
Keeping references up to date
The bundled documentation auto-syncs from the official Slidev docs:
npm run sync:referencesThis fetches the latest content from sli.dev and updates local files with deterministic writes (only changes when content actually differs).
---
Troubleshooting
Export fails with Playwright error?
# Auto-install Playwright with the export command
./scripts/slidev-export.sh slides.md --format pdf --install-playwright
# Or install manually
npm i -D playwright-chromiumMarkdown export path issues? The script automatically handles this — bare filenames are rewritten to out/<name>.md to work around a known Slidev CLI bug.
Theme eject not working? Theme ejection uses --entry internally for compatibility with current Slidev CLI versions.
---
Contributing
# Validate all shell scripts
npm run check:shell
# Refresh references from upstream docs
npm run sync:referencesSee `README.quick.md` for a contributor cheat sheet.
---
License
MIT
---
<div align="center">
Built for agents that present.
[Slidev](https://sli.dev) | [Claude Code](https://docs.anthropic.com/en/docs/agents-and-tools/claude-code/overview) | [Codex](https://platform.openai.com/docs/guides/codex) | [OpenClaw](https://openclaw.dev)
</div>
slidev-multi-agent Quick Reference
Short guide for contributors working on this skill.
Purpose
Single shared Slidev skill for Codex, Claude Code, and OpenClaw:
SKILL.md= orchestrationreferences/= docs layerscripts/= execution layer
Where to install
- Codex/OpenAI agents:
.agents/skills/slidev-multi-agent - Claude Code:
.claude/skills/slidev-multi-agent - OpenClaw:
<workspace>/skills/slidev-multi-agent
Fast setup
npm run check:shell
npm run sync:referencesWorkflow rules
1. Read references/index.md first. 2. Load only needed reference files. 3. Run scripts/* before ad-hoc CLI. 4. Fall back to direct Slidev CLI only if script path does not cover the task.
Command cheat sheet
# initialize deck
./scripts/slidev-init.sh [dir]
# run dev server
./scripts/slidev-dev.sh [entry] [--port N] [--base /x/] [--theme name]
# build SPA
./scripts/slidev-build.sh [entry] [--out dir] [--base /x/] [--without-notes]
# export deck
./scripts/slidev-export.sh [entry] [--format pdf|pptx|png|md] [--output file] [--with-clicks] [--range ...] [--dark] [--install-playwright]
# eject active theme
./scripts/slidev-theme-eject.sh [entry] [--dir theme] [--theme name]
# scaffold new theme
./scripts/slidev-theme-scaffold.sh [theme-name]Most common contributor tasks
Update references
npm run sync:referencesValidate script syntax
npm run check:shellSmoke test in temp deck
./scripts/slidev-init.sh /tmp/slidev-skill-test
cd /tmp/slidev-skill-test
<skill-path>/scripts/slidev-build.sh slides.md --out dist
<skill-path>/scripts/slidev-export.sh slides.md --format md --output export.md --install-playwrightKnown behavior
- Export requires
playwright-chromium(script can auto-install via--install-playwright). - Markdown export with bare filename is rewritten to
out/<name>.mdto avoid Slidev path issues. - Theme eject uses explicit
--entryinternally for current Slidev CLI compatibility.
Edit policy
- Keep
SKILL.mdsmall and orchestration-only. - Put detailed docs under
references/. - Put deterministic execution behavior under
scripts/.
Reference Router
Use this file first to select the minimum references needed per task.
Task routing
- Create a new deck:
references/slidev/core-syntax.mdreferences/slidev/cli.md- Use
scripts/slidev-init.sh
- Edit slides and layout:
references/slidev/core-syntax.mdreferences/slidev/layout.mdreferences/slidev/directory-structure.md- Use
scripts/slidev-dev.sh
- Theme and style changes:
references/slidev/theme-addon.mdreferences/slidev/write-theme.mdreferences/slidev/directory-structure.md- Use
scripts/slidev-theme-eject.shorscripts/slidev-theme-scaffold.sh
- Build and host:
references/slidev/hosting.mdreferences/slidev/cli.md- Use
scripts/slidev-build.sh
- Export PDF/PPTX/PNG/Markdown:
references/slidev/exporting.mdreferences/slidev/cli.md- Use
scripts/slidev-export.sh
- Platform-specific skill behavior:
- Codex:
references/platforms/codex-skills.md - Claude Code:
references/platforms/claude-skills.md - OpenClaw:
references/platforms/openclaw-skills.md
Sources of truth
- Execution layer:
scripts/* - Documentation layer:
references/* - Orchestration layer:
SKILL.md
<!-- Source: https://code.claude.com/docs/en/skills.md -->
## Documentation Index
Fetch the complete documentation index at: https://code.claude.com/docs/llms.txt
Use this file to discover all available pages before exploring further.
Extend Claude with skills
Create, manage, and share skills to extend Claude's capabilities in Claude Code. Includes custom slash commands.
Skills extend what Claude can do. Create a SKILL.md file with instructions, and Claude adds it to its toolkit. Claude uses skills when relevant, or you can invoke one directly with /skill-name.
<Note> For built-in commands like /help and /compact, see interactive mode.
Custom slash commands have been merged into skills. A file at .claude/commands/review.md and a skill at .claude/skills/review/SKILL.md both create /review and work the same way. Your existing .claude/commands/ files keep working. Skills add optional features: a directory for supporting files, frontmatter to control whether you or Claude invokes them, and the ability for Claude to load them automatically when relevant. </Note>
Claude Code skills follow the Agent Skills open standard, which works across multiple AI tools. Claude Code extends the standard with additional features like invocation control, subagent execution, and dynamic context injection.
Getting started
Create your first skill
This example creates a skill that teaches Claude to explain code using visual diagrams and analogies. Since it uses default frontmatter, Claude can load it automatically when you ask how something works, or you can invoke it directly with /explain-code.
<Steps> <Step title="Create the skill directory"> Create a directory for the skill in your personal skills folder. Personal skills are available across all your projects.
```bash theme={null} mkdir -p ~/.claude/skills/explain-code
</Step>
<Step title="Write SKILL.md">
Every skill needs a `SKILL.md` file with two parts: YAML frontmatter (between `---` markers) that tells Claude when to use the skill, and markdown content with instructions Claude follows when the skill is invoked. The `name` field becomes the `/slash-command`, and the `description` helps Claude decide when to load it automatically.
Create `~/.claude/skills/explain-code/SKILL.md`:
--- name: explain-code description: Explains code with visual diagrams and analogies. Use when explaining how code works, teaching about a codebase, or when the user asks "how does this work?" ---
When explaining code, always include:
1. Start with an analogy: Compare the code to something from everyday life 2. Draw a diagram: Use ASCII art to show the flow, structure, or relationships 3. Walk through the code: Explain step-by-step what happens 4. Highlight a gotcha: What's a common mistake or misconception?
Keep explanations conversational. For complex concepts, use multiple analogies.
</Step>
<Step title="Test the skill">
You can test it two ways:
**Let Claude invoke it automatically** by asking something that matches the description:
How does this code work?
**Or invoke it directly** with the skill name:
/explain-code src/auth/login.ts
Either way, Claude should include an analogy and ASCII diagram in its explanation.
</Step>
</Steps>
### Where skills live
Where you store a skill determines who can use it:
| Location | Path | Applies to |
| :--------- | :------------------------------------------------------- | :----------------------------- |
| Enterprise | See [managed settings](/en/permissions#managed-settings) | All users in your organization |
| Personal | `~/.claude/skills/<skill-name>/SKILL.md` | All your projects |
| Project | `.claude/skills/<skill-name>/SKILL.md` | This project only |
| Plugin | `<plugin>/skills/<skill-name>/SKILL.md` | Where plugin is enabled |
When skills share the same name across levels, higher-priority locations win: enterprise > personal > project. Plugin skills use a `plugin-name:skill-name` namespace, so they cannot conflict with other levels. If you have files in `.claude/commands/`, those work the same way, but if a skill and a command share the same name, the skill takes precedence.
#### Automatic discovery from nested directories
When you work with files in subdirectories, Claude Code automatically discovers skills from nested `.claude/skills/` directories. For example, if you're editing a file in `packages/frontend/`, Claude Code also looks for skills in `packages/frontend/.claude/skills/`. This supports monorepo setups where packages have their own skills.
Each skill is a directory with `SKILL.md` as the entrypoint:
my-skill/ ├── SKILL.md # Main instructions (required) ├── template.md # Template for Claude to fill in ├── examples/ │ └── sample.md # Example output showing expected format └── scripts/ └── validate.sh # Script Claude can execute
The `SKILL.md` contains the main instructions and is required. Other files are optional and let you build more powerful skills: templates for Claude to fill in, example outputs showing the expected format, scripts Claude can execute, or detailed reference documentation. Reference these files from your `SKILL.md` so Claude knows what they contain and when to load them. See [Add supporting files](#add-supporting-files) for more details.
<Note>
Files in `.claude/commands/` still work and support the same [frontmatter](#frontmatter-reference). Skills are recommended since they support additional features like supporting files.
</Note>
#### Skills from additional directories
Skills defined in `.claude/skills/` within directories added via `--add-dir` are loaded automatically and picked up by live change detection, so you can edit them during a session without restarting.
<Note>
CLAUDE.md files from `--add-dir` directories are not loaded by default. To load them, set `CLAUDE_CODE_ADDITIONAL_DIRECTORIES_CLAUDE_MD=1`. See [Load memory from additional directories](/en/memory#load-memory-from-additional-directories).
</Note>
## Configure skills
Skills are configured through YAML frontmatter at the top of `SKILL.md` and the markdown content that follows.
### Types of skill content
Skill files can contain any instructions, but thinking about how you want to invoke them helps guide what to include:
**Reference content** adds knowledge Claude applies to your current work. Conventions, patterns, style guides, domain knowledge. This content runs inline so Claude can use it alongside your conversation context.
--- name: api-conventions description: API design patterns for this codebase ---
When writing API endpoints:
- Use RESTful naming conventions
- Return consistent error formats
- Include request validation
**Task content** gives Claude step-by-step instructions for a specific action, like deployments, commits, or code generation. These are often actions you want to invoke directly with `/skill-name` rather than letting Claude decide when to run them. Add `disable-model-invocation: true` to prevent Claude from triggering it automatically.
--- name: deploy description: Deploy the application to production context: fork disable-model-invocation: true ---
Deploy the application: 1. Run the test suite 2. Build the application 3. Push to the deployment target
Your `SKILL.md` can contain anything, but thinking through how you want the skill invoked (by you, by Claude, or both) and where you want it to run (inline or in a subagent) helps guide what to include. For complex skills, you can also [add supporting files](#add-supporting-files) to keep the main skill focused.
### Frontmatter reference
Beyond the markdown content, you can configure skill behavior using YAML frontmatter fields between `---` markers at the top of your `SKILL.md` file:
--- name: my-skill description: What this skill does disable-model-invocation: true allowed-tools: Read, Grep ---
Your skill instructions here...
All fields are optional. Only `description` is recommended so Claude knows when to use the skill.
| Field | Required | Description |
| :------------------------- | :---------- | :---------------------------------------------------------------------------------------------------------------------------------------------------- |
| `name` | No | Display name for the skill. If omitted, uses the directory name. Lowercase letters, numbers, and hyphens only (max 64 characters). |
| `description` | Recommended | What the skill does and when to use it. Claude uses this to decide when to apply the skill. If omitted, uses the first paragraph of markdown content. |
| `argument-hint` | No | Hint shown during autocomplete to indicate expected arguments. Example: `[issue-number]` or `[filename] [format]`. |
| `disable-model-invocation` | No | Set to `true` to prevent Claude from automatically loading this skill. Use for workflows you want to trigger manually with `/name`. Default: `false`. |
| `user-invocable` | No | Set to `false` to hide from the `/` menu. Use for background knowledge users shouldn't invoke directly. Default: `true`. |
| `allowed-tools` | No | Tools Claude can use without asking permission when this skill is active. |
| `model` | No | Model to use when this skill is active. |
| `context` | No | Set to `fork` to run in a forked subagent context. |
| `agent` | No | Which subagent type to use when `context: fork` is set. |
| `hooks` | No | Hooks scoped to this skill's lifecycle. See [Hooks in skills and agents](/en/hooks#hooks-in-skills-and-agents) for configuration format. |
#### Available string substitutions
Skills support string substitution for dynamic values in the skill content:
| Variable | Description |
| :--------------------- | :------------------------------------------------------------------------------------------------------------------------------------------- |
| `$ARGUMENTS` | All arguments passed when invoking the skill. If `$ARGUMENTS` is not present in the content, arguments are appended as `ARGUMENTS: <value>`. |
| `$ARGUMENTS[N]` | Access a specific argument by 0-based index, such as `$ARGUMENTS[0]` for the first argument. |
| `$N` | Shorthand for `$ARGUMENTS[N]`, such as `$0` for the first argument or `$1` for the second. |
| `${CLAUDE_SESSION_ID}` | The current session ID. Useful for logging, creating session-specific files, or correlating skill output with sessions. |
**Example using substitutions:**
--- name: session-logger description: Log activity for this session ---
Log the following to logs/${CLAUDE_SESSION_ID}.log:
$ARGUMENTS
### Add supporting files
Skills can include multiple files in their directory. This keeps `SKILL.md` focused on the essentials while letting Claude access detailed reference material only when needed. Large reference docs, API specifications, or example collections don't need to load into context every time the skill runs.
my-skill/ ├── SKILL.md (required - overview and navigation) ├── reference.md (detailed API docs - loaded when needed) ├── examples.md (usage examples - loaded when needed) └── scripts/ └── helper.py (utility script - executed, not loaded)
Reference supporting files from `SKILL.md` so Claude knows what each file contains and when to load it:
Additional resources
- For complete API details, see reference.md
- For usage examples, see examples.md
<Tip>Keep `SKILL.md` under 500 lines. Move detailed reference material to separate files.</Tip>
### Control who invokes a skill
By default, both you and Claude can invoke any skill. You can type `/skill-name` to invoke it directly, and Claude can load it automatically when relevant to your conversation. Two frontmatter fields let you restrict this:
* **`disable-model-invocation: true`**: Only you can invoke the skill. Use this for workflows with side effects or that you want to control timing, like `/commit`, `/deploy`, or `/send-slack-message`. You don't want Claude deciding to deploy because your code looks ready.
* **`user-invocable: false`**: Only Claude can invoke the skill. Use this for background knowledge that isn't actionable as a command. A `legacy-system-context` skill explains how an old system works. Claude should know this when relevant, but `/legacy-system-context` isn't a meaningful action for users to take.
This example creates a deploy skill that only you can trigger. The `disable-model-invocation: true` field prevents Claude from running it automatically:
--- name: deploy description: Deploy the application to production disable-model-invocation: true ---
Deploy $ARGUMENTS to production:
1. Run the test suite 2. Build the application 3. Push to the deployment target 4. Verify the deployment succeeded
Here's how the two fields affect invocation and context loading:
| Frontmatter | You can invoke | Claude can invoke | When loaded into context |
| :------------------------------- | :------------- | :---------------- | :----------------------------------------------------------- |
| (default) | Yes | Yes | Description always in context, full skill loads when invoked |
| `disable-model-invocation: true` | Yes | No | Description not in context, full skill loads when you invoke |
| `user-invocable: false` | No | Yes | Description always in context, full skill loads when invoked |
<Note>
In a regular session, skill descriptions are loaded into context so Claude knows what's available, but full skill content only loads when invoked. [Subagents with preloaded skills](/en/sub-agents#preload-skills-into-subagents) work differently: the full skill content is injected at startup.
</Note>
### Restrict tool access
Use the `allowed-tools` field to limit which tools Claude can use when a skill is active. This skill creates a read-only mode where Claude can explore files but not modify them:
--- name: safe-reader description: Read files without making changes allowed-tools: Read, Grep, Glob ---
### Pass arguments to skills
Both you and Claude can pass arguments when invoking a skill. Arguments are available via the `$ARGUMENTS` placeholder.
This skill fixes a GitHub issue by number. The `$ARGUMENTS` placeholder gets replaced with whatever follows the skill name:
--- name: fix-issue description: Fix a GitHub issue disable-model-invocation: true ---
Fix GitHub issue $ARGUMENTS following our coding standards.
1. Read the issue description 2. Understand the requirements 3. Implement the fix 4. Write tests 5. Create a commit
When you run `/fix-issue 123`, Claude receives "Fix GitHub issue 123 following our coding standards..."
If you invoke a skill with arguments but the skill doesn't include `$ARGUMENTS`, Claude Code appends `ARGUMENTS: <your input>` to the end of the skill content so Claude still sees what you typed.
To access individual arguments by position, use `$ARGUMENTS[N]` or the shorter `$N`:
--- name: migrate-component description: Migrate a component from one framework to another ---
Migrate the $ARGUMENTS[0] component from $ARGUMENTS[1] to $ARGUMENTS[2]. Preserve all existing behavior and tests.
Running `/migrate-component SearchBar React Vue` replaces `$ARGUMENTS[0]` with `SearchBar`, `$ARGUMENTS[1]` with `React`, and `$ARGUMENTS[2]` with `Vue`. The same skill using the `$N` shorthand:
--- name: migrate-component description: Migrate a component from one framework to another ---
Migrate the $0 component from $1 to $2. Preserve all existing behavior and tests.
## Advanced patterns
### Inject dynamic context
The `!`command\`\` syntax runs shell commands before the skill content is sent to Claude. The command output replaces the placeholder, so Claude receives actual data, not the command itself.
This skill summarizes a pull request by fetching live PR data with the GitHub CLI. The `!`gh pr diff\`\` and other commands run first, and their output gets inserted into the prompt:
--- name: pr-summary description: Summarize changes in a pull request context: fork agent: Explore allowed-tools: Bash(gh *) ---
Pull request context
- PR diff: !
gh pr diff - PR comments: !
gh pr view --comments - Changed files: !
gh pr diff --name-only
Your task
Summarize this pull request...
When this skill runs:
1. Each `!`command\`\` executes immediately (before Claude sees anything)
2. The output replaces the placeholder in the skill content
3. Claude receives the fully-rendered prompt with actual PR data
This is preprocessing, not something Claude executes. Claude only sees the final result.
<Tip>
To enable [extended thinking](/en/common-workflows#use-extended-thinking-thinking-mode) in a skill, include the word "ultrathink" anywhere in your skill content.
</Tip>
### Run skills in a subagent
Add `context: fork` to your frontmatter when you want a skill to run in isolation. The skill content becomes the prompt that drives the subagent. It won't have access to your conversation history.
<Warning>
`context: fork` only makes sense for skills with explicit instructions. If your skill contains guidelines like "use these API conventions" without a task, the subagent receives the guidelines but no actionable prompt, and returns without meaningful output.
</Warning>
Skills and [subagents](/en/sub-agents) work together in two directions:
| Approach | System prompt | Task | Also loads |
| :--------------------------- | :---------------------------------------- | :-------------------------- | :--------------------------- |
| Skill with `context: fork` | From agent type (`Explore`, `Plan`, etc.) | SKILL.md content | CLAUDE.md |
| Subagent with `skills` field | Subagent's markdown body | Claude's delegation message | Preloaded skills + CLAUDE.md |
With `context: fork`, you write the task in your skill and pick an agent type to execute it. For the inverse (defining a custom subagent that uses skills as reference material), see [Subagents](/en/sub-agents#preload-skills-into-subagents).
#### Example: Research skill using Explore agent
This skill runs research in a forked Explore agent. The skill content becomes the task, and the agent provides read-only tools optimized for codebase exploration:
--- name: deep-research description: Research a topic thoroughly context: fork agent: Explore ---
Research $ARGUMENTS thoroughly:
1. Find relevant files using Glob and Grep 2. Read and analyze the code 3. Summarize findings with specific file references
When this skill runs:
1. A new isolated context is created
2. The subagent receives the skill content as its prompt ("Research \$ARGUMENTS thoroughly...")
3. The `agent` field determines the execution environment (model, tools, and permissions)
4. Results are summarized and returned to your main conversation
The `agent` field specifies which subagent configuration to use. Options include built-in agents (`Explore`, `Plan`, `general-purpose`) or any custom subagent from `.claude/agents/`. If omitted, uses `general-purpose`.
### Restrict Claude's skill access
By default, Claude can invoke any skill that doesn't have `disable-model-invocation: true` set. Skills that define `allowed-tools` grant Claude access to those tools without per-use approval when the skill is active. Your [permission settings](/en/permissions) still govern baseline approval behavior for all other tools. Built-in commands like `/compact` and `/init` are not available through the Skill tool.
Three ways to control which skills Claude can invoke:
**Disable all skills** by denying the Skill tool in `/permissions`:
Add to deny rules:
Skill
**Allow or deny specific skills** using [permission rules](/en/permissions):
Allow only specific skills
Skill(commit) Skill(review-pr *)
Deny specific skills
Skill(deploy *)
Permission syntax: `Skill(name)` for exact match, `Skill(name *)` for prefix match with any arguments.
**Hide individual skills** by adding `disable-model-invocation: true` to their frontmatter. This removes the skill from Claude's context entirely.
<Note>
The `user-invocable` field only controls menu visibility, not Skill tool access. Use `disable-model-invocation: true` to block programmatic invocation.
</Note>
## Share skills
Skills can be distributed at different scopes depending on your audience:
* **Project skills**: Commit `.claude/skills/` to version control
* **Plugins**: Create a `skills/` directory in your [plugin](/en/plugins)
* **Managed**: Deploy organization-wide through [managed settings](/en/permissions#managed-settings)
### Generate visual output
Skills can bundle and run scripts in any language, giving Claude capabilities beyond what's possible in a single prompt. One powerful pattern is generating visual output: interactive HTML files that open in your browser for exploring data, debugging, or creating reports.
This example creates a codebase explorer: an interactive tree view where you can expand and collapse directories, see file sizes at a glance, and identify file types by color.
Create the Skill directory:
mkdir -p ~/.claude/skills/codebase-visualizer/scripts
Create `~/.claude/skills/codebase-visualizer/SKILL.md`. The description tells Claude when to activate this Skill, and the instructions tell Claude to run the bundled script:
--- name: codebase-visualizer description: Generate an interactive collapsible tree visualization of your codebase. Use when exploring a new repo, understanding project structure, or identifying large files. allowed-tools: Bash(python *) ---
Codebase Visualizer
Generate an interactive HTML tree view that shows your project's file structure with collapsible directories.
Usage
Run the visualization script from your project root:
python ~/.claude/skills/codebase-visualizer/scripts/visualize.py .This creates codebase-map.html in the current directory and opens it in your default browser.
What the visualization shows
- Collapsible directories: Click folders to expand/collapse
- File sizes: Displayed next to each file
- Colors: Different colors for different file types
- Directory totals: Shows aggregate size of each folder
````
Create ~/.claude/skills/codebase-visualizer/scripts/visualize.py. This script scans a directory tree and generates a self-contained HTML file with:
- A summary sidebar showing file count, directory count, total size, and number of file types
- A bar chart breaking down the codebase by file type (top 8 by size)
- A collapsible tree where you can expand and collapse directories, with color-coded file type indicators
The script requires Python but uses only built-in libraries, so there are no packages to install:
```python expandable theme={null} #!/usr/bin/env python3 """Generate an interactive collapsible tree visualization of a codebase."""
import json import sys import webbrowser from pathlib import Path from collections import Counter
IGNORE = {'.git', 'node_modules', '__pycache__', '.venv', 'venv', 'dist', 'build'}
def scan(path: Path, stats: dict) -> dict: result = {"name": path.name, "children": [], "size": 0} try: for item in sorted(path.iterdir()): if item.name in IGNORE or item.name.startswith('.'): continue if item.is_file(): size = item.stat().st_size ext = item.suffix.lower() or '(no ext)' result["children"].append({"name": item.name, "size": size, "ext": ext}) result["size"] += size stats["files"] += 1 stats["extensions"][ext] += 1 stats["ext_sizes"][ext] += size elif item.is_dir(): stats["dirs"] += 1 child = scan(item, stats) if child["children"]: result["children"].append(child) result["size"] += child["size"] except PermissionError: pass return result
def generate_html(data: dict, stats: dict, output: Path) -> None: ext_sizes = stats["ext_sizes"] total_size = sum(ext_sizes.values()) or 1 sorted_exts = sorted(ext_sizes.items(), key=lambda x: -x[1])[:8] colors = { '.js': '#f7df1e', '.ts': '#3178c6', '.py': '#3776ab', '.go': '#00add8', '.rs': '#dea584', '.rb': '#cc342d', '.css': '#264de4', '.html': '#e34c26', '.json': '#6b7280', '.md': '#083fa1', '.yaml': '#cb171e', '.yml': '#cb171e', '.mdx': '#083fa1', '.tsx': '#3178c6', '.jsx': '#61dafb', '.sh': '#4eaa25', } lang_bars = "".join( f'<div class="bar-row"><span class="bar-label">{ext}</span>' f'<div class="bar" style="width:{(size/total_size)100}%;background:{colors.get(ext,"#6b7280")}"></div>' f'<span class="bar-pct">{(size/total_size)100:.1f}%</span></div>' for ext, size in sorted_exts ) def fmt(b): if b < 1024: return f"{b} B" if b < 1048576: return f"{b/1024:.1f} KB" return f"{b/1048576:.1f} MB"
html = f'''<!DOCTYPE html> <html><head> <meta charset="utf-8"><title>Codebase Explorer</title> <style> body {{ font: 14px/1.5 system-ui, sans-serif; margin: 0; background: #1a1a2e; color: #eee; }} .container {{ display: flex; height: 100vh; }} .sidebar {{ width: 280px; background: #252542; padding: 20px; border-right: 1px solid #3d3d5c; overflow-y: auto; flex-shrink: 0; }} .main {{ flex: 1; padding: 20px; overflow-y: auto; }} h1 {{ margin: 0 0 10px 0; font-size: 18px; }} h2 {{ margin: 20px 0 10px 0; font-size: 14px; color: #888; text-transform: uppercase; }} .stat {{ display: flex; justify-content: space-between; padding: 8px 0; border-bottom: 1px solid #3d3d5c; }} .stat-value {{ font-weight: bold; }} .bar-row {{ display: flex; align-items: center; margin: 6px 0; }} .bar-label {{ width: 55px; font-size: 12px; color: #aaa; }} .bar {{ height: 18px; border-radius: 3px; }} .bar-pct {{ margin-left: 8px; font-size: 12px; color: #666; }} .tree {{ list-style: none; padding-left: 20px; }} details {{ cursor: pointer; }} summary {{ padding: 4px 8px; border-radius: 4px; }} summary:hover {{ background: #2d2d44; }} .folder {{ color: #ffd700; }} .file {{ display: flex; align-items: center; padding: 4px 8px; border-radius: 4px; }} .file:hover {{ background: #2d2d44; }} .size {{ color: #888; margin-left: auto; font-size: 12px; }} .dot {{ width: 8px; height: 8px; border-radius: 50%; margin-right: 8px; }} </style> </head><body> <div class="container"> <div class="sidebar"> <h1>📊 Summary</h1> <div class="stat"><span>Files</span><span class="stat-value">{stats["files"]:,}</span></div> <div class="stat"><span>Directories</span><span class="stat-value">{stats["dirs"]:,}</span></div> <div class="stat"><span>Total size</span><span class="stat-value">{fmt(data["size"])}</span></div> <div class="stat"><span>File types</span><span class="stat-value">{len(stats["extensions"])}</span></div> <h2>By file type</h2> {lang_bars} </div> <div class="main"> <h1>📁 {data["name"]}</h1> <ul class="tree" id="root"></ul> </div> </div> <script> const data = {json.dumps(data)}; const colors = {json.dumps(colors)}; function fmt(b) {{ if (b < 1024) return b + ' B'; if (b < 1048576) return (b/1024).toFixed(1) + ' KB'; return (b/1048576).toFixed(1) + ' MB'; }} function render(node, parent) {{ if (node.children) {{ const det = document.createElement('details'); det.open = parent === document.getElementById('root'); det.innerHTML = <summary><span class="folder">📁 ${{node.name}}</span><span class="size">${{fmt(node.size)}}</span></summary>; const ul = document.createElement('ul'); ul.className = 'tree'; node.children.sort((a,b) => (b.children?1:0)-(a.children?1:0) || a.name.localeCompare(b.name)); node.children.forEach(c => render(c, ul)); det.appendChild(ul); const li = document.createElement('li'); li.appendChild(det); parent.appendChild(li); }} else {{ const li = document.createElement('li'); li.className = 'file'; li.innerHTML = <span class="dot" style="background:${{colors[node.ext]||'#6b7280'}}"></span>${{node.name}}<span class="size">${{fmt(node.size)}}</span>; parent.appendChild(li); }} }} data.children.forEach(c => render(c, document.getElementById('root'))); </script> </body></html>''' output.write_text(html)
if __name__ == '__main__': target = Path(sys.argv[1] if len(sys.argv) > 1 else '.').resolve() stats = {"files": 0, "dirs": 0, "extensions": Counter(), "ext_sizes": Counter()} data = scan(target, stats) out = Path('codebase-map.html') generate_html(data, stats, out) print(f'Generated {out.absolute()}') webbrowser.open(f'file://{out.absolute()}')
To test, open Claude Code in any project and ask "Visualize this codebase." Claude runs the script, generates `codebase-map.html`, and opens it in your browser.
This pattern works for any visual output: dependency graphs, test coverage reports, API documentation, or database schema visualizations. The bundled script does the heavy lifting while Claude handles orchestration.
## Troubleshooting
### Skill not triggering
If Claude doesn't use your skill when expected:
1. Check the description includes keywords users would naturally say
2. Verify the skill appears in `What skills are available?`
3. Try rephrasing your request to match the description more closely
4. Invoke it directly with `/skill-name` if the skill is user-invocable
### Skill triggers too often
If Claude uses your skill when you don't want it:
1. Make the description more specific
2. Add `disable-model-invocation: true` if you only want manual invocation
### Claude doesn't see all my skills
Skill descriptions are loaded into context so Claude knows what's available. If you have many skills, they may exceed the character budget. The budget scales dynamically at 2% of the context window, with a fallback of 16,000 characters. Run `/context` to check for a warning about excluded skills.
To override the limit, set the `SLASH_COMMAND_TOOL_CHAR_BUDGET` environment variable.
## Related resources
* **[Subagents](/en/sub-agents)**: delegate tasks to specialized agents
* **[Plugins](/en/plugins)**: package and distribute skills with other extensions
* **[Hooks](/en/hooks)**: automate workflows around tool events
* **[Memory](/en/memory)**: manage CLAUDE.md files for persistent context
* **[Interactive mode](/en/interactive-mode#built-in-commands)**: built-in commands and shortcuts
* **[Permissions](/en/permissions)**: control tool and skill access
<!-- Source: https://developers.openai.com/codex/skills.md -->
Agent Skills
Use agent skills to extend Codex with task-specific capabilities. A skill packages instructions, resources, and optional scripts so Codex can follow a workflow reliably. You can share skills across teams or with the community. Skills build on the open agent skills standard.
Skills are available in the Codex CLI, IDE extension, and Codex app.
Skills use progressive disclosure to manage context efficiently: Codex starts with each skill's metadata (name, description, file path, and optional metadata from agents/openai.yaml). Codex loads the full SKILL.md instructions only when it decides to use a skill.
A skill is a directory with a SKILL.md file plus optional scripts and references. The SKILL.md file must include name and description.
<FileTree class="mt-4" tree={[ { name: "my-skill/", open: true, children: [ { name: "SKILL.md", comment: "Required: instructions + metadata", }, { name: "scripts/", comment: "Optional: executable code", }, { name: "references/", comment: "Optional: documentation", }, { name: "assets/", comment: "Optional: templates, resources", }, { name: "agents/", open: true, children: [ { name: "openai.yaml", comment: "Optional: appearance and dependencies", }, ], }, ], },
]} />
How Codex uses skills
Codex can activate skills in two ways:
1. Explicit invocation: Include the skill directly in your prompt. In CLI/IDE, run /skills or type $ to mention a skill. 2. Implicit invocation: Codex can choose a skill when your task matches the skill description.
Because implicit matching depends on description, write descriptions with clear scope and boundaries.
Create a skill
Use the built-in creator first:
$skill-creatorThe creator asks what the skill does, when it should trigger, and whether it should stay instruction-only or include scripts. Instruction-only is the default.
You can also create a skill manually by creating a folder with a SKILL.md file:
---
name: skill-name
description: Explain exactly when this skill should and should not trigger.
---
Skill instructions for Codex to follow.Codex detects skill changes automatically. If an update doesn't appear, restart Codex.
Where to save skills
Codex reads skills from repository, user, admin, and system locations. For repositories, Codex scans .agents/skills in every directory from your current working directory up to the repository root. If two skills share the same name, Codex doesn't merge them; both can appear in skill selectors.
| Skill Scope | Location | Suggested use |
|---|---|---|
REPO | $CWD/.agents/skills <br /> Current working directory: where you launch Codex. | If you're in a repository or code environment, teams can check in skills relevant to a working folder. For example, skills only relevant to a microservice or a module. |
REPO | $CWD/../.agents/skills <br /> A folder above CWD when you launch Codex inside a Git repository. | If you're in a repository with nested folders, organizations can check in skills relevant to a shared area in a parent folder. |
REPO | $REPO_ROOT/.agents/skills <br /> The topmost root folder when you launch Codex inside a Git repository. | If you're in a repository with nested folders, organizations can check in skills relevant to everyone using the repository. These serve as root skills available to any subfolder in the repository. |
USER | $HOME/.agents/skills <br /> Any skills checked into the user's personal folder. | Use to curate skills relevant to a user that apply to any repository the user may work in. |
ADMIN | /etc/codex/skills <br /> Any skills checked into the machine or container in a shared, system location. | Use for SDK scripts, automation, and for checking in default admin skills available to each user on the machine. |
SYSTEM | Bundled with Codex by OpenAI. | Useful skills relevant to a broad audience such as the skill-creator and plan skills. Available to everyone when they start Codex. |
Codex supports symlinked skill folders and follows the symlink target when scanning these locations.
Install skills
To install skills beyond the built-ins, use $skill-installer:
$skill-installer install the linear skill from the .experimental folderYou can also prompt the installer to download skills from other repositories. Codex detects newly installed skills automatically; if one doesn't appear, restart Codex.
Enable or disable skills
Use [[skills.config]] entries in ~/.codex/config.toml to disable a skill without deleting it:
[[skills.config]]
path = "/path/to/skill/SKILL.md"
enabled = falseRestart Codex after changing ~/.codex/config.toml.
Optional metadata
Add agents/openai.yaml to configure UI metadata in the Codex app, to set invocation policy, and to declare tool dependencies for a more seamless experience with using the skill.
interface:
display_name: "Optional user-facing name"
short_description: "Optional user-facing description"
icon_small: "./assets/small-logo.svg"
icon_large: "./assets/large-logo.png"
brand_color: "#3B82F6"
default_prompt: "Optional surrounding prompt to use the skill with"
policy:
allow_implicit_invocation: false
dependencies:
tools:
- type: "mcp"
value: "openaiDeveloperDocs"
description: "OpenAI Docs MCP server"
transport: "streamable_http"
url: "https://developers.openai.com/mcp"allow_implicit_invocation (default: true): When false, Codex won't implicitly invoke the skill based on user prompt; explicit $skill invocation still works.
Best practices
- Keep each skill focused on one job.
- Prefer instructions over scripts unless you need deterministic behavior or external tooling.
- Write imperative steps with explicit inputs and outputs.
- Test prompts against the skill description to confirm the right trigger behavior.
For more examples, see github.com/openai/skills and the agent skills specification.
<!-- Source: https://docs.openclaw.ai/tools/skills.md -->
## Documentation Index
Fetch the complete documentation index at: https://docs.openclaw.ai/llms.txt
Use this file to discover all available pages before exploring further.
Skills
Skills (OpenClaw)
OpenClaw uses [AgentSkills](https://agentskills.io)-compatible skill folders to teach the agent how to use tools. Each skill is a directory containing a SKILL.md with YAML frontmatter and instructions. OpenClaw loads bundled skills plus optional local overrides, and filters them at load time based on environment, config, and binary presence.
Locations and precedence
Skills are loaded from three places:
1. Bundled skills: shipped with the install (npm package or OpenClaw\.app) 2. Managed/local skills: ~/.openclaw/skills 3. Workspace skills: <workspace>/skills
If a skill name conflicts, precedence is:
<workspace>/skills (highest) → ~/.openclaw/skills → bundled skills (lowest)
Additionally, you can configure extra skill folders (lowest precedence) via skills.load.extraDirs in ~/.openclaw/openclaw.json.
Per-agent vs shared skills
In multi-agent setups, each agent has its own workspace. That means:
- Per-agent skills live in
<workspace>/skillsfor that agent only. - Shared skills live in
~/.openclaw/skills(managed/local) and are visible
to all agents on the same machine.
- Shared folders can also be added via
skills.load.extraDirs(lowest
precedence) if you want a common skills pack used by multiple agents.
If the same skill name exists in more than one place, the usual precedence applies: workspace wins, then managed/local, then bundled.
Plugins + skills
Plugins can ship their own skills by listing skills directories in openclaw.plugin.json (paths relative to the plugin root). Plugin skills load when the plugin is enabled and participate in the normal skill precedence rules. You can gate them via metadata.openclaw.requires.config on the plugin’s config entry. See Plugins for discovery/config and Tools for the tool surface those skills teach.
ClawHub (install + sync)
ClawHub is the public skills registry for OpenClaw. Browse at https://clawhub.com. Use it to discover, install, update, and back up skills. Full guide: ClawHub.
Common flows:
- Install a skill into your workspace:
clawhub install <skill-slug>- Update all installed skills:
clawhub update --all- Sync (scan + publish updates):
clawhub sync --all
By default, clawhub installs into ./skills under your current working directory (or falls back to the configured OpenClaw workspace). OpenClaw picks that up as <workspace>/skills on the next session.
Security notes
- Treat third-party skills as untrusted code. Read them before enabling.
- Prefer sandboxed runs for untrusted inputs and risky tools. See Sandboxing.
skills.entries.*.envandskills.entries.*.apiKeyinject secrets into the host process
for that agent turn (not the sandbox). Keep secrets out of prompts and logs.
- For a broader threat model and checklists, see Security.
Format (AgentSkills + Pi-compatible)
SKILL.md must include at least:
```markdown theme={"theme":{"light":"min-light","dark":"min-dark"}} --- name: nano-banana-pro description: Generate or edit images via Gemini 3 Pro Image ---
Notes:
* We follow the AgentSkills spec for layout/intent.
* The parser used by the embedded agent supports **single-line** frontmatter keys only.
* `metadata` should be a **single-line JSON object**.
* Use `{baseDir}` in instructions to reference the skill folder path.
* Optional frontmatter keys:
* `homepage` — URL surfaced as “Website” in the macOS Skills UI (also supported via `metadata.openclaw.homepage`).
* `user-invocable` — `true|false` (default: `true`). When `true`, the skill is exposed as a user slash command.
* `disable-model-invocation` — `true|false` (default: `false`). When `true`, the skill is excluded from the model prompt (still available via user invocation).
* `command-dispatch` — `tool` (optional). When set to `tool`, the slash command bypasses the model and dispatches directly to a tool.
* `command-tool` — tool name to invoke when `command-dispatch: tool` is set.
* `command-arg-mode` — `raw` (default). For tool dispatch, forwards the raw args string to the tool (no core parsing).
The tool is invoked with params:
`{ command: "<raw args>", commandName: "<slash command>", skillName: "<skill name>" }`.
## Gating (load-time filters)
OpenClaw **filters skills at load time** using `metadata` (single-line JSON):
--- name: nano-banana-pro description: Generate or edit images via Gemini 3 Pro Image metadata: { "openclaw": { "requires": { "bins": ["uv"], "env": ["GEMINI_API_KEY"], "config": ["browser.enabled"] }, "primaryEnv": "GEMINI_API_KEY", }, } ---
Fields under `metadata.openclaw`:
* `always: true` — always include the skill (skip other gates).
* `emoji` — optional emoji used by the macOS Skills UI.
* `homepage` — optional URL shown as “Website” in the macOS Skills UI.
* `os` — optional list of platforms (`darwin`, `linux`, `win32`). If set, the skill is only eligible on those OSes.
* `requires.bins` — list; each must exist on `PATH`.
* `requires.anyBins` — list; at least one must exist on `PATH`.
* `requires.env` — list; env var must exist **or** be provided in config.
* `requires.config` — list of `openclaw.json` paths that must be truthy.
* `primaryEnv` — env var name associated with `skills.entries.<name>.apiKey`.
* `install` — optional array of installer specs used by the macOS Skills UI (brew/node/go/uv/download).
Note on sandboxing:
* `requires.bins` is checked on the **host** at skill load time.
* If an agent is sandboxed, the binary must also exist **inside the container**.
Install it via `agents.defaults.sandbox.docker.setupCommand` (or a custom image).
`setupCommand` runs once after the container is created.
Package installs also require network egress, a writable root FS, and a root user in the sandbox.
Example: the `summarize` skill (`skills/summarize/SKILL.md`) needs the `summarize` CLI
in the sandbox container to run there.
Installer example:
--- name: gemini description: Use Gemini CLI for coding assistance and Google search lookups. metadata: { "openclaw": { "emoji": "♊️", "requires": { "bins": ["gemini"] }, "install": [ { "id": "brew", "kind": "brew", "formula": "gemini-cli", "bins": ["gemini"], "label": "Install Gemini CLI (brew)", }, ], }, } ---
Notes:
* If multiple installers are listed, the gateway picks a **single** preferred option (brew when available, otherwise node).
* If all installers are `download`, OpenClaw lists each entry so you can see the available artifacts.
* Installer specs can include `os: ["darwin"|"linux"|"win32"]` to filter options by platform.
* Node installs honor `skills.install.nodeManager` in `openclaw.json` (default: npm; options: npm/pnpm/yarn/bun).
This only affects **skill installs**; the Gateway runtime should still be Node
(Bun is not recommended for WhatsApp/Telegram).
* Go installs: if `go` is missing and `brew` is available, the gateway installs Go via Homebrew first and sets `GOBIN` to Homebrew’s `bin` when possible.
* Download installs: `url` (required), `archive` (`tar.gz` | `tar.bz2` | `zip`), `extract` (default: auto when archive detected), `stripComponents`, `targetDir` (default: `~/.openclaw/tools/<skillKey>`).
If no `metadata.openclaw` is present, the skill is always eligible (unless
disabled in config or blocked by `skills.allowBundled` for bundled skills).
## Config overrides (`~/.openclaw/openclaw.json`)
Bundled/managed skills can be toggled and supplied with env values:
{ skills: { entries: { "nano-banana-pro": { enabled: true, apiKey: "GEMINI_KEY_HERE", env: { GEMINI_API_KEY: "GEMINI_KEY_HERE", }, config: { endpoint: "https://example.invalid", model: "nano-pro", }, }, peekaboo: { enabled: true }, sag: { enabled: false }, }, }, }
Note: if the skill name contains hyphens, quote the key (JSON5 allows quoted keys).
Config keys match the **skill name** by default. If a skill defines
`metadata.openclaw.skillKey`, use that key under `skills.entries`.
Rules:
* `enabled: false` disables the skill even if it’s bundled/installed.
* `env`: injected **only if** the variable isn’t already set in the process.
* `apiKey`: convenience for skills that declare `metadata.openclaw.primaryEnv`.
* `config`: optional bag for custom per-skill fields; custom keys must live here.
* `allowBundled`: optional allowlist for **bundled** skills only. If set, only
bundled skills in the list are eligible (managed/workspace skills unaffected).
## Environment injection (per agent run)
When an agent run starts, OpenClaw:
1. Reads skill metadata.
2. Applies any `skills.entries.<key>.env` or `skills.entries.<key>.apiKey` to
`process.env`.
3. Builds the system prompt with **eligible** skills.
4. Restores the original environment after the run ends.
This is **scoped to the agent run**, not a global shell environment.
## Session snapshot (performance)
OpenClaw snapshots the eligible skills **when a session starts** and reuses that list for subsequent turns in the same session. Changes to skills or config take effect on the next new session.
Skills can also refresh mid-session when the skills watcher is enabled or when a new eligible remote node appears (see below). Think of this as a **hot reload**: the refreshed list is picked up on the next agent turn.
## Remote macOS nodes (Linux gateway)
If the Gateway is running on Linux but a **macOS node** is connected **with `system.run` allowed** (Exec approvals security not set to `deny`), OpenClaw can treat macOS-only skills as eligible when the required binaries are present on that node. The agent should execute those skills via the `nodes` tool (typically `nodes.run`).
This relies on the node reporting its command support and on a bin probe via `system.run`. If the macOS node goes offline later, the skills remain visible; invocations may fail until the node reconnects.
## Skills watcher (auto-refresh)
By default, OpenClaw watches skill folders and bumps the skills snapshot when `SKILL.md` files change. Configure this under `skills.load`:
{ skills: { load: { watch: true, watchDebounceMs: 250, }, }, }
## Token impact (skills list)
When skills are eligible, OpenClaw injects a compact XML list of available skills into the system prompt (via `formatSkillsForPrompt` in `pi-coding-agent`). The cost is deterministic:
* **Base overhead (only when ≥1 skill):** 195 characters.
* **Per skill:** 97 characters + the length of the XML-escaped `<name>`, `<description>`, and `<location>` values.
Formula (characters):
total = 195 + Σ (97 + len(name_escaped) + len(description_escaped) + len(location_escaped))
Notes:
* XML escaping expands `& < > " '` into entities (`&`, `<`, etc.), increasing length.
* Token counts vary by model tokenizer. A rough OpenAI-style estimate is \~4 chars/token, so **97 chars ≈ 24 tokens** per skill plus your actual field lengths.
## Managed skills lifecycle
OpenClaw ships a baseline set of skills as **bundled skills** as part of the
install (npm package or OpenClaw\.app). `~/.openclaw/skills` exists for local
overrides (for example, pinning/patching a skill without changing the bundled
copy). Workspace skills are user-owned and override both on name conflicts.
## Config reference
See [Skills config](/tools/skills-config) for the full configuration schema.
## Looking for more skills?
Browse [https://clawhub.com](https://clawhub.com).
***
<!-- Source: https://sli.dev/builtin/cli.md -->
--- url: /builtin/cli.md ---
Slidev CLI
@slidev/cli exposes a binary called slidev that you can use to develop, build, and export your slides.
Prerequisites
To use the CLI, you can either install @slidev/cli globally or install it locally in your Node.js project. If you created your project with npm init slidev, the CLI is already installed locally.
::: warning Usually npx slidev is not supported because the package name is actually @slidev/cli. :::
The CLI options of the commands obey the following conventions:
- the value of the option can be passed after a space or a
=character:
Example: slidev --port 8080 is equivalent to slidev --port=8080
truecan be omitted for boolean options:
Example: slidev --open is equivalent to slidev --open true
::: info
If you use npm, please don't forget to add -- before the options to pass them to Slidev:
npm run slidev -- --remote --port 8080 --open:::
slidev [entry] {#dev}
Start a local server for Slidev.
[entry](string, default:slides.md): path to the markdown file containing your slides.
Options:
--port,-p(number, default:3030): port number.--base(string, default:/): base URL (see https://vitejs.dev/config/shared-options.html#base).--open,-o(boolean, default:false): open in the browser.--remote [password](string): listen to the public host and enable remote control, if a value is passed then the presenter mode is private and only accessible by passing the given password in the URL querypasswordparameter.--bind(string, default:0.0.0.0): specify which IP addresses the server should listen on in the remote mode.--log('error', 'warn', 'info', 'silent', default:'warn'): Log level.--force,-f(boolean, default:false): force the optimizer to ignore the cache and re-bundle.--theme,-t(string): override theme.
slidev build [entry] {#build}
Build a hostable SPA. See for more details.
[entry](string, default:slides.md): path to the slides markdown file.
Options:
--out,-o(string, default:dist): output directory--base(string, default:/): base URL (see https://vitejs.dev/config/shared-options.html#base)--download(boolean, default:false): allow the download of the slides as a PDF inside the SPA--theme,-t(string): override theme--without-notes(boolean, default:false): exclude speaker notes from the SPA
slidev export [...entry] {#export}
Export slides to PDF (or other format). See for more details.
[entry](string, default:slides.md): path to the slides markdown entry.
Options:
--output(string, default: useexportFilename(see https://sli.dev/custom/#frontmatter-configures) or use[entry]-export): path to the output.--format('pdf', 'png', 'pptx', 'md', default:'pdf'): output format.--timeout(number, default:30000): timeout for rendering the print page (see https://playwright.dev/docs/api/class-page#page-goto).--range(string): page ranges to export (example:'1,4-5,6').--dark(boolean, default:false): export as dark theme.--with-clicks,-c(boolean, default:false): export pages for every click animation (see https://sli.dev/guide/animations.html#click-animation).--theme,-t(string): override theme.--omit-background(boolean, default:false): remove the default browser background
slidev format [entry] {#format}
Format the markdown file. Note that this won't format the content of the slides, only the organization of the markdown file.
[entry](string, default:slides.md): path to the slides markdown entry.
slidev theme [subcommand] {#theme}
Theme-related operations.
Subcommands:
eject [entry]: Eject the current theme into the local file system. See .[entry](string, default:slides.md): path to the slides markdown entry.- Options:
--dir(string, default:theme): the output dir.--theme,-t(string): override theme.
<!-- Source: https://sli.dev/guide/syntax.md -->
--- url: /guide/syntax.md ---
Syntax Guide
Slidev's slides are written as Markdown files, which are called Slidev Markdowns. A presentation has a Slidev Markdown as its entry, which is ./slides.md by default, but you can change it by passing the file path as an argument to the CLI commands.
In a Slidev Markdown, not only the basic Markdown features can be used as usual, Slidev also provides additional features to enhance your slides. This section covers the syntax introduced by Slidev. Please make sure you know the basic Markdown syntax before reading this guide.
Slide Separators {#slide-separators}
Use --- padded with a new line to separate your slides.
````md {5,15}
Title
Hello, Slidev!
---
Slide 2
Use code blocks for highlighting:
console.log('Hello, World!')---
Slide 3
Use UnoCSS classes and Vue components to style and enrich your slides:
<div class="p-3"> <Tweet id="..." /> </div> ````
Frontmatter & Headmatter {#frontmatter}
At the beginning of each slide, you can add an optional frontmatter to configure the slide. The first frontmatter block is called headmatter and can configure the whole slide deck. The rest are frontmatters for individual slides. Texts in the headmatter or the frontmatter should be an object in YAML format. For example:
```md {1-4,10-14,26-28} --- theme: seriph title: Welcome to Slidev ---
Slide 1
The frontmatter of this slide is also the headmatter
--- layout: center background: /background-1.png class: text-white ---
Slide 2
A page with the layout center and a background image
---
Slide 3
A page without frontmatter
--- src: ./pages/4.md # This slide only contains a frontmatter ---
---
Slide 5
Configurations you can set are described in the [Slides deck configurations](/custom/#headmatter) and [Per slide configurations](/custom/#frontmatter) sections.
To make the headmatter more readable, you can install the VSCode extension:
Also, there is another possible frontmatter format:
## Notes {#notes}
You can also create presenter notes for each slide. They will show up in for you to reference during presentations.
The comment blocks at the end of each slide are treated as the note of the slide:
--- layout: cover ---
Slide 1
This is the cover page.
<!-- This is a note -->
---
Slide 2
<!-- This is NOT a note because it is not at the end of the slide -->
The second page
<!-- This is _another_ note -->
Basic Markdown and HTML are also supported in notes and will be rendered.
## Code Blocks {#code-block}
One big reason that led to the creation of Slidev was the need to perfectly display code in slides. Consequently, you can use Markdown-flavored code blocks to highlight your code.
console.log('Hello, World!')````
Slidev has Shiki built in as the syntax highlighter. Refer to Configure Shiki for more details.
More about code blocks:
LaTeX Blocks {#latex-block}
Slidev supports LaTeX blocks for mathematical and chemical formulas:
Diagrams {#diagrams}
Slidev supports Mermaid and PlantUML for creating diagrams from text:
MDC Syntax {#mdc-syntax}
MDC Syntax is the easiest way to apply styles and classes to elements:
Scoped CSS {#scoped-css}
You can use scoped CSS to style your slides:
Importing Slides {#importing-slides}
<!-- Source: https://sli.dev/custom/directory-structure.md -->
--- url: /custom/directory-structure.md ---
Directory Structure
Slidev employs some directory structure conventions to minimize the configuration surface and to make the functionality extensions flexible and intuitive.
The conventional directory structure is:
your-slidev/
├── components/ # custom components
├── layouts/ # custom layouts
├── public/ # static assets
├── setup/ # custom setup / hooks
├── snippets/ # code snippets
├── styles/ # custom style
├── index.html # injections to index.html
├── slides.md # the main slides entry
└── vite.config.ts # extending vite configAll of them are optional.
Components
Pattern: ./components/*.{vue,js,ts,jsx,tsx,md}
Layouts
Pattern: ./layouts/*.{vue,js,ts,jsx,tsx}
Public
Pattern: ./public/*
Assets in this directory will be served at root path / during dev, and copied to the root of the dist directory as-is. Read more about Assets Handling.
Style
Pattern: ./style.css | ./styles/index.{css,js,ts}
Files following this convention will be injected to the App root. If you need to import multiple CSS entries, you can create the following structure and manage the import order yourself.
your-slidev/
├── ...
└── styles/
├── index.ts
├── base.css
├── code.css
└── layouts.css// styles/index.ts
import './base.css'
import './code.css'
import './layouts.css'Styles will be processed by UnoCSS and PostCSS, so you can use CSS nesting and at-directives and Nested CSS out-of-box. For example:
.slidev-layout {
--uno: px-14 py-10 text-[1.1rem];
h1, h2, h3, h4, p, div {
--uno: select-none;
}
pre, code {
--uno: select-text;
}
a {
color: theme('colors.primary');
}
}Learn more about the syntax here.
index.html
Pattern: index.html
The index.html provides the ability to inject meta tags and/or scripts to the main index.html
For example, for the following custom index.html:
```html [index.html] <head> <link rel="preconnect" href="https://fonts.gstatic.com"> <link href="https://fonts.googleapis.com/css2?family=Fira+Code:wght@400;600&family=Nunito+Sans:wght@200;400;600&display=swap" rel="stylesheet"> </head>
<body> <script src="./your-scripts"></script> </body>
The final hosted `index.html` will be:
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <link rel="icon" type="image/png" href="https://cdn.jsdelivr.net/gh/slidevjs/slidev/assets/favicon.png"> <!-- injected head --> <link rel="preconnect" href="https://fonts.gstatic.com"> <link href="https://fonts.googleapis.com/css2?family=Fira+Code:wght@400;600&family=Nunito+Sans:wght@200;400;600&display=swap" rel="stylesheet"> </head> <body> <div id="app"></div> <script type="module" src="__ENTRY__"></script> <!-- injected body --> <script src="./your-scripts"></script> </body> </html>
## Global Layers
Pattern: `global-top.vue` | `global-bottom.vue` | `custom-nav-controls.vue` | `slide-top.vue` | `slide-bottom.vue`
<!-- Source: https://sli.dev/guide/exporting.md -->
--- url: /guide/exporting.md ---
Exporting
Usually the slides are displayed in a web browser, but you can also export them to PDF, PPTX, PNG, or Markdown files for sharing or printing. This feature is available through the CLI command `slidev export`.
However, interactive features in your slides may not be available in the exported files. You can build and host your slides as a web application to keep the interactivity. See Building and Hosting for more information.
The Browser Exporter Recommended {#browser}
Available since v0.50.0-beta.11
Slidev provides a UI in the browser for exporting your slides. You can access it by clicking the "Export" button in "More options" menu in the navigation bar, or go to http://localhost:<port>/export directly.
In the UI, you can export the slides as PDF, or capture the slides as images and download them as a PPTX or zip file.
Note that browsers other than modern Chromium-based browsers may not work well with the exporting UI. If you encounter any issues, please try use the CLI instead.
The following content of this page is for the CLI only.
The CLI {#cli}
Exporting to PDF, PPTX, or PNG relies on Playwright for rendering the slides. Therefore `playwright-chromium` is required to be installed in your project:
::: code-group
```bash [pnpm] $ pnpm add -D playwright-chromium
$ npm i -D playwright-chromium
$ yarn add -D playwright-chromium
$ bun add -D playwright-chromium
$ deno add -D npm:playwright-chromium
:::
## Formats
### PDF
After installing `playwright-chromium` as described above, you can export your slides to PDF using the following command:
$ slidev export
By default, the PDF will be placed at `./slides-export.pdf`.
### PPTX
Slidev can also export your slides as a PPTX file:
$ slidev export --format pptx
Note that all the slides in the PPTX file will be exported as images, so the text will not be selectable. Presenter notes will be conveyed into the PPTX file on a per-slide basis.
In this mode, the `--with-clicks` option is enabled by default. To disable it, pass `--with-clicks false`.
### PNGs and Markdown
When passing in the `--format png` option, Slidev will export PNG images for each slide instead of a PDF:
$ slidev export --format png
You can also compile a markdown file composed of compiled png using `--format md`:
$ slidev export --format md
## Options
Here are some common options you can use with the `slidev export` command. For a full list of options, see the [CLI documentation](../builtin/cli#export).
### Export Clicks Steps
By default, Slidev exports one page per slide with clicks animations disabled. If you want to export slides with multiple steps into multiple pages, pass the `--with-clicks` option:
$ slidev export --with-clicks
### Output Filename
You can specify the output filename with the `--output` option:
$ slidev export --output my-pdf-export
Or in the headmatter configuration:
--- exportFilename: my-pdf-export ---
### Export with Range
By default, all slides in the presentation are exported. If you want to export a specific slide or a range of slides you can set the `--range` option and specify which slides you would like to export:
$ slidev export --range 1,6-8,10
This option accepts both specific slide numbers and ranges. The example above would export slides 1,6,7,8 and 10.
### Multiple Exports
You can also export multiple slides at once:
$ slidev export slides1.md slides2.md
Or (only available in certain shells):
$ slidev export *.md
In this case, each input file will generate its own PDF file.
### Dark Mode
In case you want to export your slides using the dark version of the theme, use the `--dark` option:
$ slidev export --dark
### Timeouts
For big presentations, you might want to increase the Playwright timeout with `--timeout`:
$ slidev export --timeout 60000
### Wait
Some parts of your slides may require a longer time to render. You can use the `--wait` option to have an extra delay before exporting:
$ slidev export --wait 10000
There is also a `--wait-until` option to wait for a state before exporting each slide. If you keep encountering timeout issues, you can try setting this option:
$ slidev export --wait-until none
Possible values:
* `'networkidle'` - (*default*) consider operation to be finished when there are no network connections for at least `500` ms. This is the safest, but may cause timeouts.
* `'domcontentloaded'` - consider operation to be finished when the `DOMContentLoaded` event is fired.
* `'load'` - consider operation to be finished when the `load` event is fired.
* `'none'` - do not wait for any event.
::: warning
When specifying values other than `'networkidle'`, please make sure the printed slides are complete and correct. If some contents are missing, you may need to use the `--wait` option.
:::
### Executable Path
Chromium may miss some features like codecs that are required to decode some videos. You can set the browser executable path for Playwright to your Chrome or Edge using `--executable-path`:
$ slidev export --executable-path [path_to_chromium]
### PDF Outline
> Available since v0.36.10
You can generate the PDF outline by passing the `--with-toc` option:
$ slidev export --with-toc
### Omit Background
When exporting to PNGs, you can remove the default browser background by passing `--omit-background`:
$ slidev export --omit-background
The default browser background is the white background visible on all browser windows and is different than other backgrounds applied throughout the application using CSS styling. [See Playwright docs](https://playwright.dev/docs/api/class-page#page-screenshot-option-omit-background). You will then need to apply additional CSS styling to the application to reveal the transparency.
Here is a basic example that covers all backgrounds in the application:
- {
background: transparent !important; }
## Troubleshooting
### Missing Content or Animation not Finished
If you find that some content is missing or the animations are not finished in the exported PDF, you can try adding a wait time before exporting each slide:
$ slidev export --wait 1000
### Broken Emojis
If the PDF or PNG are missing Emojis, you are likely missing required fonts (such as. e.g. [Google's *Noto Emoji*](https://fonts.google.com/noto/specimen/Noto+Emoji)) in your environment.
This can affect e.g. CI/CD-like in-container sort of Linux environments. It can be fixed e.g. like this:
$ curl -L --output NotoColorEmoji.ttf https://github.com/googlefonts/noto-emoji/raw/main/fonts/NotoColorEmoji.ttf $ sudo mv NotoColorEmoji.ttf /usr/local/share/fonts/ $ fc-cache -fv
### Wrong Context in Global Layers
See the tip in https://sli.dev/features/global-layers.
<!-- Source: https://sli.dev/guide/hosting.md -->
--- url: /guide/hosting.md ---
Building and Hosting
Slidev is designed to run as a web server when you are editing or presenting your slides. However, after the presentation, you may still want to share your interactive slides with others. This guide will show you how to build and host your slides.
Build as a SPA {#spa}
You can build the slides into a static Single-page application (SPA) via the following command:
$ slidev buildBy default, the generated files are placed in the dist folder. You can test the built version of you slides by running: npx vite preview or any other static server.
Base Path {#base}
To deploy your slides under sub-routes, you need to pass the --base option. The --base path must begin and end with a slash `/`. For example:
$ slidev build --base /talks/my-cool-talk/Refer to Vite's documentation for more details.
Output directory {#output-directory}
You can change the output directory using --out.
$ slidev build --out my-build-folderRemove speaker notes {#without-notes}
If you are sharing the built slides publicly and don't want to include your speaker notes, run the build with --without-notes:
$ slidev build --without-notesMultiple Builds {#multiple-builds}
You can build multiple slide decks in one go by passing multiple markdown files as arguments:
$ slidev build slides1.md slides2.mdOr if your shell supports it, you can use a glob pattern:
$ slidev build *.mdIn this case, each input file will generate a folder containing the build in the output directory.
Examples {#examples}
Here are a few examples of the exported SPA:
- Demo Slides
- Composable Vue by Anthony Fu
- More in Showcases
Options {#options}
Hosting {#hosting}
We recommend using npm init slidev@latest to scaffold your project, which contains the necessary configuration files for hosting services out-of-the-box.
GitHub Pages {#github-pages}
To deploy your slides on GitHub Pages via GitHub Actions, follow these steps:
1. In your repository, go to Settings > Pages. Under Build and deployment, select GitHub Actions. (Do not choose Deploy from a branch and upload the dist directory, which is not recommended.) 2. Create .github/workflows/deploy.yml with the following content to deploy your slides to GitHub Pages via GitHub Actions.
::: details deploy.yml
name: Deploy pages
on:
workflow_dispatch:
push:
branches: [main]
permissions:
contents: read
pages: write
id-token: write
concurrency:
group: pages
cancel-in-progress: false
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 'lts/*'
- name: Setup @antfu/ni
run: npm i -g @antfu/ni
- name: Install dependencies
run: nci
- name: Build
run: nr build --base /${{github.event.repository.name}}/
- name: Setup Pages
uses: actions/configure-pages@v4
- uses: actions/upload-pages-artifact@v3
with:
path: dist
deploy:
environment:
name: github-pages
url: ${{ steps.deployment.outputs.page_url }}
needs: build
runs-on: ubuntu-latest
name: Deploy
steps:
- name: Deploy to GitHub Pages
id: deployment
uses: actions/deploy-pages@v4:::
3. Commit and push the changes to your repository. The GitHub Actions workflow will automatically deploy your slides to GitHub Pages every time you push to the main branch. 4. You can access your slides at https://<username>.github.io/<repository-name>/.
Netlify
Create netlify.toml in your project root with the following content:
::: details netlify.toml
[build]
publish = 'dist'
command = 'npm run build'
[build.environment]
NODE_VERSION = '20'
[[redirects]]
from = '/*'
to = '/index.html'
status = 200:::
Then go to your Netlify dashboard and create a new site with the repository.
Vercel
Create vercel.json in your project root with the following content:
::: details vercel.json
{
"rewrites": [
{ "source": "/(.*)", "destination": "/index.html" }
]
}:::
Then go to your Vercel dashboard and create a new site with the repository.
Host on Docker {#docker}
If you need a rapid way to run a presentation with containers, you can use the prebuilt docker image maintained by tangramor, or build your own.
::: details Use the Docker Image
Just run the following command in your work folder:
docker run --name slidev --rm -it \
--user node \
-v ${PWD}:/slidev \
-p 3030:3030 \
-e NPM_MIRROR="https://registry.npmmirror.com" \
tangramor/slidev:latest*Note*: You can use NPM_MIRROR to specify a npm mirror to speed up the installation process.
If your work folder is empty, it will generate a template slides.md and other related files under your work folder, and launch the server on port 3030.
You can access your slides from http://localhost:3030/
To create an Docker Image for your slides, you can use the following Dockerfile:
FROM tangramor/slidev:latest
ADD . /slidevCreate the docker image: docker build -t myslides .
And run the container: docker run --name myslides --rm --user node -p 3030:3030 myslides
You can visit your slides at http://localhost:3030/
:::
<!-- Source: https://sli.dev/guide/layout.md -->
--- url: /guide/layout.md ---
Slide Layout
Layouts in Slidev are used to define the structure for each slide. They are Vue components that wrap the content of the slides.
Using Layouts {#use}
To use a layout, you can specify it in the frontmatter of the slide:
---
layout: quote
---
A quote from someoneBy default, the layout of the first slide is cover, and the rest are default.
The layouts are loaded in the following order, and the last one loaded will override the previous ones:
1. default layouts. See Built-in Layouts. 2. layouts provided by the theme 3. layouts provided by the addons 4. custom layouts in the layouts directory
Writing Layouts {#write}
<!-- Source: https://sli.dev/guide/theme-addon.md -->
--- url: /guide/theme-addon.md ---
Theme and Addons
A slides project can have one theme and multiple addons. All of them can provide styles, components, layouts, and other configs to your slides project.
Use a Theme {#use-theme}
Changing the theme in Slidev is surprisingly easy. All you need to do is to add the theme option in your headmatter:
---
theme: seriph
---
# The first slideYou can find the list of official themes and community themes in the Themes Gallery.
::: info Theme name convention
- You can also pass a relative or absolute path to a local theme folder, like
../my-theme - You can always use the full package name as the theme name
- If the theme is official or is named like
slidev-theme-name, you can omit theslidev-theme-prefix - For scoped packages like
@org/slidev-theme-name, the full package name is required
:::
You can start the server and will be prompted to install the theme after a confirmation.
or install the theme manually via:
$ npm install @slidev/theme-seriphAnd that's all, enjoy the new theme! For more details about the usage, you can refer to the theme's README.
Use an Addon {#use-addon}
Addons are similar to themes, but they are more flexible and can be used to add extra features to your slides project. You can add multiple addons to your project, and they can be used to add extra features to your slides project.
To use an addon, you can add the addons option in your headmatter:
---
addons:
- excalidraw
- '@slidev/plugin-notes'
---You can find the list of official addons and community addons in the Addons Gallery.
<!-- Source: https://sli.dev/guide/work-with-ai.md -->
--- url: /guide/work-with-ai.md ---
Work with AI
Thanks to Slidev being markdown-based, it works great with AI coding agents.
Skills
Slidev provides official skills for AI coding agents, enabling them to understand Slidev's syntax, features, and best practices when helping you create presentations.
Installation
Install the Slidev skill to your AI coding agent:
npx skills add slidevjs/slidevThe source code of the skill is here.
Example Prompts
Once installed, you can ask agents to help with various Slidev tasks:
Create a Slidev presentation about TypeScript generics with code examplesAdd a two-column slide with code on the left and explanation on the rightSet up click animations to reveal bullet points one by oneConfigure the presentation for PDF export with speaker notesWhat's Included
The Slidev skill provides knowledge about:
- Markdown syntax, slide separators, and frontmatter
- Click animations and transitions
- Code highlighting, Monaco editor, and magic-move
- Diagrams (Mermaid, PlantUML) and LaTeX math
- Built-in layouts and components
- Exporting and hosting options
VS Code Extension
The provides Language Model Tools that allow VS Code's Copilot and other AI assistants to interact with your Slidev project directly. These tools enable AI to:
- Get information about the active slide and project
- Retrieve content of specific slides
- List and search slides by title
- Navigate between slides
See for more details.
<!-- Source: https://sli.dev/guide/write-theme.md -->
--- url: /guide/write-theme.md ---
Writing Themes
Please read first.
Each slides project can only have one theme. Themes should focus on providing the appearance of slides. If the feature isn't related to the appearance and can be used separately, it should be implemented as an addon.
To get started, we recommend you use our generator for scaffolding your first theme
::: code-group
```bash [pnpm] $ pnpm create slidev-theme
$ npm init slidev-theme@latest
$ yarn create slidev-theme
$ bun create slidev-theme
$ deno init --npm slidev-theme
:::
Then you can modify and play with it. You can also refer to the [official themes](../resources/theme-gallery#official-themes) as examples.
## Capability
A theme can contribute to the following points:
* Global styles
* Provide default configurations
* Provide custom layouts or override the existing ones
* Provide custom components
* Configure tools like UnoCSS, Shiki, etc.
However, the following points are **not** recommended to be done in a theme, and may be better implemented as an [addon](./write-addon):
* New code snippets
* New code runners
* Other things that can be used separately
Basically, the way to provide global styles, layouts, components and configure tools is the same as doing these in a slides project. For example, to configure Shiki, you can create a `./setup/shiki.ts` as described in [Configure Highlighter](../custom/config-highlighter). You can refer to the [customization guide](/custom/) for more information.
To provide default Slidev configurations, you can add a `slidev.defaults` field in the `package.json` file, which will be merged with the user's configurations:
{ "slidev": { "defaults": { "transition": "slide-left", "aspectRatio": "4/3" } } }
### Require Slidev Version
If the theme is relying on a specific feature of Slidev that is newly introduced, you can set the minimal Slidev version required to have your theme working properly:
{ "engines": { "slidev": ">=0.48.0" } }
An error message will be shown when the an incompatible version is used.
### Theme Metadata
By default, Slidev assumes themes support both light mode and dark mode. If you only want your theme to be presented in a specific color schema, you need to specify it explicitly in the `package.json`:
{ "slidev": { "colorSchema": "light" // or "dark" or "both" } }
## Previewing
You can preview your theme when developing by using a demo slide deck. To do so, create a `./slides.md` file with the following headmatter:
--- theme: ./ # Use the theme in the current directory ---
Then you can start the demo slides as usual.
## Publishing
When publishing the theme, non-JS files like `.vue` and `.ts` files can be published directly without compiling. Slidev will automatically compile them when using the theme.
Themes should follow the following conventions:
* Package name should start with `slidev-theme-`. For example, `slidev-theme-name` or `@scope/slidev-theme-name`
* Add `"slidev-theme"` and `"slidev"` in the `keywords` field of your `package.json`
Theme can be used locally without publishing to NPM. If your theme is only for personal use, you can simply use it as a local theme, or publish it as a private scoped package. However, it is recommended to publish it to the NPM registry if you want to share it with others.
#!/usr/bin/env bash
set -euo pipefail
SLIDEV_CMD=()
print_error() {
printf 'Error: %s\n' "$1" >&2
}
resolve_slidev_cmd() {
if [[ -x "./node_modules/.bin/slidev" ]]; then
SLIDEV_CMD=("./node_modules/.bin/slidev")
return 0
fi
if command -v slidev >/dev/null 2>&1; then
SLIDEV_CMD=("slidev")
return 0
fi
if command -v npx >/dev/null 2>&1; then
SLIDEV_CMD=("npx" "-y" "@slidev/cli")
return 0
fi
print_error "Could not find Slidev CLI. Install @slidev/cli or ensure slidev is on PATH."
return 1
}
run_slidev() {
resolve_slidev_cmd
"${SLIDEV_CMD[@]}" "$@"
}
#!/usr/bin/env bash
set -euo pipefail
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
# shellcheck source=_slidev_common.sh
source "$SCRIPT_DIR/_slidev_common.sh"
usage() {
cat <<'USAGE'
Usage: slidev-build.sh [entry] [--out dir] [--base /x/] [--without-notes]
Build Slidev deck as SPA.
Arguments:
entry Slide entry file (default: slides.md)
Options:
--out dir Output directory
--base /x/ Base path
--without-notes Exclude speaker notes
-h, --help Show help
USAGE
}
entry="slides.md"
out=""
base=""
without_notes=0
has_entry=0
while [[ $# -gt 0 ]]; do
case "$1" in
-h|--help)
usage
exit 0
;;
--out)
shift
[[ $# -gt 0 ]] || { print_error "Missing value for --out"; exit 1; }
out="$1"
shift
;;
--base)
shift
[[ $# -gt 0 ]] || { print_error "Missing value for --base"; exit 1; }
base="$1"
shift
;;
--without-notes)
without_notes=1
shift
;;
*)
if [[ $has_entry -eq 1 ]]; then
print_error "Unexpected argument: $1"
usage >&2
exit 1
fi
entry="$1"
has_entry=1
shift
;;
esac
done
args=("build" "$entry")
[[ -n "$out" ]] && args+=("--out" "$out")
[[ -n "$base" ]] && args+=("--base" "$base")
[[ $without_notes -eq 1 ]] && args+=("--without-notes")
run_slidev "${args[@]}"
#!/usr/bin/env bash
set -euo pipefail
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
# shellcheck source=_slidev_common.sh
source "$SCRIPT_DIR/_slidev_common.sh"
usage() {
cat <<'USAGE'
Usage: slidev-dev.sh [entry] [--port N] [--base /x/] [--theme name]
Start Slidev dev server.
Arguments:
entry Slide entry file (default: slides.md)
Options:
--port N Port number
--base /x/ Base path
--theme name Theme override
-h, --help Show help
USAGE
}
entry="slides.md"
port=""
base=""
theme=""
has_entry=0
while [[ $# -gt 0 ]]; do
case "$1" in
-h|--help)
usage
exit 0
;;
--port)
shift
[[ $# -gt 0 ]] || { print_error "Missing value for --port"; exit 1; }
port="$1"
shift
;;
--base)
shift
[[ $# -gt 0 ]] || { print_error "Missing value for --base"; exit 1; }
base="$1"
shift
;;
--theme)
shift
[[ $# -gt 0 ]] || { print_error "Missing value for --theme"; exit 1; }
theme="$1"
shift
;;
*)
if [[ $has_entry -eq 1 ]]; then
print_error "Unexpected argument: $1"
usage >&2
exit 1
fi
entry="$1"
has_entry=1
shift
;;
esac
done
args=("$entry")
[[ -n "$port" ]] && args+=("--port" "$port")
[[ -n "$base" ]] && args+=("--base" "$base")
[[ -n "$theme" ]] && args+=("--theme" "$theme")
run_slidev "${args[@]}"
#!/usr/bin/env bash
set -euo pipefail
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
# shellcheck source=_slidev_common.sh
source "$SCRIPT_DIR/_slidev_common.sh"
usage() {
cat <<'USAGE'
Usage: slidev-export.sh [entry] [--format pdf|pptx|png|md] [--output file] [--with-clicks] [--range ...] [--dark]
Export Slidev deck.
Arguments:
entry Slide entry file (default: slides.md)
Options:
--format value pdf|pptx|png|md (default: pdf)
--output file Output path
--with-clicks Export click steps
--range value Slide range, e.g. 1,3-5
--dark Export using dark mode
--install-playwright
Install playwright-chromium automatically when missing
-h, --help Show help
USAGE
}
entry="slides.md"
format="pdf"
output=""
with_clicks=0
range=""
dark=0
install_playwright=0
has_entry=0
while [[ $# -gt 0 ]]; do
case "$1" in
-h|--help)
usage
exit 0
;;
--format)
shift
[[ $# -gt 0 ]] || { print_error "Missing value for --format"; exit 1; }
format="$1"
shift
;;
--output)
shift
[[ $# -gt 0 ]] || { print_error "Missing value for --output"; exit 1; }
output="$1"
shift
;;
--with-clicks)
with_clicks=1
shift
;;
--range)
shift
[[ $# -gt 0 ]] || { print_error "Missing value for --range"; exit 1; }
range="$1"
shift
;;
--dark)
dark=1
shift
;;
--install-playwright)
install_playwright=1
shift
;;
*)
if [[ $has_entry -eq 1 ]]; then
print_error "Unexpected argument: $1"
usage >&2
exit 1
fi
entry="$1"
has_entry=1
shift
;;
esac
done
case "$format" in
pdf|pptx|png|md) ;;
*)
print_error "Invalid --format: $format"
exit 1
;;
esac
has_playwright() {
node -e "require.resolve('playwright-chromium/package.json')" >/dev/null 2>&1
}
if ! has_playwright; then
if [[ $install_playwright -eq 1 ]]; then
if ! command -v npm >/dev/null 2>&1; then
print_error "playwright-chromium is missing and npm is not available to install it"
exit 1
fi
printf 'Installing playwright-chromium...\n' >&2
npm install -D playwright-chromium >/dev/null
fi
fi
if ! has_playwright; then
print_error "playwright-chromium is required for slidev export. Install with: npm i -D playwright-chromium or pass --install-playwright"
exit 1
fi
if [[ "$format" == "md" ]]; then
if [[ -z "$output" ]]; then
entry_base="$(basename "$entry")"
entry_stem="${entry_base%.*}"
output="out/${entry_stem}-export.md"
printf 'Using md export output path: %s\n' "$output" >&2
elif [[ "$output" != */* ]]; then
output="out/$output"
printf 'Adjusted md export output path to avoid Slidev rmdir bug: %s\n' "$output" >&2
fi
mkdir -p "$(dirname "$output")"
fi
args=("export" "$entry" "--format" "$format")
[[ -n "$output" ]] && args+=("--output" "$output")
[[ $with_clicks -eq 1 ]] && args+=("--with-clicks")
[[ -n "$range" ]] && args+=("--range" "$range")
[[ $dark -eq 1 ]] && args+=("--dark")
run_slidev "${args[@]}"
#!/usr/bin/env bash
set -euo pipefail
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
usage() {
cat <<'USAGE'
Usage: slidev-init.sh [dir]
Initialize a Slidev deck directory.
Arguments:
dir Target directory (default: current directory)
Options:
-h, --help Show this help message
--no-install Do not install @slidev/cli
USAGE
}
target_dir="."
install_cli=1
has_target=0
while [[ $# -gt 0 ]]; do
case "$1" in
-h|--help)
usage
exit 0
;;
--no-install)
install_cli=0
shift
;;
*)
if [[ $has_target -eq 1 ]]; then
printf 'Error: Unexpected argument: %s\n' "$1" >&2
usage >&2
exit 1
fi
target_dir="$1"
has_target=1
shift
;;
esac
done
mkdir -p "$target_dir"
if [[ ! -f "$target_dir/slides.md" ]]; then
cat > "$target_dir/slides.md" <<'SLIDES'
---
title: New Slidev Deck
---
# Welcome
Start editing your Slidev presentation.
SLIDES
printf 'Created %s/slides.md\n' "$target_dir"
else
printf 'Found existing %s/slides.md\n' "$target_dir"
fi
if [[ ! -f "$target_dir/package.json" ]]; then
cat > "$target_dir/package.json" <<'PKG'
{
"name": "slidev-deck",
"private": true,
"devDependencies": {
"@slidev/cli": "^52.0.0",
"@slidev/theme-default": "^0.25.0"
},
"scripts": {
"dev": "slidev",
"build": "slidev build",
"export": "slidev export"
}
}
PKG
printf 'Created %s/package.json\n' "$target_dir"
fi
if [[ $install_cli -eq 1 ]]; then
if command -v npm >/dev/null 2>&1; then
(
cd "$target_dir"
npm install -D @slidev/cli @slidev/theme-default >/dev/null
)
printf 'Installed @slidev/cli and @slidev/theme-default in %s\n' "$target_dir"
else
printf 'Warning: npm not found, skipped Slidev dependency installation\n' >&2
fi
fi
printf 'Initialization complete. Start dev server with: %s %s/slides.md\n' "$SCRIPT_DIR/slidev-dev.sh" "$target_dir"
#!/usr/bin/env bash
set -euo pipefail
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
# shellcheck source=_slidev_common.sh
source "$SCRIPT_DIR/_slidev_common.sh"
usage() {
cat <<'USAGE'
Usage: slidev-theme-eject.sh [entry] [--dir theme] [--theme name]
Eject active Slidev theme into a local directory.
Arguments:
entry Slide entry file (default: slides.md)
Options:
--dir theme Output directory for ejected theme (default: theme)
--theme name Theme override
-h, --help Show help
USAGE
}
entry="slides.md"
dir="theme"
theme=""
has_entry=0
while [[ $# -gt 0 ]]; do
case "$1" in
-h|--help)
usage
exit 0
;;
--dir)
shift
[[ $# -gt 0 ]] || { print_error "Missing value for --dir"; exit 1; }
dir="$1"
shift
;;
--theme)
shift
[[ $# -gt 0 ]] || { print_error "Missing value for --theme"; exit 1; }
theme="$1"
shift
;;
*)
if [[ $has_entry -eq 1 ]]; then
print_error "Unexpected argument: $1"
usage >&2
exit 1
fi
entry="$1"
has_entry=1
shift
;;
esac
done
args=("theme" "eject" "--entry" "$entry" "--dir" "$dir")
[[ -n "$theme" ]] && args+=("--theme" "$theme")
run_slidev "${args[@]}"
#!/usr/bin/env bash
set -euo pipefail
usage() {
cat <<'USAGE'
Usage: slidev-theme-scaffold.sh [theme-name]
Scaffold a local Slidev theme package.
Arguments:
theme-name Theme folder name (default: slidev-theme-custom)
Options:
-h, --help Show help
USAGE
}
theme_name="slidev-theme-custom"
if [[ $# -gt 0 ]]; then
case "$1" in
-h|--help)
usage
exit 0
;;
*)
theme_name="$1"
shift
;;
esac
fi
if [[ $# -gt 0 ]]; then
printf 'Error: Unexpected extra arguments\n' >&2
usage >&2
exit 1
fi
if [[ -e "$theme_name" ]]; then
printf 'Error: %s already exists\n' "$theme_name" >&2
exit 1
fi
mkdir -p "$theme_name/layouts" "$theme_name/styles" "$theme_name/setup"
theme_basename="$(basename "$theme_name")"
pkg_slug="$(printf '%s' "$theme_basename" | tr '[:upper:]' '[:lower:]' | sed 's/[^a-z0-9-]/-/g' | sed 's/--*/-/g' | sed 's/^-//; s/-$//')"
if [[ -z "$pkg_slug" ]]; then
pkg_slug="custom"
fi
pkg_name="$pkg_slug"
if [[ "$pkg_name" != slidev-theme-* ]]; then
pkg_name="slidev-theme-$pkg_name"
fi
cat > "$theme_name/package.json" <<EOF_PKG
{
"name": "$pkg_name",
"version": "0.0.1",
"private": true,
"type": "module",
"keywords": ["slidev", "slidev-theme"],
"slidev": {
"colorSchema": "both",
"defaults": {
"layout": "default"
}
}
}
EOF_PKG
cat > "$theme_name/layouts/default.vue" <<'EOF_LAYOUT'
<template>
<div class="slidev-layout default px-12 py-8">
<slot />
</div>
</template>
EOF_LAYOUT
cat > "$theme_name/styles/index.css" <<'EOF_STYLE'
:root {
--slidev-theme-primary: #2563eb;
}
.slidev-layout {
font-family: ui-sans-serif, system-ui, -apple-system, Segoe UI, Roboto, Arial, sans-serif;
}
EOF_STYLE
cat > "$theme_name/setup/main.ts" <<'EOF_SETUP'
import './styles/index.css'
EOF_SETUP
printf 'Scaffolded local theme at %s\n' "$theme_name"
if [[ "$theme_name" = /* ]]; then
printf 'Use it in slides headmatter with: theme: %s\n' "$theme_name"
else
printf 'Use it in slides headmatter with: theme: ./%s\n' "$theme_name"
fi
#!/usr/bin/env node
import { mkdir, readFile, writeFile } from 'node:fs/promises'
import path from 'node:path'
const docs = [
{
target: 'references/slidev/core-syntax.md',
url: 'https://sli.dev/guide/syntax.md',
},
{
target: 'references/slidev/cli.md',
url: 'https://sli.dev/builtin/cli.md',
},
{
target: 'references/slidev/theme-addon.md',
url: 'https://sli.dev/guide/theme-addon.md',
},
{
target: 'references/slidev/write-theme.md',
url: 'https://sli.dev/guide/write-theme.md',
},
{
target: 'references/slidev/layout.md',
url: 'https://sli.dev/guide/layout.md',
},
{
target: 'references/slidev/directory-structure.md',
url: 'https://sli.dev/custom/directory-structure.md',
},
{
target: 'references/slidev/exporting.md',
url: 'https://sli.dev/guide/exporting.md',
},
{
target: 'references/slidev/hosting.md',
url: 'https://sli.dev/guide/hosting.md',
},
{
target: 'references/slidev/work-with-ai.md',
url: 'https://sli.dev/guide/work-with-ai.md',
},
{
target: 'references/platforms/codex-skills.md',
url: 'https://developers.openai.com/codex/skills.md',
},
{
target: 'references/platforms/claude-skills.md',
url: 'https://code.claude.com/docs/en/skills.md',
},
{
target: 'references/platforms/openclaw-skills.md',
url: 'https://docs.openclaw.ai/tools/skills.md',
},
]
const root = process.cwd()
async function fetchText(url) {
const response = await fetch(url, {
headers: {
'user-agent': 'slidev-multi-agent-sync/1.0',
accept: 'text/markdown,text/plain;q=0.9,*/*;q=0.5',
},
})
if (!response.ok) {
throw new Error(`Failed ${url}: HTTP ${response.status}`)
}
const text = await response.text()
if (!text.trim()) {
throw new Error(`Failed ${url}: empty response body`)
}
return text.replace(/\r\n/g, '\n').trimEnd() + '\n'
}
async function writeIfChanged(targetPath, content) {
let current = null
try {
current = await readFile(targetPath, 'utf8')
} catch {
current = null
}
if (current === content) {
return false
}
await writeFile(targetPath, content, 'utf8')
return true
}
async function main() {
let updated = 0
for (const doc of docs) {
const outputPath = path.join(root, doc.target)
await mkdir(path.dirname(outputPath), { recursive: true })
const body = await fetchText(doc.url)
const content = [`<!-- Source: ${doc.url} -->`, '', body].join('\n')
const changed = await writeIfChanged(outputPath, content)
if (changed) {
updated += 1
console.log(`updated ${doc.target}`)
} else {
console.log(`unchanged ${doc.target}`)
}
}
console.log(`done: ${updated} file(s) updated`)
}
main().catch((error) => {
console.error(error.message)
process.exit(1)
})
/home/aa/clawd/skills/slidev-agent-skill