AgentStack
SKILL verified Apache-2.0 Self-run

Publish Skills

skill-prosusai-prism-publish-skills · by ProsusAI

Publish changed skills from _analysis/extracted_skills_codebase/ and _analysis/extracted_skills_history/ to the Prism registry via the Worker API with delta tracking. Only publishes skills whose content has changed since last publish unless --all is specified.

No reviews yet
0 installs
6 views
0.0% view→install

Install

$ agentstack add skill-prosusai-prism-publish-skills

✓ scanned · ✓ verified — works with Claude Code, Cursor, and more.

Security review

✓ Passed

No issues found. Passed automated security review. · v0.1.0 How review works →

  • Prompt-injection patterns
  • Secret / credential exfiltration
  • Dangerous shell & filesystem operations
  • Untrusted network calls
  • Known-malicious package signatures

What it can access

  • Network access Used
  • Filesystem access Used
  • Shell / process execution No
  • Environment & secrets No
  • Dynamic code execution No

From automated source analysis of v0.1.0. “Used” means the capability is present in the source — more access means more to trust, not that it’s unsafe.

Are you the author of Publish Skills? Claim this listing to set pricing, connect Stripe payouts, and keep 70% of every sale.
Sign up to claim

About

When to use this skill

Use when the user wants to publish extracted or promoted skills to a Prism skill registry. Invoked as /publish-skills (Claude Code) or @publish-skills (Cursor), optionally with flags: --all (publish everything regardless of delta state) or --registry NAME (target a named registry).

Prerequisites

  • Skills must exist in _analysis/extracted_skills_codebase/ (created by /extract-skills (Claude Code) or @extract-skills (Cursor), or prism promote) and/or _analysis/extracted_skills_history/ (created by /run-history-pipeline (Claude Code) or @run-history-pipeline (Cursor))
  • Each skill directory must contain both plugin.json and SKILL.md
  • A registry must be configured in ~/.prism/registries.json (or legacy ~/.prism/config.json)
  • The REGISTRY_TOKEN environment variable or per-registry token in registries.json must be available

Flags

| Flag | Effect | |------|--------| | --all | Publish all valid skills, ignoring delta state (force republish) | | --registry NAME | Target a specific named registry from registries.json. If omitted, uses the default registry. The target must have writable: true. |

Instructions

Step 1 -- Resolve target registry

Resolve the target registry from ~/.prism/registries.json (with fallback to legacy ~/.prism/config.json). If --registry NAME was specified, use that name; otherwise use the default registry.

python3 -c "
import json, os, sys
reg_path = os.path.expanduser('~/.prism/registries.json')
try:
    with open(reg_path) as f:
        data = json.load(f)
except (FileNotFoundError, json.JSONDecodeError):
    data = {'registries': [], 'default': None}
# Fall back to config.json migration
if not data.get('registries'):
    cfg_path = os.path.expanduser('~/.prism/config.json')
    try:
        with open(cfg_path) as f:
            cfg = json.load(f)
        url = cfg.get('registry_url', '')
        if url:
            print(json.dumps({'name': 'default', 'url': url, 'token': '', 'writable': True}))
            sys.exit(0)
    except: pass
    print('NOT_CONFIGURED')
    sys.exit(0)
# Resolve target: --registry NAME or default
target_name = None  #  tell user to run `prism registry add`
- `NO_DEFAULT` -> tell user to run `prism registry default NAME`
- `READ_ONLY:name` -> tell user the registry is read-only, cannot publish
- `NOT_FOUND:name` -> registry not found in registries.json

If the result is valid JSON, extract the `name`, `url`, and `token` fields from the JSON result. Resolve the final API token:
1. Check `REGISTRY_TOKEN` environment variable (backward compat, takes precedence)
2. If not set, use the `token` field from the resolved registry entry

Store the resolved token for use in Step 4. If both sources are empty, tell the user:

> **No API token found.** Set the `REGISTRY_TOKEN` environment variable or add a token to your registry:
> ```
> prism registry add NAME --url URL --token YOUR_TOKEN
> ```
> Or generate one: `prism registry token create NAME`

Then stop.

### Step 2 -- Discover valid skills

Scan both `_analysis/extracted_skills_codebase/` (from `/extract-skills` (Claude Code) or `@extract-skills` (Cursor), and `prism promote`) and `_analysis/extracted_skills_history/` (from `/run-history-pipeline` (Claude Code) or `@run-history-pipeline` (Cursor)) for skill directories. Each valid skill directory must contain both `plugin.json` and `SKILL.md`.

```bash
for base_dir in _analysis/extracted_skills_codebase _analysis/extracted_skills_history; do
    [ -d "$base_dir" ] || continue
    for dir in "$base_dir"/*/; do
        [ -d "$dir" ] || continue
        if [ -f "$dir/plugin.json" ] && [ -f "$dir/SKILL.md" ]; then
            echo "VALID: $dir"
        else
            echo "SKIP (missing files): $dir"
        fi
    done
done

For each valid skill, read plugin.json and verify it has the required fields: name, description, author, repository, category, source, commit_date, source_hash.

python3  **All skills up to date.** Use `--all` to force republish.

Then stop.

### Step 4 -- Publish to registry

Build the request payload and POST to the registry Worker API. Use Python for the HTTP call (not curl/jq) to handle JSON encoding of SKILL.md content with special characters correctly.

```python
python3 << 'PYEOF'
import json, os, urllib.request, urllib.error

# Configuration (filled from Steps 1-3)
REGISTRY_URL = ""       # <-- from Step 1
TOKEN = ""              # <-- resolved token from Step 1 (REGISTRY_TOKEN env var or registry entry token)
SKILLS_TO_PUBLISH = []  # <-- list of (skill_name, skill_dir) from Step 3

skills_payload = []
for skill_name, skill_dir in SKILLS_TO_PUBLISH:
    with open(os.path.join(skill_dir, "plugin.json")) as f:
        plugin = json.load(f)
    with open(os.path.join(skill_dir, "SKILL.md")) as f:
        content = f.read()

    skills_payload.append({
        "name": plugin["name"],
        "description": plugin["description"],
        "author": plugin["author"],
        "repository": plugin["repository"],
        "category": plugin["category"],
        "source": plugin["source"],
        "commit_date": plugin["commit_date"],
        "source_hash": plugin["source_hash"],
        "content": content,
    })

payload = json.dumps({"skills": skills_payload}).encode("utf-8")

req = urllib.request.Request(
    f"{REGISTRY_URL}/api/skills/publish",
    data=payload,
    headers={
        "Authorization": f"Bearer {TOKEN}",
        "Content-Type": "application/json",
        "User-Agent": "Prism/1.0",
    },
    method="POST",
)

try:
    with urllib.request.urlopen(req) as resp:
        result = json.loads(resp.read().decode())
        print(f"HTTP {resp.status}: Published {result.get('published', len(skills_payload))} skill(s)")
        if result.get("errors"):
            for err in result["errors"]:
                print(f"  Error: {err}")
except urllib.error.HTTPError as e:
    body = e.read().decode()
    print(f"HTTP {e.code}: {body}")
    if e.code in (401, 403):
        print("Registry authentication failed. Check your token (REGISTRY_TOKEN env var or registry entry).")
except urllib.error.URLError as e:
    print(f"Connection failed: {e.reason}")
    print(f"Check that registry_url is correct: {REGISTRY_URL}")
PYEOF

Important:

  • The API endpoint is POST {registry_url}/api/skills/publish
  • Auth header: Authorization: Bearer {TOKEN} (resolved token from Step 1)
  • Include User-Agent: Prism/1.0 to avoid Cloudflare bot detection
  • The content field contains the raw SKILL.md text (not base64 encoded)
  • All skills in a batch should have the same repository value

Step 5 -- Update delta tracking

After a successful publish (HTTP 2xx), update _analysis/.published.json with the new content hashes and timestamps. Write atomically using a temp file and rename.

python3 << 'PYEOF'
import json, os, hashlib
from datetime import datetime, timezone

published_path = "_analysis/.published.json"
tmp_path = "_analysis/.published.json.tmp"

# Load existing state
if os.path.isfile(published_path):
    with open(published_path) as f:
        published = json.load(f)
else:
    published = {}

# Update entries for each published skill
PUBLISHED_SKILLS = []  # <-- list of (skill_name, content_hash) from Steps 3-4

now = datetime.now(timezone.utc).strftime("%Y-%m-%dT%H:%M:%SZ")

REGISTRY_NAME = ""  # <-- set to resolved registry name from Step 1

for skill_name, content_hash in PUBLISHED_SKILLS:
    if skill_name not in published:
        published[skill_name] = {}
    published[skill_name][REGISTRY_NAME] = {
        "published_at": now,
        "content_hash": content_hash,
    }

# Atomic write: write to .tmp then rename
with open(tmp_path, "w") as f:
    json.dump(published, f, indent=2)
    f.write("\n")
os.rename(tmp_path, published_path)

print(f"Updated {published_path} with {len(PUBLISHED_SKILLS)} entry/entries")
PYEOF

Step 6 -- Summary

Print a summary of the publish operation:

Published {N} skill(s) to {registry_url}
Skipped {M} skill(s) (unchanged)
Failed {F} skill(s)
  • If any skills were published: "Published {N} skills to {registry_url}"
  • If all skills were unchanged: "All skills up to date. Use --all to force republish."
  • If there were failures: list each failed skill name and error

.published.json format reference

The delta tracking file at _analysis/.published.json has this structure:

{
  "skill-name": {
    "default": {
      "published_at": "2026-04-14T12:00:00Z",
      "content_hash": "a1b2c3d4e5f6"
    }
  }
}
  • Top-level keys are skill names
  • Second-level keys are registry names (e.g., "team", "community", or "default" for legacy/migrated registries)
  • content_hash is the first 12 hex characters of SHA256(plugin.json bytes + SKILL.md bytes)
  • published_at is the UTC timestamp of the last successful publish

Important differences from previous approaches

  • Worker-only publishing -- skills are published via the registry Worker API (POST /api/skills/publish). There is no GitHub-direct publishing path.
  • Registry from registries.json -- the target registry is resolved from ~/.prism/registries.json (with fallback to legacy config.json registry_url). Use --registry NAME or the default registry.
  • Auth token resolution -- checks REGISTRY_TOKEN env var first (backward compat), then per-registry token from registries.json. The resolved token is passed to the publish request directly.
  • Delta tracking -- .published.json tracks content hashes so only changed skills are republished. This avoids unnecessary API calls and makes publish idempotent.
  • Atomic writes -- .published.json is written via temp file + rename to prevent corruption.

Output rules

  • Report skill counts (published, skipped, failed) as a clear summary
  • Show the registry URL that was published to
  • If using --all, note that delta checking was bypassed
  • Do not print full SKILL.md contents in the output

Source & license

This open-source skill is cataloged on AgentStack and links to its original source — we do not rehost the code.

Install and usage instructions live in the source repository linked above.

Reviews

No reviews yet — be the first.

Versions

  • v0.1.0 Imported from the upstream source.