diff --git a/.github/workflows/skills-discovery.yml b/.github/workflows/skills-discovery.yml new file mode 100644 index 0000000..30d8b82 --- /dev/null +++ b/.github/workflows/skills-discovery.yml @@ -0,0 +1,32 @@ +name: Skills checks +on: + pull_request: + push: + branches: [main] +jobs: + discovery: + name: Every SKILL.md is discovered + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-node@v4 + with: + node-version: 20 + - run: scripts/check-discovery.sh + + links: + name: Doc links carry tracking params + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - run: scripts/check-links.sh + + versions: + name: Changed skills bump their version + if: github.event_name == 'pull_request' + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + with: + fetch-depth: 0 + - run: scripts/check-versions.sh "${{ github.event.pull_request.base.sha }}" "${{ github.event.pull_request.head.sha }}" diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..a6de900 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +specs/ diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000..9e2aa71 --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,112 @@ +# Contributing + +Rules for adding or changing skills in this repo. The `skills` CLI is unforgiving +about layout and frontmatter: a skill that breaks these rules is dropped from +discovery with no warning, so read this before opening a PR. + +## Layout + +Every skill lives at `skills///SKILL.md`, exactly two directories +deep. The CLI does not look any deeper, so a skill nested at depth three is +invisible to users. + +Categories: + +- `platform` — reference skills for a Cloudinary capability (uploads, + transformations, docs lookup) +- `frameworks` — patterns for using Cloudinary in a specific framework or SDK +- `use-cases` — end-to-end recipes that combine several capabilities +- `utilities` — tools that do a job using Cloudinary + +Category directories are created with their first skill. Do not add a README or +placeholder to an empty category. + +Users install skills by name, not by path, so the category is invisible to them. +Moving a skill between categories does not affect existing installs. + +## Frontmatter + +```yaml +--- +name: cloudinary-example +description: "Reference for . Use when ." +license: MIT +metadata: + author: cloudinary + version: '1.0.0' +--- +``` + +- `name` must equal the directory name. +- `name` and `description` must both be strings. If either is missing, or the + frontmatter fails to parse, the skill silently vanishes from discovery. +- A description containing a colon must be quoted. Unquoted, YAML reads the colon + as a nested key, the description is no longer a string, and the skill vanishes. +- Include `license`, `metadata.author`, and `metadata.version` (semver). +- Set `metadata.internal: true` on a work-in-progress skill that should not appear + in the default install. Users can reveal it with `INSTALL_INTERNAL_SKILLS=1`. + +## Description shapes + +Agents see the catalog as a flat list of names and descriptions, so every +description does two jobs. It opens with a clause that self-locates the skill in +that list, then follows with a "Use when..." sentence that carries the trigger +phrases. + +| Category | Opening shape | +|---|---| +| Platform | `Reference for .` | +| Frameworks | `Patterns for using Cloudinary in .` | +| Use-case | `End-to-end recipe for .` | +| Utility | `Tool that using Cloudinary.` | + +Rules: + +- The opening clause never contains a colon before its first period unless the + whole description is quoted. +- Trigger phrases live in the "Use when" sentence, not in the opening clause. + +Existing skills predate these shapes and are not yet retrofitted. New skills must +follow them. + +## Referencing other skills + +Refer to another skill by its name only, for example "use cloudinary-docs for +anything outside this skill's scope". Never reference another skill by file path. +Installed paths differ per agent and per install scope, so a path that works on +your machine will be wrong on someone else's. + +## Versioning + +Bump `metadata.version` on any content change to a skill, including its +references and assets. Moving or renaming files with identical content does not +bump the version; the lock-file hash is unchanged and `skills update` correctly +reports nothing to do. CI enforces this on every PR: a skill with changed +content and an unchanged version fails the `versions` job. + +## Documentation links + +Every link to a Cloudinary docs `.md` page or `llms.txt` must carry +`?install_source=skillspack&referrer=-skill` so traffic from skills is +attributable. Use the same `referrer` value throughout a skill, for example +`trans-skill` or `react-skill`. CI fails the `links` job on any link missing +either param. + +## Local check + +Before opening a PR, run the same checks CI runs: + +```bash +scripts/check-discovery.sh +scripts/check-links.sh +scripts/check-versions.sh origin/main HEAD +``` + +The discovery script compares the `name` in every `SKILL.md` under `skills/` +against the names the skills CLI actually lists, and names any skill that is +missing. A missing skill means it is being dropped: check for bad frontmatter, +an unquoted colon in the description, a missing `name` or `description`, or a +directory nested at the wrong depth. The other two scripts report any untracked +doc link (and fail if they find no links at all) and any changed skill whose +version was not bumped. CI (`.github/workflows/skills-discovery.yml`) runs all +three and fails the PR on the same conditions. diff --git a/README.md b/README.md index c9fe919..04d8bed 100644 --- a/README.md +++ b/README.md @@ -4,20 +4,38 @@ The agent skills in this package help your AI coding assistant write correct Clo ## Available Skills +Skills are grouped by category below. The category is a folder in this repo only: you install a skill by its name, and the folder layout has no effect on how it is installed or used. + +### Platform + | Skill | Description | |---|---| | `cloudinary-docs` | Selects the most relevant markdown pages from the current documentation using the latest llms.txt. Use when answering Cloudinary questions or integrating Cloudinary into code. | | `cloudinary-transformations` | Turns natural language image and video transformation requirements into valid URL transformation strings that follow Cloudinary best practices. Use when building delivery URLs, applying transformations, optimizing media, or debugging transformation syntax errors. | + +### Frameworks + +| Skill | Description | +|---|---| | `cloudinary-react` | Provides opinionated React SDK patterns for configuration, common integration scenarios, and troubleshooting for frequent errors and TypeScript pitfalls. Use when developing React apps with Cloudinary. | | `cloudinary-next` | Provides opinionated Next.js SDK patterns for Server and Client Component boundaries, server-side uploads and deletes, and troubleshooting for frequent errors and TypeScript pitfalls. Use when developing Next.js apps with Cloudinary. | ## Install +Install the skills you need by name: + +```bash +npx skills add cloudinary-devs/skills --skill cloudinary-docs,cloudinary-transformations +npx skills add cloudinary-devs/skills --skill cloudinary-next +``` + +Or install everything: + ```bash npx skills add cloudinary-devs/skills ``` -Install all skills or select the ones you need. You can install globally or per project. +You can install globally or per project. > **💡 Tip:** [Cloudinary MCP servers](https://cloudinary.com/documentation/cloudinary_llm_mcp#mcp_servers) complement the Cloudinary Skills. Used together, skills and MCP servers improve the end-to-end workflow. For example, our skills spell out best practices such as using named transformations to apply the same transformation across many assets. Based on that, the model can decide which MCP tools to run to create named transformations that match your needs. diff --git a/scripts/check-discovery.sh b/scripts/check-discovery.sh new file mode 100755 index 0000000..4f112c8 --- /dev/null +++ b/scripts/check-discovery.sh @@ -0,0 +1,50 @@ +#!/usr/bin/env bash +# Every SKILL.md under skills/ must be discovered by the skills CLI, by name. +# The CLI drops a skill silently (bad frontmatter, wrong depth, missing +# name/description) whenever at least one other skill is found, so a count +# alone is not enough. Usage: scripts/check-discovery.sh [dir] (default: .) +set -u +dir="${1:-.}" + +# Expected: the frontmatter name of every SKILL.md. +expected=$(find "$dir/skills" -name SKILL.md | while IFS= read -r f; do + # First name: key inside the leading frontmatter block only. + sed -nE '1,/^---$/{s/^name:[[:space:]]*['"'"'"]?([^'"'"'"]+)['"'"'"]?[[:space:]]*$/\1/p;}' "$f" | head -1 +done | LC_ALL=C sort -u) + +if [ -z "$expected" ]; then + echo "::error::No SKILL.md with a name found under $dir/skills." + exit 1 +fi + +output=$(INSTALL_INTERNAL_SKILLS=1 NO_COLOR=1 npx -y skills@latest add "$dir" --list 2>&1 || true) +# Strip ANSI escapes (the CLI colours output whenever CI is set), then keep +# the lines between "Available Skills" and the trailing hint. Skill names are +# printed alone on a line; descriptions are multi-word, so a line that is a +# single slug token is a skill name. +found=$(printf '%s\n' "$output" \ + | sed -E 's/\x1b\[[0-9;?]*[a-zA-Z]//g' \ + | sed -n '/Available Skills/,/Use --skill/p' \ + | sed -E 's/^[^[:alnum:]]*//; s/[[:space:]]+$//' \ + | grep -xE '[a-z0-9][a-z0-9._-]*' \ + | LC_ALL=C sort -u) + +missing=$(comm -23 <(printf '%s\n' "$expected") <(printf '%s\n' "$found")) +extra=$(comm -13 <(printf '%s\n' "$expected") <(printf '%s\n' "$found")) + +if [ -n "$missing" ] || [ -n "$extra" ]; then + for n in $missing; do + echo "::error::Skill '$n' has a SKILL.md but is not discovered by the skills CLI (bad frontmatter, wrong depth, or missing name/description)." + done + for n in $extra; do + echo "::error::Skills CLI reports '$n' but no SKILL.md declares that name." + done + echo "--- expected ---"; printf '%s\n' "$expected" + echo "--- found ---"; printf '%s\n' "$found" + echo "--- cli output ---"; printf '%s\n' "$output" + exit 1 +fi + +count=$(printf '%s\n' "$expected" | wc -l | tr -d ' ') +echo "All $count skills discovered by name:" +printf ' %s\n' $expected diff --git a/scripts/check-links.sh b/scripts/check-links.sh new file mode 100755 index 0000000..0889eab --- /dev/null +++ b/scripts/check-links.sh @@ -0,0 +1,31 @@ +#!/usr/bin/env bash +# Every cloudinary.com link to a .md page or llms.txt must carry the +# install_source and referrer query params so docs traffic from skills is +# attributable. Usage: scripts/check-links.sh [dir] (default: skills) +set -u +dir="${1:-skills}" +url_re='https?://[A-Za-z0-9.-]*cloudinary\.com/[^[:space:])>"'"'"'`]*(\.md|/llms\.txt)([?#][^[:space:])>"'"'"'`]*)?' + +fail=0 +checked=0 +while IFS= read -r hit; do + checked=$((checked + 1)) + file="${hit%%:*}"; rest="${hit#*:}"; line="${rest%%:*}"; url="${rest#*:}" + missing="" + case "$url" in (*install_source=*) ;; (*) missing="install_source";; esac + case "$url" in (*referrer=*) ;; (*) missing="${missing:+$missing, }referrer";; esac + if [ -n "$missing" ]; then + echo "::error file=$file,line=$line::Missing $missing on $url" + fail=1 + fi +done < <(grep -rnoE "$url_re" "$dir" --include='*.md' --include='*.txt' --include='*.ts' --include='*.tsx' --include='*.js' | LC_ALL=C sort -u) + +if [ "$checked" -eq 0 ]; then + echo "::error::No cloudinary.com .md or llms.txt links found under '$dir'. The check matched nothing, so it cannot pass." + exit 1 +fi +if [ "$fail" -ne 0 ]; then + echo "Add '?install_source=skillspack&referrer=-skill' to each link above (see CONTRIBUTING.md)." + exit 1 +fi +echo "All $checked .md and llms.txt links carry install_source and referrer." diff --git a/scripts/check-versions.sh b/scripts/check-versions.sh new file mode 100755 index 0000000..b67a41c --- /dev/null +++ b/scripts/check-versions.sh @@ -0,0 +1,58 @@ +#!/usr/bin/env bash +# Any skill whose content changed between BASE and HEAD must bump +# metadata.version in its SKILL.md. Pure moves/renames (identical content) do +# not count as changes. New skills pass. Usage: scripts/check-versions.sh BASE HEAD +set -u +base="${1:?base ref}"; head="${2:?head ref}" + +version_at() { # -> version string or empty + git show "$1:$2" 2>/dev/null | sed -nE "s/^ version: *['\"]?([^'\"]+)['\"]?[[:space:]]*$/\1/p" | head -1 +} +skill_name_of() { # -> name of the skill (dir holding nearest SKILL.md), or empty + local d; d=$(dirname "$2") + while [ "$d" != "." ] && [ "$d" != "skills" ]; do + if git cat-file -e "$1:$d/SKILL.md" 2>/dev/null; then basename "$d"; return; fi + d=$(dirname "$d") + done +} +skill_md_in() { # -> path of that skill's SKILL.md in ref, or empty + git ls-tree -r --name-only "$1" -- skills | grep -E "(^|/)$2/SKILL\.md$" | head -1 +} + +# Collect the names of skills touched by non-identical changes. +changed_names() { + git diff --name-status -M100% "$base" "$head" -- skills | while read -r status p1 p2; do + case "$status" in + (R100) continue ;; # pure move, identical content + (D) skill_name_of "$base" "$p1" ;; # deletion: file lived in base + (R*) skill_name_of "$head" "$p2" ;; + (*) skill_name_of "$head" "$p1" ;; + esac + done | sort -u +} +names=$(changed_names) + +if [ -z "$names" ]; then + echo "No skill content changed." + exit 0 +fi + +fail=0 +for name in $names; do + head_md=$(skill_md_in "$head" "$name") + base_md=$(skill_md_in "$base" "$name") + if [ -z "$head_md" ]; then echo "skip $name: removed"; continue; fi + hv=$(version_at "$head" "$head_md") + if [ -z "$base_md" ]; then echo "ok $name: new skill ($hv)"; continue; fi + bv=$(version_at "$base" "$base_md") + if [ -z "$hv" ]; then + echo "::error file=$head_md::$name has no metadata.version"; fail=1; continue + fi + if [ "$bv" = "$hv" ]; then + echo "::error file=$head_md,line=7::$name changed but metadata.version is still $hv. Bump it (see CONTRIBUTING.md, Versioning)." + fail=1 + else + echo "ok $name: $bv -> $hv" + fi +done +exit $fail diff --git a/skills/cloudinary-next/SKILL.md b/skills/frameworks/cloudinary-next/SKILL.md similarity index 100% rename from skills/cloudinary-next/SKILL.md rename to skills/frameworks/cloudinary-next/SKILL.md diff --git a/skills/cloudinary-next/assets/app-router-signature-route.ts b/skills/frameworks/cloudinary-next/assets/app-router-signature-route.ts similarity index 100% rename from skills/cloudinary-next/assets/app-router-signature-route.ts rename to skills/frameworks/cloudinary-next/assets/app-router-signature-route.ts diff --git a/skills/cloudinary-next/assets/server-action-delete.ts b/skills/frameworks/cloudinary-next/assets/server-action-delete.ts similarity index 100% rename from skills/cloudinary-next/assets/server-action-delete.ts rename to skills/frameworks/cloudinary-next/assets/server-action-delete.ts diff --git a/skills/cloudinary-next/assets/server-action-upload.ts b/skills/frameworks/cloudinary-next/assets/server-action-upload.ts similarity index 100% rename from skills/cloudinary-next/assets/server-action-upload.ts rename to skills/frameworks/cloudinary-next/assets/server-action-upload.ts diff --git a/skills/cloudinary-next/references/api-decision-tree.md b/skills/frameworks/cloudinary-next/references/api-decision-tree.md similarity index 100% rename from skills/cloudinary-next/references/api-decision-tree.md rename to skills/frameworks/cloudinary-next/references/api-decision-tree.md diff --git a/skills/cloudinary-next/references/cldimage-transformations.md b/skills/frameworks/cloudinary-next/references/cldimage-transformations.md similarity index 100% rename from skills/cloudinary-next/references/cldimage-transformations.md rename to skills/frameworks/cloudinary-next/references/cldimage-transformations.md diff --git a/skills/cloudinary-next/references/cldimage.md b/skills/frameworks/cloudinary-next/references/cldimage.md similarity index 100% rename from skills/cloudinary-next/references/cldimage.md rename to skills/frameworks/cloudinary-next/references/cldimage.md diff --git a/skills/cloudinary-next/references/environment.md b/skills/frameworks/cloudinary-next/references/environment.md similarity index 100% rename from skills/cloudinary-next/references/environment.md rename to skills/frameworks/cloudinary-next/references/environment.md diff --git a/skills/cloudinary-next/references/imports.md b/skills/frameworks/cloudinary-next/references/imports.md similarity index 100% rename from skills/cloudinary-next/references/imports.md rename to skills/frameworks/cloudinary-next/references/imports.md diff --git a/skills/cloudinary-next/references/official-docs.md b/skills/frameworks/cloudinary-next/references/official-docs.md similarity index 100% rename from skills/cloudinary-next/references/official-docs.md rename to skills/frameworks/cloudinary-next/references/official-docs.md diff --git a/skills/cloudinary-next/references/og-images.md b/skills/frameworks/cloudinary-next/references/og-images.md similarity index 100% rename from skills/cloudinary-next/references/og-images.md rename to skills/frameworks/cloudinary-next/references/og-images.md diff --git a/skills/cloudinary-next/references/overlays.md b/skills/frameworks/cloudinary-next/references/overlays.md similarity index 100% rename from skills/cloudinary-next/references/overlays.md rename to skills/frameworks/cloudinary-next/references/overlays.md diff --git a/skills/cloudinary-next/references/project-setup.md b/skills/frameworks/cloudinary-next/references/project-setup.md similarity index 100% rename from skills/cloudinary-next/references/project-setup.md rename to skills/frameworks/cloudinary-next/references/project-setup.md diff --git a/skills/cloudinary-next/references/quick-checklist.md b/skills/frameworks/cloudinary-next/references/quick-checklist.md similarity index 100% rename from skills/cloudinary-next/references/quick-checklist.md rename to skills/frameworks/cloudinary-next/references/quick-checklist.md diff --git a/skills/cloudinary-next/references/responsive-images.md b/skills/frameworks/cloudinary-next/references/responsive-images.md similarity index 100% rename from skills/cloudinary-next/references/responsive-images.md rename to skills/frameworks/cloudinary-next/references/responsive-images.md diff --git a/skills/cloudinary-next/references/server-upload-delete.md b/skills/frameworks/cloudinary-next/references/server-upload-delete.md similarity index 100% rename from skills/cloudinary-next/references/server-upload-delete.md rename to skills/frameworks/cloudinary-next/references/server-upload-delete.md diff --git a/skills/cloudinary-next/references/signed-uploads.md b/skills/frameworks/cloudinary-next/references/signed-uploads.md similarity index 100% rename from skills/cloudinary-next/references/signed-uploads.md rename to skills/frameworks/cloudinary-next/references/signed-uploads.md diff --git a/skills/cloudinary-next/references/troubleshooting.md b/skills/frameworks/cloudinary-next/references/troubleshooting.md similarity index 100% rename from skills/cloudinary-next/references/troubleshooting.md rename to skills/frameworks/cloudinary-next/references/troubleshooting.md diff --git a/skills/cloudinary-next/references/typescript.md b/skills/frameworks/cloudinary-next/references/typescript.md similarity index 100% rename from skills/cloudinary-next/references/typescript.md rename to skills/frameworks/cloudinary-next/references/typescript.md diff --git a/skills/cloudinary-next/references/upload-widget.md b/skills/frameworks/cloudinary-next/references/upload-widget.md similarity index 100% rename from skills/cloudinary-next/references/upload-widget.md rename to skills/frameworks/cloudinary-next/references/upload-widget.md diff --git a/skills/cloudinary-next/references/video-player.md b/skills/frameworks/cloudinary-next/references/video-player.md similarity index 100% rename from skills/cloudinary-next/references/video-player.md rename to skills/frameworks/cloudinary-next/references/video-player.md diff --git a/skills/cloudinary-react/SKILL.md b/skills/frameworks/cloudinary-react/SKILL.md similarity index 100% rename from skills/cloudinary-react/SKILL.md rename to skills/frameworks/cloudinary-react/SKILL.md diff --git a/skills/cloudinary-react/references/signed-uploads.md b/skills/frameworks/cloudinary-react/references/signed-uploads.md similarity index 100% rename from skills/cloudinary-react/references/signed-uploads.md rename to skills/frameworks/cloudinary-react/references/signed-uploads.md diff --git a/skills/cloudinary-react/references/troubleshooting.md b/skills/frameworks/cloudinary-react/references/troubleshooting.md similarity index 100% rename from skills/cloudinary-react/references/troubleshooting.md rename to skills/frameworks/cloudinary-react/references/troubleshooting.md diff --git a/skills/cloudinary-react/references/typescript-patterns.md b/skills/frameworks/cloudinary-react/references/typescript-patterns.md similarity index 100% rename from skills/cloudinary-react/references/typescript-patterns.md rename to skills/frameworks/cloudinary-react/references/typescript-patterns.md diff --git a/skills/cloudinary-react/references/video-player.md b/skills/frameworks/cloudinary-react/references/video-player.md similarity index 100% rename from skills/cloudinary-react/references/video-player.md rename to skills/frameworks/cloudinary-react/references/video-player.md diff --git a/skills/cloudinary-docs/SKILL.md b/skills/platform/cloudinary-docs/SKILL.md similarity index 100% rename from skills/cloudinary-docs/SKILL.md rename to skills/platform/cloudinary-docs/SKILL.md diff --git a/skills/cloudinary-transformations/SKILL.md b/skills/platform/cloudinary-transformations/SKILL.md similarity index 100% rename from skills/cloudinary-transformations/SKILL.md rename to skills/platform/cloudinary-transformations/SKILL.md diff --git a/skills/cloudinary-transformations/references/advanced-features.md b/skills/platform/cloudinary-transformations/references/advanced-features.md similarity index 100% rename from skills/cloudinary-transformations/references/advanced-features.md rename to skills/platform/cloudinary-transformations/references/advanced-features.md diff --git a/skills/cloudinary-transformations/references/ai-transformations.md b/skills/platform/cloudinary-transformations/references/ai-transformations.md similarity index 100% rename from skills/cloudinary-transformations/references/ai-transformations.md rename to skills/platform/cloudinary-transformations/references/ai-transformations.md diff --git a/skills/cloudinary-transformations/references/debugging.md b/skills/platform/cloudinary-transformations/references/debugging.md similarity index 100% rename from skills/cloudinary-transformations/references/debugging.md rename to skills/platform/cloudinary-transformations/references/debugging.md diff --git a/skills/cloudinary-transformations/references/examples.md b/skills/platform/cloudinary-transformations/references/examples.md similarity index 100% rename from skills/cloudinary-transformations/references/examples.md rename to skills/platform/cloudinary-transformations/references/examples.md diff --git a/skills/cloudinary-transformations/references/named-transformations.md b/skills/platform/cloudinary-transformations/references/named-transformations.md similarity index 100% rename from skills/cloudinary-transformations/references/named-transformations.md rename to skills/platform/cloudinary-transformations/references/named-transformations.md diff --git a/skills/cloudinary-transformations/references/responsive-images.md b/skills/platform/cloudinary-transformations/references/responsive-images.md similarity index 100% rename from skills/cloudinary-transformations/references/responsive-images.md rename to skills/platform/cloudinary-transformations/references/responsive-images.md diff --git a/skills/cloudinary-transformations/references/transformation-costs.md b/skills/platform/cloudinary-transformations/references/transformation-costs.md similarity index 100% rename from skills/cloudinary-transformations/references/transformation-costs.md rename to skills/platform/cloudinary-transformations/references/transformation-costs.md diff --git a/skills/cloudinary-transformations/references/video-transformations.md b/skills/platform/cloudinary-transformations/references/video-transformations.md similarity index 100% rename from skills/cloudinary-transformations/references/video-transformations.md rename to skills/platform/cloudinary-transformations/references/video-transformations.md