Initial commit
This commit is contained in:
261
skills/uv-scripts/SKILL.md
Normal file
261
skills/uv-scripts/SKILL.md
Normal file
@@ -0,0 +1,261 @@
|
||||
---
|
||||
name: box-factory-uv-scripts
|
||||
description: UV-specific patterns for single-file Python scripts using inline metadata (PEP 723). Use when creating Python hooks, standalone utilities, or executable scripts in UV-managed projects.
|
||||
---
|
||||
|
||||
# UV Scripts Skill
|
||||
|
||||
This skill documents UV-specific patterns for single-file Python scripts with inline dependency metadata. For general Python knowledge, Claude relies on base training.
|
||||
|
||||
## Required Reading Before Creating UV Scripts
|
||||
|
||||
Fetch this documentation with WebFetch:
|
||||
|
||||
- **https://docs.astral.sh/uv/guides/scripts/** - Official UV scripts guide with current syntax
|
||||
|
||||
## Core Understanding
|
||||
|
||||
**What UV scripts solve:** Traditional Python scripts require separate environment setup or requirements.txt files. UV's inline metadata format (PEP 723) embeds dependencies directly in the script, enabling automatic environment creation on-demand.
|
||||
|
||||
**Key insight:** UV scripts are ideal for Claude Code hooks and standalone utilities because they're self-contained and executable without external configuration.
|
||||
|
||||
## Inline Metadata Format (Official Specification)
|
||||
|
||||
Dependencies declared in TOML comment block at top of file:
|
||||
|
||||
```python
|
||||
# /// script
|
||||
# dependencies = [
|
||||
# "requests<3",
|
||||
# "rich",
|
||||
# ]
|
||||
# ///
|
||||
|
||||
import requests
|
||||
import rich
|
||||
|
||||
# Your script logic here
|
||||
```
|
||||
|
||||
**Critical requirement:** The `dependencies` field MUST be provided even if empty:
|
||||
|
||||
```python
|
||||
# /// script
|
||||
# dependencies = []
|
||||
# ///
|
||||
```
|
||||
|
||||
**Optional Python version requirement:**
|
||||
|
||||
```python
|
||||
# /// script
|
||||
# requires-python = ">=3.12"
|
||||
# dependencies = ["requests<3"]
|
||||
# ///
|
||||
```
|
||||
|
||||
## Execution Pattern (Official Specification)
|
||||
|
||||
**Run with `uv run`:**
|
||||
|
||||
```bash
|
||||
uv run script.py
|
||||
```
|
||||
|
||||
UV automatically:
|
||||
- Parses inline metadata
|
||||
- Creates isolated environment
|
||||
- Installs dependencies
|
||||
- Executes script
|
||||
|
||||
**Important behavior:** When inline metadata exists, project dependencies are ignored (no need for `--no-project`).
|
||||
|
||||
## Shebang Pattern for Executables (Best Practice)
|
||||
|
||||
For standalone executable scripts (common for hooks):
|
||||
|
||||
```python
|
||||
#!/usr/bin/env -S uv run --script
|
||||
# /// script
|
||||
# dependencies = ["rich"]
|
||||
# ///
|
||||
|
||||
import rich
|
||||
|
||||
if __name__ == "__main__":
|
||||
rich.print("[green]Hello from UV script![/green]")
|
||||
```
|
||||
|
||||
**Make executable:**
|
||||
|
||||
```bash
|
||||
chmod +x script.py
|
||||
./script.py # Runs directly without `uv run` prefix
|
||||
```
|
||||
|
||||
**Why this works:** Shebang enables PATH-based execution and simplifies hook scripts.
|
||||
|
||||
## When to Use UV Scripts (Best Practices)
|
||||
|
||||
**Use UV scripts for:**
|
||||
|
||||
- Claude Code hooks (deterministic execution, self-contained)
|
||||
- Standalone utilities (formatting, linting, code generation)
|
||||
- Shareable scripts (no separate environment setup needed)
|
||||
- One-off automation tasks
|
||||
|
||||
**Example use case (hook script):**
|
||||
|
||||
```python
|
||||
#!/usr/bin/env -S uv run --script
|
||||
# /// script
|
||||
# dependencies = ["ruff"]
|
||||
# ///
|
||||
|
||||
import subprocess
|
||||
import sys
|
||||
|
||||
result = subprocess.run(["ruff", "check", "."], capture_output=True)
|
||||
sys.exit(result.returncode)
|
||||
```
|
||||
|
||||
**Don't use UV scripts for:**
|
||||
|
||||
- Large applications (use proper UV projects instead)
|
||||
- Scripts with many files (multi-file projects need project structure)
|
||||
- Development requiring lockfile management (scripts don't auto-lock)
|
||||
|
||||
## Critical Gotchas (Best Practices)
|
||||
|
||||
### Gotcha #1: Empty Dependencies Must Be Explicit
|
||||
|
||||
**Problem:** Omitting `dependencies` field causes UV to fail.
|
||||
|
||||
```python
|
||||
# /// script
|
||||
# requires-python = ">=3.11"
|
||||
# ///
|
||||
# ERROR: Missing required 'dependencies' field
|
||||
```
|
||||
|
||||
**Solution:** Always include `dependencies`, even if empty:
|
||||
|
||||
```python
|
||||
# /// script
|
||||
# requires-python = ">=3.11"
|
||||
# dependencies = []
|
||||
# ///
|
||||
```
|
||||
|
||||
### Gotcha #2: Script Locking Requires Explicit Command
|
||||
|
||||
**Problem:** Unlike UV projects, scripts don't auto-generate lockfiles.
|
||||
|
||||
**Solution:** Explicitly lock if reproducibility needed:
|
||||
|
||||
```bash
|
||||
uv lock --script script.py
|
||||
```
|
||||
|
||||
This creates `script.lock` alongside `script.py`.
|
||||
|
||||
### Gotcha #3: Shebang Requires -S Flag
|
||||
|
||||
**Problem:** Standard shebang won't work with multi-word commands.
|
||||
|
||||
```python
|
||||
#!/usr/bin/env uv run --script
|
||||
# ERROR: env can't handle multiple arguments
|
||||
```
|
||||
|
||||
**Solution:** Use `-S` flag:
|
||||
|
||||
```python
|
||||
#!/usr/bin/env -S uv run --script
|
||||
# SUCCESS: Splits arguments correctly
|
||||
```
|
||||
|
||||
## Quality Checklist
|
||||
|
||||
Before finalizing a UV script:
|
||||
|
||||
**Format (official requirements):**
|
||||
- Script starts with `# /// script` comment block
|
||||
- `dependencies` field present (even if empty)
|
||||
- Comment block closed with `# ///`
|
||||
- No syntax errors in TOML metadata
|
||||
|
||||
**Best practices:**
|
||||
- Shebang uses `-S` flag for executables
|
||||
- Dependencies pinned appropriately (exact versions for reproducibility, ranges for flexibility)
|
||||
- Script made executable with `chmod +x` if intended for direct execution
|
||||
- Hook scripts return proper exit codes (0 = success, non-zero = failure)
|
||||
|
||||
## Common Pattern: Claude Code Hook Script
|
||||
|
||||
**Template for hook scripts:**
|
||||
|
||||
```python
|
||||
#!/usr/bin/env -S uv run --script
|
||||
# /// script
|
||||
# dependencies = [
|
||||
# "tool-name>=1.0.0",
|
||||
# ]
|
||||
# ///
|
||||
|
||||
import subprocess
|
||||
import sys
|
||||
import os
|
||||
|
||||
def main():
|
||||
"""Hook logic here."""
|
||||
# Get file paths from environment
|
||||
file_paths = os.environ.get("CLAUDE_FILE_PATHS", "").split()
|
||||
|
||||
if not file_paths:
|
||||
sys.exit(0) # Nothing to process
|
||||
|
||||
# Run tool
|
||||
result = subprocess.run(
|
||||
["tool-name", *file_paths],
|
||||
capture_output=True,
|
||||
text=True
|
||||
)
|
||||
|
||||
if result.returncode != 0:
|
||||
print(result.stderr, file=sys.stderr)
|
||||
sys.exit(2) # Block operation
|
||||
|
||||
sys.exit(0) # Success
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
```
|
||||
|
||||
**Hook registration in hooks.json:**
|
||||
|
||||
```json
|
||||
{
|
||||
"PostToolUse": [
|
||||
{
|
||||
"matcher": "Write|Edit",
|
||||
"hooks": [
|
||||
{
|
||||
"type": "command",
|
||||
"command": "./hooks/format-code.py"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
## Documentation References
|
||||
|
||||
**Official UV documentation:**
|
||||
- https://docs.astral.sh/uv/guides/scripts/ - Current syntax and features
|
||||
- https://peps.python.org/pep-0723/ - PEP 723 specification for inline metadata
|
||||
|
||||
**Related patterns:**
|
||||
- Fetch box-factory:hooks-design skill for hook lifecycle and execution patterns
|
||||
- UV project documentation for multi-file Python projects
|
||||
Reference in New Issue
Block a user