Install
$ agentstack add skill-prosusai-prism-publish-skills ✓ scanned · ✓ verified — works with Claude Code, Cursor, and more.
Security review
✓ PassedNo 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.
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), orprism 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.jsonandSKILL.md - A registry must be configured in
~/.prism/registries.json(or legacy~/.prism/config.json) - The
REGISTRY_TOKENenvironment variable or per-registry token inregistries.jsonmust 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.0to avoid Cloudflare bot detection - The
contentfield contains the raw SKILL.md text (not base64 encoded) - All skills in a batch should have the same
repositoryvalue
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_hashis the first 12 hex characters of SHA256(plugin.jsonbytes +SKILL.mdbytes)published_atis 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 legacyconfig.jsonregistry_url). Use--registry NAMEor the default registry. - Auth token resolution -- checks
REGISTRY_TOKENenv var first (backward compat), then per-registry token fromregistries.json. The resolved token is passed to the publish request directly. - Delta tracking --
.published.jsontracks content hashes so only changed skills are republished. This avoids unnecessary API calls and makes publish idempotent. - Atomic writes --
.published.jsonis 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.
- Author: ProsusAI
- Source: ProsusAI/prism
- License: Apache-2.0
Install and usage instructions live in the source repository linked above.
Reviews
No reviews yet — be the first.
Write a review
Versions
- v0.1.0 Imported from the upstream source.