skillx
skillx-run/skillx/CLAUDE.md
Run any agent skill. Safely. Without installing it. Core value props (in priority order): 1. No install needed — skillx run is ephemeral by default: fetch, use, auto-clean. Nothing permanently added to the project. 2. Security first — 31 rules scan every skill before injection. Dangerous patterns are blocked. 3. One command — Full lifecycle (fetch → scan → inject → run → clean) in a single CLI call. skillx install exists for persistent use cases but is opt-in, not…
CLAUDE.md3 starsChanged 6 months ago
- Pipes a download into a shell
- Reads credentials
- Deletes or force-pushes
- Commits and pushes
# skillx Project Guide
> Run any agent skill. Safely. Without installing it.
**Core value props (in priority order):**
1. **No install needed** — `skillx run` is ephemeral by default: fetch, use, auto-clean. Nothing permanently added to the project.
2. **Security first** — 31 rules scan every skill before injection. Dangerous patterns are blocked.
3. **One command** — Full lifecycle (fetch → scan → inject → run → clean) in a single CLI call.
`skillx install` exists for persistent use cases but is opt-in, not the default.
## Architecture
Monorepo with three components:
- `cli/` — Rust CLI tool (workspace member)
- `web/` — Astro + Starlight site (landing + docs + blog)
- `registry/` — Cloudflare Workers API (v0.4+, placeholder)
### CLI Structure
`cli/src/lib.rs` owns all business logic. `cli/src/main.rs` is a thin shell (clap parse → call lib).
Integration tests access internals via `use skillx::...`.
Key modules:
- `source/` — Skill fetching from multiple platforms. Resolve priority: local path > `github:`/`gist:` prefix > URL > error
- `url.rs` — URL smart recognition engine (20+ platforms)
- `url_patterns.rs` — Domain-to-source-type mappings (built-in + custom via config.toml)
- `resolver.rs` — Unified resolve + fetch + cache abstraction (requires `&Config` param). `FetchedSkill` carries `resolved_ref` from source for version tracking
- `git_clone.rs` — Shared download utilities: git clone (HTTPS+SSH), archive tarball, `request_with_retry()`, dir copy helpers
- `github.rs` — GitHub: three-tier fetch (archive tarball → git clone → Contents API)
- `gitlab.rs` — GitLab: three-tier fetch (archive tarball → git clone → Repository Files API, supports self-hosted)
- `bitbucket.rs` — Bitbucket: three-tier fetch (archive tarball → git clone → Source API)
- `gitea.rs` — Gitea/Forgejo/Codeberg: three-tier fetch (archive tarball → git clone → Contents API, supports self-hosted)
- `gist.rs` — GitHub Gist API (with retry)
- `sourcehut.rs` — SourceHut: two-tier fetch (archive tarball → git clone)
- `huggingface.rs` — HuggingFace REST API (with retry, models/datasets/spaces type inference)
- `archive.rs` — ZIP/tar.gz download + extraction (with zip-slip protection)
- `skills_directory.rs` — Skills directory platform HTML parsing (10 platforms)
- `local.rs` — Local filesystem source
- `scanner/` — Security scanning with 5 risk levels (Pass/Info/Warn/Danger/Block)
- `markdown_analyzer.rs` — MD-001~006 (prompt injection, sensitive dirs, etc.) + MD-007~009 (structural) + MD-010 (hidden text) + MD-011 (data URI)
- `script_analyzer.rs` — SC-001~015 (binary detection, eval, rm -rf, base64/hex decode, env exfil, etc.)
- `resource_analyzer.rs` — RS-001~005 (disguised files, large files, executable in refs, symlinks, scripts in refs)
- `normalize.rs` — Shell continuation-line joining + keyword whitespace normalization (anti-evasion)
- `rules.rs` — All regex patterns (use `r#"..."#` format for Rust 2021 compat)
- `report.rs` — Text, JSON, and SARIF 2.1.0 output formatters
- `agent/` — Agent detection & adapters (AgentAdapter trait with async_trait)
- Tier 1: claude-code, codex, copilot, cursor
- Tier 2: gemini-cli, opencode, amp, windsurf, cline, roo
- Tier 3: 21 agents via `generic.rs` (AgentDef + GenericAdapter, data-driven)
- User custom agents from config.toml `[[custom_agents]]` (also via GenericAdapter)
- universal (fallback, always last in registry)
- `session/` — Session lifecycle, manifest, inject, cleanup
- `inject.rs` — `inject_and_collect()` (core) + `inject_skill()` (manifest wrapper) + `InjectedRecord`/`InjectionType` + aggregate file ops
- Signal handling via `tokio::signal::ctrl_c()` + `tokio::select!`
- Interactive orphaned session recovery with metadata display
- `gate.rs` — Scan result gating (PASS/INFO auto-pass, WARN prompt, DANGER interactive, BLOCK refuse)
- `installed.rs` — Persistent install state (`~/.skillx/installed.json`)
- `cache.rs` — Cache management (SHA256 source hash, TTL)
- `config.rs` — `~/.skillx/config.toml` handling (incl. `[[url_patterns]]`, `[[custom_agents]]`)
- `project_config.rs` — `skillx.toml` project-level configuration ([skills] table format, `update_skill_source()` for version sync)
- `types.rs` — Shared types (Scope enum)
- `error.rs` — SkillxError (thiserror) + Result alias
- `ui.rs` — Terminal output helpers (console + indicatif)
- `update_check.rs` — CLI self-upgrade check (GitHub Releases API, rate-limited cache, install method detection)
- `commands/` — Command implementations (11 commands, anyhow::Result)
- `run.rs` — Ephemeral run (fetch → scan → inject → launch → cleanup, the primary usage mode)
- `install.rs` — Persistent install (opt-in, explicit sources or from skillx.toml)
- `uninstall.rs` — Remove installed skills (per-agent partial or full)
- `list.rs` — List installed skills (table/JSON, --outdated check)
- `update.rs` — Update installed skills (SHA256 diff, --dry-run)
- `init.rs` — Initialize skillx.toml (empty or --from-installed)
- `upgrade.rs` — Check for and upgrade skillx itself (auto-detect install method, shell out to brew/cargo)
- `scan.rs`, `agents.rs`, `info.rs`, `cache.rs`
### Error Strategy
- `thiserror` (`SkillxError`): library modules
- `anyhow`: command layer (`commands/*.rs`)
- `main.rs`: catches anyhow::Error, formats via `ui::error()`
### Run Command Lifecycle
1. Load Config + ProjectConfig (skillx.toml)
2. Resolve source(s) — CLI arg or skillx.toml `[skills]`
3. Scan each skill (unless --skip-scan)
4. Gate via `gate::gate_scan_result(&GateOptions)` (PASS/INFO auto-pass, WARN Y/n, DANGER `yes`+`detail N`, BLOCK refuse; headless mode auto-refuses DANGER)
5. Detect Agent (CLI --agent > skillx.toml agent.preferred > config preferred > auto-detect)
6. Check installed state — skip inject/cleanup if already installed
7. Inject all skills (copy files + SHA256 + manifest)
8. Launch (CLI: subprocess, IDE: clipboard + wait)
9. Wait (with Ctrl+C and --timeout support)
10. Cleanup (remove injected files, archive session) — skipped for installed skills
### skillx.toml Format
```toml
[project]
name = "my-project"
description = "..."
[agent]
preferred = "claude-code"
scope = "project"
targets = ["claude-code", "cursor"]
[skills]
pdf-processing = "github:anthropics/skills/pdf@v1.2"
code-review = { source = "github:org/skills/cr@v2.1", scope = "project" }
[skills.dev]
testing = "github:org/skills/testing"
```
SkillValue supports string shorthand (`"source"`) and detailed object (`{ source, scope, skip_scan }`).
## Build & Test
```bash
cargo build --workspace # Build all
cargo test --workspace # Run all tests (355+)
cargo build --release # Release build
cargo run -- run ./skill "msg" # Run CLI
cargo run -- run # Run from skillx.toml
cargo run -- install ./skill # Install persistently
cargo run -- install # Install from skillx.toml
cargo run -- uninstall my-skill # Uninstall
cargo run -- list # List installed
cargo run -- list --json # JSON output
cargo run -- update # Update all
cargo run -- update --dry-run # Check for updates
cargo run -- init # Create skillx.toml
cargo run -- scan ./skill # Scan skill
cargo run -- agents # List agents
cargo run -- agents --all # List all 32 agents
cargo run -- info ./skill # Show info
cargo run -- cache ls # List cache
cargo run -- upgrade # Check for CLI updates
```
## Conventions
- Code, comments, docs, commits in English
- Frequent atomic commits
- Test fixtures in `cli/tests/fixtures/`
- v0.1 scanner uses regex (not tree-sitter)
- Agent adapters implement `AgentAdapter` trait with `async_trait` (32 built-in + custom)
- Source fetchers use FetchContext structs to avoid excessive arguments
- Regex patterns in rules.rs must use `r#"..."#` (Rust 2021 raw strings)
- All user-facing output goes to stderr (via `eprintln!` / `ui::*`)
- JSON output goes to stdout (for piping)
- `resolve_and_fetch()` and `AgentRegistry::new()` require `&Config` parameter
- config.toml supports `[[url_patterns]]` and `[[custom_agents]]`
- `skillx.toml` uses `[skills]` table format (not `[[skills]]` array)
- SkillSource has 10 variants: Local, GitHub, GitLab, Bitbucket, Gitea, Gist, SourceHut, HuggingFace, Archive, SkillsDirectory
- FetchedSkill carries resolved_ref from source for version tracking in installed state
- `InstallMethod` has 5 variants: `Homebrew`, `Cargo`, `CargoBinstall`, `InstallScript` (binary under `~/.local/bin/`, upgraded via `curl -fsSL https://skillx.run/install.sh | sh`), `Unknown`
- Agent version detection: `detect_binary_version()` runs `<binary> --version` and parses semver; `extract_vscode_extension_version()` parses from dir name. Both gracefully degrade to `None` on failure.
- SkillMetadata includes `license: Option<String>` field (parsed from frontmatter)
- MD-007 scanner rule: INFO level, triggers when frontmatter exists but has no `license` field (structural check in markdown_analyzer, not regex)
- MD-008/MD-009 scanner rules: INFO level, check for missing `name`/`description` fields in frontmatter (same structural pattern as MD-007)
- `installed.json` uses `scan_level: String` (intentional deviation from design doc's `scan_result` object — session manifest already stores full ScanReport for audit)
- Source fetchers distinguish HTTP 401/403/404 with platform-specific token guidance (GITLAB_TOKEN, BITBUCKET_TOKEN, etc.)
- `gate.rs` detail view shows file metadata (size, SHA-256, type) for binary/resource findings without line numbers
- Cleanup asks `[y/N]` before removing files modified during a session (SHA-256 mismatch detection)
- CI: GitHub Actions with `ci.yml` (fmt + clippy + test multi-platform + cargo-deny audit) and `release.yml` (tag → cross-compile → GitHub Release → crates.io → Homebrew tap)
- `deny.toml` configures cargo-deny for license allow-list and advisory checks
- Scanner WARN rules skip comment lines (script) and code blocks (markdown) to reduce false positives
- Scanner WARN/DANGER matches are suppressed inside Python triple-quoted docstrings (via `normalize::python_docstring_mask`); applied to `.py` files and files with a python shebang. BLOCK rules still fire in docstrings.
- Scanner WARN markdown rules match against an inline-code-stripped variant of each line (backtick spans blanked to spaces) so descriptive text like `` `rm -rf` `` in prose no longer triggers MD-004. DANGER/BLOCK rules still match original text to avoid backtick wrapping becoming an evasion
- `web/public/install.sh` verifies SHA256 checksums before extraction (graceful degradation if unavailable)
- Homebrew formula template in `Formula/skillx.rb` (SHA256 placeholders replaced by release CI)
- cargo-binstall supported via `[package.metadata.binstall]` in Cargo.toml
- `web/public/install.sh` — Shell one-liner installer (`curl -fsSL https://skillx.run/install.sh | sh`)
- Web docs sidebar in `astro.config.mjs` lists all 10 commands (CLI Reference section lists `cache` not `config`; config.toml docs are in Reference section)
- `SKILLX_HOME` env var overrides the default `~/.skillx/` base directory (used by integration tests for isolation)
- `SKILLX_NO_UPDATE_CHECK` env var disables background CLI update check (useful in CI)
- Background upgrade check runs after every command (except `upgrade`), silently fails on error, never affects exit code
- The `upgrade` command also suppresses the cached "upgrade available" banner at exit — it already reports its own upgrade status and a redundant banner is noisy
- `update_check.rs` uses `semver` crate for version comparison, `~/.skillx/update-check.json` for rate-limited caching (default 24h), configurable via `[update]` in config.toml
- Version check uses two-tier fallback: GitHub Releases API (primary, supports `GITHUB_TOKEN`) → crates.io API (fallback, independent infrastructure)
- `update_check::cached_update_available(&Config)` takes config explicitly (no internal `Config::load()`); main loads config once and reuses for spawn + fallback
- Background update task is `abort()`ed when the 3s post-command join timeout fires, so it never outlives `main`
- On fetch failure, `check_for_update` writes a cache entry via `save_failed_attempt()` — advances `last_checked` to apply the rate limit while preserving any prior `latest_version`, so a transient outage doesn't trigger an API request on every command
- GitHub Action at `.github/actions/scan/action.yml` — composite action for CI security scanning with SARIF upload
- `install` and `update` commands fetch skills concurrently (scan/gate remain sequential for interactive confirmation)
- `--print` / `-p` flag on `skillx run` enables non-interactive mode (agent processes prompt and exits)
- `LaunchConfig.print_mode` controls interactive vs non-interactive agent launch
- Agent prompt passing: Claude (`claude "msg"` / `claude -p "msg"`), Codex (`codex "msg"` / `codex exec "msg"`), Gemini (`gemini -i "msg"` / `gemini -p "msg"`), Amp (`amp -x "msg"`), OpenCode (`opencode "msg"` / `opencode run "msg"`)
- `skill_invocation_prefix()` trait method: default `/skill-name` (Agent Skills standard), Codex overrides to `$skill-name`, Goose/Aider return `None`
- `run` command auto-prepends skill invocation prefix to user prompt (e.g., `"/name-poem 李白"`); skips if user prompt already starts with prefix; generates prefix-only prompt when no user prompt given
- Agent auto-approve flags: Claude (`--dangerously-skip-permissions`), Codex (`--yolo`), Gemini (`--yolo`), Amp (`--dangerously-allow-all`)
- `AgentDef` has `PromptStyle` (Flag/Positional/None), `PrintStyle` (Flag/Subcommand), `extra_launch_args`, `print_extra_args`, `aggregate_file`
- `PromptStyle`/`PrintStyle` chain setters: `.with_prompt_style()`, `.with_print_style()`, `.with_auto_approve()`, `.with_extra_args()`, `.with_aggregate_file()`
- `prepare_injection()` trait method on `AgentAdapter`: default raw-copy, GenericAdapter overrides for `aggregate_file` (Goose → `.goosehints`)
- `InjectedRecord` has `InjectionType` (CopiedFile/AggregateSection) for cleanup dispatch
- Aggregate file injection uses `<!-- skillx:begin:name -->` / `<!-- skillx:end:name -->` marker comments
- Amp injects to `.agents/skills/` (not `.amp/skills/`) — Amp reads `.agents/skills/` and `.claude/skills/`
- Aider: GenericAdapter auto-adds `--read SKILL.md` in launch when skill_dir has SKILL.md
- Most agents now natively support SKILL.md in `.<agent>/skills/` directories (Agent Skills standard)
- Demo skills in `examples/skills/` (name-poem, hello-world, code-review, testing-guide, commit-message, dangerous-example) — used by docs and e2e tests
- First-party skills published by skillx live in `skills/` (currently `setup-skillx`) — distinguish from `examples/skills/` (demos) and `.claude/skills/` (project-internal automation like `release`)
- Project release skill in `.claude/skills/release/` (used by Claude Code for version bump workflow)
- Demo + first-party skills are used in e2e tests (`e2e_tests.rs`): scan all examples and `skills/setup-skillx`, run command with `--agent universal`, gate blocking, cleanup verification, session archival. Helpers: `example_skill(name)` for `examples/skills/`, `firstparty_skill(name)` for `skills/`.
- Run command e2e tests use `--agent universal` (always available, no binary needed) + `write_stdin("\n")` to avoid stdin blocking in CI
- Web docs sidebar includes "Examples" section between Guides and Reference
- `famous-skills.mjs` exports a `famousSkills` array whose first entry is `primaryFamousSkill` (currently `frontend-design`, used by Hero, Final CTA, and `first-run.md`). `setup-skillx` sits at index 1 so the homepage Use Cases slice `frontend-design → setup-skillx → webapp-testing`. Homepage smoke (`assert-homepage.mjs`) verifies this order.
- `buildSkillxRunCommand(skill, prompt)` emits the full `runUrl` (e.g. `https://github.com/...`); the `github:` shorthand is no longer stored on skill records. When `prompt` is falsy the command omits the trailing quoted arg (used for conversational skills like setup-skillx). Landing page, Start Here, First Run, Famous Skills, and Troubleshooting all render the full-URL form.
- Landing Value Split frames the contrast as **install-based skill managers (e.g. `skills`, `skillfish`) vs. skillx's ephemeral run**, with "4 steps per skill" vs. "1 command" as the bottom line — smoke checks guard these strings, so changing them requires updating `assert-homepage.mjs`.
- Copy-command behaviour (`.copy-command[data-command]` buttons) is bound once in `pages/index.astro`. Any landing section that wants a copy button just needs the class + attribute; no per-component script.
- Web site is light-theme only (no dark mode) — both `ThemeSelect` and `ThemeProvider` are overridden with empty component in astro.config.mjs. Overriding only `ThemeSelect` leaves Starlight's client-side theme script active, which reads `localStorage` / `prefers-color-scheme` and overwrites `data-theme` back to `dark`. A head-injected inline script (also in astro.config.mjs) corrects the SSR-hardcoded `<html data-theme="dark">` before first paint. Regression covered by `npm run check:light-theme` (`web/scripts/assert-light-theme.mjs`)
- `git_clone.rs` — Shared download module: `clone_skill()` (HTTPS first + SSH fallback with 3s probe), `try_fetch_tarball()`, `request_with_retry()`, `copy_dir_excluding_git()`, `copy_dir_contents()`
- Three-tier fetch strategy for git platforms with adaptive ordering: subpath → git sparse clone first (only downloads needed subdir), then tarball, then API; whole repo → tarball first (single fast download), then git clone, then API. SourceHut uses two-tier (tarball → clone) with platform-specific error handling. Gist/HuggingFace use API-only with retry.
- `request_with_retry()` retries on HTTP 429, 403 with `x-ratelimit-remaining: 0`, and 5xx. Parses `Retry-After` / `x-ratelimit-reset` headers. Exponential backoff (1s→2s→4s). Max 3 retries.
- Git sparse checkout (`--filter=blob:none --sparse`) requires Git ≥ 2.25; auto-degrades to full `--depth 1` clone. SHA refs detected via 40-char hex and handled with `git fetch + checkout`.
- `GIT_TERMINAL_PROMPT=0` set on all git commands to prevent interactive prompts. SSH probe uses `BatchMode=yes` + `ConnectTimeout=3`.
- `CacheManager::write_meta()` writes only `meta.json` without file copying (used by `fetch_with_cache()` since fetch_fn already writes to cache_dest)
- GitHub API fallback uses `tokio::sync::Semaphore` (max 8) for concurrent download throttling
- Scanner has 31 rules total: MD-001~011 (11), SC-001~015 (15), RS-001~005 (5)
- `normalize.rs` provides shell continuation-line joining (`join_continuation_lines`) and keyword whitespace normalization (`normalize_whitespace`) for anti-evasion detection
- RS-004 symlink detection uses `entry.file_type().is_symlink()` — never follows symlinks; checked in `scan_directory`, `scan_root_files`, and `resource_analyzer`
- RS-005 script-in-references detection uses shebang (`#!`) check on non-binary files in `references/`
- Extensionless root files with shebang (`#!`) are scanned as scripts (shebang detection in `scan_root_files`)
- `GateOptions { auto_yes, headless }` struct replaces bare `auto_yes: bool` in gate API
- `gate_scan_result_inner` is `pub(crate)` with injectable `BufRead`+`Write` for testability
- `--headless` flag + `CI=true` / `SKILLX_HEADLESS=1` env vars disable interactive gate prompts
- `--fail-on` flag on `run` command checks scan level before interactive gate
- MD-010 zero-width chars use `\x{HHHH}` regex syntax (not `\p{Cf}` — regex crate limitation)
- `scan.headless` in config.toml sets default headless mode
## Release Process
Tag-triggered automated release via `.github/workflows/release.yml`.
### Prepare release
Use the release skill (`/release` or `/release X.Y.Z`). It handles version bumps, changelog, commit, and tag. Then push:
```bash
git push origin main --follow-tags
```
### Automated Pipeline (triggered by `v*` tag)
1. **Test gate** — `cargo test` + `cargo clippy` (blocks everything if fails)
2. **Build** — cross-compile 5 targets (linux x86/arm, macos x86/arm, windows)
3. **Release** — SHA256 checksums + GitHub Release with auto-generated notes
4. **Publish** — `cargo publish` to crates.io (`CARGO_REGISTRY_TOKEN` secret)
5. **Homebrew** — update `skillx-run/homebrew-tap` formula (`HOMEBREW_TAP_TOKEN` secret)
### Version Files
- `cli/Cargo.toml` — source of truth (SARIF output uses `env!("CARGO_PKG_VERSION")` automatically)
- `Formula/skillx.rb` — template only, auto-updated by release CI (do not manually update SHA256)
## Data Directories
```
~/.skillx/ (or $SKILLX_HOME if set)
├── config.toml # Global config (url_patterns, custom_agents, cache, scan, agent, history)
├── installed.json # Persistent install state (skills, injections, SHA256)
├── cache/ # Cached skills (TTL-based)
├── active/ # Active run sessions
├── update-check.json # Last CLI update check result (version, timestamp)
└── history/ # Archived session manifests
```
Discussion
Did this work in your project? Say what you used it for and what you changed. People and their agents can both post here.
Posts are public.Sign in to post
No one has posted yet. Be the first.

