From e504f0d1bf264c65bac874c7475d48440a9c3297 Mon Sep 17 00:00:00 2001 From: Florian BRUNIAUX Date: Sun, 15 Feb 2026 20:55:16 +0100 Subject: [PATCH] feat: add session summary screenshot, skills, and GitHub templates - Add session-summary-v3.png screenshot for hook documentation - Add GitHub issue templates (bug report, feature request, question) - Add new skills: ccboard, guide-recap, landing-page-generator, release-notes-generator, skill-creator Co-Authored-By: Claude Opus 4.6 --- .github/ISSUE_TEMPLATE/bug_report.yml | 56 ++ .github/ISSUE_TEMPLATE/feature_request.yml | 65 ++ .github/ISSUE_TEMPLATE/question.yml | 52 + docs/images/session-summary-v3.png | Bin 0 -> 116768 bytes .../skills/ccboard/.claude-plugin/plugin.json | 46 + examples/skills/ccboard/README.md | 185 ++++ examples/skills/ccboard/SKILL.md | 398 ++++++++ examples/skills/ccboard/commands/costs.md | 84 ++ examples/skills/ccboard/commands/dashboard.md | 65 ++ examples/skills/ccboard/commands/install.md | 110 +++ .../skills/ccboard/commands/mcp-status.md | 68 ++ examples/skills/ccboard/commands/sessions.md | 90 ++ examples/skills/ccboard/commands/web.md | 94 ++ .../skills/ccboard/scripts/check-install.sh | 38 + .../skills/ccboard/scripts/install-ccboard.sh | 85 ++ examples/skills/guide-recap/SKILL.md | 217 +++++ .../guide-recap/assets/linkedin-template.md | 101 ++ .../guide-recap/assets/newsletter-template.md | 124 +++ .../guide-recap/assets/slack-template.md | 86 ++ .../guide-recap/assets/twitter-template.md | 145 +++ .../guide-recap/examples/version-output.md | 145 +++ .../guide-recap/examples/week-output.md | 203 ++++ .../references/changelog-parsing-rules.md | 142 +++ .../references/content-transformation.md | 139 +++ .../guide-recap/references/tone-guidelines.md | 77 ++ .../skills/landing-page-generator/SKILL.md | 238 +++++ .../assets/search-base.js | 401 ++++++++ .../assets/section-snippets/faq.html | 62 ++ .../section-snippets/features-grid.html | 59 ++ .../assets/section-snippets/footer.html | 40 + .../assets/section-snippets/header.html | 54 ++ .../assets/section-snippets/hero.html | 42 + .../assets/section-snippets/install.html | 90 ++ .../assets/section-snippets/risk-banner.html | 22 + .../assets/section-snippets/search-modal.html | 40 + .../assets/static-workflow.yml | 43 + .../assets/styles-base.css | 917 ++++++++++++++++++ .../references/landing-pattern.md | 478 +++++++++ .../skills/release-notes-generator/SKILL.md | 259 +++++ .../release-notes-generator/assets/README.md | 18 + .../assets/changelog-template.md | 93 ++ .../assets/slack-template.md | 85 ++ .../references/README.md | 17 + .../references/commit-categories.md | 124 +++ .../references/tech-to-product-mappings.md | 91 ++ .../release-notes-generator/scripts/README.md | 17 + examples/skills/skill-creator/SKILL.md | 186 ++++ .../skill-creator/scripts/init_skill.py | 228 +++++ .../skill-creator/scripts/package_skill.py | 135 +++ 49 files changed, 6554 insertions(+) create mode 100644 .github/ISSUE_TEMPLATE/bug_report.yml create mode 100644 .github/ISSUE_TEMPLATE/feature_request.yml create mode 100644 .github/ISSUE_TEMPLATE/question.yml create mode 100644 docs/images/session-summary-v3.png create mode 100644 examples/skills/ccboard/.claude-plugin/plugin.json create mode 100644 examples/skills/ccboard/README.md create mode 100644 examples/skills/ccboard/SKILL.md create mode 100644 examples/skills/ccboard/commands/costs.md create mode 100644 examples/skills/ccboard/commands/dashboard.md create mode 100644 examples/skills/ccboard/commands/install.md create mode 100644 examples/skills/ccboard/commands/mcp-status.md create mode 100644 examples/skills/ccboard/commands/sessions.md create mode 100644 examples/skills/ccboard/commands/web.md create mode 100755 examples/skills/ccboard/scripts/check-install.sh create mode 100755 examples/skills/ccboard/scripts/install-ccboard.sh create mode 100644 examples/skills/guide-recap/SKILL.md create mode 100644 examples/skills/guide-recap/assets/linkedin-template.md create mode 100644 examples/skills/guide-recap/assets/newsletter-template.md create mode 100644 examples/skills/guide-recap/assets/slack-template.md create mode 100644 examples/skills/guide-recap/assets/twitter-template.md create mode 100644 examples/skills/guide-recap/examples/version-output.md create mode 100644 examples/skills/guide-recap/examples/week-output.md create mode 100644 examples/skills/guide-recap/references/changelog-parsing-rules.md create mode 100644 examples/skills/guide-recap/references/content-transformation.md create mode 100644 examples/skills/guide-recap/references/tone-guidelines.md create mode 100644 examples/skills/landing-page-generator/SKILL.md create mode 100644 examples/skills/landing-page-generator/assets/search-base.js create mode 100644 examples/skills/landing-page-generator/assets/section-snippets/faq.html create mode 100644 examples/skills/landing-page-generator/assets/section-snippets/features-grid.html create mode 100644 examples/skills/landing-page-generator/assets/section-snippets/footer.html create mode 100644 examples/skills/landing-page-generator/assets/section-snippets/header.html create mode 100644 examples/skills/landing-page-generator/assets/section-snippets/hero.html create mode 100644 examples/skills/landing-page-generator/assets/section-snippets/install.html create mode 100644 examples/skills/landing-page-generator/assets/section-snippets/risk-banner.html create mode 100644 examples/skills/landing-page-generator/assets/section-snippets/search-modal.html create mode 100644 examples/skills/landing-page-generator/assets/static-workflow.yml create mode 100644 examples/skills/landing-page-generator/assets/styles-base.css create mode 100644 examples/skills/landing-page-generator/references/landing-pattern.md create mode 100644 examples/skills/release-notes-generator/SKILL.md create mode 100644 examples/skills/release-notes-generator/assets/README.md create mode 100644 examples/skills/release-notes-generator/assets/changelog-template.md create mode 100644 examples/skills/release-notes-generator/assets/slack-template.md create mode 100644 examples/skills/release-notes-generator/references/README.md create mode 100644 examples/skills/release-notes-generator/references/commit-categories.md create mode 100644 examples/skills/release-notes-generator/references/tech-to-product-mappings.md create mode 100644 examples/skills/release-notes-generator/scripts/README.md create mode 100644 examples/skills/skill-creator/SKILL.md create mode 100755 examples/skills/skill-creator/scripts/init_skill.py create mode 100755 examples/skills/skill-creator/scripts/package_skill.py diff --git a/.github/ISSUE_TEMPLATE/bug_report.yml b/.github/ISSUE_TEMPLATE/bug_report.yml new file mode 100644 index 0000000..87a83b0 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug_report.yml @@ -0,0 +1,56 @@ +name: Bug Report +description: Report an error or issue in the guide +title: "[Bug]: " +labels: ["bug"] +body: + - type: markdown + attributes: + value: | + Thanks for reporting this issue! + + - type: dropdown + id: section + attributes: + label: Guide Section + description: Which section contains the error? + options: + - Getting Started + - Installation + - Configuration + - MCP Servers + - Tools & Commands + - Best Practices + - Security + - Agent Teams + - Advanced Topics + - Examples + - Other + validations: + required: true + + - type: textarea + id: description + attributes: + label: Description + description: What's the issue? + placeholder: "The guide says X but actually Y is correct" + validations: + required: true + + - type: textarea + id: location + attributes: + label: Location + description: Where in the guide is this issue? + placeholder: "Section 5.3, paragraph 2 OR guide/05-tools/mcp-servers.md line 42" + validations: + required: true + + - type: textarea + id: correction + attributes: + label: Suggested Correction + description: What should it say instead? + placeholder: "It should say..." + validations: + required: false diff --git a/.github/ISSUE_TEMPLATE/feature_request.yml b/.github/ISSUE_TEMPLATE/feature_request.yml new file mode 100644 index 0000000..99999d5 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/feature_request.yml @@ -0,0 +1,65 @@ +name: Feature Request +description: Suggest new content or improvements for the guide +title: "[Feature]: " +labels: ["enhancement"] +body: + - type: markdown + attributes: + value: | + Thanks for suggesting an improvement! + + - type: dropdown + id: type + attributes: + label: Type + description: What kind of addition is this? + options: + - New section/chapter + - New example + - Expand existing section + - New best practice + - New MCP server documentation + - New tool documentation + - Tutorial/walkthrough + - Cheatsheet addition + - Other + validations: + required: true + + - type: textarea + id: topic + attributes: + label: Topic + description: What topic should be covered? + placeholder: "I'd like to see coverage of..." + validations: + required: true + + - type: textarea + id: rationale + attributes: + label: Why is this important? + description: What problem does this solve? Who would benefit? + placeholder: "This would help users who..." + validations: + required: true + + - type: textarea + id: outline + attributes: + label: Suggested Outline + description: What should this cover? (optional) + placeholder: | + - Key concept 1 + - Key concept 2 + - Example + validations: + required: false + + - type: textarea + id: resources + attributes: + label: Relevant Resources + description: Links to documentation, examples, or references + validations: + required: false diff --git a/.github/ISSUE_TEMPLATE/question.yml b/.github/ISSUE_TEMPLATE/question.yml new file mode 100644 index 0000000..02aca06 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/question.yml @@ -0,0 +1,52 @@ +name: Question +description: Ask about Claude Code usage or guide content +title: "[Question]: " +labels: ["question"] +body: + - type: markdown + attributes: + value: | + Before asking, please check: + - [The Guide](https://cc.bruniaux.com/) + - [Existing Issues](https://github.com/FlorianBruniaux/claude-code-ultimate-guide/issues) + - [Official Docs](https://docs.claude.ai/) + + - type: dropdown + id: category + attributes: + label: Category + description: What's your question about? + options: + - Claude Code usage + - MCP servers + - Configuration + - Tools & commands + - Best practices + - Security + - Agent teams + - Installation + - Troubleshooting + - Guide content + - Other + validations: + required: true + + - type: textarea + id: question + attributes: + label: Question + description: What would you like to know? + placeholder: "How do I..." + validations: + required: true + + - type: textarea + id: context + attributes: + label: Context + description: What have you tried? What's your use case? + placeholder: | + I'm trying to... + I've looked at... + validations: + required: false diff --git a/docs/images/session-summary-v3.png b/docs/images/session-summary-v3.png new file mode 100644 index 0000000000000000000000000000000000000000..8deab10be2e8d32c9cf1c1b3ad97a4fb16cceb03 GIT binary patch literal 116768 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
    OsuDrX|Ir*FJN;^{WO}P=NO$Y{)5w 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 |m4~dDgf_$-G19iQw-_iK_YHxB?~9Hfd^%VVv)T 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 Comprehensive TUI/Web dashboard for monitoring and managing Claude Code + +[![License](https://img.shields.io/badge/license-MIT%20OR%20Apache--2.0-blue.svg)](../../LICENSE) +[![Rust](https://img.shields.io/badge/rust-1.70%2B-orange.svg)](https://www.rust-lang.org) + +## Quick Start + +### Installation + +```bash +# Using Claude Code command +/ccboard-install + +# Or manually via cargo +cargo install ccboard +``` + +### Launch Dashboard + +```bash +# Launch TUI +/dashboard + +# Or run directly +ccboard +``` + +## Commands + +| Command | Description | +|---------|-------------| +| `/dashboard` | Launch interactive TUI dashboard | +| `/mcp-status` | Monitor MCP servers (press `8`) | +| `/costs` | View cost analytics (press `6`) | +| `/sessions` | Browse conversation history (press `2`) | +| `/ccboard-web` | Launch web interface | +| `/ccboard-install` | Install or update ccboard | + +## Features + +- **8 Interactive Tabs**: Dashboard, Sessions, Config, Hooks, Agents, Costs, History, MCP +- **Real-time Monitoring**: File watcher for live updates +- **MCP Management**: Server status and configuration +- **Cost Tracking**: Token usage and pricing analytics (e.g., $9,145 total) +- **Session Explorer**: Browse 1.2K+ conversations across 33+ projects +- **File Editing**: Press `e` to edit files in $EDITOR +- **Dual Interface**: Terminal (TUI) and Web UI from single binary + +## Navigation + +**Jump to Tab**: +- `1` Dashboard +- `2` Sessions +- `3` Config +- `4` Hooks +- `5` Agents +- `6` Costs +- `7` History +- `8` MCP + +**Common Keys**: +- `Tab` / `Shift+Tab` : Navigate tabs +- `e` : Edit file in editor +- `o` : Reveal file in finder +- `q` : Quit +- `F5` : Refresh + +## MCP Server Monitoring + +The MCP tab (press `8`) provides: + +- **Live Status**: ● Running, ○ Stopped, ? Unknown +- **Server Details**: Full command, args, environment variables +- **Quick Actions**: + - `e` : Edit `claude_desktop_config.json` + - `o` : Reveal config in finder + - `r` : Refresh server status + +## Cost Analytics + +Track your Claude Code spending: + +- Total tokens: 17.32M +- Total cost: $9,145.20 +- Breakdown by model: Opus 4.5 (76%), Sonnet 4.5 (14%) +- Cache hit rate: 99.9% + +## Session Explorer + +Browse and search conversations: + +- 1.2K+ sessions across 33+ projects +- Full-text search (press `/`) +- Metadata: timestamps, tokens, models +- Edit JSONL files directly + +## Web Interface + +```bash +# Launch web UI +/ccboard-web + +# Or with custom port +ccboard web --port 8080 + +# Run both TUI and Web +ccboard both --port 3333 +``` + +## Requirements + +- **Rust 1.70+** and Cargo +- **Claude Code** installed (reads from `~/.claude/`) + +## Architecture + +Single Rust binary (2.4MB) with: +- **TUI**: Ratatui-based terminal interface +- **Web**: Axum + Leptos web interface +- **Core**: Shared data layer with file watcher + +## Data Sources + +ccboard reads from: +- `~/.claude/stats-cache.json` - Statistics +- `~/.claude/claude_desktop_config.json` - MCP config +- `~/.claude/projects/*/` - Session JSONL files +- `.claude/settings.json` - Configuration + +**Read-only**: Non-invasive monitoring, safe to run with Claude Code. + +## Performance + +- Initial load: <2s for 1,000+ sessions +- Memory: ~50MB typical usage +- Lazy loading: Session content loaded on-demand + +## Limitations + +Current version (0.1.0): + +- **Read-only**: No write operations +- **MCP status**: Unix only (macOS/Linux) +- **Web UI**: In development + +## Troubleshooting + +### ccboard not found +```bash +which ccboard # Check if installed +/ccboard-install # Install if needed +``` + +### No data visible +```bash +ls ~/.claude/ # Verify Claude Code directory +cat ~/.claude/stats-cache.json # Check stats file +``` + +### MCP status "Unknown" +- Requires Unix (macOS/Linux) +- Windows shows "Unknown" by default +- Verify server running: `ps aux | grep ` + +## Documentation + +- **Full Guide**: See [SKILL.md](SKILL.md) for complete documentation +- **Commands**: See [commands/](commands/) directory +- **Scripts**: See [scripts/](scripts/) directory + +## Links + +- **Repository**: https://github.com/FlorianBruniaux/ccboard +- **Issues**: https://github.com/FlorianBruniaux/ccboard/issues +- **Claude Code**: https://claude.ai/code + +## License + +MIT OR Apache-2.0 + +--- + +**Made with ❤️ for the Claude Code community** diff --git a/examples/skills/ccboard/SKILL.md b/examples/skills/ccboard/SKILL.md new file mode 100644 index 0000000..43a5f74 --- /dev/null +++ b/examples/skills/ccboard/SKILL.md @@ -0,0 +1,398 @@ +--- +name: ccboard +description: Comprehensive TUI/Web dashboard for Claude Code monitoring +version: 0.1.0 +category: monitoring +keywords: [dashboard, tui, mcp, sessions, costs, analytics] +tags: [dashboard, tui, monitoring, claude-code, costs] +--- + +# ccboard - Claude Code Dashboard + +Comprehensive TUI/Web dashboard for monitoring and managing your Claude Code usage. + +## Overview + +ccboard provides a unified interface to visualize and explore all your Claude Code data: + +- **Sessions**: Browse all conversations across your projects +- **Statistics**: Real-time token usage, cache hit rates, activity trends +- **MCP Servers**: Monitor and manage Model Context Protocol servers +- **Costs**: Track spending with detailed token breakdown and pricing +- **Configuration**: View cascading settings (Global > Project > Local) +- **Hooks**: Explore pre/post execution hooks and automation +- **Agents**: Manage custom agents, commands, and skills +- **History**: Search across all messages with full-text search + +## Installation + +### Via Cargo (Recommended) + +```bash +# Using Claude Code command +/ccboard-install + +# Or manually +cargo install ccboard +``` + +### Requirements + +- Rust 1.70+ and Cargo +- Claude Code installed (reads from `~/.claude/`) + +## Commands + +| Command | Description | Shortcut | +|---------|-------------|----------| +| `/dashboard` | Launch TUI dashboard | `ccboard` | +| `/mcp-status` | Open MCP servers tab | Press `8` | +| `/costs` | Open costs analysis | Press `6` | +| `/sessions` | Browse sessions | Press `2` | +| `/ccboard-web` | Launch web UI | `ccboard web` | +| `/ccboard-install` | Install/update ccboard | - | + +## Features + +### 8 Interactive Tabs + +#### 1. Dashboard (Press `1`) +- Token usage statistics (17.32M total) +- Session count (1.2K tracked) +- Messages sent (327.7K) +- Cache hit ratio (99.9%) +- MCP server count (3 servers) +- 7-day activity sparkline +- Top 5 models usage gauges + +#### 2. Sessions (Press `2`) +- Dual-pane: Project tree (33 projects) + Session list (1.2K sessions) +- Metadata: timestamps, duration, tokens, models +- Search: Filter by project, message, or model (press `/`) +- File operations: `e` to edit JSONL, `o` to reveal in finder + +#### 3. Config (Press `3`) +- 4-column cascading view: Global | Project | Local | Merged +- Settings inheritance visualization +- MCP servers configuration +- Rules (CLAUDE.md) preview +- Permissions, hooks, environment variables +- Edit config with `e` key + +#### 4. Hooks (Press `4`) +- Event-based hook browsing (PreToolUse, UserPromptSubmit) +- Hook bash script preview +- Match patterns and conditions +- File path tracking for easy editing + +#### 5. Agents (Press `5`) +- 3 sub-tabs: Agents (12) | / Commands (5) | ★ Skills (0) +- Frontmatter metadata extraction +- File preview and editing +- Recursive directory scanning + +#### 6. Costs (Press `6`) +- 3 views: Overview | By Model | Daily Trend +- Token breakdown: input, output, cache read/write +- Pricing: $9,145.20 total estimated +- Model distribution: Opus 4.5 (76%), Sonnet 4.5 (14%) + +#### 7. History (Press `7`) +- Full-text search across 2,311 sessions +- Activity by hour histogram (24h) +- 7-day sparkline +- 297.9K messages searchable + +#### 8. MCP (Press `8`) **NEW** +- Dual-pane: Server list (35%) | Details (65%) +- Live status detection: ● Running, ○ Stopped, ? Unknown +- Full server details: command, args, environment vars +- Quick actions: `e` edit config, `o` reveal file, `r` refresh status + +### Navigation + +**Global Keys**: +- `1-8` : Jump to tab +- `Tab` / `Shift+Tab` : Navigate tabs +- `q` : Quit +- `F5` : Refresh data + +**Vim-style**: +- `h/j/k/l` : Navigate (left/down/up/right) +- `←/→/↑/↓` : Arrow alternatives + +**Common Actions**: +- `Enter` : View details / Focus pane +- `e` : Edit file in $EDITOR +- `o` : Reveal file in finder +- `/` : Search (in Sessions/History tabs) +- `Esc` : Close popup / Cancel + +### Real-time Monitoring + +ccboard includes a file watcher that monitors `~/.claude/` for changes: + +- **Stats updates**: Live refresh when `stats-cache.json` changes +- **Session updates**: New sessions appear automatically +- **Config updates**: Settings changes reflected in UI +- **500ms debounce**: Prevents excessive updates + +### File Editing + +Press `e` on any item to open in your preferred editor: + +- Uses `$VISUAL` > `$EDITOR` > platform default (nano/notepad) +- Supports: Sessions (JSONL), Config (JSON), Hooks (Shell), Agents (Markdown) +- Terminal state preserved (alternate screen mode) +- Cross-platform (macOS, Linux, Windows) + +### MCP Server Management + +The MCP tab provides comprehensive server monitoring: + +**Status Detection** (Unix): +- Checks running processes via `ps aux` +- Extracts package name from command +- Displays PID when running +- Windows shows "Unknown" status + +**Server Details**: +- Full command and arguments +- Environment variables with values +- Config file path (`~/.claude/claude_desktop_config.json`) +- Quick edit/reveal actions + +**Navigation**: +- `h/l` or `←/→` : Switch between list and details +- `j/k` or `↑/↓` : Select server +- `Enter` : Focus detail pane +- `e` : Edit MCP config +- `o` : Reveal config in finder +- `r` : Refresh server status + +## Usage Examples + +### Daily Monitoring + +```bash +# Launch dashboard +/dashboard + +# Check activity and costs +# Press '1' for overview +# Press '6' for costs breakdown +# Press '7' for recent history +``` + +### MCP Troubleshooting + +```bash +# Open MCP tab +/mcp-status + +# Or: ccboard then press '8' + +# Check server status (● green = running) +# Press 'e' to edit config if needed +# Press 'r' to refresh status after changes +``` + +### Session Analysis + +```bash +# Browse sessions +/sessions + +# Press '/' to search +# Filter by project: /aristote +# Filter by model: /opus +# Press 'e' on session to view full JSONL +``` + +### Cost Tracking + +```bash +# View costs +/costs + +# Press '1' for overview +# Press '2' for breakdown by model +# Press '3' for daily trend + +# Identify expensive sessions +# Track cache efficiency (99.9% hit rate) +``` + +## Web Interface + +Launch browser-based interface for remote monitoring: + +```bash +# Launch web UI +/ccboard-web + +# Or with custom port +ccboard web --port 8080 + +# Access at http://localhost:3333 +``` + +**Features**: +- Same data as TUI (shared backend) +- Server-Sent Events (SSE) for live updates +- Responsive design (desktop/tablet/mobile) +- Concurrent multi-user access + +**Run both simultaneously**: +```bash +ccboard both --port 3333 +``` + +## Architecture + +ccboard is a single Rust binary with dual frontends: + +``` +ccboard/ +├── ccboard-core/ # Parsers, models, data store, watcher +├── ccboard-tui/ # Ratatui frontend (8 tabs) +└── ccboard-web/ # Axum + Leptos frontend +``` + +**Data Sources**: +- `~/.claude/stats-cache.json` - Statistics +- `~/.claude/claude_desktop_config.json` - MCP config +- `~/.claude/projects/*/` - Session JSONL files +- `~/.claude/settings.json` - Global settings +- `.claude/settings.json` - Project settings +- `.claude/settings.local.json` - Local overrides +- `.claude/CLAUDE.md` - Rules and behavior + +## Troubleshooting + +### ccboard not found + +```bash +# Check installation +which ccboard + +# Install if needed +/ccboard-install +``` + +### No data visible + +```bash +# Verify Claude Code is installed +ls ~/.claude/ + +# Check stats file exists +cat ~/.claude/stats-cache.json + +# Run with specific project +ccboard --project ~/path/to/project +``` + +### MCP status shows "Unknown" + +- Status detection requires Unix (macOS/Linux) +- Windows shows "Unknown" by default +- Check if server process is actually running: `ps aux | grep ` + +### File watcher not working + +- Ensure `notify` crate supports your platform +- Check file permissions on `~/.claude/` +- Restart ccboard if file system events missed + +## Advanced Usage + +### Command-line Options + +```bash +ccboard --help # Show all options +ccboard --claude-home PATH # Custom Claude directory +ccboard --project PATH # Specific project +ccboard stats # Print stats and exit +ccboard web --port 8080 # Web UI on port 8080 +ccboard both # TUI + Web simultaneously +``` + +### Environment Variables + +```bash +# Editor preference +export EDITOR=vim +export VISUAL=code + +# Custom Claude home +export CLAUDE_HOME=~/custom/.claude +``` + +### Integration with Claude Code + +ccboard reads **read-only** from Claude Code directories: + +- Non-invasive monitoring +- No modifications to Claude data +- Safe to run concurrently with Claude Code +- File watcher detects changes in real-time + +## Performance + +- **Binary size**: 2.4MB (release build) +- **Initial load**: <2s for 1,000+ sessions +- **Memory**: ~50MB typical usage +- **CPU**: <5% during monitoring +- **Lazy loading**: Session content loaded on-demand + +## Limitations + +Current version (0.1.0): + +- **Read-only**: No write operations to Claude data +- **MCP status**: Unix only (Windows shows "Unknown") +- **Web UI**: In development (TUI is primary interface) +- **Search**: Basic substring matching (no fuzzy search yet) + +Future roadmap: + +- Enhanced MCP server management (start/stop) +- MCP protocol health checks +- Export reports (PDF, JSON, CSV) +- Config editing (write settings.json) +- Session resume integration +- Enhanced search with fuzzy matching + +## Contributing + +ccboard is open source (MIT OR Apache-2.0). + +Repository: https://github.com/FlorianBruniaux/ccboard + +Contributions welcome: +- Bug reports and feature requests +- Pull requests for new features +- Documentation improvements +- Platform-specific testing (Windows, Linux) + +## Credits + +Built with: +- [Ratatui](https://ratatui.rs/) - Terminal UI framework +- [Axum](https://github.com/tokio-rs/axum) - Web framework +- [Leptos](https://leptos.dev/) - Reactive frontend +- [Notify](https://github.com/notify-rs/notify) - File watcher +- [Serde](https://serde.rs/) - Serialization + +## License + +MIT OR Apache-2.0 + +--- + +**Questions?** + +- GitHub Issues: https://github.com/FlorianBruniaux/ccboard/issues +- Documentation: https://github.com/FlorianBruniaux/ccboard +- Claude Code: https://claude.ai/code diff --git a/examples/skills/ccboard/commands/costs.md b/examples/skills/ccboard/commands/costs.md new file mode 100644 index 0000000..324a03b --- /dev/null +++ b/examples/skills/ccboard/commands/costs.md @@ -0,0 +1,84 @@ +--- +name: costs +description: Open ccboard costs analysis tab +category: analytics +--- + +# Costs Analysis Command + +Launch ccboard and jump directly to the costs tracking and analytics tab. + +## Features + +- **3 Views**: + - Overview: Total costs and breakdown + - By Model: Cost per AI model (Opus, Sonnet, Haiku) + - Daily Trend: Cost evolution over time + +- **Token Breakdown**: + - Input tokens (prompt) + - Output tokens (generation) + - Cache read tokens (reused) + - Cache write tokens (stored) + +- **Pricing**: Automatic calculation based on 2024 Anthropic rates + +## Usage + +```bash +# Open costs tab directly +/costs + +# Alternative: run with tab argument +ccboard --tab costs +``` + +## Costs Tab Navigation + +- `1` : Overview view +- `2` : By Model view +- `3` : Daily Trend view +- `Tab` : Switch between views +- `↑/↓` : Scroll through data + +## Example Output + +``` +Total Tokens: 17.32M +Total Cost: $9,145.20 + +Breakdown by Model: +- Opus 4.5: 76% ($7,828) ████████████████ +- Sonnet 4.5: 14% ($1,314) ███ +- Haiku 3.5: 10% ($3) █ + +Token Distribution: +- Input: 10.70M (65%) +- Output: 4.58M (28%) +- Cache Read: 1.01M (6%) +- Cache Write: 1.04B (1%) +``` + +## Requirements + +ccboard must be installed. Run `/ccboard-install` if needed. + +## Implementation + +```bash +#!/bin/bash + +# Check if ccboard is installed +if ! command -v ccboard &> /dev/null; then + echo "❌ ccboard is not installed" + echo "Run: /ccboard-install" + exit 1 +fi + +# Launch ccboard with Costs tab (tab index 5, accessible with '6' key) +# For now, launch and user presses '6' +exec ccboard +``` + +**Note**: Currently launches ccboard in dashboard view. Press `6` to access Costs tab. +Future version will support `ccboard --tab costs` for direct access. diff --git a/examples/skills/ccboard/commands/dashboard.md b/examples/skills/ccboard/commands/dashboard.md new file mode 100644 index 0000000..f95f0d7 --- /dev/null +++ b/examples/skills/ccboard/commands/dashboard.md @@ -0,0 +1,65 @@ +--- +name: dashboard +description: Launch ccboard TUI dashboard +category: monitoring +--- + +# Dashboard Command + +Launch the interactive ccboard TUI to visualize and monitor your Claude Code usage. + +## Features + +- **8 Interactive Tabs**: Dashboard, Sessions, Config, Hooks, Agents, Costs, History, MCP +- **Real-time Monitoring**: File watcher for live updates +- **MCP Management**: Server status and configuration +- **Cost Tracking**: Token usage and pricing analytics +- **Session Explorer**: Browse and search conversation history +- **File Editing**: Press `e` to edit files in $EDITOR + +## Usage + +```bash +# Launch TUI dashboard +/dashboard + +# Alternative: run directly +ccboard +``` + +## Navigation + +- `1-8` : Jump to specific tab +- `Tab` / `Shift+Tab` : Navigate tabs +- `q` : Quit +- `F5` : Refresh data +- `e` : Edit selected file +- `o` : Reveal file in finder + +## Requirements + +ccboard must be installed. If not installed, run: +```bash +/ccboard-install +``` + +## Implementation + +```bash +#!/bin/bash + +# Check if ccboard is installed +if ! command -v ccboard &> /dev/null; then + echo "❌ ccboard is not installed" + echo "" + echo "Install with:" + echo " /ccboard-install" + echo "" + echo "Or manually:" + echo " cargo install ccboard" + exit 1 +fi + +# Launch ccboard TUI +exec ccboard +``` diff --git a/examples/skills/ccboard/commands/install.md b/examples/skills/ccboard/commands/install.md new file mode 100644 index 0000000..d8aab26 --- /dev/null +++ b/examples/skills/ccboard/commands/install.md @@ -0,0 +1,110 @@ +--- +name: ccboard-install +description: Install or update ccboard +category: setup +--- + +# Install ccboard Command + +Install or update the ccboard binary via cargo. + +## What is ccboard? + +ccboard is a comprehensive TUI/Web dashboard for monitoring and managing Claude Code: + +- **8 Interactive Tabs**: Dashboard, Sessions, Config, Hooks, Agents, Costs, History, MCP +- **Real-time Monitoring**: File watcher for live updates +- **MCP Management**: Server status and configuration +- **Cost Analytics**: Token usage and pricing tracking +- **Session Explorer**: Browse and search conversation history +- **Dual Interface**: Terminal (TUI) and Web UI + +## Requirements + +- **Rust**: Version 1.70 or higher +- **Cargo**: Rust package manager (comes with Rust) + +If Rust is not installed: +```bash +curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh +``` + +## Usage + +```bash +# Install ccboard +/ccboard-install + +# Or manually +cargo install ccboard +``` + +## Installation Process + +1. Checks if cargo is installed +2. Detects existing ccboard installation +3. Prompts for update confirmation if already installed +4. Installs via `cargo install ccboard --force` +5. Verifies installation and shows version + +## After Installation + +Once installed, use these commands: + +- `/dashboard` - Launch TUI dashboard +- `/mcp-status` - Open MCP servers tab +- `/costs` - Open costs analysis +- `/sessions` - Browse sessions history +- `/ccboard-web` - Launch web interface + +Or run directly: +```bash +ccboard # Launch TUI +ccboard web # Launch web UI +ccboard --help # Show all options +``` + +## Troubleshooting + +### Cargo not found +```bash +# Install Rust and cargo +curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh + +# Reload shell +source $HOME/.cargo/env +``` + +### Installation fails +```bash +# Update Rust toolchain +rustup update + +# Try manual installation from source +git clone https://github.com/FlorianBruniaux/ccboard +cd ccboard +cargo install --path crates/ccboard +``` + +### Permission denied +```bash +# Ensure ~/.cargo/bin is in PATH +echo 'export PATH="$HOME/.cargo/bin:$PATH"' >> ~/.bashrc +source ~/.bashrc +``` + +## Implementation + +```bash +#!/bin/bash + +# Run installation script +exec "$(dirname "$0")/../scripts/install-ccboard.sh" +``` + +## Uninstallation + +To remove ccboard: +```bash +cargo uninstall ccboard +``` diff --git a/examples/skills/ccboard/commands/mcp-status.md b/examples/skills/ccboard/commands/mcp-status.md new file mode 100644 index 0000000..35bd93c --- /dev/null +++ b/examples/skills/ccboard/commands/mcp-status.md @@ -0,0 +1,68 @@ +--- +name: mcp-status +description: Open ccboard MCP servers tab +category: monitoring +--- + +# MCP Status Command + +Launch ccboard and jump directly to the MCP servers management tab. + +## Features + +- **Server List**: All configured MCP servers from `claude_desktop_config.json` +- **Status Detection**: Live status (● Running, ○ Stopped, ? Unknown) +- **Server Details**: Full command, arguments, environment variables +- **Quick Actions**: + - `e` : Edit MCP configuration + - `o` : Reveal config in finder + - `r` : Refresh server status + +## Usage + +```bash +# Open MCP tab directly +/mcp-status + +# Alternative: run with tab argument +ccboard --tab mcp +``` + +## MCP Tab Navigation + +- `h/j/k/l` or `←/→/↑/↓` : Navigate +- `Enter` : Focus detail pane +- `e` : Edit `~/.claude/claude_desktop_config.json` +- `o` : Reveal config file +- `r` : Refresh server status + +## Server Status + +- **● Green** : Server process is running +- **○ Red** : Server process is stopped +- **? Gray** : Status unknown (Windows or detection failed) + +## Requirements + +ccboard must be installed. Run `/ccboard-install` if needed. + +## Implementation + +```bash +#!/bin/bash + +# Check if ccboard is installed +if ! command -v ccboard &> /dev/null; then + echo "❌ ccboard is not installed" + echo "Run: /ccboard-install" + exit 1 +fi + +# Launch ccboard with MCP tab (tab index 7, accessible with '8' key) +# For now, launch and user presses '8' +# TODO: Add --tab flag to ccboard CLI in future version +exec ccboard +``` + +**Note**: Currently launches ccboard in dashboard view. Press `8` to access MCP tab. +Future version will support `ccboard --tab mcp` for direct access. diff --git a/examples/skills/ccboard/commands/sessions.md b/examples/skills/ccboard/commands/sessions.md new file mode 100644 index 0000000..4e2cc67 --- /dev/null +++ b/examples/skills/ccboard/commands/sessions.md @@ -0,0 +1,90 @@ +--- +name: sessions +description: Browse Claude Code sessions history +category: exploration +--- + +# Sessions Browser Command + +Launch ccboard and jump directly to the sessions exploration tab. + +## Features + +- **Project Tree**: Navigate 33+ projects with nested structure +- **Session List**: 1.2K+ sessions with metadata +- **Search**: Filter sessions by project, message, or model (press `/`) +- **Session Details**: + - Timestamps (start, end, duration) + - Token usage breakdown + - Models used + - First message preview +- **File Operations**: + - `e` : Open session JSONL in editor + - `o` : Reveal session file in finder + +## Usage + +```bash +# Open sessions tab directly +/sessions + +# Alternative: run with tab argument +ccboard --tab sessions +``` + +## Sessions Tab Navigation + +- `←/→` : Switch between project tree and session list +- `↑/↓` : Navigate items +- `Enter` : View session details +- `/` : Open search input +- `e` : Edit selected session JSONL file +- `o` : Reveal session file + +## Session Metadata + +Each session shows: +- **ID**: Unique session identifier +- **Started**: First message timestamp +- **Duration**: Total conversation time +- **Messages**: Message count +- **Tokens**: Total tokens used +- **Models**: AI models used (e.g., opus-4.5, sonnet-4.5) +- **Preview**: First user message (200 chars) + +## Search Examples + +``` +# Search by project name +/aristote + +# Search by model +/opus + +# Search by message content +/implement feature +``` + +## Requirements + +ccboard must be installed. Run `/ccboard-install` if needed. + +## Implementation + +```bash +#!/bin/bash + +# Check if ccboard is installed +if ! command -v ccboard &> /dev/null; then + echo "❌ ccboard is not installed" + echo "Run: /ccboard-install" + exit 1 +fi + +# Launch ccboard with Sessions tab (tab index 1, accessible with '2' key) +# For now, launch and user presses '2' +exec ccboard +``` + +**Note**: Currently launches ccboard in dashboard view. Press `2` to access Sessions tab. +Future version will support `ccboard --tab sessions` for direct access. diff --git a/examples/skills/ccboard/commands/web.md b/examples/skills/ccboard/commands/web.md new file mode 100644 index 0000000..e74a899 --- /dev/null +++ b/examples/skills/ccboard/commands/web.md @@ -0,0 +1,94 @@ +--- +name: ccboard-web +description: Launch ccboard web interface +category: monitoring +--- + +# Web Interface Command + +Launch the ccboard web UI for browser-based monitoring and visualization. + +## Features + +- **Web Dashboard**: Access ccboard from any browser +- **Live Updates**: Server-Sent Events (SSE) for real-time data +- **Responsive Design**: Works on desktop, tablet, mobile +- **Same Data**: Shares data layer with TUI (single binary) +- **Concurrent Access**: Multiple users can view simultaneously + +## Usage + +```bash +# Launch web UI on default port 3333 +/ccboard-web + +# Or specify custom port +ccboard web --port 8080 +``` + +## Access + +Once launched, open in your browser: +``` +http://localhost:3333 +``` + +## Modes + +ccboard supports 3 execution modes: + +1. **TUI only** (default): + ```bash + ccboard + ``` + +2. **Web only**: + ```bash + ccboard web --port 3333 + ``` + +3. **Both simultaneously**: + ```bash + ccboard both --port 3333 + ``` + Runs TUI in terminal + web server on port 3333 + +## Web UI Features + +- Dashboard with real-time stats +- Sessions browser with pagination +- Configuration viewer (read-only) +- Hooks, agents, costs visualization +- MCP server status +- History and search + +## Requirements + +ccboard must be installed. Run `/ccboard-install` if needed. + +## Implementation + +```bash +#!/bin/bash + +# Check if ccboard is installed +if ! command -v ccboard &> /dev/null; then + echo "❌ ccboard is not installed" + echo "Run: /ccboard-install" + exit 1 +fi + +# Default port +PORT="${1:-3333}" + +echo "🌐 Launching ccboard web interface..." +echo "Access at: http://localhost:$PORT" +echo "" +echo "Press Ctrl+C to stop" +echo "" + +# Launch web UI +exec ccboard web --port "$PORT" +``` + +**Note**: Web UI is currently in development. TUI is the primary interface with full feature set. diff --git a/examples/skills/ccboard/scripts/check-install.sh b/examples/skills/ccboard/scripts/check-install.sh new file mode 100755 index 0000000..ef08d03 --- /dev/null +++ b/examples/skills/ccboard/scripts/check-install.sh @@ -0,0 +1,38 @@ +#!/bin/bash +# Check if ccboard is installed and return status + +set -e + +# Colors for output +RED='\033[0;31m' +GREEN='\033[0;32m' +YELLOW='\033[1;33m' +NC='\033[0m' # No Color + +echo "🔍 Checking ccboard installation..." +echo "" + +# Check if ccboard is in PATH +if command -v ccboard &> /dev/null; then + CCBOARD_PATH=$(which ccboard) + CCBOARD_VERSION=$(ccboard --version 2>&1 | head -n1 || echo "unknown") + + echo -e "${GREEN}✅ ccboard is installed${NC}" + echo " Location: $CCBOARD_PATH" + echo " Version: $CCBOARD_VERSION" + echo "" + echo "Run with:" + echo " ccboard # Launch TUI" + echo " ccboard web # Launch web UI" + echo " ccboard --help # Show all options" + exit 0 +else + echo -e "${RED}❌ ccboard is not installed${NC}" + echo "" + echo "Install with:" + echo " cargo install ccboard" + echo "" + echo "Or use Claude Code command:" + echo " /ccboard-install" + exit 1 +fi diff --git a/examples/skills/ccboard/scripts/install-ccboard.sh b/examples/skills/ccboard/scripts/install-ccboard.sh new file mode 100755 index 0000000..e1a4714 --- /dev/null +++ b/examples/skills/ccboard/scripts/install-ccboard.sh @@ -0,0 +1,85 @@ +#!/bin/bash +# Install ccboard via cargo + +set -e + +# Colors for output +RED='\033[0;31m' +GREEN='\033[0;32m' +YELLOW='\033[1;33m' +CYAN='\033[0;36m' +NC='\033[0m' # No Color + +echo -e "${CYAN}📦 ccboard Installation${NC}" +echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" +echo "" + +# Check if cargo is installed +if ! command -v cargo &> /dev/null; then + echo -e "${RED}❌ Error: cargo is not installed${NC}" + echo "" + echo "Install Rust and cargo from: https://rustup.rs" + echo "" + echo "Run:" + echo " curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh" + exit 1 +fi + +echo -e "${GREEN}✅ cargo found${NC}" +echo "" + +# Check if ccboard is already installed +if command -v ccboard &> /dev/null; then + CURRENT_VERSION=$(ccboard --version 2>&1 | head -n1 || echo "unknown") + echo -e "${YELLOW}⚠️ ccboard is already installed: $CURRENT_VERSION${NC}" + echo "" + read -p "Do you want to update to the latest version? (y/N) " -n 1 -r + echo "" + if [[ ! $REPLY =~ ^[Yy]$ ]]; then + echo "Installation cancelled." + exit 0 + fi + echo "" +fi + +# Install ccboard +echo -e "${CYAN}Installing ccboard...${NC}" +echo "" + +if cargo install ccboard --force; then + echo "" + echo -e "${GREEN}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}" + echo -e "${GREEN}✅ ccboard installed successfully!${NC}" + echo -e "${GREEN}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}" + echo "" + + # Get installed version + INSTALLED_VERSION=$(ccboard --version 2>&1 | head -n1 || echo "unknown") + echo "Version: $INSTALLED_VERSION" + echo "Location: $(which ccboard)" + echo "" + + echo "Quick start:" + echo " ccboard # Launch TUI dashboard" + echo " ccboard web # Launch web interface" + echo " ccboard --help # Show all options" + echo "" + echo "Or use Claude Code commands:" + echo " /dashboard # Launch TUI" + echo " /mcp-status # Open MCP tab" + echo " /costs # Open costs analysis" +else + echo "" + echo -e "${RED}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}" + echo -e "${RED}❌ Installation failed${NC}" + echo -e "${RED}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}" + echo "" + echo "Troubleshooting:" + echo " 1. Ensure cargo is up to date: rustup update" + echo " 2. Check network connection" + echo " 3. Try manual installation from source:" + echo " git clone https://github.com/FlorianBruniaux/ccboard" + echo " cd ccboard" + echo " cargo install --path crates/ccboard" + exit 1 +fi diff --git a/examples/skills/guide-recap/SKILL.md b/examples/skills/guide-recap/SKILL.md new file mode 100644 index 0000000..85910a1 --- /dev/null +++ b/examples/skills/guide-recap/SKILL.md @@ -0,0 +1,217 @@ +--- +name: guide-recap +description: Transform CHANGELOG entries into social content (LinkedIn, Twitter/X, Newsletter, Slack) in FR + EN. Use after releases or weekly to generate ready-to-post content from guide updates. +argument-hint: " [--interactive] [--format=linkedin|twitter|newsletter|slack] [--lang=fr|en] [--save]" +--- + +# Guide Recap + +Generate social media content from CHANGELOG.md entries. Produces 8 outputs by default (4 formats x 2 languages). + +## When to Use + +- After running `/release` to create social announcements +- Weekly to summarize multiple releases +- Before posting on LinkedIn, Twitter/X, newsletter, or Slack + +## Usage + +``` +/guide-recap latest # Latest released version +/guide-recap v3.20.5 # Specific version +/guide-recap week # Current week (Monday to today) +/guide-recap week 2026-01-27 # Specific week (Monday to Sunday) +``` + +### Flags + +| Flag | Effect | Default | +|------|--------|---------| +| `--interactive` | Guide mode: choose angle, audience, highlight | Off (auto-draft) | +| `--format=X` | Single format: `linkedin`, `twitter`, `newsletter`, `slack` | All 4 formats | +| `--lang=X` | Single language: `fr`, `en` | Both FR + EN | +| `--save` | Save output to `claudedocs/social-posts/` | Display only | +| `--force` | Generate even if only maintenance entries | Skip low-score | + +## Workflow (7 Steps) + +### Step 1: Parse Input + +Parse `$ARGUMENTS` to determine mode: + +| Input | Mode | Target | +|-------|------|--------| +| `latest` | Single version | First `## [X.Y.Z]` after `[Unreleased]` | +| `vX.Y.Z` or `X.Y.Z` | Single version | Exact version match | +| `week` | Week range | Monday of current week -> today | +| `week YYYY-MM-DD` | Week range | That Monday -> following Sunday | + +If no argument or invalid argument, display usage and exit. + +### Step 2: Extract CHANGELOG Entries + +Read `CHANGELOG.md` from the project root. + +**Single version:** +1. Find line matching `## [{version}]` +2. Extract all content until next `## [` line +3. Parse `### Added`, `### Changed`, `### Fixed` sections + +**Week range:** +1. Collect all `## [X.Y.Z] - YYYY-MM-DD` entries where date falls in range +2. Parse all sections from all matching versions + +**Error: version not found** -> List last 5 versions, suggest `latest`. +**Error: week has no entries** -> Show date of last release, suggest that version. + +### Step 3: Categorize Entries + +For each top-level entry (first-level bullet under `###`), assign a category: + +| Category | Weight | Detection | +|----------|--------|-----------| +| `NEW_CONTENT` | 3 | New files, new sections, new diagrams, new quiz questions | +| `GROWTH_METRIC` | 2 | Line count growth, item count changes | +| `RESEARCH` | 1 | Resource evaluations, external source integrations | +| `FIX` | 1 | Under `### Fixed`, corrections | +| `MAINTENANCE` | 0 | README updates, badge syncs, landing syncs, count updates | + +See `references/changelog-parsing-rules.md` for detailed classification rules. + +### Step 4: Transform to User Value + +Apply mappings from `references/content-transformation.md`: + +- Technical language -> user benefit +- Extract concrete numbers +- Credit named sources +- Cluster related entries + +Validate against `references/tone-guidelines.md` DO/DON'T checklist. + +### Step 4b: Interactive Mode (--interactive only) + +If `--interactive` flag is set, insert between steps 4 and 5: + +1. Display candidate highlights with scores: + ``` + Highlights (by score): + [14] 4 new ASCII diagrams (16 -> 20) [NEW_CONTENT] + [ 9] 30 new quiz questions (227 -> 257) [NEW_CONTENT] + [ 6] Docker sandbox isolation guide [NEW_CONTENT] + [ 1] README updated [MAINTENANCE] + ``` + +2. Ask angle: + - Auto (highest scored = hook) + - User picks specific entry as hook + - Custom angle (user provides theme) + +3. Ask target audience: + - `devs` (technical depth) + - `tech-leads` (impact focus) + - `general` (accessible language) + - `all` (default, balanced) + +4. Ask primary highlight: + - Auto (top score) + - User selects from list + +5. Confirm selection and proceed to step 5. + +### Step 5: Score and Select + +Compute score for each entry: + +``` +score = (category_weight * 3) + + (has_number * 2) + + (named_source * 1) + + (new_file * 1) + + (min(impact_files, 3)) + + (breaking * 2) +``` + +Select top 3-4 entries by score. Highest score = hook line. + +**If all scores < 3**: Output "No social content recommended for this version. Use `--force` to generate anyway." and exit (unless `--force`). + +### Step 6: Generate Content + +For each requested format (default: all 4) and language (default: both): + +1. Read the corresponding template from `assets/` +2. Fill template fields using scored and transformed entries +3. Apply tone-guidelines.md quality checklist + +**Links:** + +| Format | Link Target | +|--------|-------------| +| LinkedIn | Landing site URL | +| Twitter | GitHub repo URL | +| Newsletter | Both (landing + GitHub) | +| Slack | GitHub repo URL | + +**URLs:** +- Landing: `https://florianbruniaux.github.io/claude-code-ultimate-guide-landing/` +- GitHub: `https://github.com/FlorianBruniaux/claude-code-ultimate-guide` + +### Step 7: Output + +Display each generated post in a fenced code block, labeled by format and language: + +``` +## LinkedIn (FR) + +```text +[content] +`` ` + +## LinkedIn (EN) + +```text +[content] +`` ` + +## Twitter/X (FR) + +```text +[content] +`` ` + +... +``` + +If `--save` flag: write all outputs to `claudedocs/social-posts/YYYY-MM-DD-vX.Y.Z.md` (for version) or `claudedocs/social-posts/YYYY-MM-DD-week.md` (for week). Create `claudedocs/social-posts/` directory if it doesn't exist. + +## Error Handling + +| Error | Response | +|-------|----------| +| No argument | Display usage block | +| Invalid argument | Display usage block with examples | +| Version not found | List 5 most recent versions, suggest `latest` | +| Week has no entries | Show date of last release, suggest version | +| All entries MAINTENANCE (score 0) | "No social content recommended. Use `--force` to override." | +| CHANGELOG.md not found | "CHANGELOG.md not found in project root." | + +## Reference Files + +- `references/tone-guidelines.md` - DO/DON'T rules, emoji budget, language register +- `references/changelog-parsing-rules.md` - CHANGELOG format, extraction, scoring algorithm +- `references/content-transformation.md` - Technical -> user value mappings (30+) +- `assets/linkedin-template.md` - ~1300 chars, hook + bullets + CTA + hashtags +- `assets/twitter-template.md` - 280 chars single or 2-3 tweet thread +- `assets/newsletter-template.md` - ~500 words, structured sections +- `assets/slack-template.md` - Compact, emoji-rich, Slack formatting +- `examples/version-output.md` - Full example output for v3.20.5 +- `examples/week-output.md` - Full example output for week 2026-01-27 + +## Tips + +- Run `/guide-recap latest` right after `/release` to prepare social posts +- Use `--interactive` the first few times to understand the scoring +- Use `--format=linkedin --lang=fr` when you only need one specific output +- `--save` outputs are gitignored via `claudedocs/` convention +- Review and personalize before posting (these are drafts, not final copy) diff --git a/examples/skills/guide-recap/assets/linkedin-template.md b/examples/skills/guide-recap/assets/linkedin-template.md new file mode 100644 index 0000000..7852ab6 --- /dev/null +++ b/examples/skills/guide-recap/assets/linkedin-template.md @@ -0,0 +1,101 @@ +# LinkedIn Template + +Target: ~1300 characters. Structure: hook + context + bullets + CTA + hashtags. + +## FR Template + +``` +{hook_line_fr} + +{context_line_fr} + +{bullet_1_fr} +{bullet_2_fr} +{bullet_3_fr} + +{cta_fr} + +#ClaudeCode #CodingWithAI #DeveloperTools +``` + +## EN Template + +``` +{hook_line_en} + +{context_line_en} + +{bullet_1_en} +{bullet_2_en} +{bullet_3_en} + +{cta_en} + +#ClaudeCode #CodingWithAI #DeveloperTools +``` + +## Field Rules + +### hook_line (1 line, max 150 chars) + +The highest-scored highlight, transformed to user value. May include 0-1 emoji. + +| Pattern | FR Example | EN Example | +|---------|------------|------------| +| Number-led | `30 nouvelles questions quiz pour tester vos connaissances Claude Code` | `30 new quiz questions to test your Claude Code knowledge` | +| Question | `Vous apprenez mieux en visuel ? 4 nouveaux diagrammes ASCII` | `Visual learner? 4 new ASCII diagrams just added` | +| Source-led | `Pat Cullen partage son workflow de code review multi-agent` | `Pat Cullen shares his multi-agent code review workflow` | + +### context_line (1-2 lines, max 200 chars) + +Version reference + what changed at a high level. + +``` +FR: "Guide v3.20.5 - mise a jour de la reference visuelle." +EN: "Guide v3.20.5 - visual reference update." +``` + +For week mode: +``` +FR: "N releases cette semaine dans le Claude Code Ultimate Guide." +EN: "N releases this week in the Claude Code Ultimate Guide." +``` + +### bullets (3 items, each max 200 chars) + +Top 3 highlights by score. Each starts with a relevant emoji (max 1 per bullet). + +``` +FR: +- [emoji] [Transformed highlight in vous-form] +- [emoji] [Transformed highlight in vous-form] +- [emoji] [Transformed highlight in vous-form] + +EN: +- [emoji] [Transformed highlight, direct "you" address] +- [emoji] [Transformed highlight, direct "you" address] +- [emoji] [Transformed highlight, direct "you" address] +``` + +### cta (1 line, max 150 chars) + +Soft value statement or genuine question. Links to landing site. + +``` +FR: "Guide complet disponible en open source : {landing_url}" +EN: "Full guide available open source: {landing_url}" +``` + +### hashtags (1 line, exactly 3) + +Fixed: `#ClaudeCode #CodingWithAI #DeveloperTools` + +Add 1 topic-specific tag if clearly relevant: `#CodeReview`, `#Security`, `#TDD` + +## Constraints + +- Total post: 1100-1500 characters +- Emoji budget: 3-4 total (0-1 hook, 1 per bullet, 0-1 CTA) +- No hype words (see tone-guidelines.md) +- FR: vouvoiement +- EN: American English diff --git a/examples/skills/guide-recap/assets/newsletter-template.md b/examples/skills/guide-recap/assets/newsletter-template.md new file mode 100644 index 0000000..3d8f53e --- /dev/null +++ b/examples/skills/guide-recap/assets/newsletter-template.md @@ -0,0 +1,124 @@ +# Newsletter Template + +Target: ~500 words. Structured sections with depth. + +## FR Template + +```markdown +# {title_fr} + +{intro_paragraph_fr} + +## Ce qui a change + +{highlights_section_fr} + +## En detail + +{detail_section_fr} + +## A retenir + +{takeaway_fr} + +--- + +[Guide complet]({landing_url}) | [GitHub]({github_url}) +``` + +## EN Template + +```markdown +# {title_en} + +{intro_paragraph_en} + +## What changed + +{highlights_section_en} + +## In detail + +{detail_section_en} + +## Key takeaway + +{takeaway_en} + +--- + +[Full guide]({landing_url}) | [GitHub]({github_url}) +``` + +## Field Rules + +### title (max 80 chars) + +Version or week framing, descriptive. + +``` +FR: "Guide v3.20.5 : Reference visuelle enrichie" +EN: "Guide v3.20.5: Enhanced Visual Reference" + +FR: "Semaine du 27 janvier : 4 releases, 9 patterns avances" +EN: "Week of January 27: 4 releases, 9 advanced patterns" +``` + +### intro_paragraph (2-3 sentences, max 150 words) + +What happened and why it matters. No hype. + +``` +FR: "La version 3.20.5 du Claude Code Ultimate Guide ajoute 4 nouveaux +diagrammes ASCII a la reference visuelle. Le guide contient maintenant +20 diagrammes couvrant TDD, securite et workflows d'apprentissage." + +EN: "Version 3.20.5 of the Claude Code Ultimate Guide adds 4 new ASCII +diagrams to the visual reference. The guide now contains 20 diagrams +covering TDD, security, and learning workflows." +``` + +### highlights_section (bullet list, 3-5 items) + +Top scored entries, transformed. Each bullet: 1-2 sentences max. + +``` +FR: +- **Cycle TDD Red-Green-Refactor** : Diagramme du flux iteratif test-code-refactor +- **Protocole UVAL** : Visualisation du framework Comprendre-Verifier-Appliquer-Apprendre +- **Defense securite 3 couches** : Prevention, detection, reponse avec guide d'adoption +- **Timeline d'exposition de secrets** : Actions d'urgence par fenetre temporelle (15min/1h/24h) + +EN: +- **TDD Red-Green-Refactor Cycle**: Diagram of the iterative test-code-refactor flow +- **UVAL Protocol Flow**: Visualization of the Understand-Verify-Apply-Learn framework +- **Security 3-Layer Defense**: Prevention, detection, response with adoption guide +- **Secret Exposure Timeline**: Emergency actions by time window (15min/1h/24h) +``` + +### detail_section (1-2 paragraphs, max 200 words) + +Expand on the most interesting highlight. Provide context, explain what the user gains. +Credit sources if applicable. + +### takeaway (1-2 sentences) + +Single actionable insight or summary. + +``` +FR: "Si vous apprenez mieux en visuel, les 20 diagrammes du guide couvrent +maintenant les workflows les plus frequents, de TDD a la gestion d'incidents." + +EN: "If you're a visual learner, the guide's 20 diagrams now cover the most +common workflows, from TDD to incident management." +``` + +## Constraints + +- Total: 400-600 words +- Emoji budget: 2-3 (section headers only) +- No hype words +- FR: vouvoiement +- EN: American English +- Both links (landing + GitHub) in footer +- Credit all named sources diff --git a/examples/skills/guide-recap/assets/slack-template.md b/examples/skills/guide-recap/assets/slack-template.md new file mode 100644 index 0000000..f41769a --- /dev/null +++ b/examples/skills/guide-recap/assets/slack-template.md @@ -0,0 +1,86 @@ +# Slack Template + +Compact, scannable, emoji-rich. Ready to paste. + +## FR Template + +``` +:newspaper: *{title_fr}* + +{highlights_fr} + +:link: {link} +``` + +## EN Template + +``` +:newspaper: *{title_en}* + +{highlights_en} + +:link: {link} +``` + +## Field Rules + +### title (max 60 chars) + +``` +FR: "Guide v3.20.5 - Reference visuelle" +EN: "Guide v3.20.5 - Visual reference" + +FR: "Recap semaine : 4 releases" +EN: "Week recap: 4 releases" +``` + +### highlights (3-5 lines) + +Each line: Slack emoji + short description. No bold in bullet text. + +``` +FR: +:art: 4 nouveaux diagrammes ASCII (TDD, UVAL, securite, incidents) +:brain: 30 nouvelles questions quiz (257 total) +:shield: Guide sandbox isolation Docker +:mag: 9 patterns avances identifies via claudelog.com + +EN: +:art: 4 new ASCII diagrams (TDD, UVAL, security, incidents) +:brain: 30 new quiz questions (257 total) +:shield: Docker sandbox isolation guide +:mag: 9 advanced patterns identified via claudelog.com +``` + +### link + +GitHub repo URL. + +## Slack Emoji Reference + +Use standard Slack emojis that render in all workspaces: + +| Emoji | Code | Use For | +|-------|------|---------| +| :newspaper: | `:newspaper:` | Title marker | +| :art: | `:art:` | Visual content, diagrams, UI | +| :brain: | `:brain:` | Quiz, learning, knowledge | +| :shield: | `:shield:` | Security content | +| :mag: | `:mag:` | Research, analysis, competitive intel | +| :wrench: | `:wrench:` | Tools, workflows, configuration | +| :books: | `:books:` | New guides, documentation | +| :chart_with_upwards_trend: | `:chart_with_upwards_trend:` | Growth metrics | +| :white_check_mark: | `:white_check_mark:` | Fixes, corrections | +| :link: | `:link:` | Link marker | +| :arrow_right: | `:arrow_right:` | Growth indicator (X -> Y) | + +## Constraints + +- Max 500 characters total +- Emoji budget: 4-6 (1 title + 1 per highlight + 1 link) +- No hype words +- FR: tutoiement +- EN: American English +- Single link (GitHub) +- No hashtags (not a Slack convention) +- Use Slack formatting: `*bold*`, `_italic_`, `:emoji:` codes diff --git a/examples/skills/guide-recap/assets/twitter-template.md b/examples/skills/guide-recap/assets/twitter-template.md new file mode 100644 index 0000000..d6bfb8f --- /dev/null +++ b/examples/skills/guide-recap/assets/twitter-template.md @@ -0,0 +1,145 @@ +# Twitter/X Template + +Two modes: single tweet (280 chars) or thread (2-3 tweets). + +## Single Tweet + +Use when: 1-2 highlights, simple version update. + +### FR Template + +``` +{hook_line_fr} + +{highlight_fr} + +{link} +``` + +### EN Template + +``` +{hook_line_en} + +{highlight_en} + +{link} +``` + +### Rules + +- Max 280 characters total (including link) +- Max 2 emojis +- Link to GitHub repo +- FR: tutoiement +- EN: direct address + +## Thread (2-3 tweets) + +Use when: 3+ highlights, rich version/week. + +### FR Template + +``` +Tweet 1/N: +{hook_line_fr} + +{context_fr} + +Thread [down_arrow] + +--- + +Tweet 2/N: +{highlight_1_fr} +{highlight_2_fr} + +--- + +Tweet 3/N (optional): +{highlight_3_fr} + +{cta_fr} +{link} +``` + +### EN Template + +``` +Tweet 1/N: +{hook_line_en} + +{context_en} + +Thread [down_arrow] + +--- + +Tweet 2/N: +{highlight_1_en} +{highlight_2_en} + +--- + +Tweet 3/N (optional): +{highlight_3_en} + +{cta_en} +{link} +``` + +## Field Rules + +### hook_line (max 100 chars) + +Shortest form of top highlight. Must fit in first tweet with context. + +| Pattern | FR | EN | +|---------|-----|-----| +| Number-led | `30 nouvelles questions quiz Claude Code` | `30 new Claude Code quiz questions` | +| Direct | `Nouveau guide : sandbox isolation Docker` | `New guide: Docker sandbox isolation` | + +### context (max 80 chars) + +``` +FR: "Guide v3.20.5 vient de sortir" +EN: "Guide v3.20.5 just dropped" +``` + +### highlights (max 120 chars each) + +Transformed entries, one per line. No bullet points (use line breaks). + +``` +FR: "4 diagrammes ASCII pour TDD, UVAL, securite" +EN: "4 ASCII diagrams for TDD, UVAL, security" +``` + +### cta (max 60 chars) + +``` +FR: "Tout est open source" +EN: "All open source" +``` + +### link + +GitHub repo URL. Counts toward 280 char limit (23 chars for t.co). + +## Decision: Single vs Thread + +| Condition | Format | +|-----------|--------| +| 1-2 highlights, all fit in 280 chars | Single tweet | +| 3+ highlights or rich content | Thread (2-3 tweets) | +| Week with multiple versions | Thread | +| Only maintenance changes | Single tweet (or skip) | + +## Constraints + +- Each tweet: max 280 characters +- Emoji budget: 2 total across thread +- No hype words +- FR: tutoiement +- EN: American English +- Thread max: 3 tweets (not 5+) diff --git a/examples/skills/guide-recap/examples/version-output.md b/examples/skills/guide-recap/examples/version-output.md new file mode 100644 index 0000000..5806be3 --- /dev/null +++ b/examples/skills/guide-recap/examples/version-output.md @@ -0,0 +1,145 @@ +# Example: /guide-recap v3.20.5 + +Input: `/guide-recap v3.20.5` + +## Scoring + +| Entry | Category | Score | Breakdown | +|-------|----------|-------|-----------| +| 4 new ASCII diagrams (16 -> 20) | NEW_CONTENT | 14 | (3*3)+(1*2)+(0*1)+(1*1)+(2)+(0*2) = 14 | +| README: Added Visual Reference to table | MAINTENANCE | 1 | (0*3)+(0*2)+(0*1)+(0*1)+(1)+(0*2) = 1 | + +Selected highlights: #1 only (1 entry above threshold). +Hook line: "4 new ASCII diagrams (16 -> 20)" + +## Output + +### LinkedIn (FR) + +```text +4 nouveaux diagrammes ASCII dans le guide Claude Code (16 -> 20 au total) + +Guide v3.20.5 - La reference visuelle s'enrichit. + +- Cycle TDD Red-Green-Refactor : le flux iteratif test-code-refactor en un coup d'oeil +- Protocole UVAL : le framework Comprendre-Verifier-Appliquer-Apprendre visualise +- Defense securite 3 couches : prevention, detection, reponse avec parcours d'adoption par taille d'equipe +- Timeline d'exposition de secrets : actions d'urgence par fenetre (15min/1h/24h) + +Guide complet disponible en open source : https://florianbruniaux.github.io/claude-code-ultimate-guide-landing/ + +#ClaudeCode #CodingWithAI #DeveloperTools +``` + +### LinkedIn (EN) + +```text +4 new ASCII diagrams in the Claude Code guide (16 -> 20 total) + +Guide v3.20.5 - Visual reference update. + +- TDD Red-Green-Refactor Cycle: the iterative test-code-refactor flow at a glance +- UVAL Protocol Flow: the Understand-Verify-Apply-Learn framework visualized +- Security 3-Layer Defense: prevention, detection, response with adoption path by team size +- Secret Exposure Timeline: emergency actions by time window (15min/1h/24h) + +Full guide available open source: https://florianbruniaux.github.io/claude-code-ultimate-guide-landing/ + +#ClaudeCode #CodingWithAI #DeveloperTools +``` + +### Twitter/X (FR) + +```text +4 nouveaux diagrammes ASCII dans le guide Claude Code + +TDD, UVAL, securite 3 couches, timeline incidents - reference visuelle v3.20.5 + +https://github.com/FlorianBruniaux/claude-code-ultimate-guide +``` + +### Twitter/X (EN) + +```text +4 new ASCII diagrams added to the Claude Code guide + +TDD, UVAL, 3-layer security, incident timeline - visual reference v3.20.5 + +https://github.com/FlorianBruniaux/claude-code-ultimate-guide +``` + +### Newsletter (FR) + +```markdown +# Guide v3.20.5 : Reference visuelle enrichie + +La version 3.20.5 du Claude Code Ultimate Guide ajoute 4 nouveaux diagrammes ASCII a la reference visuelle. Le guide contient maintenant 20 diagrammes couvrant TDD, securite et workflows d'apprentissage. + +## Ce qui a change + +- **Cycle TDD Red-Green-Refactor** : Diagramme du flux iteratif test-code-refactor, montrant la boucle cyclique entre ecriture du test, code minimal et refactoring +- **Protocole UVAL** : Visualisation du framework Comprendre-Verifier-Appliquer-Apprendre avec les chemins de retour en cas d'echec +- **Defense securite 3 couches** : Vue d'ensemble Prevention/Detection/Reponse avec parcours d'adoption selon la taille de l'equipe +- **Timeline d'exposition de secrets** : Actions d'urgence par fenetre temporelle (15min/1h/24h) avec guide de severite + +## En detail + +Les 4 nouveaux diagrammes (#17-#20) couvrent des workflows que le guide documentait deja en texte, mais qui beneficient d'une representation visuelle pour une comprehension rapide. Le diagramme TDD est le plus demande : il montre la boucle Red-Green-Refactor comme un cycle continu, pas comme des etapes lineaires. Le diagramme UVAL est le premier a montrer les chemins de retour en cas d'echec a chaque etape du framework d'apprentissage. + +## A retenir + +Si vous apprenez mieux en visuel, les 20 diagrammes du guide couvrent maintenant les workflows les plus frequents, de TDD a la gestion d'incidents securite. + +--- + +[Guide complet](https://florianbruniaux.github.io/claude-code-ultimate-guide-landing/) | [GitHub](https://github.com/FlorianBruniaux/claude-code-ultimate-guide) +``` + +### Newsletter (EN) + +```markdown +# Guide v3.20.5: Enhanced Visual Reference + +Version 3.20.5 of the Claude Code Ultimate Guide adds 4 new ASCII diagrams to the visual reference. The guide now contains 20 diagrams covering TDD, security, and learning workflows. + +## What changed + +- **TDD Red-Green-Refactor Cycle**: Diagram of the iterative test-code-refactor flow, showing the cyclic loop between writing tests, minimal code, and refactoring +- **UVAL Protocol Flow**: Visualization of the Understand-Verify-Apply-Learn framework with failure backtrack paths +- **Security 3-Layer Defense**: Prevention/Detection/Response overview with adoption path by team size +- **Secret Exposure Timeline**: Emergency actions by time window (15min/1h/24h) with severity guide + +## In detail + +The 4 new diagrams (#17-#20) cover workflows the guide already documented in text, but that benefit from visual representation for quick comprehension. The TDD diagram is the most requested: it shows the Red-Green-Refactor loop as a continuous cycle, not linear steps. The UVAL diagram is the first to show failure backtrack paths at each step of the learning framework. + +## Key takeaway + +If you're a visual learner, the guide's 20 diagrams now cover the most common workflows, from TDD to security incident management. + +--- + +[Full guide](https://florianbruniaux.github.io/claude-code-ultimate-guide-landing/) | [GitHub](https://github.com/FlorianBruniaux/claude-code-ultimate-guide) +``` + +### Slack (FR) + +```text +:newspaper: *Guide v3.20.5 - Reference visuelle* + +:art: 4 nouveaux diagrammes ASCII (TDD, UVAL, securite, incidents) +:arrow_right: 16 -> 20 diagrammes au total + +:link: https://github.com/FlorianBruniaux/claude-code-ultimate-guide +``` + +### Slack (EN) + +```text +:newspaper: *Guide v3.20.5 - Visual reference* + +:art: 4 new ASCII diagrams (TDD, UVAL, security, incidents) +:arrow_right: 16 -> 20 diagrams total + +:link: https://github.com/FlorianBruniaux/claude-code-ultimate-guide +``` diff --git a/examples/skills/guide-recap/examples/week-output.md b/examples/skills/guide-recap/examples/week-output.md new file mode 100644 index 0000000..7d813f4 --- /dev/null +++ b/examples/skills/guide-recap/examples/week-output.md @@ -0,0 +1,203 @@ +# Example: /guide-recap week 2026-01-27 + +Input: `/guide-recap week 2026-01-27` + +Date range: 2026-01-27 (Monday) to 2026-02-02 (Sunday) + +## Versions in Range + +| Version | Date | Entries | +|---------|------|---------| +| 3.20.5 | 2026-01-31 | 2 entries | +| 3.20.4 | 2026-01-31 | 3 entries | +| 3.20.3 | 2026-01-31 | 5 entries | +| 3.20.2 | 2026-01-31 | 4 entries | +| 3.20.1 | 2026-01-30 | 2 entries | +| 3.20.0 | 2026-01-30 | 5 entries | + +**6 releases this week.** + +## Scoring (Top Entries) + +| Entry | Version | Category | Score | +|-------|---------|----------|-------| +| 9 Gaps from claudelog.com (9 new patterns) | 3.20.3 | NEW_CONTENT | 15 | +| 30 New Quiz Questions (227 -> 257) | 3.20.4 | NEW_CONTENT | 14 | +| 4 new ASCII diagrams (16 -> 20) | 3.20.5 | NEW_CONTENT | 14 | +| Multi-Agent PR Review (Pat Cullen) | 3.20.0 | NEW_CONTENT | 13 | +| Docker sandbox isolation | 3.20.2 | NEW_CONTENT | 11 | +| Contribution Metrics (Anthropic blog) | 3.20.2 | RESEARCH | 8 | + +Selected: top 4 (scores 15, 14, 14, 13). + +## Output + +### LinkedIn (FR) + +```text +6 releases cette semaine dans le Claude Code Ultimate Guide + +Semaine du 27 janvier : la plus grosse semaine de contenu depuis le lancement du guide. + +- 9 patterns avances identifies via claudelog.com : Permutation Frameworks, Split-Role Agents, Mechanic Stacking et 6 autres +- 30 nouvelles questions quiz (257 au total) couvrant 11 categories +- 4 nouveaux diagrammes ASCII pour TDD, UVAL, securite et incidents +- Workflow de code review multi-agent base sur le travail de Pat Cullen : 3 agents specialises, regles anti-hallucination, classification de severite + +Guide complet en open source : https://florianbruniaux.github.io/claude-code-ultimate-guide-landing/ + +#ClaudeCode #CodingWithAI #DeveloperTools +``` + +### LinkedIn (EN) + +```text +6 releases this week in the Claude Code Ultimate Guide + +Week of January 27: the biggest content week since the guide launched. + +- 9 advanced patterns identified via claudelog.com: Permutation Frameworks, Split-Role Agents, Mechanic Stacking and 6 more +- 30 new quiz questions (257 total) across 11 categories +- 4 new ASCII diagrams for TDD, UVAL, security, and incidents +- Multi-agent code review workflow based on Pat Cullen's work: 3 specialized agents, anti-hallucination rules, severity classification + +Full guide available open source: https://florianbruniaux.github.io/claude-code-ultimate-guide-landing/ + +#ClaudeCode #CodingWithAI #DeveloperTools +``` + +### Twitter/X (FR) + +```text +Tweet 1/3: +6 releases cette semaine dans le Claude Code Ultimate Guide + +La plus grosse semaine de contenu du guide + +--- + +Tweet 2/3: +9 patterns avances (Permutation Frameworks, Split-Role Agents...) +30 nouvelles questions quiz (257 total) +4 diagrammes ASCII (TDD, UVAL, securite) + +--- + +Tweet 3/3: +Code review multi-agent (Pat Cullen) +Sandbox isolation Docker +Metriques Anthropic : +67% PRs/jour + +https://github.com/FlorianBruniaux/claude-code-ultimate-guide +``` + +### Twitter/X (EN) + +```text +Tweet 1/3: +6 releases this week in the Claude Code Ultimate Guide + +Biggest content week since the guide launched + +--- + +Tweet 2/3: +9 advanced patterns (Permutation Frameworks, Split-Role Agents...) +30 new quiz questions (257 total) +4 ASCII diagrams (TDD, UVAL, security) + +--- + +Tweet 3/3: +Multi-agent code review (Pat Cullen) +Docker sandbox isolation +Anthropic metrics: +67% PRs/day + +https://github.com/FlorianBruniaux/claude-code-ultimate-guide +``` + +### Newsletter (FR) + +```markdown +# Semaine du 27 janvier : 6 releases, 9 patterns avances + +La semaine du 27 janvier a ete la plus dense du Claude Code Ultimate Guide avec 6 releases. Voici les changements les plus significatifs. + +## Ce qui a change + +- **9 patterns avances (claudelog.com)** : Analyse systematique de 313 pages communautaires. Nouveaux patterns : Permutation Frameworks, Split-Role Agents, Mechanic Stacking, Rev the Engine, Task Lists as Diagnostic, "You Are the Main Thread" +- **30 nouvelles questions quiz** : 257 questions au total, couvrant 11 categories dont Advanced Patterns (+8), MCP Servers (+3), Architecture (+3) +- **4 diagrammes ASCII** : TDD Red-Green-Refactor, UVAL Protocol, Defense securite 3 couches, Timeline d'exposition de secrets +- **Code review multi-agent** : Workflow de production de Pat Cullen avec 3 agents specialises (Consistency, SOLID, Defensive Code), regles anti-hallucination et boucle de convergence + +## En detail + +Le travail de veille contre claudelog.com a permis d'identifier 9 gaps dans le guide. Le pattern Permutation Frameworks est le plus interessant : il propose une approche systematique pour tester des variations d'architecture (REST vs GraphQL vs tRPC) en utilisant CLAUDE.md comme driver. Mechanic Stacking combine 5 couches d'intelligence (Plan Mode, Extended Thinking, Rev, Split-Role, Permutation) avec une matrice de decision selon l'impact. + +Le workflow de code review de Pat Cullen apporte des safeguards anti-hallucination concrets : verification des patterns avec Grep/Glob avant toute recommandation, regle d'occurrence (>10 = etabli, 3-10 = emergent, <3 = non etabli), et lecture du contexte complet. + +## A retenir + +Cette semaine marque un tournant vers les patterns avances et la qualite de code. Si vous utilisez Claude Code pour des reviews, le workflow multi-agent merite d'etre explore. + +--- + +[Guide complet](https://florianbruniaux.github.io/claude-code-ultimate-guide-landing/) | [GitHub](https://github.com/FlorianBruniaux/claude-code-ultimate-guide) +``` + +### Newsletter (EN) + +```markdown +# Week of January 27: 6 Releases, 9 Advanced Patterns + +The week of January 27 was the densest week for the Claude Code Ultimate Guide with 6 releases. Here are the most significant changes. + +## What changed + +- **9 advanced patterns (claudelog.com)**: Systematic analysis of 313 community pages. New patterns: Permutation Frameworks, Split-Role Agents, Mechanic Stacking, Rev the Engine, Task Lists as Diagnostic, "You Are the Main Thread" +- **30 new quiz questions**: 257 total, covering 11 categories including Advanced Patterns (+8), MCP Servers (+3), Architecture (+3) +- **4 ASCII diagrams**: TDD Red-Green-Refactor, UVAL Protocol, Security 3-Layer Defense, Secret Exposure Timeline +- **Multi-agent code review**: Pat Cullen's production workflow with 3 specialized agents (Consistency, SOLID, Defensive Code), anti-hallucination rules, and convergence loop + +## In detail + +The competitive analysis against claudelog.com identified 9 gaps in the guide. The Permutation Frameworks pattern is the most interesting: it proposes a systematic approach to testing architecture variations (REST vs GraphQL vs tRPC) using CLAUDE.md as a driver. Mechanic Stacking combines 5 intelligence layers (Plan Mode, Extended Thinking, Rev, Split-Role, Permutation) with a decision matrix based on impact level. + +Pat Cullen's code review workflow brings concrete anti-hallucination safeguards: pattern verification with Grep/Glob before any recommendation, occurrence rule (>10 = established, 3-10 = emerging, <3 = not established), and full file context reading. + +## Key takeaway + +This week marks a shift toward advanced patterns and code quality. If you use Claude Code for reviews, the multi-agent workflow is worth exploring. + +--- + +[Full guide](https://florianbruniaux.github.io/claude-code-ultimate-guide-landing/) | [GitHub](https://github.com/FlorianBruniaux/claude-code-ultimate-guide) +``` + +### Slack (FR) + +```text +:newspaper: *Recap semaine : 6 releases (27 jan - 31 jan)* + +:mag: 9 patterns avances via claudelog.com (Permutation Frameworks, Split-Role Agents...) +:brain: 30 nouvelles questions quiz (257 total) +:art: 4 diagrammes ASCII (TDD, UVAL, securite, incidents) +:wrench: Code review multi-agent (Pat Cullen) : 3 agents, anti-hallucination, convergence +:shield: Guide sandbox isolation Docker + +:link: https://github.com/FlorianBruniaux/claude-code-ultimate-guide +``` + +### Slack (EN) + +```text +:newspaper: *Week recap: 6 releases (Jan 27 - Jan 31)* + +:mag: 9 advanced patterns via claudelog.com (Permutation Frameworks, Split-Role Agents...) +:brain: 30 new quiz questions (257 total) +:art: 4 ASCII diagrams (TDD, UVAL, security, incidents) +:wrench: Multi-agent code review (Pat Cullen): 3 agents, anti-hallucination, convergence +:shield: Docker sandbox isolation guide + +:link: https://github.com/FlorianBruniaux/claude-code-ultimate-guide +``` diff --git a/examples/skills/guide-recap/references/changelog-parsing-rules.md b/examples/skills/guide-recap/references/changelog-parsing-rules.md new file mode 100644 index 0000000..91932bc --- /dev/null +++ b/examples/skills/guide-recap/references/changelog-parsing-rules.md @@ -0,0 +1,142 @@ +# CHANGELOG Parsing Rules + +How to extract and categorize entries from `CHANGELOG.md` for social content generation. + +## CHANGELOG Format + +The project follows [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). + +``` +## [Unreleased] + +## [X.Y.Z] - YYYY-MM-DD + +### Added +- **Title**: Description + - Sub-detail 1 + - Sub-detail 2 + +### Changed +- **Title**: Description + +### Fixed +- **Title**: Description + +--- +(horizontal rule separates grouped entries within same version) +``` + +## Extraction Methods + +### By Version (`/guide-recap v3.20.5`) + +1. Read CHANGELOG.md +2. Find line matching `## [3.20.5]` +3. Extract everything until next `## [` line +4. Parse all `### Added/Changed/Fixed` sections + +### Latest (`/guide-recap latest`) + +1. Read CHANGELOG.md +2. Skip `## [Unreleased]` +3. Extract first `## [X.Y.Z]` block (= latest released version) + +### By Week (`/guide-recap week` or `/guide-recap week 2026-01-27`) + +1. Determine date range: + - `week` with no date: Monday of current week -> today + - `week YYYY-MM-DD`: That Monday -> following Sunday +2. Read CHANGELOG.md +3. Collect all `## [X.Y.Z] - YYYY-MM-DD` entries where date falls in range +4. Aggregate all entries across versions + +## Entry Structure + +Each top-level bullet under `### Added/Changed/Fixed` is one **entry**. Parse: + +| Field | Source | Example | +|-------|--------|---------| +| `title` | Bold text after `- **` | `Visual Reference` | +| `description` | Text after `:` or `--` on same line | `4 new high-value ASCII diagrams (16 -> 20 total)` | +| `sub_items` | Indented bullets below | List of detail lines | +| `section` | Parent `###` header | `Added`, `Changed`, `Fixed` | +| `files` | Filenames/paths in description | `guide/ultimate-guide.md`, `machine-readable/reference.yaml` | +| `source` | URLs or named references | `Pat Cullen's Final Review Gist` | +| `metrics` | Numbers in description | `227 -> 257`, `+522 lines`, `4 new` | +| `score` | Evaluation score if present | `Score: 4/5` | + +## Category Classification + +Each entry gets exactly one category with a weight: + +| Category | Weight | Pattern | +|----------|--------|---------| +| `NEW_CONTENT` | 3 | New guide sections, new files, new diagrams, new quiz questions | +| `GROWTH_METRIC` | 2 | Line count increases, template counts, quiz count changes | +| `RESEARCH` | 1 | Resource evaluations, external source integrations | +| `FIX` | 1 | Bug fixes, corrections, accuracy improvements | +| `MAINTENANCE` | 0 | README updates, badge updates, count syncs, landing syncs | + +### Classification Rules + +1. If entry creates a new `.md` file or new section -> `NEW_CONTENT` +2. If entry contains `-> ` with numbers (growth) -> `GROWTH_METRIC` +3. If entry references external source with evaluation score -> `RESEARCH` +4. If entry is under `### Fixed` -> `FIX` +5. If entry only updates counts, badges, or sync -> `MAINTENANCE` +6. If entry has sub-items with substantial content -> upgrade one level +7. When ambiguous, prefer higher-weight category + +### Examples + +``` +"4 new ASCII diagrams (16 -> 20)" -> NEW_CONTENT (new diagrams) +"30 New Quiz Questions (227 -> 257)" -> NEW_CONTENT (new questions) +"Quiz badge updated (227 -> 257)" -> MAINTENANCE (badge sync) +"Guide line count: 15,771 -> 16,293" -> GROWTH_METRIC (growth) +"Score: 4/5 - Docker Sandboxes" -> RESEARCH (evaluation) +"Fixed 14 -> 15 categories in landing" -> FIX (correction) +"README.md: Added Visual Reference to table" -> MAINTENANCE (nav update) +``` + +## Scoring Algorithm + +For each entry, compute: + +``` +score = (category_weight * 3) + + (has_number * 2) + + (named_source * 1) + + (new_file * 1) + + (min(impact_files, 3)) + + (breaking * 2) +``` + +| Factor | Value | Detection | +|--------|-------|-----------| +| `category_weight` | 0-3 | From category table above | +| `has_number` | 0 or 1 | Entry contains numeric change (`N -> M`, `+N lines`, `N new`) | +| `named_source` | 0 or 1 | Entry credits a person or external source | +| `new_file` | 0 or 1 | Entry mentions creating a new file (`NEW`, new `.md`) | +| `impact_files` | 0-3 | Count of distinct files mentioned (capped at 3) | +| `breaking` | 0 or 1 | Entry is under `### Breaking` or mentions breaking change | + +### Score Interpretation + +| Score | Action | +|-------|--------| +| 10+ | Lead highlight (hook line) | +| 6-9 | Secondary highlight (bullet point) | +| 3-5 | Include if space allows | +| 0-2 | Skip (maintenance noise) | + +Select top 3-4 entries by score. If all scores < 3, flag as "no social content recommended." + +## Week Aggregation Rules + +When generating for a week with multiple versions: + +1. Score all entries across all versions in the date range +2. De-duplicate: if same topic appears in multiple versions, keep highest-scored one +3. Prefix week output with version count: `X releases this week` +4. Use date range in header, not individual version numbers diff --git a/examples/skills/guide-recap/references/content-transformation.md b/examples/skills/guide-recap/references/content-transformation.md new file mode 100644 index 0000000..b7d58a0 --- /dev/null +++ b/examples/skills/guide-recap/references/content-transformation.md @@ -0,0 +1,139 @@ +# Content Transformation + +Maps technical CHANGELOG language to user-facing social value. Apply tone-guidelines.md rules to all outputs. + +## Transformation Principle + +``` +Technical fact (CHANGELOG) -> User benefit (social post) +``` + +Never invent benefits. Every transformation must trace back to a concrete CHANGELOG line. + +## Mapping Table + +### New Content + +| Technical (CHANGELOG) | Social (User Value) | +|---|---| +| `N new ASCII diagrams (X -> Y total)` | `Visual learner? N new diagrams for [topics]` | +| `N New Quiz Questions (X -> Y total)` | `Test your Claude Code knowledge: N new questions covering [top categories]` | +| `New [guide-name].md (N lines)` | `New guide: [topic in plain language]` | +| `New section: [Section Name] (~N lines)` | `[What you can now learn/do]: [plain description]` | +| `New workflow: [name]` | `Step-by-step: how to [action] with Claude Code` | +| `Enhanced [command/agent] (+N lines)` | `[Command] now supports [new capability]` | +| `N new entries in reference.yaml` | Skip (internal indexing, no user value) | + +### Research & Sources + +| Technical (CHANGELOG) | Social (User Value) | +|---|---| +| `Score: 5/5 - [Source]` | `Critical finding from [Author]: [key insight]` | +| `Score: 4/5 - [Source]` | `From [Author]'s research: [practical takeaway]` | +| `Score: 3/5 - [Source]` | `[Author] confirms: [relevant finding]` | +| `Source: [URL] ([Author], [Date])` | `Based on [Author]'s work` | +| `Competitive Analysis: N Gaps from [source]` | `N advanced patterns identified: [top 2-3 names]` | +| `Resource evaluation: [file]` | Skip (internal process, not user-facing) | +| `Fact-checked: N/N claims verified` | Can mention as credibility signal: `All N claims verified` | + +### Growth Metrics + +| Technical (CHANGELOG) | Social (User Value) | +|---|---| +| `Guide line count: X -> Y (+N lines)` | `+N lines of documentation added this [week/version]` | +| `X -> Y total [items]` | `Now Y [items] (was X)` | +| `+N lines: X -> Y` | `[Feature] expanded with N lines of [content type]` | + +### Fixes & Changes + +| Technical (CHANGELOG) | Social (User Value) | +|---|---| +| `Fixed [technical issue]` | `Corrected: [what users see fixed]` | +| `[File]: Updated [field] (X -> Y)` | Skip unless user-visible change | +| `Landing synced` | Skip (infrastructure) | +| `Badge updated` | Skip (infrastructure) | + +### Maintenance (Usually Skip) + +| Technical (CHANGELOG) | Social Value | +|---|---| +| `README.md: Added [X] to table` | Skip | +| `Updated counts in [files]` | Skip | +| `Sync: [description]` | Skip | +| `reference.yaml: +N entries` | Skip | +| `CLAUDE.md: [update]` | Skip | + +## Pattern Recognition + +### Numbers to Highlight + +When an entry contains numeric changes, extract and format: + +``` +"30 New Quiz Questions (227 -> 257)" +-> number: 30 +-> growth: "227 -> 257" +-> highlight: "30 new questions" or "now 257 questions" + +"4 new ASCII diagrams (16 -> 20 total)" +-> number: 4 +-> growth: "16 -> 20" +-> highlight: "4 new diagrams" or "now 20 diagrams" + +"+522 lines" +-> number: 522 +-> highlight: "+522 lines of new content" +``` + +### Named Sources to Credit + +Extract author names and give proper attribution: + +``` +"Pat Cullen's Final Review Gist" -> "From Pat Cullen's production workflow" +"Addy Osmani's 80% Problem" -> "Based on Addy Osmani's research" +"Shen & Tamkin RCT" -> "From Shen & Tamkin's study (Anthropic)" +"claudelog.com (InventorBlack)" -> "Identified via claudelog.com community" +"Jude Gao (Vercel)" -> "From Vercel's benchmarks (Jude Gao)" +``` + +### Topic Clustering + +When multiple entries share a theme, cluster them: + +``` +Entries about code review: +- "Multi-Agent PR Review" +- "Enhanced /review-pr command" +- "Enhanced code-reviewer agent" +-> Cluster: "Code review overhaul: multi-agent workflow, anti-hallucination rules, severity classification" + +Entries about security: +- "Docker sandbox isolation" +- "Security 3-Layer Defense diagram" +- "Secret Exposure Timeline diagram" +-> Cluster: "Security focus: sandbox isolation, defense layers, incident response timeline" +``` + +## Version vs Week Framing + +### Single Version + +``` +FR: "Claude Code Ultimate Guide v3.20.5" +EN: "Claude Code Ultimate Guide v3.20.5" +``` + +### Week (Multiple Versions) + +``` +FR: "Cette semaine dans le guide (N releases)" +EN: "This week in the guide (N releases)" +``` + +### Week (Single Version) + +``` +FR: "Cette semaine : guide v3.20.5" +EN: "This week: guide v3.20.5" +``` diff --git a/examples/skills/guide-recap/references/tone-guidelines.md b/examples/skills/guide-recap/references/tone-guidelines.md new file mode 100644 index 0000000..60387fc --- /dev/null +++ b/examples/skills/guide-recap/references/tone-guidelines.md @@ -0,0 +1,77 @@ +# Tone Guidelines + +Rules for social content generated from CHANGELOG entries. Central principle: **engagement through value, not hype**. + +## DO / DON'T Checklist + +### DO + +- Use concrete numbers from the CHANGELOG (`227 -> 257`, `+522 lines`, `4 new diagrams`) +- State what the user can do now (`Test your knowledge`, `New visual guide for...`) +- Ask genuine questions (`Visual learner?`, `How do you review PRs?`) +- Credit named sources (`Based on Pat Cullen's workflow`, `From Addy Osmani's research`) +- Use precise action verbs (`added`, `integrated`, `documented`, `evaluated`) +- Reference specific patterns by name (`Permutation Frameworks`, `Split-Role Agents`) + +### DON'T + +- Use hype words: `game-changer`, `revolutionary`, `incredible`, `amazing`, `must-have` +- Use FOMO: `You're missing out`, `Don't fall behind`, `Everyone is using this` +- Use fake urgency: `Act now`, `Limited time`, `Before it's too late` +- Use clickbait hooks: `This ONE trick`, `You won't believe`, `Hidden feature` +- Invent metrics: `10x faster`, `saves hours`, `boosts productivity by 300%` +- Use more than 3-4 emojis per LinkedIn post, 2 per tweet +- Over-promise: `The only guide you'll ever need`, `Complete mastery` + +## Language Rules + +### French (FR) + +| Format | Register | Example | +|--------|----------|---------| +| LinkedIn | Vouvoiement | `Vous utilisez Claude Code au quotidien ?` | +| Newsletter | Vouvoiement | `Vous trouverez dans cette version...` | +| Twitter/X | Tutoiement | `Tu connais les Permutation Frameworks ?` | +| Slack | Tutoiement | `Nouvelle version dispo, check ca` | + +### English (EN) + +- Direct address (`you`) in all formats +- American English spelling (`optimize`, `analyze`, not `optimise`, `analyse`) +- No British idioms or spellings + +## Emoji Budget + +| Format | Max Emojis | Placement | +|--------|-----------|-----------| +| LinkedIn | 3-4 | Hook line (0-1), bullets (1 each, max 3), CTA (0-1) | +| Twitter | 2 | Hook (1), key point (1) | +| Newsletter | 2-3 | Section headers only | +| Slack | 4-6 | Status markers, emphasis | + +Allowed emojis: `+`, `->`, technical symbols preferred over decorative ones. +Avoid: fire, rocket, explosion, 100, mind-blown (marketing cliches). + +## CTA Rules + +| Format | CTA Style | Link Target | +|--------|-----------|-------------| +| LinkedIn | Soft question or value statement | Landing site URL | +| Twitter | Short action or link | GitHub repo | +| Newsletter | Explicit link with context | Landing site URL | +| Slack | Direct link | GitHub repo | + +No `Click here`, `Check this out`, `Link in bio` patterns. + +## Quality Checklist (Pre-Output) + +Before outputting any social content, verify: + +1. [ ] Every number comes from the actual CHANGELOG entry +2. [ ] No hype words (grep against DON'T list) +3. [ ] Emoji count within budget +4. [ ] FR register matches format (vous/tu) +5. [ ] EN uses American spelling +6. [ ] CTA links to correct target +7. [ ] Named sources credited when used +8. [ ] No invented metrics or percentages diff --git a/examples/skills/landing-page-generator/SKILL.md b/examples/skills/landing-page-generator/SKILL.md new file mode 100644 index 0000000..8a8d0f2 --- /dev/null +++ b/examples/skills/landing-page-generator/SKILL.md @@ -0,0 +1,238 @@ +--- +name: landing-page-generator +description: Generate complete landing pages from repositories. Analyzes README, features, and structure to create static sites following established patterns. Use for new project landings, open-source showcases, documentation portals. +tags: [landing-page, static-site, github-pages, marketing] +--- + +# Landing Page Generator + +Generate a complete, deploy-ready landing page from any repository by analyzing its documentation and structure. + +## When to Use This Skill + +- Creating a landing page for a GitHub repository +- Generating static sites from existing documentation +- Standardizing landing pages across multiple projects +- Converting README content to marketing/showcase pages + +## What This Skill Does + +1. **Analyze Repository**: Read README.md, CHANGELOG.md, package.json/VERSION, docs/, assets/ +2. **Extract Content**: Identify title, tagline, features, installation, screenshots +3. **Map to Sections**: Hero, Features, Install, FAQ, Footer (+ optional: Risk Banner, Pricing) +4. **Generate Landing**: Create complete static site (HTML + CSS + JS) +5. **Deploy-Ready Output**: Include GitHub Actions workflow for GitHub Pages + +## How to Use + +### Basic Usage + +``` +/landing-page-generator from ~/path/to/repo +``` + +### With Options + +``` +/landing-page-generator from ~/path/to/repo --risk-banner --pricing-table +``` + +### Available Options + +| Option | Description | Default | +|--------|-------------|---------| +| `--risk-banner` | Add prominent warning/disclaimer banner above fold | false | +| `--pricing-table` | Include pricing comparison section | false | +| `--screenshots ` | Path to screenshots folder | ./assets/ | +| `--theme [dark\|light]` | Color theme variant | dark | +| `--search` | Enable Cmd+K search | true | +| `--output ` | Output directory | ./[repo-name]-landing/ | + +## Workflow + +### Step 1: Repository Analysis + +Read and analyze these files from the source repo: + +``` +README.md → Primary content source (title, tagline, features, install) +CHANGELOG.md → Version info, recent changes +package.json → Version number, dependencies, metadata +VERSION → Alternative version source +docs/ → Additional documentation pages +assets/ → Screenshots, images +LICENSE → License type for badge +``` + +### Step 2: Content Extraction Map + +| Source | Target Section | Extraction Method | +|--------|---------------|-------------------| +| README title/badges | Hero | First H1 + shield.io badge lines | +| README TL;DR | Hero tagline | First paragraph or blockquote after title | +| README features | Features grid | H2/H3 sections with bullet lists | +| README install | Quick Start | Code blocks with shell commands | +| README usage | Examples | Code blocks with examples | +| README FAQ | FAQ | Details/summary or H3+P patterns | +| CHANGELOG | What's New | Latest 1-3 releases | +| assets/*.png | Screenshots | Gallery section | + +### Step 3: Section Generation + +Generate these sections in order: + +1. **Header** (sticky) + - Logo/project name + - Nav links: Features, Install, FAQ + - Actions: Search (Cmd+K), GitHub Star, primary CTA + +2. **Risk Banner** (if `--risk-banner`) + - Orange/warning style above fold + - Clear, visible disclaimer text + - Link to detailed disclosure section + +3. **Hero Section** + - Title from README H1 + - Tagline from TL;DR/first paragraph + - Stats badges (version, license, platform) + - CTAs: "Quick Start" (primary), "View on GitHub" (secondary) + +4. **Architecture/Overview** (if diagram in README) + - ASCII diagram converted to styled block + - Or overview cards + +5. **Features Grid** + - 4-6 feature cards from README features + - Icon + title + description pattern + +6. **Pricing Table** (if `--pricing-table`) + - Plans comparison table + - Multipliers/usage table if present + +7. **Screenshots Gallery** (if assets exist) + - Tab-based or carousel gallery + - Captions from alt text + +8. **Quick Start Section** + - One-liner install command (featured code block) + - Setup steps + - First usage example + +9. **Risk Disclosure** (if `--risk-banner`) + - Full disclaimer section + - ToS considerations + - Recommendations + +10. **FAQ Section** + - Generated from README FAQ or common questions + - Collapsible details pattern + +11. **Related Projects** (if links in README) + - Cards linking to dependencies/related repos + +12. **Footer** + - Quick links + - License badge + - Version info + - Author/repo links + +### Step 4: Output Structure + +``` +[project-name]-landing/ +├── index.html # Main landing page +├── styles.css # Complete stylesheet +├── search.js # Cmd+K search functionality +├── search-data.js # Search index (FAQ, features) +├── favicon.svg # Generated or copied +├── robots.txt # SEO +├── CLAUDE.md # Project instructions +├── README.md # Landing repo documentation +├── assets/ # Copied screenshots +│ └── [copied from source] +└── .github/ + └── workflows/ + └── static.yml # GitHub Pages deployment +``` + +## Tech Stack + +- **No build step**: Pure HTML + CSS + JS +- **Search**: MiniSearch lazy-loaded from CDN with fallback +- **Deployment**: GitHub Pages via Actions +- **Styling**: CSS custom properties, responsive, dark theme default +- **Accessibility**: Skip links, ARIA labels, keyboard navigation + +## CSS Patterns (from established landings) + +### Component Classes + +```css +/* Buttons */ +.btn, .btn-primary, .btn-secondary, .btn-github-star, .btn-outline + +/* Cards */ +.feature-card, .comparison-card, .path-card + +/* Layout */ +.container, .features-grid, .hero, .section + +/* Utilities */ +.visually-hidden, .skip-link +``` + +### CSS Variables + +```css +:root { + --color-bg: #0d1117; + --color-surface: #161b22; + --color-border: #30363d; + --color-text: #c9d1d9; + --color-text-muted: #8b949e; + --color-primary: #58a6ff; + --color-success: #3fb950; + --color-warning: #d29922; + --color-danger: #f85149; + --space-xs: 0.25rem; + --space-sm: 0.5rem; + --space-md: 1rem; + --space-lg: 1.5rem; + --space-xl: 2rem; + --radius: 6px; +} +``` + +## Example + +**User**: `/landing-page-generator from ~/Sites/perso/cc-copilot-bridge --risk-banner --pricing-table` + +**Output**: + +Creates `/Users/florianbruniaux/Sites/perso/cc-copilot-bridge-landing/` with: +- Complete landing page showcasing the multi-provider router +- Prominent ToS risk banner (orange, above fold) +- Provider cards (Anthropic, Copilot, Ollama) +- Pricing tables from README +- Screenshots gallery +- GitHub Pages deployment ready + +## Tips + +- Always include `--risk-banner` for projects with legal/ToS considerations +- Screenshots significantly improve landing quality - ensure assets/ is populated +- The skill preserves README language (English/French) +- Review generated FAQ - may need customization +- Test responsive design after generation + +## Related Use Cases + +- Open-source project showcases +- Documentation portals +- Product landing pages +- Tool/utility marketing sites + +## References + +See `references/landing-pattern.md` for detailed pattern documentation. +See `assets/` for reusable templates and snippets. \ No newline at end of file diff --git a/examples/skills/landing-page-generator/assets/search-base.js b/examples/skills/landing-page-generator/assets/search-base.js new file mode 100644 index 0000000..d0d92cd --- /dev/null +++ b/examples/skills/landing-page-generator/assets/search-base.js @@ -0,0 +1,401 @@ +/** + * Global Search - Cmd+K / Ctrl+K + * Lazy loads MiniSearch on first use + * Customize SEARCH_* arrays in search-data.js + */ + +(function() { + 'use strict'; + + let searchReady = false; + let miniSearch = null; + let searchIndex = []; + let selectedIndex = -1; + let lastFocusedElement = null; + + // DOM elements (cached after init) + let modal, input, results, statusEl; + + /** + * Load script dynamically + */ + function loadScript(src) { + return new Promise((resolve, reject) => { + const script = document.createElement('script'); + script.src = src; + script.onload = resolve; + script.onerror = reject; + document.head.appendChild(script); + }); + } + + /** + * Build search index from available sources + * Customize by adding your own window.SEARCH_* arrays + */ + function buildIndex() { + const items = []; + + // Features + if (window.SEARCH_FEATURES) { + items.push(...window.SEARCH_FEATURES); + } + + // FAQ + if (window.SEARCH_FAQ) { + items.push(...window.SEARCH_FAQ); + } + + // Docs + if (window.SEARCH_DOCS) { + items.push(...window.SEARCH_DOCS); + } + + // Custom items + if (window.SEARCH_CUSTOM) { + items.push(...window.SEARCH_CUSTOM); + } + + return items; + } + + /** + * Initialize search (called on first Cmd+K) + */ + async function initSearch() { + if (searchReady) return true; + + // Build index + searchIndex = buildIndex(); + + if (searchIndex.length === 0) { + console.warn('Search: No items to index'); + return false; + } + + // Try to load MiniSearch from CDN + if (!window.MiniSearch) { + try { + await loadScript('https://cdn.jsdelivr.net/npm/minisearch@7/dist/umd/index.min.js'); + } catch (e) { + console.warn('Search: MiniSearch CDN failed, using fallback'); + } + } + + // Initialize MiniSearch or fallback + if (window.MiniSearch) { + miniSearch = new MiniSearch({ + fields: ['title', 'content', 'category'], + storeFields: ['title', 'type', 'url', 'category'], + searchOptions: { + fuzzy: 0.2, + prefix: true, + boost: { title: 2 } + } + }); + miniSearch.addAll(searchIndex); + } else { + // Fallback: simple substring search + miniSearch = { + search: (query) => { + const q = query.toLowerCase(); + return searchIndex.filter(item => + item.title.toLowerCase().includes(q) || + item.content.toLowerCase().includes(q) || + (item.category && item.category.toLowerCase().includes(q)) + ).map(item => ({ ...item, score: 1 })); + } + }; + } + + searchReady = true; + return true; + } + + /** + * Perform search and return results + */ + function search(query) { + if (!searchReady || !query.trim()) return []; + + const searchResults = miniSearch.search(query); + + // Group by type and limit + const grouped = {}; + searchResults.forEach(r => { + const type = r.type || 'item'; + if (!grouped[type]) grouped[type] = []; + if (grouped[type].length < 5) { + grouped[type].push(r); + } + }); + + // Flatten all groups + return Object.values(grouped).flat(); + } + + /** + * Render search results + */ + function renderResults(items) { + if (!results) return; + + selectedIndex = -1; + + if (items.length === 0) { + results.innerHTML = '
  • No results found
  • '; + announceResults(0); + return; + } + + const typeIcons = { + feature: '⚡', + faq: '❓', + doc: '📖', + install: '📦', + provider: '🔌', + default: '📄' + }; + + results.innerHTML = items.map((item, i) => ` +
  • + ${typeIcons[item.type] || typeIcons.default} ${item.type || 'item'} + ${escapeHtml(item.title)} + ${item.category ? `${item.category}` : ''} +
  • + `).join(''); + + // Add click handlers + results.querySelectorAll('.search-result-item').forEach((el, i) => { + el.addEventListener('click', () => navigateTo(items[i].url)); + }); + + announceResults(items.length); + } + + /** + * Announce results to screen readers + */ + function announceResults(count) { + if (statusEl) { + statusEl.textContent = count === 0 + ? 'No results found' + : `${count} result${count > 1 ? 's' : ''} found. Use arrow keys to navigate.`; + } + } + + /** + * Navigate to result URL + */ + function navigateTo(url) { + closeModal(); + window.location.href = url; + } + + /** + * Update selected item + */ + function updateSelection(newIndex) { + const items = results.querySelectorAll('.search-result-item'); + if (items.length === 0) return; + + // Wrap around + if (newIndex < 0) newIndex = items.length - 1; + if (newIndex >= items.length) newIndex = 0; + + // Update aria-selected + items.forEach((el, i) => { + el.setAttribute('aria-selected', i === newIndex ? 'true' : 'false'); + }); + + // Scroll into view + items[newIndex].scrollIntoView({ block: 'nearest' }); + + selectedIndex = newIndex; + } + + /** + * Handle keyboard navigation + */ + function handleKeydown(e) { + const items = results.querySelectorAll('.search-result-item'); + + switch (e.key) { + case 'ArrowDown': + e.preventDefault(); + updateSelection(selectedIndex + 1); + break; + + case 'ArrowUp': + e.preventDefault(); + updateSelection(selectedIndex - 1); + break; + + case 'Enter': + e.preventDefault(); + if (selectedIndex >= 0 && items[selectedIndex]) { + const url = items[selectedIndex].dataset.url; + if (url) navigateTo(url); + } + break; + + case 'Home': + e.preventDefault(); + updateSelection(0); + break; + + case 'End': + e.preventDefault(); + updateSelection(items.length - 1); + break; + + case 'Escape': + e.preventDefault(); + closeModal(); + break; + } + } + + /** + * Open search modal + */ + async function openModal() { + if (!modal) return; + + // Initialize search on first open + const ready = await initSearch(); + if (!ready) { + console.error('Search: Failed to initialize'); + return; + } + + // Save current focus + lastFocusedElement = document.activeElement; + + // Show modal + modal.hidden = false; + document.body.style.overflow = 'hidden'; + + // Focus input + setTimeout(() => { + input.value = ''; + input.focus(); + renderResults([]); + }, 10); + } + + /** + * Close search modal + */ + function closeModal() { + if (!modal) return; + + modal.hidden = true; + document.body.style.overflow = ''; + + // Restore focus + if (lastFocusedElement) { + lastFocusedElement.focus(); + } + } + + /** + * Escape HTML + */ + function escapeHtml(str) { + const div = document.createElement('div'); + div.textContent = str; + return div.innerHTML; + } + + /** + * Focus trap for accessibility + */ + function trapFocus(e) { + if (!modal || modal.hidden) return; + + const focusable = modal.querySelectorAll( + 'input, button, [tabindex]:not([tabindex="-1"])' + ); + const first = focusable[0]; + const last = focusable[focusable.length - 1]; + + if (e.shiftKey && document.activeElement === first) { + e.preventDefault(); + last.focus(); + } else if (!e.shiftKey && document.activeElement === last) { + e.preventDefault(); + first.focus(); + } + } + + /** + * Initialize when DOM is ready + */ + function init() { + // Get DOM elements + modal = document.getElementById('search-modal'); + input = document.getElementById('search-input'); + results = document.getElementById('search-results'); + statusEl = document.getElementById('search-status'); + + if (!modal || !input || !results) { + console.warn('Search: Modal elements not found'); + return; + } + + // Input handler with debounce + let debounceTimer; + input.addEventListener('input', (e) => { + clearTimeout(debounceTimer); + debounceTimer = setTimeout(() => { + const items = search(e.target.value); + renderResults(items); + }, 150); + }); + + // Keyboard navigation + input.addEventListener('keydown', handleKeydown); + + // Close on backdrop click + modal.querySelector('.search-modal-backdrop')?.addEventListener('click', closeModal); + + // Global keyboard shortcut: Cmd+K / Ctrl+K + document.addEventListener('keydown', (e) => { + if ((e.metaKey || e.ctrlKey) && e.key === 'k') { + e.preventDefault(); + if (modal.hidden) { + openModal(); + } else { + closeModal(); + } + } + }); + + // Focus trap + modal.addEventListener('keydown', (e) => { + if (e.key === 'Tab') { + trapFocus(e); + } + }); + + // Search trigger button + document.querySelectorAll('.search-trigger').forEach(btn => { + btn.addEventListener('click', (e) => { + e.preventDefault(); + openModal(); + }); + }); + } + + // Initialize on DOM ready + if (document.readyState === 'loading') { + document.addEventListener('DOMContentLoaded', init); + } else { + init(); + } +})(); diff --git a/examples/skills/landing-page-generator/assets/section-snippets/faq.html b/examples/skills/landing-page-generator/assets/section-snippets/faq.html new file mode 100644 index 0000000..a8b607b --- /dev/null +++ b/examples/skills/landing-page-generator/assets/section-snippets/faq.html @@ -0,0 +1,62 @@ + +
    +
    +

    Frequently Asked Questions

    + +
    + +
    + {{FAQ_1_QUESTION}} +
    +

    {{FAQ_1_ANSWER}}

    +
    +
    + + +
    + {{FAQ_2_QUESTION}} +
    +

    {{FAQ_2_ANSWER}}

    +
    +
    + + +
    + {{FAQ_3_QUESTION}} +
    +

    {{FAQ_3_ANSWER}}

    +
    +
    + + +
    + {{FAQ_4_QUESTION}} +
    +

    {{FAQ_4_ANSWER}}

    +
    +
    + + +
    + {{FAQ_5_QUESTION}} +
    +

    {{FAQ_5_ANSWER}}

    +
    +
    +
    +
    +
    + + diff --git a/examples/skills/landing-page-generator/assets/section-snippets/features-grid.html b/examples/skills/landing-page-generator/assets/section-snippets/features-grid.html new file mode 100644 index 0000000..80098a6 --- /dev/null +++ b/examples/skills/landing-page-generator/assets/section-snippets/features-grid.html @@ -0,0 +1,59 @@ + +
    +
    +

    Features

    + +
    + +
    +
    {{FEATURE_1_ICON}}
    +

    {{FEATURE_1_TITLE}}

    +

    {{FEATURE_1_DESC}}

    +
    + + +
    +
    {{FEATURE_2_ICON}}
    +

    {{FEATURE_2_TITLE}}

    +

    {{FEATURE_2_DESC}}

    +
    + + +
    +
    {{FEATURE_3_ICON}}
    +

    {{FEATURE_3_TITLE}}

    +

    {{FEATURE_3_DESC}}

    +
    + + +
    +
    {{FEATURE_4_ICON}}
    +

    {{FEATURE_4_TITLE}}

    +

    {{FEATURE_4_DESC}}

    +
    + + +
    +
    {{FEATURE_5_ICON}}
    +

    {{FEATURE_5_TITLE}}

    +

    {{FEATURE_5_DESC}}

    +
    + + +
    +
    {{FEATURE_6_ICON}}
    +

    {{FEATURE_6_TITLE}}

    +

    {{FEATURE_6_DESC}}

    +
    +
    +
    +
    + + diff --git a/examples/skills/landing-page-generator/assets/section-snippets/footer.html b/examples/skills/landing-page-generator/assets/section-snippets/footer.html new file mode 100644 index 0000000..559c41b --- /dev/null +++ b/examples/skills/landing-page-generator/assets/section-snippets/footer.html @@ -0,0 +1,40 @@ + + + + diff --git a/examples/skills/landing-page-generator/assets/section-snippets/header.html b/examples/skills/landing-page-generator/assets/section-snippets/header.html new file mode 100644 index 0000000..60e0c17 --- /dev/null +++ b/examples/skills/landing-page-generator/assets/section-snippets/header.html @@ -0,0 +1,54 @@ + +
    +
    + +
    +
    + + diff --git a/examples/skills/landing-page-generator/assets/section-snippets/hero.html b/examples/skills/landing-page-generator/assets/section-snippets/hero.html new file mode 100644 index 0000000..97be21d --- /dev/null +++ b/examples/skills/landing-page-generator/assets/section-snippets/hero.html @@ -0,0 +1,42 @@ + +
    +
    + +
    + MIT License + Version + Platform +
    + + +

    {{PROJECT_TITLE}}

    + + +

    {{PROJECT_TAGLINE}}

    + + +
    + {{STAT_1_VALUE}} {{STAT_1_LABEL}} + {{STAT_2_VALUE}} {{STAT_2_LABEL}} + {{STAT_3_VALUE}} {{STAT_3_LABEL}} +
    + + + +
    +
    + + diff --git a/examples/skills/landing-page-generator/assets/section-snippets/install.html b/examples/skills/landing-page-generator/assets/section-snippets/install.html new file mode 100644 index 0000000..7698eb1 --- /dev/null +++ b/examples/skills/landing-page-generator/assets/section-snippets/install.html @@ -0,0 +1,90 @@ + +
    +
    +

    Quick Start

    + + +
    +

    Get started in {{INSTALL_TIME}}:

    + +
    +
    + bash + +
    +
    {{INSTALL_COMMAND}}
    +
    +
    + + +
    +

    Setup

    + +
    +
    + bash + +
    +
    {{SETUP_COMMANDS}}
    +
    +
    + + +
    +

    First Usage

    + +
    +
    + bash + +
    +
    {{USAGE_EXAMPLE}}
    +
    +
    + + +

    + Full installation guide → +

    +
    +
    + + + + diff --git a/examples/skills/landing-page-generator/assets/section-snippets/risk-banner.html b/examples/skills/landing-page-generator/assets/section-snippets/risk-banner.html new file mode 100644 index 0000000..66d44b7 --- /dev/null +++ b/examples/skills/landing-page-generator/assets/section-snippets/risk-banner.html @@ -0,0 +1,22 @@ + + + + diff --git a/examples/skills/landing-page-generator/assets/section-snippets/search-modal.html b/examples/skills/landing-page-generator/assets/section-snippets/search-modal.html new file mode 100644 index 0000000..14dcf52 --- /dev/null +++ b/examples/skills/landing-page-generator/assets/section-snippets/search-modal.html @@ -0,0 +1,40 @@ + + + + diff --git a/examples/skills/landing-page-generator/assets/static-workflow.yml b/examples/skills/landing-page-generator/assets/static-workflow.yml new file mode 100644 index 0000000..f2c9e97 --- /dev/null +++ b/examples/skills/landing-page-generator/assets/static-workflow.yml @@ -0,0 +1,43 @@ +# Simple workflow for deploying static content to GitHub Pages +name: Deploy static content to Pages + +on: + # Runs on pushes targeting the default branch + push: + branches: ["main"] + + # Allows you to run this workflow manually from the Actions tab + workflow_dispatch: + +# Sets permissions of the GITHUB_TOKEN to allow deployment to GitHub Pages +permissions: + contents: read + pages: write + id-token: write + +# Allow only one concurrent deployment, skipping runs queued between the run in-progress and latest queued. +# However, do NOT cancel in-progress runs as we want to allow these production deployments to complete. +concurrency: + group: "pages" + cancel-in-progress: false + +jobs: + # Single deploy job since we're just deploying + deploy: + environment: + name: github-pages + url: ${{ steps.deployment.outputs.page_url }} + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v4 + - name: Setup Pages + uses: actions/configure-pages@v5 + - name: Upload artifact + uses: actions/upload-pages-artifact@v3 + with: + # Upload entire repository + path: '.' + - name: Deploy to GitHub Pages + id: deployment + uses: actions/deploy-pages@v4 diff --git a/examples/skills/landing-page-generator/assets/styles-base.css b/examples/skills/landing-page-generator/assets/styles-base.css new file mode 100644 index 0000000..58a19d3 --- /dev/null +++ b/examples/skills/landing-page-generator/assets/styles-base.css @@ -0,0 +1,917 @@ +/* ============================================ + Landing Page Base Styles + Dark Theme - GitHub-inspired + ============================================ */ + +/* CSS Custom Properties */ +:root { + /* Colors - Dark Theme */ + --bg-primary: #0d1117; + --bg-secondary: #161b22; + --bg-tertiary: #21262d; + --text-primary: #c9d1d9; + --text-secondary: #9ca3af; + --text-muted: #8b949e; + --accent: #58a6ff; + --accent-hover: #79b8ff; + --accent-secondary: #4f46e5; + --accent-secondary-hover: #6366f1; + --border: #30363d; + --border-light: #21262d; + --success: #3fb950; + --warning: #d29922; + --danger: #f85149; + + /* Typography */ + --font-sans: system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; + --font-mono: ui-monospace, SFMono-Regular, 'SF Mono', Menlo, Consolas, monospace; + + /* Spacing */ + --space-xs: 0.25rem; + --space-sm: 0.5rem; + --space-md: 1rem; + --space-lg: 1.5rem; + --space-xl: 2rem; + --space-2xl: 3rem; + --space-3xl: 4rem; + + /* Layout */ + --max-width: 1200px; + --border-radius: 8px; + --border-radius-lg: 12px; + + /* Transitions */ + --transition-fast: 150ms ease; + --transition-normal: 250ms ease; +} + +/* Reset & Base */ +*, *::before, *::after { + box-sizing: border-box; + margin: 0; + padding: 0; +} + +html { + scroll-behavior: smooth; + font-size: 16px; +} + +body { + font-family: var(--font-sans); + background-color: var(--bg-primary); + color: var(--text-primary); + line-height: 1.6; + -webkit-font-smoothing: antialiased; +} + +/* Container */ +.container { + max-width: var(--max-width); + margin: 0 auto; + padding: 0 var(--space-lg); +} + +/* Typography */ +h1, h2, h3, h4, h5, h6 { + font-weight: 600; + line-height: 1.3; + color: var(--text-primary); +} + +a { + color: var(--accent); + text-decoration: none; + transition: color var(--transition-fast); +} + +a:hover { + color: var(--accent-hover); +} + +code { + font-family: var(--font-mono); + background-color: var(--bg-tertiary); + padding: 0.15em 0.4em; + border-radius: 4px; + font-size: 0.9em; +} + +kbd { + display: inline-block; + padding: 0.2em 0.5em; + font-family: var(--font-mono); + font-size: 0.85em; + background-color: var(--bg-tertiary); + border: 1px solid var(--border); + border-radius: 4px; +} + +/* Skip Link */ +.skip-link { + position: absolute; + top: -40px; + left: 0; + padding: 8px; + background-color: var(--accent); + color: var(--bg-primary); + z-index: 1000; +} + +.skip-link:focus { + top: 0; +} + +/* ============================================ + Buttons + ============================================ */ +.btn { + display: inline-flex; + align-items: center; + justify-content: center; + gap: var(--space-sm); + padding: 0.75rem 1.5rem; + font-size: 1rem; + font-weight: 500; + border-radius: var(--border-radius); + border: none; + cursor: pointer; + transition: all var(--transition-fast); + text-decoration: none; +} + +.btn:hover { + text-decoration: none; +} + +.btn-primary { + background-color: var(--accent-secondary); + color: #ffffff; + font-weight: 600; +} + +.btn-primary:hover { + background-color: var(--accent-secondary-hover); + color: #ffffff; + transform: translateY(-2px); +} + +.btn-secondary { + background: transparent; + color: var(--text-primary); + border: 1px solid var(--border); +} + +.btn-secondary:hover { + border-color: var(--accent); + color: var(--accent); +} + +.btn-github-star { + background: linear-gradient(135deg, #24292e 0%, #1a1e22 100%); + color: #ffffff; + border: 1px solid rgba(255, 255, 255, 0.1); + font-weight: 600; + padding: 0.5rem 1rem; +} + +.btn-github-star:hover { + background: linear-gradient(135deg, #2f363d 0%, #24292e 100%); + transform: translateY(-2px); +} + +/* ============================================ + Header + ============================================ */ +.header { + position: sticky; + top: 0; + z-index: 100; + background-color: rgba(13, 17, 23, 0.95); + backdrop-filter: blur(8px); + border-bottom: 1px solid var(--border); +} + +.nav { + display: flex; + align-items: center; + justify-content: space-between; + height: 64px; +} + +.logo { + display: flex; + align-items: center; + gap: var(--space-sm); + font-weight: 600; + font-size: 1.1rem; + color: var(--text-primary); +} + +.logo:hover { + color: var(--text-primary); + text-decoration: none; +} + +.logo-icon { + font-family: var(--font-mono); + color: var(--accent); +} + +.nav-links { + display: flex; + align-items: center; + gap: var(--space-lg); +} + +.nav-links a { + color: var(--text-secondary); + font-size: 0.95rem; +} + +.nav-links a:hover { + color: var(--text-primary); + text-decoration: none; +} + +.nav-actions { + display: flex; + align-items: center; + gap: 1rem; +} + +/* ============================================ + Risk Banner + ============================================ */ +.risk-banner { + background: linear-gradient(90deg, rgba(210, 153, 34, 0.15), rgba(248, 81, 73, 0.15)); + border-bottom: 1px solid var(--warning); + padding: var(--space-md) 0; +} + +.risk-banner .container { + display: flex; + align-items: center; + gap: var(--space-md); + flex-wrap: wrap; +} + +.risk-icon { + font-size: 1.25rem; +} + +.risk-text { + flex: 1; + font-size: 0.95rem; +} + +.risk-text strong { + color: var(--warning); +} + +.risk-link { + color: var(--warning); + font-weight: 500; + white-space: nowrap; +} + +.risk-link:hover { + color: var(--danger); +} + +/* ============================================ + Hero + ============================================ */ +.hero { + padding: var(--space-3xl) 0; + text-align: center; +} + +.hero-badges { + display: flex; + justify-content: center; + gap: var(--space-sm); + margin-bottom: var(--space-lg); + flex-wrap: wrap; +} + +.hero-badges img { + height: 20px; +} + +.hero-title { + font-size: 3rem; + font-weight: 700; + margin-bottom: var(--space-md); + background: linear-gradient(135deg, var(--text-primary), var(--accent)); + -webkit-background-clip: text; + -webkit-text-fill-color: transparent; + background-clip: text; +} + +.hero-tagline { + font-size: 1.25rem; + color: var(--text-secondary); + max-width: 700px; + margin: 0 auto var(--space-lg); +} + +.hero-stats { + display: flex; + justify-content: center; + gap: var(--space-xl); + margin-bottom: var(--space-xl); +} + +.hero-stats .stat { + color: var(--text-secondary); +} + +.hero-stats strong { + color: var(--accent); +} + +.hero-ctas { + display: flex; + justify-content: center; + gap: var(--space-md); + flex-wrap: wrap; +} + +/* ============================================ + Sections + ============================================ */ +section { + padding: var(--space-3xl) 0; +} + +.section-title { + font-size: 2rem; + text-align: center; + margin-bottom: var(--space-2xl); +} + +/* ============================================ + Features Grid + ============================================ */ +.features-grid { + display: grid; + grid-template-columns: repeat(auto-fit, minmax(280px, 1fr)); + gap: var(--space-xl); +} + +.feature-card { + background-color: var(--bg-secondary); + border: 1px solid var(--border); + border-radius: var(--border-radius-lg); + padding: var(--space-xl); + transition: transform var(--transition-fast), box-shadow var(--transition-fast); +} + +.feature-card:hover { + transform: translateY(-4px); + box-shadow: 0 8px 24px rgba(0, 0, 0, 0.3); +} + +.feature-icon { + font-size: 2rem; + margin-bottom: var(--space-md); +} + +.feature-title { + font-size: 1.1rem; + margin-bottom: var(--space-sm); +} + +.feature-desc { + color: var(--text-secondary); + font-size: 0.95rem; +} + +/* ============================================ + Provider Cards + ============================================ */ +.providers-grid { + display: grid; + grid-template-columns: repeat(auto-fit, minmax(300px, 1fr)); + gap: var(--space-xl); +} + +.provider-card { + background-color: var(--bg-secondary); + border: 1px solid var(--border); + border-radius: var(--border-radius-lg); + padding: var(--space-xl); + position: relative; + overflow: hidden; +} + +.provider-card::before { + content: ''; + position: absolute; + top: 0; + left: 0; + right: 0; + height: 4px; +} + +.provider-card.anthropic::before { + background: linear-gradient(90deg, #d97706, #ea580c); +} + +.provider-card.copilot::before { + background: linear-gradient(90deg, #58a6ff, #3b82f6); +} + +.provider-card.ollama::before { + background: linear-gradient(90deg, #3fb950, #22c55e); +} + +.provider-header { + display: flex; + align-items: center; + gap: var(--space-md); + margin-bottom: var(--space-md); +} + +.provider-icon { + font-size: 2rem; +} + +.provider-name { + font-size: 1.25rem; + font-weight: 600; +} + +.provider-command { + font-family: var(--font-mono); + color: var(--accent); + font-size: 0.9rem; +} + +.provider-desc { + color: var(--text-secondary); + margin-bottom: var(--space-md); +} + +.provider-meta { + display: flex; + gap: var(--space-md); + font-size: 0.9rem; + color: var(--text-muted); +} + +/* ============================================ + Code Blocks + ============================================ */ +.code-block { + background-color: var(--bg-secondary); + border: 1px solid var(--border); + border-radius: var(--border-radius); + overflow: hidden; + margin: var(--space-md) 0; +} + +.code-header { + display: flex; + justify-content: space-between; + align-items: center; + padding: var(--space-sm) var(--space-md); + background-color: var(--bg-tertiary); + border-bottom: 1px solid var(--border); +} + +.code-lang { + color: var(--text-muted); + font-size: 0.85rem; + font-family: var(--font-mono); +} + +.copy-btn { + background: none; + border: none; + color: var(--text-secondary); + cursor: pointer; + font-size: 0.85rem; + padding: var(--space-xs) var(--space-sm); + border-radius: 4px; + transition: all var(--transition-fast); +} + +.copy-btn:hover { + color: var(--text-primary); + background-color: var(--bg-tertiary); +} + +.code-block pre { + padding: var(--space-md); + overflow-x: auto; + margin: 0; +} + +.code-block code { + background: none; + padding: 0; + font-size: 0.9rem; + color: var(--text-primary); +} + +/* ============================================ + Pricing Table + ============================================ */ +.pricing-table { + width: 100%; + border-collapse: collapse; + margin: var(--space-xl) 0; +} + +.pricing-table th, +.pricing-table td { + padding: var(--space-md); + text-align: left; + border-bottom: 1px solid var(--border); +} + +.pricing-table th { + background-color: var(--bg-secondary); + font-weight: 600; +} + +.pricing-table tr:hover { + background-color: var(--bg-secondary); +} + +.pricing-highlight { + color: var(--success); + font-weight: 600; +} + +/* ============================================ + Screenshots Gallery + ============================================ */ +.screenshots-tabs { + display: flex; + justify-content: center; + gap: var(--space-sm); + margin-bottom: var(--space-xl); + flex-wrap: wrap; +} + +.screenshot-tab { + padding: var(--space-sm) var(--space-md); + background: none; + border: 1px solid var(--border); + border-radius: var(--border-radius); + color: var(--text-secondary); + cursor: pointer; + transition: all var(--transition-fast); +} + +.screenshot-tab:hover, +.screenshot-tab.active { + border-color: var(--accent); + color: var(--accent); + background-color: rgba(88, 166, 255, 0.1); +} + +.screenshot-content { + display: none; + text-align: center; +} + +.screenshot-content.active { + display: block; +} + +.screenshot-content img { + max-width: 100%; + border-radius: var(--border-radius-lg); + border: 1px solid var(--border); +} + +/* ============================================ + FAQ + ============================================ */ +.faq-list { + max-width: 800px; + margin: 0 auto; +} + +.faq-item { + border: 1px solid var(--border); + border-radius: var(--border-radius); + margin-bottom: var(--space-md); + overflow: hidden; +} + +.faq-question { + padding: var(--space-md) var(--space-lg); + cursor: pointer; + font-weight: 500; + list-style: none; + display: flex; + justify-content: space-between; + align-items: center; +} + +.faq-question::-webkit-details-marker { + display: none; +} + +.faq-question::after { + content: '+'; + font-size: 1.5rem; + color: var(--text-muted); + transition: transform var(--transition-fast); +} + +.faq-item[open] .faq-question::after { + transform: rotate(45deg); +} + +.faq-answer { + padding: 0 var(--space-lg) var(--space-lg); + color: var(--text-secondary); +} + +/* ============================================ + Risk Disclosure Section + ============================================ */ +.risk-disclosure { + background-color: rgba(248, 81, 73, 0.05); + border: 1px solid rgba(248, 81, 73, 0.3); + border-radius: var(--border-radius-lg); + padding: var(--space-xl); +} + +.risk-disclosure h3 { + color: var(--danger); + margin-bottom: var(--space-md); +} + +.risk-disclosure ul { + list-style: disc; + padding-left: var(--space-xl); + color: var(--text-secondary); +} + +.risk-disclosure li { + margin-bottom: var(--space-sm); +} + +/* ============================================ + Footer + ============================================ */ +.footer { + background-color: var(--bg-secondary); + border-top: 1px solid var(--border); + padding: var(--space-2xl) 0; +} + +.footer-content { + display: flex; + justify-content: space-between; + align-items: center; + flex-wrap: wrap; + gap: var(--space-lg); +} + +.footer-brand { + display: flex; + flex-direction: column; + gap: var(--space-xs); +} + +.footer-tagline { + color: var(--text-muted); + font-size: 0.9rem; +} + +.footer-links { + display: flex; + gap: var(--space-lg); +} + +.footer-links a { + color: var(--text-secondary); + font-size: 0.9rem; +} + +.footer-links a:hover { + color: var(--text-primary); +} + +.footer-meta { + display: flex; + gap: var(--space-md); + color: var(--text-muted); + font-size: 0.85rem; +} + +/* ============================================ + Search Modal + ============================================ */ +.search-modal { + position: fixed; + inset: 0; + z-index: 1000; + display: flex; + align-items: flex-start; + justify-content: center; + padding-top: 15vh; +} + +.search-modal[hidden] { + display: none; +} + +.search-modal-backdrop { + position: absolute; + inset: 0; + background-color: rgba(0, 0, 0, 0.7); +} + +.search-modal-content { + position: relative; + width: 100%; + max-width: 600px; + background-color: var(--bg-secondary); + border: 1px solid var(--border); + border-radius: var(--border-radius-lg); + overflow: hidden; + margin: 0 var(--space-md); +} + +.search-input-wrapper { + display: flex; + align-items: center; + padding: var(--space-md); + border-bottom: 1px solid var(--border); +} + +.search-input { + flex: 1; + background: none; + border: none; + color: var(--text-primary); + font-size: 1.1rem; + outline: none; +} + +.search-input::placeholder { + color: var(--text-muted); +} + +.search-results { + max-height: 400px; + overflow-y: auto; + list-style: none; +} + +.search-result-item { + padding: var(--space-md) var(--space-lg); + cursor: pointer; + display: flex; + align-items: center; + gap: var(--space-md); + border-bottom: 1px solid var(--border-light); +} + +.search-result-item:hover, +.search-result-item[aria-selected="true"] { + background-color: var(--bg-tertiary); +} + +.search-result-type { + font-size: 0.8rem; + color: var(--text-muted); + min-width: 80px; +} + +.search-result-title { + flex: 1; +} + +.search-result-category { + font-size: 0.8rem; + color: var(--text-muted); + background-color: var(--bg-tertiary); + padding: 2px 8px; + border-radius: 4px; +} + +.search-no-results { + padding: var(--space-xl); + text-align: center; + color: var(--text-muted); +} + +.search-status { + position: absolute; + width: 1px; + height: 1px; + padding: 0; + margin: -1px; + overflow: hidden; + clip: rect(0, 0, 0, 0); + border: 0; +} + +/* Search trigger button */ +.search-trigger { + display: flex; + align-items: center; + gap: var(--space-sm); + padding: var(--space-sm) var(--space-md); + background-color: var(--bg-tertiary); + border: 1px solid var(--border); + border-radius: var(--border-radius); + color: var(--text-muted); + cursor: pointer; + transition: all var(--transition-fast); +} + +.search-trigger:hover { + border-color: var(--accent); + color: var(--text-primary); +} + +/* ============================================ + Responsive + ============================================ */ +@media (max-width: 768px) { + .hero-title { + font-size: 2rem; + } + + .hero-tagline { + font-size: 1.1rem; + } + + .nav-links { + display: none; + } + + .hero-ctas { + flex-direction: column; + align-items: center; + } + + .footer-content { + flex-direction: column; + text-align: center; + } + + .risk-banner .container { + flex-direction: column; + text-align: center; + } +} + +@media (max-width: 480px) { + .hero { + padding: var(--space-2xl) 0; + } + + .hero-stats { + flex-direction: column; + gap: var(--space-sm); + } + + section { + padding: var(--space-2xl) 0; + } +} + +/* ============================================ + Utilities + ============================================ */ +.visually-hidden { + position: absolute; + width: 1px; + height: 1px; + padding: 0; + margin: -1px; + overflow: hidden; + clip: rect(0, 0, 0, 0); + border: 0; +} + +/* Focus visible for accessibility */ +:focus-visible { + outline: 2px solid var(--accent); + outline-offset: 2px; +} + +/* Reduced motion */ +@media (prefers-reduced-motion: reduce) { + *, *::before, *::after { + animation-duration: 0.01ms !important; + animation-iteration-count: 1 !important; + transition-duration: 0.01ms !important; + } + + html { + scroll-behavior: auto; + } +} diff --git a/examples/skills/landing-page-generator/references/landing-pattern.md b/examples/skills/landing-page-generator/references/landing-pattern.md new file mode 100644 index 0000000..4458430 --- /dev/null +++ b/examples/skills/landing-page-generator/references/landing-pattern.md @@ -0,0 +1,478 @@ +# Landing Page Pattern Reference + +Documentation of the established landing page pattern used in `claude-code-ultimate-guide-landing` and `claude-cowork-guide-landing`. + +## Tech Stack + +| Component | Choice | Rationale | +|-----------|--------|-----------| +| Framework | None (vanilla) | Simplicity, no build step, easy hosting | +| Styling | Single CSS file | Maintainable, no preprocessor needed | +| JavaScript | Vanilla + MiniSearch CDN | Minimal dependencies, lazy-loaded | +| Deployment | GitHub Pages + Actions | Free, automatic, reliable | +| Search | MiniSearch with fallback | Client-side, fast, no backend needed | + +## File Structure + +``` +project-landing/ +├── index.html # Main landing (all sections) +├── styles.css # Complete stylesheet (~3000 lines) +├── search.js # Search modal + keyboard nav +├── search-data.js # Search index arrays +├── *-data.js # Additional data files (optional) +├── favicon.svg # Project icon +├── robots.txt # SEO +├── CLAUDE.md # Claude instructions +├── README.md # Repo documentation +├── assets/ # Images, screenshots +└── .github/workflows/ + └── static.yml # Pages deployment +``` + +## HTML Structure + +### Document Head + +```html + + + + + + [Project Name] - [Tagline] + + + + + + + + + + + + + + + + + + + + + + + + +``` + +### Body Structure + +```html + + + +
    ...
    + +
    +
    ...
    +
    ...
    +
    ...
    +
    ...
    + +
    + +
    ...
    + + + + + + + + +``` + +## Section Patterns + +### Header + +```html +
    +
    + + +
    + + + ⭐ Star on GitHub + +
    +
    +
    +``` + +### Hero Section + +```html +
    +
    +
    + ... + +
    +

    [Main Title]

    +

    [Tagline/TL;DR]

    +
    + [N] features + [N] examples +
    + +
    +
    +``` + +### Risk Banner (Optional) + +```html + +``` + +### Features Grid + +```html +
    +
    +

    Features

    +
    +
    +
    [emoji/icon]
    +

    [Title]

    +

    [Description]

    +
    + +
    +
    +
    +``` + +### Code Block with Copy + +```html +
    +
    + [language] + +
    +
    [code content]
    +
    +``` + +### FAQ Section + +```html +
    +
    +

    FAQ

    +
    +
    + [Question]? +
    +

    [Answer]

    +
    +
    + +
    +
    +
    +``` + +### Footer + +```html +
    +
    + +
    +
    +``` + +## CSS Architecture + +### Custom Properties (Theme) + +```css +:root { + /* Colors - Dark Theme */ + --color-bg: #0d1117; + --color-surface: #161b22; + --color-surface-hover: #21262d; + --color-border: #30363d; + --color-text: #c9d1d9; + --color-text-muted: #8b949e; + --color-heading: #f0f6fc; + --color-primary: #58a6ff; + --color-primary-hover: #79b8ff; + --color-success: #3fb950; + --color-warning: #d29922; + --color-danger: #f85149; + + /* Spacing */ + --space-xs: 0.25rem; + --space-sm: 0.5rem; + --space-md: 1rem; + --space-lg: 1.5rem; + --space-xl: 2rem; + --space-2xl: 3rem; + --space-3xl: 4rem; + + /* Typography */ + --font-sans: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; + --font-mono: 'SF Mono', Consolas, 'Liberation Mono', monospace; + --font-size-sm: 0.875rem; + --font-size-base: 1rem; + --font-size-lg: 1.125rem; + --font-size-xl: 1.25rem; + --font-size-2xl: 1.5rem; + --font-size-3xl: 2rem; + --font-size-4xl: 2.5rem; + + /* Layout */ + --container-max: 1200px; + --radius: 6px; + --radius-lg: 12px; + + /* Shadows */ + --shadow-sm: 0 1px 2px rgba(0,0,0,0.3); + --shadow-md: 0 4px 6px rgba(0,0,0,0.3); + --shadow-lg: 0 10px 15px rgba(0,0,0,0.3); +} +``` + +### Component Patterns + +```css +/* Container */ +.container { + max-width: var(--container-max); + margin: 0 auto; + padding: 0 var(--space-lg); +} + +/* Buttons */ +.btn { + display: inline-flex; + align-items: center; + gap: var(--space-sm); + padding: var(--space-sm) var(--space-lg); + border-radius: var(--radius); + font-weight: 500; + text-decoration: none; + transition: all 0.2s; +} + +.btn-primary { + background: var(--color-primary); + color: var(--color-bg); +} + +.btn-secondary { + background: var(--color-surface); + color: var(--color-text); + border: 1px solid var(--color-border); +} + +/* Cards */ +.feature-card { + background: var(--color-surface); + border: 1px solid var(--color-border); + border-radius: var(--radius-lg); + padding: var(--space-xl); + transition: transform 0.2s, box-shadow 0.2s; +} + +.feature-card:hover { + transform: translateY(-2px); + box-shadow: var(--shadow-md); +} + +/* Grids */ +.features-grid { + display: grid; + grid-template-columns: repeat(auto-fit, minmax(280px, 1fr)); + gap: var(--space-xl); +} +``` + +### Responsive Breakpoints + +```css +/* Tablet */ +@media (max-width: 768px) { + .hero-title { font-size: var(--font-size-3xl); } + .header-actions { display: none; } + .nav { display: none; } + /* Mobile nav toggle */ +} + +/* Mobile */ +@media (max-width: 480px) { + .hero-ctas { flex-direction: column; } + .features-grid { grid-template-columns: 1fr; } +} +``` + +## JavaScript Patterns + +### Search Implementation + +```javascript +(function() { + 'use strict'; + + let searchIndex = null; + let miniSearchLoaded = false; + + // Lazy load MiniSearch + async function loadMiniSearch() { + if (miniSearchLoaded) return; + await loadScript('https://cdn.jsdelivr.net/npm/minisearch@7/dist/umd/index.min.js'); + miniSearchLoaded = true; + } + + // Build index from window.SEARCH_* data + function buildIndex() { + const items = [ + ...(window.SEARCH_FEATURES || []), + ...(window.SEARCH_FAQ || []), + ]; + // ... index building + } + + // Keyboard navigation + document.addEventListener('keydown', (e) => { + if ((e.metaKey || e.ctrlKey) && e.key === 'k') { + e.preventDefault(); + openSearchModal(); + } + }); +})(); +``` + +### Copy Code Function + +```javascript +async function copyCode(button) { + const codeBlock = button.closest('.code-block'); + const code = codeBlock.querySelector('code').textContent; + + try { + await navigator.clipboard.writeText(code); + button.textContent = '✓ Copied!'; + setTimeout(() => { + button.textContent = '📋 Copy'; + }, 2000); + } catch (err) { + console.error('Copy failed:', err); + } +} +``` + +## Deployment + +### GitHub Actions Workflow + +```yaml +name: Deploy to GitHub Pages + +on: + push: + branches: ["main"] + workflow_dispatch: + +permissions: + contents: read + pages: write + id-token: write + +concurrency: + group: "pages" + cancel-in-progress: false + +jobs: + deploy: + environment: + name: github-pages + url: ${{ steps.deployment.outputs.page_url }} + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: actions/configure-pages@v5 + - uses: actions/upload-pages-artifact@v3 + with: + path: '.' + - id: deployment + uses: actions/deploy-pages@v4 +``` + +## Accessibility Checklist + +- [ ] Skip link to main content +- [ ] Semantic HTML (header, main, section, footer) +- [ ] ARIA labels on interactive elements +- [ ] Keyboard navigation for modals +- [ ] Focus visible styles +- [ ] Color contrast WCAG AA +- [ ] Reduced motion respect +- [ ] Alt text on images + +## SEO Checklist + +- [ ] Descriptive title tag +- [ ] Meta description +- [ ] Canonical URL +- [ ] Open Graph tags +- [ ] Twitter Card tags +- [ ] Structured data (Schema.org) +- [ ] robots.txt +- [ ] Semantic heading hierarchy \ No newline at end of file diff --git a/examples/skills/release-notes-generator/SKILL.md b/examples/skills/release-notes-generator/SKILL.md new file mode 100644 index 0000000..db053ec --- /dev/null +++ b/examples/skills/release-notes-generator/SKILL.md @@ -0,0 +1,259 @@ +--- +name: release-notes-generator +description: Generate release notes in 3 formats (CHANGELOG.md, PR body, Slack announcement) from git commits. Automatically categorizes changes and converts technical language to user-friendly messaging. Use when preparing a production release. +--- + +# Release Notes Generator + +Generate comprehensive release notes in 3 formats from git commits, optimized for the Methode Aristote project workflow. + +## When to Use This Skill + +- Preparing a production release (develop -> main) +- Creating release PR with proper documentation +- Generating Slack announcement for stakeholders +- Updating CHANGELOG.md with new version +- Analyzing commits since last release + +## What This Skill Does + +1. **Analyzes Git History**: Scans commits since last release tag or specified version +2. **Fetches PR Details**: Gets PR titles, descriptions, and labels via `gh api` +3. **Categorizes Changes**: Groups into features, bug fixes, improvements, security, breaking changes +4. **Generates 3 Outputs**: + - **CHANGELOG.md section** (technical, complete) + - **PR Release body** (semi-technical, checklist) + - **Slack message** (product-focused, user-friendly) +5. **Transforms Language**: Converts technical jargon to accessible messaging +6. **Migration Alert**: Displays prominent warning if database migrations are required + +## How to Use + +### Basic Usage + +``` +Generate release notes since last release +``` + +``` +Create release notes for version 0.18.0 +``` + +### With Specific Range + +``` +Generate release notes from v0.17.0 to HEAD +``` + +### Preview Only + +``` +Preview release notes without writing files +``` + +## Output Formats + +### 1. CHANGELOG.md Section + +Technical format for developers: + +```markdown +## [0.18.0] - 2025-12-08 + +### Objectif +[1-2 sentence summary] + +### Nouvelles Fonctionnalites +#### [Feature Name] (#PR) +- **Description**: ... +- **Impact**: ... + +### Corrections de Bugs +- **[Module]**: Description (#issue, Sentry ISSUE-XX) + +### Ameliorations Techniques +#### Performance / UI/UX / Architecture +- [Description] + +### Migrations Base de Donnees +[If applicable] + +### Statistiques +- PRs: X +- Features: Y +- Bugs: Z +``` + +### 2. PR Release Body + +Uses template from `.github/PULL_REQUEST_TEMPLATE/release.md`: +- Objective summary +- Features with specs links +- Bug fixes with Sentry references +- Improvements by category +- Migration instructions +- Deployment checklist + +### 3. Slack Announcement + +Product-focused format from `.github/COMMUNICATION_TEMPLATE/slack-release.md`: +- **PR link** included for traceability +- Non-technical language +- Focus on user impact (tuteurs, eleves, admin) +- Emojis for readability +- Statistics summary + +## Workflow Integration + +This skill integrates with the release workflow in CLAUDE.md Section VI.5: + +``` +1. Analyze commits: git log ..HEAD +2. Determine version number (MAJOR.MINOR.PATCH) +3. Generate 3 outputs +4. Create PR develop -> main with "Release" label +5. Update CHANGELOG.md +6. After merge: create git tag +7. Generate Slack announcement +``` + +## Tech-to-Product Transformation + +The skill automatically transforms technical language: + +| Technical | Product | +|-----------|---------| +| "Optimisation N+1 queries avec DataLoader" | "Chargement plus rapide des listes" | +| "Implementation embeddings AI avec pgvector" | "Nouvelle recherche intelligente d'activites" | +| "Correction scope permissions dans getPermissionScope()" | "Correction d'un bug d'acces aux sessions" | +| "Migration webpack -> Turbopack" | *Ne pas communiquer* | +| "Refactorisation hooks React" | *Ne pas communiquer* | +| "Fix N+1 in user loaders" | "Amelioration des performances" | +| "Add retry logic for Prisma P1001" | "Meilleure stabilite de connexion" | + +## Commit Categories + +Commits are categorized by conventional commit prefix: + +| Prefix | Category | Include in Slack? | +|--------|----------|-------------------| +| `feat:` | Nouvelles Fonctionnalites | Yes | +| `fix:` | Corrections de Bugs | Yes (if user-facing) | +| `perf:` | Performance | Yes (simplified) | +| `security:` | Securite | Yes | +| `refactor:` | Architecture | No | +| `chore:` | Maintenance | No | +| `docs:` | Documentation | No | +| `test:` | Tests | No | +| `style:` | Style | No | + +## Console Output Format + +When generating release notes, ALWAYS display a clear summary in the console with: + +### Migration Alert (CRITICAL) + +If migrations are detected, display prominently: + +``` +╔══════════════════════════════════════════════════════════════════╗ +║ ⚠️ [ATTENTION] MIGRATIONS BASE DE DONNÉES REQUISES ║ +╠══════════════════════════════════════════════════════════════════╣ +║ ║ +║ Cette release contient X migration(s) Prisma : ║ +║ ║ +║ • 20251210123456_add_activity_description ║ +║ • 20251211094532_update_session_status ║ +║ ║ +║ Action requise AVANT déploiement : ║ +║ → Exécuter : pnpm prisma migrate deploy ║ +║ ║ +╚══════════════════════════════════════════════════════════════════╝ +``` + +If NO migrations: + +``` +✅ [OK] Aucune migration base de données requise +``` + +### Detection Method + +Check for new migration files since last release: + +```bash +# Find new migrations since last tag +git diff ..HEAD --name-only -- prisma/migrations/ +``` + +## Example + +**User**: "Generate release notes since v0.17.0" + +**Output**: + +``` +Analyzing commits from v0.17.0 to HEAD... + +Found: +- 15 commits +- 8 PRs merged +- 2 new features +- 5 bug fixes +- 3 improvements + +╔══════════════════════════════════════════════════════════════════╗ +║ ⚠️ [ATTENTION] MIGRATIONS BASE DE DONNÉES REQUISES ║ +╠══════════════════════════════════════════════════════════════════╣ +║ Cette release contient 1 migration(s) Prisma : ║ +║ • 20251208143021_add_user_preferences ║ +║ Action requise : pnpm prisma migrate deploy ║ +╚══════════════════════════════════════════════════════════════════╝ + +--- CHANGELOG.md Section --- +[Technical format output] + +--- PR Release Body --- +[Semi-technical format output] + +--- Slack Announcement --- +[Product-focused format output] + +Write to files? (CHANGELOG.md, clipboard for PR/Slack) +``` + +## Commands Used + +```bash +# Get last release tag +git tag --sort=-v:refname | head -n 1 + +# List commits since tag +git log ..HEAD --oneline --no-merges + +# Get PR details +gh api repos/{owner}/{repo}/pulls/{number} + +# Get commit details +git show --stat +``` + +## Tips + +- Run from repository root +- Ensure `gh` CLI is authenticated +- Review generated content before publishing +- Adjust product language for your audience +- Use `--preview` to see output without writing + +## Reference Files + +- `assets/changelog-template.md` - CHANGELOG section template +- `assets/slack-template.md` - Slack announcement template +- `references/tech-to-product-mappings.md` - Transformation rules +- `references/commit-categories.md` - Categorization rules + +## Related Skills + +- `github-actions-templates` - For CI/CD workflows +- `changelog-generator` - Original inspiration (ComposioHQ) diff --git a/examples/skills/release-notes-generator/assets/README.md b/examples/skills/release-notes-generator/assets/README.md new file mode 100644 index 0000000..c0e0b41 --- /dev/null +++ b/examples/skills/release-notes-generator/assets/README.md @@ -0,0 +1,18 @@ +# Assets + +This directory contains templates, images, and boilerplate code. + +**Note**: Assets are NOT automatically loaded into context. They must be explicitly referenced. + +## Guidelines + +- Use descriptive filenames +- Include usage instructions in file headers +- Keep templates minimal and customizable + +## Files + +Add your assets here. Examples: +- `template.md` - Output template +- `boilerplate.ts` - Code boilerplate +- `config.json` - Configuration template diff --git a/examples/skills/release-notes-generator/assets/changelog-template.md b/examples/skills/release-notes-generator/assets/changelog-template.md new file mode 100644 index 0000000..ca394b9 --- /dev/null +++ b/examples/skills/release-notes-generator/assets/changelog-template.md @@ -0,0 +1,93 @@ +# CHANGELOG Section Template + +Use this template for generating CHANGELOG.md entries. + +```markdown +## [X.Y.Z] - YYYY-MM-DD + +### Objectif +[Resume en 1-2 phrases de cette release] + +### Nouvelles Fonctionnalites + +#### [Feature Name] (#PR_NUMBER) +- **Description** : [Description fonctionnelle claire] +- **Spec Notion** : [Lien vers la spec si disponible] +- **Composants impactes** : `component-a`, `service-b`, etc. +- **Impact** : [Qui est concerne : Tuteurs / Eleves / Admin / Tous] + +### Corrections de Bugs + +#### [Module/Composant] (#PR_NUMBER) +- **Issue** : [Description du bug] +- **Cause** : [Cause racine identifiee] +- **Fix** : [Description de la correction] +- **Sentry** : METHODE-ARISTOTE-APP-XX (si applicable) + +### Ameliorations Techniques + +#### Performance +- [Description optimisation avec impact mesurable si possible] + +#### UI/UX +- [Description amelioration interface] + +#### Architecture +- [Description refactoring important] + +### Securite +- **[CVE-XXXX-XXXXX]** : [Description et impact] + +### Migrations Base de Donnees + +#### Processus de Deploiement + +**Etape 1 : Appliquer les migrations Prisma** +```bash +pnpm prisma migrate deploy +``` + +**Etape 2 : Scripts de data migration** (si applicable) +```bash +pnpm dotenv -e .env.production -- tsx scripts/db/migrations/[script-name].ts +``` + +**Verification post-migration** +```sql +-- Requetes de verification +SELECT COUNT(*) FROM [table]; +``` + +### Breaking Changes + +**Aucun** ou: + +- **[Composant/API]** : Description du breaking change + - **Migration requise** : Comment migrer + - **Impact** : Qui est affecte + +### Deprecations + +**Aucune** ou: + +- **[Fonctionnalite X]** : Depreciee dans cette version + - **Raison** : Pourquoi + - **Alternative** : Quoi utiliser a la place + - **Suppression prevue** : Version X.Y.Z + +### Tests +- [X] tests unitaires pour [feature] +- [X] tests d'integration pour [module] + +### Statistiques +- **PRs** : #XX, #YY, #ZZ +- **Fichiers impactes** : XX+ +- **Nouvelles tables** : [liste si applicable] +- **Migrations Prisma** : X migrations +- **Breaking changes** : 0 + +### Liens +- PR: https://github.com/methode-aristote/app/pull/XXX +- PRs incluses : #XX, #YY, #ZZ +- Issues Sentry : METHODE-ARISTOTE-APP-XX +``` diff --git a/examples/skills/release-notes-generator/assets/slack-template.md b/examples/skills/release-notes-generator/assets/slack-template.md new file mode 100644 index 0000000..76f032a --- /dev/null +++ b/examples/skills/release-notes-generator/assets/slack-template.md @@ -0,0 +1,85 @@ +# Slack Announcement Template + +Use this template for generating product-focused Slack messages. + +``` +Version X.Y.Z - Deployee en production + +PR : [URL de la PR de release, ex: https://github.com/methode-aristote/app/pull/XXX] + +En bref : [Phrase decrivant l'objectif principal de cette release] + +--- + +Nouvelles fonctionnalites + +[Nom Feature 1] +> [Description en 1-2 phrases de ce que ca apporte aux utilisateurs] +> Qui est concerne : [Tuteurs / Eleves / Admin / Tous] + +[Nom Feature 2] +> [Description en 1-2 phrases] +> Qui est concerne : [Roles] + +--- + +Corrections importantes + +- [Description du bug corrige en langage utilisateur] +- [Description du bug corrige en langage utilisateur] + +--- + +Ameliorations + +- [Amelioration UX/UI ou workflow en langage utilisateur] +- [...] + +--- + +En chiffres + +- X nouvelles fonctionnalites +- Y bugs corriges +- Z ameliorations + +--- + +Questions ? Contactez @florian ou l'equipe tech +``` + +## Guidelines + +### A FAIRE +- Utiliser un langage accessible (pas de jargon technique) +- Focus sur l'impact utilisateur +- Etre concis (max 10 lignes par section) +- Utiliser des emojis avec parcimonie + +### A EVITER +- "Implementation du pattern DataLoader pour resoudre les N+1 queries" +- "Refactorisation complete du systeme de permissions avec scope ANY/ASSIGNED" +- "Migration de webpack vers Turbopack" + +### Transformation Tech -> Produit + +| Technique | Produit | +|-----------|---------| +| Optimisation N+1 queries avec DataLoader | Chargement plus rapide des listes d'utilisateurs et de sessions | +| Implementation embeddings AI avec pgvector | Nouvelle recherche intelligente d'activites similaires | +| Correction scope permissions dans getPermissionScope() | Correction d'un bug qui empechait certains utilisateurs d'acceder a leurs sessions | +| Migration vers kebab-case pour les fichiers | *Ne pas communiquer (purement technique)* | +| Fix Prisma P1001 avec retry logic | Meilleure stabilite de connexion a la base de donnees | +| Add Sentry monitoring for orphan chats | Detection automatique des conversations orphelines | + +### Sections Optionnelles + +Si la release contient des elements importants, ajouter: + +Points d'attention +- [Information importante que les utilisateurs doivent savoir] +- [Changement de comportement qu'ils pourraient remarquer] + +A venir prochainement +- [Teaser de la prochaine grosse feature] +- [Feature en cours de developpement qui arrive bientot] diff --git a/examples/skills/release-notes-generator/references/README.md b/examples/skills/release-notes-generator/references/README.md new file mode 100644 index 0000000..98bb9c3 --- /dev/null +++ b/examples/skills/release-notes-generator/references/README.md @@ -0,0 +1,17 @@ +# References + +This directory contains documentation that will be loaded contextually during skill execution. + +## Guidelines + +- Keep files focused and well-organized +- Use markdown format for readability +- Include concrete examples where possible +- Update when domain knowledge changes + +## Files + +Add your reference documents here. Examples: +- `api-docs.md` - API documentation +- `style-guide.md` - Formatting guidelines +- `domain-knowledge.md` - Domain-specific information diff --git a/examples/skills/release-notes-generator/references/commit-categories.md b/examples/skills/release-notes-generator/references/commit-categories.md new file mode 100644 index 0000000..05fef0d --- /dev/null +++ b/examples/skills/release-notes-generator/references/commit-categories.md @@ -0,0 +1,124 @@ +# Commit Categorization Rules + +This document defines how to categorize commits based on Conventional Commits format. + +## Primary Categories + +### Features (`feat:`) +**CHANGELOG**: Nouvelles Fonctionnalites +**Slack**: Yes - always include +**Examples**: +- `feat(session): add parent report system` +- `feat(activity): add multi-level topics` +- `feat(search): add accent-insensitive search` + +### Bug Fixes (`fix:`) +**CHANGELOG**: Corrections de Bugs +**Slack**: Yes - if user-facing; No - if internal +**Examples**: +- `fix(session): correct status transition` -> Include in Slack +- `fix(test): correct mock setup` -> Do NOT include in Slack + +### Performance (`perf:`) +**CHANGELOG**: Ameliorations Techniques > Performance +**Slack**: Yes - simplified ("Performances ameliorees") +**Examples**: +- `perf(loader): optimize N+1 queries with batching` +- `perf(build): reduce bundle size by 30%` + +### Security (`security:` or `fix(security):`) +**CHANGELOG**: Securite +**Slack**: Yes - always, with appropriate detail level +**Examples**: +- `security: fix CVE-2025-55182 React2Shell RCE` +- `fix(security): prevent XSS in chat messages` + +## Secondary Categories (CHANGELOG only) + +### Refactoring (`refactor:`) +**CHANGELOG**: Ameliorations Techniques > Architecture +**Slack**: No +**Examples**: +- `refactor(hooks): migrate to new pattern` +- `refactor(permissions): extract to service layer` + +### Documentation (`docs:`) +**CHANGELOG**: Documentation (if significant) +**Slack**: No +**Examples**: +- `docs: update CLAUDE.md with new patterns` +- `docs(api): add tRPC endpoint documentation` + +### Tests (`test:`) +**CHANGELOG**: Tests (count only) +**Slack**: No +**Examples**: +- `test(activity): add prompt resolver tests` +- `test(e2e): add session workflow tests` + +### Chores (`chore:`) +**CHANGELOG**: No (unless significant) +**Slack**: No +**Examples**: +- `chore: update dependencies` +- `chore(ci): fix workflow permissions` + +### Style (`style:`) +**CHANGELOG**: No +**Slack**: No +**Examples**: +- `style: apply prettier formatting` +- `style(eslint): fix linting errors` + +## Scope Patterns + +Common scopes in Methode Aristote: + +| Scope | Area | +|-------|------| +| `session` | Session management | +| `activity` | Activities and prompts | +| `chat` | Chat and AI features | +| `user` | User management | +| `auth` | Authentication | +| `visio` | Video conferencing | +| `training` | Training/Formation system | +| `admin` | Admin panel | +| `calendar` | Calendar and scheduling | +| `permissions` | Permission system | +| `db` | Database and migrations | +| `api` | API endpoints | +| `ui` | UI components | + +## Breaking Changes + +Indicated by `!` after type/scope or `BREAKING CHANGE:` in footer: +- `feat(api)!: change session status enum` +- `fix(auth)!: require new token format` + +**CHANGELOG**: Breaking Changes section +**Slack**: Yes - with migration instructions + +## PR Number Extraction + +Extract PR numbers from: +1. Commit message: `(#123)` +2. Merge commit: `Merge pull request #123` +3. GitHub API: cross-reference with commit SHA + +## Sentry Issue Linking + +Match patterns: +- `Sentry: METHODE-ARISTOTE-APP-XX` +- `fixes METHODE-ARISTOTE-APP-XX` +- `closes #XX` (GitHub issue) + +## Statistics Calculation + +Count for release stats: +- **PRs**: Unique PR numbers +- **Features**: `feat:` commits +- **Bugs**: `fix:` commits (excluding test/internal) +- **Improvements**: `perf:` + `refactor:` + UI improvements +- **Security**: `security:` commits +- **Breaking**: Commits with `!` or `BREAKING CHANGE` \ No newline at end of file diff --git a/examples/skills/release-notes-generator/references/tech-to-product-mappings.md b/examples/skills/release-notes-generator/references/tech-to-product-mappings.md new file mode 100644 index 0000000..044613f --- /dev/null +++ b/examples/skills/release-notes-generator/references/tech-to-product-mappings.md @@ -0,0 +1,91 @@ +# Tech-to-Product Transformation Rules + +This document defines how to transform technical commit messages into user-friendly product language. + +## Transformation Categories + +### 1. COMMUNICATE (Transform to product language) + +| Technical Pattern | Product Message | +|-------------------|-----------------| +| `N+1 queries`, `DataLoader`, `batching` | "Chargement plus rapide des listes" | +| `embeddings`, `vector search`, `pgvector` | "Recherche intelligente amelioree" | +| `permissions`, `scope`, `access control` | "Correction d'un bug d'acces" | +| `retry logic`, `resilience`, `P1001` | "Meilleure stabilite de connexion" | +| `SSE`, `real-time`, `WebSocket` | "Mises a jour en temps reel" | +| `cache`, `memoization` | "Performances ameliorees" | +| `responsive`, `mobile` | "Meilleure experience mobile" | +| `accessibility`, `a11y`, `WCAG` | "Accessibilite amelioree" | +| `Sentry`, `monitoring`, `alerting` | "Meilleur suivi des erreurs" | +| `validation`, `sanitization` | "Securite renforcee" | + +### 2. DO NOT COMMUNICATE (Internal/Technical only) + +These patterns should NOT appear in Slack announcements: + +| Technical Pattern | Reason | +|-------------------|--------| +| `refactor`, `refactoring` | Internal code quality | +| `webpack`, `turbopack`, `bundler` | Build tooling | +| `eslint`, `prettier`, `linting` | Code style | +| `kebab-case`, `naming convention` | Internal standards | +| `TypeScript`, `type safety` | Developer experience | +| `test`, `spec`, `coverage` | Testing infrastructure | +| `chore`, `maintenance` | Routine maintenance | +| `docs`, `documentation` | Internal docs | +| `deps`, `dependencies`, `bump` | Dependency updates | +| `CI`, `CD`, `workflow` | DevOps infrastructure | + +### 3. SECURITY (Always communicate, simplified) + +| Technical | Product | +|-----------|---------| +| `CVE-XXXX-XXXXX` | "Correction d'une vulnerabilite de securite" | +| `XSS`, `injection` | "Renforcement de la protection des donnees" | +| `authentication`, `auth bypass` | "Securite de connexion amelioree" | +| `CORS`, `CSRF` | "Protection contre les attaques web" | + +## Context-Aware Transformations + +### Session-related +- "Fix session status transition" -> "Correction du suivi des seances" +- "Add session conflict detection" -> "Detection automatique des conflits de planning" +- "Improve visio sync" -> "Synchronisation video amelioree" + +### User-related +- "Fix user profile access" -> "Correction de l'acces aux profils" +- "Add accent-insensitive search" -> "Recherche amelioree (accents non sensibles)" +- "Improve user loader performance" -> "Chargement des utilisateurs plus rapide" + +### Activity-related +- "Fix activity prompt resolution" -> "Correction de l'affichage des activites" +- "Add multi-level topics" -> "Organisation des activites par niveau amelioree" +- "Improve embeddings generation" -> "Recherche d'activites similaires amelioree" + +### Chat-related +- "Fix streaming race condition" -> "Stabilite du chat amelioree" +- "Add vocal history" -> "Historique des messages vocaux" +- "Prevent empty messages" -> "Correction des messages vides" + +## Role-Based Impact + +Always specify who is affected: + +| Impact | Roles | +|--------|-------| +| Dashboard changes | Tuteurs, Eleves, Admin | +| Session management | Tuteurs, Admin | +| Activity library | Tuteurs, Admin | +| Chat/AI features | Tuteurs, Eleves | +| Admin panel | Admin only | +| Billing/Payment | Admin, Parents | +| Reports/Analytics | Admin, Tuteurs | + +## Severity Indicators + +Use these prefixes when appropriate: + +- **Critique** : Production-blocking issues +- **Important** : User-facing bugs +- **Mineur** : Quality of life improvements +- *Ne pas mentionner* : Internal fixes diff --git a/examples/skills/release-notes-generator/scripts/README.md b/examples/skills/release-notes-generator/scripts/README.md new file mode 100644 index 0000000..a5ca780 --- /dev/null +++ b/examples/skills/release-notes-generator/scripts/README.md @@ -0,0 +1,17 @@ +# Scripts + +This directory contains executable scripts for deterministic, repeatable tasks. + +## Guidelines + +- Scripts should be self-contained and well-documented +- Include usage examples in script headers +- Handle errors gracefully with clear messages +- Use appropriate exit codes (0 for success, 1 for failure) + +## Files + +Add your scripts here. Examples: +- `generate.py` - Main generation script +- `validate.py` - Validation utilities +- `transform.py` - Data transformation helpers diff --git a/examples/skills/skill-creator/SKILL.md b/examples/skills/skill-creator/SKILL.md new file mode 100644 index 0000000..fe697c0 --- /dev/null +++ b/examples/skills/skill-creator/SKILL.md @@ -0,0 +1,186 @@ +--- +name: skill-creator +description: Create new Claude Code skills with proper structure, YAML frontmatter, and bundled resources. Generates skill templates following best practices for modular, self-contained capability packages. +tags: [meta, skill, generator, claude-code] +--- + +# Skill Creator + +This skill helps you create new Claude Code skills with proper structure and best practices. + +## When to Use This Skill + +- Creating a new custom skill for your project +- Standardizing skill structure across your team +- Generating skill templates with scripts, references, and assets +- Packaging skills for distribution + +## Skill Structure + +A Claude skill consists of: + +``` +skill-name/ +├── SKILL.md # Required: Main skill file with YAML frontmatter +├── scripts/ # Optional: Executable code for deterministic tasks +├── references/ # Optional: Documentation loaded contextually +└── assets/ # Optional: Templates, images, boilerplate (not loaded into context) +``` + +## SKILL.md Format + +Every skill requires a `SKILL.md` file with: + +```markdown +--- +name: skill-name +description: One-line description of what the skill does and when to use it. +--- + +# Skill Name + +Brief introduction explaining the skill's purpose. + +## When to Use This Skill + +- Trigger condition 1 +- Trigger condition 2 +- Trigger condition 3 + +## What This Skill Does + +1. **Step 1**: Description +2. **Step 2**: Description +3. **Step 3**: Description + +## How to Use + +### Basic Usage +[Examples of how to invoke the skill] + +### With Options +[Advanced usage patterns] + +## Example + +**User**: "Example prompt" + +**Output**: +[Example output] + +## Tips + +- Best practice 1 +- Best practice 2 + +## Related Use Cases + +- Related task 1 +- Related task 2 +``` + +## How to Use + +### Create a New Skill + +``` +Create a new skill called "my-skill-name" in ~/.claude/skills/ +``` + +### Create with Specific Purpose + +``` +Create a skill for generating release notes from git commits, +with templates for CHANGELOG.md and Slack announcements +``` + +### Initialize Skill Structure + +Run the initialization script: + +```bash +python3 ~/.claude/skills/skill-creator/scripts/init_skill.py --path +``` + +### Package Skill for Distribution + +```bash +python3 ~/.claude/skills/skill-creator/scripts/package_skill.py [output-directory] +``` + +## Design Principles + +### Progressive Disclosure + +Context loads hierarchically to optimize token usage: +1. **Metadata** (~100 words): Always present via skill description +2. **SKILL.md** (<5k words): Loaded when skill is triggered +3. **Bundled resources**: Loaded as needed during execution + +### Organizational Patterns + +Choose the pattern that fits your skill: + +| Pattern | Best For | Structure | +|---------|----------|-----------| +| **Workflow-Based** | Sequential procedures | Step-by-step instructions | +| **Task-Based** | Multiple operations | Collection of tasks | +| **Reference/Guidelines** | Standards, specs | Rules and examples | +| **Capabilities-Based** | Interrelated features | Feature descriptions | + +### Naming Conventions + +- Use `kebab-case` for skill names: `release-notes-generator` +- Use descriptive names that indicate purpose +- Keep names concise but meaningful + +## Bundled Resources + +### scripts/ + +Executable code for deterministic, repeatable tasks: +- `init_skill.py` - Initialize new skill structure +- `package_skill.py` - Package skill for distribution + +### references/ + +Documentation loaded contextually: +- API documentation +- Style guides +- Domain knowledge + +### assets/ + +Templates and resources (not auto-loaded): +- Output templates +- Boilerplate code +- Images and fonts + +## Example: Creating a Release Notes Skill + +**User**: "Create a skill for generating release notes with 3 output formats: CHANGELOG.md, PR body, and Slack message" + +**Steps**: +1. Initialize structure: `init_skill.py release-notes-generator --path ~/.claude/skills/` +2. Add templates to `assets/`: + - `changelog-template.md` + - `pr-release-template.md` + - `slack-template.md` +3. Add transformation rules to `references/`: + - `tech-to-product-mappings.md` +4. Complete `SKILL.md` with usage instructions +5. Package: `package_skill.py ~/.claude/skills/release-notes-generator` + +## Tips + +- Keep SKILL.md under 5000 words for efficient context usage +- Use references/ for domain knowledge that doesn't change often +- Put templates in assets/ so they're not auto-loaded +- Test your skill with real use cases before packaging +- Include concrete examples in your SKILL.md + +## Related Use Cases + +- Creating project-specific automation skills +- Building team-shared development workflows +- Packaging reusable Claude capabilities diff --git a/examples/skills/skill-creator/scripts/init_skill.py b/examples/skills/skill-creator/scripts/init_skill.py new file mode 100755 index 0000000..7891647 --- /dev/null +++ b/examples/skills/skill-creator/scripts/init_skill.py @@ -0,0 +1,228 @@ +#!/usr/bin/env python3 +""" +Initialize a new Claude Code skill with proper directory structure. + +Usage: + python3 init_skill.py --path + +Example: + python3 init_skill.py release-notes-generator --path ~/.claude/skills/ +""" + +import argparse +import os +import sys +from pathlib import Path + + +SKILL_MD_TEMPLATE = '''--- +name: {skill_name} +description: [One-line description of what this skill does and when to use it] +--- + +# {skill_title} + +[Brief introduction explaining the skill's purpose] + +## When to Use This Skill + +- [Trigger condition 1] +- [Trigger condition 2] +- [Trigger condition 3] + +## What This Skill Does + +1. **[Step 1]**: [Description] +2. **[Step 2]**: [Description] +3. **[Step 3]**: [Description] + +## How to Use + +### Basic Usage + +``` +[Example prompt that triggers this skill] +``` + +### With Options + +``` +[Advanced usage example with parameters] +``` + +## Example + +**User**: "[Example user prompt]" + +**Output**: +```markdown +[Example output from the skill] +``` + +## Tips + +- [Best practice 1] +- [Best practice 2] +- [Best practice 3] + +## Related Use Cases + +- [Related task 1] +- [Related task 2] +''' + +SCRIPTS_README = '''# Scripts + +This directory contains executable scripts for deterministic, repeatable tasks. + +## Guidelines + +- Scripts should be self-contained and well-documented +- Include usage examples in script headers +- Handle errors gracefully with clear messages +- Use appropriate exit codes (0 for success, 1 for failure) + +## Files + +Add your scripts here. Examples: +- `generate.py` - Main generation script +- `validate.py` - Validation utilities +- `transform.py` - Data transformation helpers +''' + +REFERENCES_README = '''# References + +This directory contains documentation that will be loaded contextually during skill execution. + +## Guidelines + +- Keep files focused and well-organized +- Use markdown format for readability +- Include concrete examples where possible +- Update when domain knowledge changes + +## Files + +Add your reference documents here. Examples: +- `api-docs.md` - API documentation +- `style-guide.md` - Formatting guidelines +- `domain-knowledge.md` - Domain-specific information +''' + +ASSETS_README = '''# Assets + +This directory contains templates, images, and boilerplate code. + +**Note**: Assets are NOT automatically loaded into context. They must be explicitly referenced. + +## Guidelines + +- Use descriptive filenames +- Include usage instructions in file headers +- Keep templates minimal and customizable + +## Files + +Add your assets here. Examples: +- `template.md` - Output template +- `boilerplate.ts` - Code boilerplate +- `config.json` - Configuration template +''' + + +def validate_skill_name(name: str) -> bool: + """Validate skill name follows kebab-case convention.""" + if not name: + return False + if not all(c.isalnum() or c == '-' for c in name): + return False + if name.startswith('-') or name.endswith('-'): + return False + if '--' in name: + return False + return True + + +def skill_name_to_title(name: str) -> str: + """Convert kebab-case skill name to Title Case.""" + return ' '.join(word.capitalize() for word in name.split('-')) + + +def create_skill(skill_name: str, output_path: str) -> bool: + """Create a new skill with proper directory structure.""" + + if not validate_skill_name(skill_name): + print(f"Invalid skill name: '{skill_name}'") + print(" Use kebab-case (e.g., 'my-skill-name')") + return False + + output_dir = Path(output_path).expanduser().resolve() + skill_dir = output_dir / skill_name + + if skill_dir.exists(): + print(f"Skill already exists: {skill_dir}") + return False + + try: + print(f"Creating skill: {skill_name}") + + skill_dir.mkdir(parents=True, exist_ok=True) + print(f" Created {skill_dir}") + + for subdir in ['scripts', 'references', 'assets']: + (skill_dir / subdir).mkdir(exist_ok=True) + print(f" Created {subdir}/") + + skill_title = skill_name_to_title(skill_name) + skill_md_content = SKILL_MD_TEMPLATE.format( + skill_name=skill_name, + skill_title=skill_title + ) + (skill_dir / 'SKILL.md').write_text(skill_md_content) + print(" Created SKILL.md") + + (skill_dir / 'scripts' / 'README.md').write_text(SCRIPTS_README) + (skill_dir / 'references' / 'README.md').write_text(REFERENCES_README) + (skill_dir / 'assets' / 'README.md').write_text(ASSETS_README) + print(" Created README files") + + print(f"\nSkill '{skill_name}' created successfully!") + print(f"\nLocation: {skill_dir}") + print("\nNext steps:") + print(" 1. Edit SKILL.md with your skill's instructions") + print(" 2. Add scripts to scripts/ for automation") + print(" 3. Add reference docs to references/") + print(" 4. Add templates to assets/") + print(f" 5. Test with: skill: \"{skill_name}\"") + + return True + + except Exception as e: + print(f"Error creating skill: {e}") + return False + + +def main(): + parser = argparse.ArgumentParser( + description='Initialize a new Claude Code skill' + ) + + parser.add_argument( + 'skill_name', + help='Name of the skill (kebab-case, e.g., "my-skill-name")' + ) + + parser.add_argument( + '--path', + required=True, + help='Output directory where skill folder will be created' + ) + + args = parser.parse_args() + + success = create_skill(args.skill_name, args.path) + sys.exit(0 if success else 1) + + +if __name__ == '__main__': + main() \ No newline at end of file diff --git a/examples/skills/skill-creator/scripts/package_skill.py b/examples/skills/skill-creator/scripts/package_skill.py new file mode 100755 index 0000000..d315ce2 --- /dev/null +++ b/examples/skills/skill-creator/scripts/package_skill.py @@ -0,0 +1,135 @@ +#!/usr/bin/env python3 +""" +Package a Claude Code skill into a distributable zip file. + +Usage: + python3 package_skill.py [output-directory] + +Example: + python3 package_skill.py ~/.claude/skills/my-skill ./dist +""" + +import argparse +import os +import sys +import zipfile +from pathlib import Path + + +def validate_skill(skill_path: Path) -> tuple[bool, list[str]]: + """Validate skill structure and return (is_valid, errors).""" + errors = [] + + # Check SKILL.md exists + skill_md = skill_path / 'SKILL.md' + if not skill_md.exists(): + errors.append("Missing required file: SKILL.md") + else: + # Check SKILL.md has frontmatter + content = skill_md.read_text() + if not content.startswith('---'): + errors.append("SKILL.md missing YAML frontmatter (must start with ---)") + elif content.count('---') < 2: + errors.append("SKILL.md frontmatter not properly closed (needs second ---)") + else: + # Check required frontmatter fields + frontmatter_end = content.index('---', 3) + frontmatter = content[3:frontmatter_end] + if 'name:' not in frontmatter: + errors.append("SKILL.md frontmatter missing 'name' field") + if 'description:' not in frontmatter: + errors.append("SKILL.md frontmatter missing 'description' field") + + return len(errors) == 0, errors + + +def package_skill(skill_path: str, output_dir: str = '.') -> bool: + """Package skill folder into a zip file.""" + + skill_dir = Path(skill_path).expanduser().resolve() + output_path = Path(output_dir).expanduser().resolve() + + # Validate skill folder exists + if not skill_dir.exists(): + print(f"Skill folder not found: {skill_dir}") + return False + + if not skill_dir.is_dir(): + print(f"Not a directory: {skill_dir}") + return False + + # Validate skill structure + print(f"Validating skill: {skill_dir.name}") + is_valid, errors = validate_skill(skill_dir) + + if not is_valid: + print("Validation failed:") + for error in errors: + print(f" - {error}") + return False + + print(" Validation passed") + + # Create output directory if needed + output_path.mkdir(parents=True, exist_ok=True) + + # Create zip file + zip_name = f"{skill_dir.name}.zip" + zip_path = output_path / zip_name + + try: + print(f"Packaging skill...") + + with zipfile.ZipFile(zip_path, 'w', zipfile.ZIP_DEFLATED) as zipf: + for file_path in skill_dir.rglob('*'): + if file_path.is_file(): + # Skip common ignored files + if file_path.name.startswith('.'): + continue + if file_path.name == '__pycache__': + continue + if file_path.suffix == '.pyc': + continue + + arcname = file_path.relative_to(skill_dir.parent) + zipf.write(file_path, arcname) + print(f" Added: {arcname}") + + print(f"\nSkill packaged successfully!") + print(f"Output: {zip_path}") + print(f"Size: {zip_path.stat().st_size / 1024:.1f} KB") + + return True + + except Exception as e: + print(f"Error packaging skill: {e}") + if zip_path.exists(): + zip_path.unlink() + return False + + +def main(): + parser = argparse.ArgumentParser( + description='Package a Claude Code skill into a zip file' + ) + + parser.add_argument( + 'skill_path', + help='Path to the skill folder' + ) + + parser.add_argument( + 'output_dir', + nargs='?', + default='.', + help='Output directory for the zip file (default: current directory)' + ) + + args = parser.parse_args() + + success = package_skill(args.skill_path, args.output_dir) + sys.exit(0 if success else 1) + + +if __name__ == '__main__': + main() \ No newline at end of file