⚡ Skill Hub
+ New Skill
← Back
Edit skill
Name
*
Slug
(leave blank to keep current)
Description
*
Category
Author
Tags
(comma-separated)
Content
*
(Markdown)
--- name: skill-hub-publisher description: "Publish or upload Markdown skills to the Skill Hub at https://skills.xn--2-4ga.no/. Use when the user asks to publish, upload, submit, or list a skill on the Skill Hub, or to verify a Skill Hub publication." --- # Skill Hub Publisher Publish a Markdown skill to the Skill Hub safely and verify the resulting public listing. ## Scope Use the live form at `https://skills.xn--2-4ga.no/skills/new`. Never assume that its Next.js server-action identifier remains stable. Publishing changes a public service. Only submit when the user has explicitly asked to publish. Never delete, replace, or overwrite an existing listing without separate explicit approval. ## Required inputs Resolve these values before submitting: - `name`: human-readable skill name - `slug`: concise lowercase kebab-case identifier - `description`: one clear sentence describing purpose and trigger conditions - `category`: requested category, or a context-appropriate category approved by the user - `author`: requested author - `tags`: comma-separated, relevant, non-duplicated tags - `content_file`: local Markdown file to publish Treat `name`, `description`, and non-empty Markdown content as mandatory because the live form marks them required. Also require an explicit slug, category, author, and at least one tag so the public listing is complete and verification is deterministic. Do not publish credentials, tokens, private URLs, personal data, machine-specific paths, temporary action identifiers, or unrelated local instructions. ## Preflight validation Set values explicitly. Use the ASCII/Punycode host in scripts so the URL is shell-safe. ```sh BASE_URL='https://skills.xn--2-4ga.no' NAME='Example Skill' SLUG='example-skill' DESCRIPTION='Describe what the skill does and when to use it.' CATEGORY='Productivity' AUTHOR='Anonymous' TAGS='example, productivity' CONTENT_FILE='/absolute/path/to/SKILL.md' ``` Fail before network submission unless all values are valid: ```sh set -eu for value in "$NAME" "$SLUG" "$DESCRIPTION" "$CATEGORY" "$AUTHOR" "$TAGS"; do [ -n "$value" ] || { printf '%s\n' 'Required metadata is empty.' >&2; exit 1; } done case "$SLUG" in *[!a-z0-9-]*|'') printf '%s\n' 'Slug must contain only lowercase ASCII letters, digits, and hyphens.' >&2; exit 1 ;; esac case "$SLUG" in -*|*-) printf '%s\n' 'Slug must not start or end with a hyphen.' >&2; exit 1 ;; esac [ -f "$CONTENT_FILE" ] && [ -s "$CONTENT_FILE" ] || { printf '%s\n' 'Content file is missing or empty.' >&2 exit 1 } grep -q '^---$' "$CONTENT_FILE" || { printf '%s\n' 'Content does not appear to contain skill YAML frontmatter.' >&2 exit 1 } grep -q '^name:' "$CONTENT_FILE" || { printf '%s\n' 'Content frontmatter is missing name.' >&2 exit 1 } grep -q '^description:' "$CONTENT_FILE" || { printf '%s\n' 'Content frontmatter is missing description.' >&2 exit 1 } ``` Review the complete content for secrets and transient values. Do not rely only on pattern matching, but use a focused scan as a backstop: ```sh if grep -Ein '(api[_-]?key|access[_-]?token|authoriza[t]ion:|bearer [A-Za-z0-9._-]+|password[[:space:]]*=|\$ACTION_ID_[0-9a-f]{20,})' "$CONTENT_FILE"; then printf '%s\n' 'Potential secret or temporary action identifier found; review before publishing.' >&2 exit 1 fi ``` ## Inspect the live form Fetch a fresh copy immediately before every submission and extract the current hidden server-action field dynamically: ```sh WORK_DIR="$(mktemp -d)" trap 'rm -rf "$WORK_DIR"' EXIT HUP INT TERM FORM_HTML="$WORK_DIR/new.html" curl -fsS "$BASE_URL/skills/new" -o "$FORM_HTML" ACTION_FIELD="$(grep -oE 'name="\$ACTION_ID_[0-9a-f]+"' "$FORM_HTML" | head -n 1 | cut -d '"' -f 2)" case "$ACTION_FIELD" in '$ACTION_ID_'[0-9a-f]*) ;; *) printf '%s\n' 'Could not extract a valid current Next.js action field.' >&2; exit 1 ;; esac for field in name slug description category author tags content; do grep -qE '(^|[[:space:]])name="'"$field"'"' "$FORM_HTML" || { printf 'Expected form field is missing: %s\n' "$field" >&2 exit 1 } done ``` Do not copy an action identifier from this skill, a previous run, logs, or another session. Abort if the form shape has changed. ## Prevent collisions Check the requested public URL before POSTing: ```sh EXISTING_STATUS="$(curl -sS -o "$WORK_DIR/existing.html" -w '%{http_code}' "$BASE_URL/skills/$SLUG")" case "$EXISTING_STATUS" in 404) ;; 200) printf 'Slug already exists: %s/skills/%s\n' "$BASE_URL" "$SLUG" >&2; exit 2 ;; *) printf 'Unexpected slug-check status: %s\n' "$EXISTING_STATUS" >&2; exit 1 ;; esac ``` If the slug exists, stop and report it. Do not probe for edit/delete APIs, choose a surprise alternate slug, or submit replacement content. Obtain explicit user approval for any overwrite, deletion, or changed slug. ## Submit once Use multipart form data and the freshly extracted action field. Send text metadata with `--form-string` so leading characters and file-like values are not reinterpreted by curl. ```sh HEADERS="$WORK_DIR/headers.txt" RESPONSE_BODY="$WORK_DIR/response.html" POST_STATUS="$(curl -sS \ -D "$HEADERS" \ -o "$RESPONSE_BODY" \ -w '%{http_code}' \ -X POST "$BASE_URL/skills/new" \ -F "$ACTION_FIELD=" \ --form-string "name=$NAME" \ --form-string "slug=$SLUG" \ --form-string "description=$DESCRIPTION" \ --form-string "category=$CATEGORY" \ --form-string "author=$AUTHOR" \ --form-string "tags=$TAGS" \ -F "content=<$CONTENT_FILE;type=text/plain")" LOCATION="$(awk 'tolower($1) == "location:" {sub(/\r$/, "", $2); print $2}' "$HEADERS" | tail -n 1)" EXPECTED_PATH="/skills/$SLUG" case "$POST_STATUS" in 301|302|303|307|308) ;; *) printf 'Unexpected POST status: %s\n' "$POST_STATUS" >&2; exit 1 ;; esac case "$LOCATION" in "$EXPECTED_PATH"|"$BASE_URL$EXPECTED_PATH") ;; *) printf 'Unexpected redirect: %s\n' "$LOCATION" >&2; exit 1 ;; esac ``` Do not blindly retry after an ambiguous POST failure. First recheck the public slug: the server may have created the listing even if the response was interrupted. ## Verify the public listing A redirect is necessary but not sufficient. Fetch the public page separately and require HTTP 200: ```sh PUBLIC_URL="$BASE_URL/skills/$SLUG" PUBLIC_HTML="$WORK_DIR/public.html" PUBLIC_STATUS="$(curl -sS -o "$PUBLIC_HTML" -w '%{http_code}' "$PUBLIC_URL")" [ "$PUBLIC_STATUS" = 200 ] || { printf 'Published page returned HTTP %s\n' "$PUBLIC_STATUS" >&2 exit 1 } ``` Verify the rendered page includes all expected metadata and multiple distinctive content markers. Choose markers from headings or phrases in the content, not generic site chrome. ```sh html_text() { sed 's/<[^>]*>/ /g; s/"/"/g; s/'/'"'"'/g; s/&/\&/g' "$PUBLIC_HTML" \ | tr -s '[:space:]' ' ' } PAGE_TEXT="$(html_text)" for expected in "$NAME" "$DESCRIPTION" "$CATEGORY" "$AUTHOR"; do printf '%s' "$PAGE_TEXT" | grep -Fq "$expected" || { printf 'Published page is missing expected value: %s\n' "$expected" >&2 exit 1 } done OLD_IFS=$IFS IFS=, for tag in $TAGS; do tag="$(printf '%s' "$tag" | sed 's/^[[:space:]]*//; s/[[:space:]]*$//')" printf '%s' "$PAGE_TEXT" | grep -Fq "$tag" || { printf 'Published page is missing tag: %s\n' "$tag" >&2 exit 1 } done IFS=$OLD_IFS for marker in 'DISTINCTIVE CONTENT MARKER ONE' 'DISTINCTIVE CONTENT MARKER TWO'; do printf '%s' "$PAGE_TEXT" | grep -Fq "$marker" || { printf 'Published page is missing content marker: %s\n' "$marker" >&2 exit 1 } done ``` Replace the two placeholder marker strings before running verification. When HTML entity decoding or client rendering makes text extraction unreliable, inspect the fetched HTML or use a browser; do not weaken verification merely to produce a pass. ## Completion report Claim success only after all verification checks pass. Return: - canonical public URL - name and slug - description - category and author - normalized tags - content markers verified If verification fails after creation, report the listing as created but unverified, include the exact mismatch, and do not retry or modify it without user approval. bump
Save changes
Cancel