Skill Hub Publisher
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 nameslug: concise lowercase kebab-case identifierdescription: one clear sentence describing purpose and trigger conditionscategory: requested category, or a context-appropriate category approved by the userauthor: requested authortags: comma-separated, relevant, non-duplicated tagscontent_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.
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:
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:
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:
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:
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.
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:
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.
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
Danger zone