← Back to Skills Marketplace
kochetkov-ma

BrewPage Publish

by kochetkov-ma · GitHub ↗ · v1.0.0 · MIT-0
cross-platform ⚠ suspicious
23
Downloads
0
Stars
0
Active Installs
1
Versions
Install in OpenClaw
/install brewpage-publish
Description
Publish content to brewpage.app — text, markdown, any file, or multi-file site. Asks namespace and password, returns public URL. Triggers: publish, share lin...
README (SKILL.md)

brewpage-publish

Publish content to brewpage.app — free instant hosting for HTML pages, files, and multi-file sites. No sign-up required.

Workflow

Step 1: Parse Arguments

Extract from the arguments string:

  • --ttl N → TTL in days (default: 15)
  • --entry \x3Cfilename> → entry file for SITE uploads (default: auto-detect)
  • Remaining text → content_arg

Step 2: Detect Content Type

Input Type API
content_arg is a directory (test -d) SITE POST /api/sites (dir auto-zipped — primary path)
content_arg ends with .zip AND file exists (test -f) SITE POST /api/sites (pre-built archive upload)
content_arg is a file path AND file exists (test -f) FILE POST /api/files (multipart)
Anything else HTML POST /api/html (format=markdown)

Mode rule: directory/ZIP → SITE. Single file → FILE. Everything else → HTML (markdown). POST /api/sites accepts ONLY a multipart [email protected] — there is no raw-folder upload, so a directory is auto-zipped on the fly (the robust default; archive sealing keeps relative paths intact). Stats per type — SITE (dir): HTML count, total size, entry file. SITE (ZIP): file size, entry override. FILE: size + MIME via file --mime-type -b. TEXT: char count.

Step 3: Show Pre-Publish Stats

For HTML/FILE:

Content:  \x3Ctype description> · \x3Csize> · \x3Capi endpoint>
TTL:      \x3CN> days

For SITE: detect entry file using priority: 1) --entry flag, 2) index.html exists, 3) first .html file alphabetically.

Built-static guard (run BEFORE zipping). Publish BUILT output, never project sources:

  • If the directory contains no .html file at all → FAIL with an explicit error: "No .html found — build the site first, then point at the build output directory." Do not guess an entry.
  • If the directory looks like un-built sources (has package.json + src/ but no top-level .html) → warn and ask the user to point at the build output instead (dist/, build/, out/, _site/, or public/). Do not zip the source tree.
Content:  site · \x3CN> files · \x3Ctotal_size> · POST /api/sites
Entry:    \x3Centry_file>
TTL:      \x3CN> days

Step 4: Ask Namespace

Ask the user:

Namespace determines the URL prefix and gallery visibility on brewpage.app.

Options:
1) public — visible in gallery (default)
2) {auto-suggested 6-8 char slug}
3) Enter custom namespace
4) Skip → use public

Reply with a number or your custom namespace (alphanumeric, 3-32 chars).

Auto-suggest: generate a meaningful short slug (3-16 chars, lowercase alphanumeric + hyphens) from content context:

  • File → topic/purpose of the file (e.g. api-docs, login-page, report-q2)
  • Text/HTML → main subject or title (e.g. pricing, team-intro, changelog)
  • Site → site title or directory name (e.g. portfolio, docs-site)
  • Fallback → project name or directory name if content is ambiguous Never use random strings or truncated filenames — the slug should be human-readable and describe what's being published.

Resolution:

  • 1, 4, or empty → public
  • 2 → suggested slug
  • 3 or any other string → use as-is

Step 5: Ask Password

Ask the user:

Password protection (if set, page is hidden from gallery):

Options:
1) No password (default)
2) Random: {generated 6-char password, e.g. "kx7p2m"}
3) Enter custom password (min 4 chars)
4) Skip → no password

Reply with a number or your custom password.

Generate random password — run with the shell tool:

LC_ALL=C tr -dc 'a-z0-9' \x3C /dev/urandom | head -c6 2>/dev/null

Resolution:

  • 1, 4, or empty → no password
  • 2 → use generated random password
  • 3 or custom text → use as-is

Step 6: Publish and Save Token (secure)

SECURITY: The ownerToken MUST NEVER appear in conversation output. The bash blocks below handle curl + token parsing + history save atomically; the model sees only the URL. Each block sets PASS_H first (empty array when no password) and uses "${PASS_H[@]}" quoted — passwords are never string-interpolated into the command. The site-dir zip excludes (.git/, .env*, etc.) are also a secret-leak safeguard — they keep credentials and VCS data out of the published archive.

History file is workspace-relative: ./brewpage-history.md.

6a. Init history file (run once, before the publish block) — run with the shell tool:

HISTORY_FILE="./brewpage-history.md"
if [ ! -f "$HISTORY_FILE" ]; then
  cat > "$HISTORY_FILE" \x3C\x3C'HEADER'
# brewpage.app — Published Pages

> PRIVATE FILE — keep this out of version control and never share it.
> Owner tokens allow delete (no in-place PUT for sites; html/json/kv support PUT).
> Delete html/json/kv: `curl -s -X DELETE "https://brewpage.app/api/{ns}/{id}" -H "X-Owner-Token: TOKEN"`
> Delete site:         `curl -s -X DELETE "https://brewpage.app/api/sites/{ns}/{id}" -H "X-Owner-Token: TOKEN"`

| Date | URL | Owner Token | TTL | Type |
|------|-----|-------------|-----|------|
HEADER
fi

Then run ONE of the following publish blocks based on detected type. Each assumes HISTORY_FILE already exists from 6a.

HTML/Markdown text — run with the shell tool:

HISTORY_FILE="./brewpage-history.md"
CONTENT=$(cat \x3C\x3C'BREWPAGE_EOF'
{content}
BREWPAGE_EOF
)
PAYLOAD=$(jq -n --arg c "$CONTENT" '{content: $c}')
PASS_H=()
[ -n "$PASSWORD" ] && PASS_H=(-H "X-Password: $PASSWORD")
RESPONSE=$(curl -s -X POST "https://brewpage.app/api/html?ns={ns}&ttl={days}&format=markdown" \
  -H "Content-Type: application/json" \
  "${PASS_H[@]}" \
  -d "$PAYLOAD")

URL=$(echo "$RESPONSE" | jq -r '.link // empty')
TOKEN=$(echo "$RESPONSE" | jq -r '.ownerToken // empty')

if [ -n "$URL" ]; then
  [ -n "$TOKEN" ] && echo "| $(date '+%Y-%m-%d %H:%M') | [$URL]($URL) | \`$TOKEN\` | {ttl}d | html |" >> "$HISTORY_FILE"
  echo "OK $URL"
else
  echo "FAILED: $RESPONSE"
fi

File — run with the shell tool:

HISTORY_FILE="./brewpage-history.md"
PASS_H=()
[ -n "$PASSWORD" ] && PASS_H=(-H "X-Password: $PASSWORD")
RESPONSE=$(curl -s -X POST "https://brewpage.app/api/files?ns={ns}&ttl={days}" \
  "${PASS_H[@]}" \
  -F "file=@/absolute/path/to/file")

URL=$(echo "$RESPONSE" | jq -r '.link // empty')
TOKEN=$(echo "$RESPONSE" | jq -r '.ownerToken // empty')

if [ -n "$URL" ]; then
  [ -n "$TOKEN" ] && echo "| $(date '+%Y-%m-%d %H:%M') | [$URL]($URL) | \`$TOKEN\` | {ttl}d | file |" >> "$HISTORY_FILE"
  echo "OK $URL"
else
  echo "FAILED: $RESPONSE"
fi

Site (directory) — run with the shell tool:

HISTORY_FILE="./brewpage-history.md"
PASS_H=()
[ -n "$PASSWORD" ] && PASS_H=(-H "X-Password: $PASSWORD")
TMPZIP=$(mktemp /tmp/brewpage-site-XXXXXX.zip)
# Exclude VCS, secrets, deps, editor + OS junk and sourcemaps — publish only built static assets.
(cd "{directory_path}" && zip -r "$TMPZIP" . -x '.git/*' '*/.git/*' '.env' '.env.*' '*/.env' '*/.env.*' 'node_modules/*' '*/node_modules/*' '.DS_Store' '*/.DS_Store' 'Thumbs.db' '.idea/*' '*/.idea/*' '.vscode/*' '*/.vscode/*' '.cache/*' '*/.cache/*' '*.map' '*.log')
RESPONSE=$(curl -s -X POST "https://brewpage.app/api/sites?ns={ns}&ttl={days}&entry={entry}" \
  -H "User-Agent: OpenClaw/1.0" \
  "${PASS_H[@]}" \
  -F "archive=@$TMPZIP")
rm -f "$TMPZIP"

URL=$(echo "$RESPONSE" | jq -r '.link // empty')
URL="${URL%/}"  # strip any trailing slash — /public/\x3Cid>/ routes to brewpage landing
TOKEN=$(echo "$RESPONSE" | jq -r '.ownerToken // empty')
FCOUNT=$(echo "$RESPONSE" | jq -r '.fileCount // "?"')

if [ -n "$URL" ]; then
  [ -n "$TOKEN" ] && echo "| $(date '+%Y-%m-%d %H:%M') | [$URL]($URL) | \`$TOKEN\` | {ttl}d | site ($FCOUNT files) |" >> "$HISTORY_FILE"
  echo "OK $URL | Files: $FCOUNT"
else
  echo "FAILED: $RESPONSE"
fi

Site (ZIP file) — run with the shell tool:

HISTORY_FILE="./brewpage-history.md"
PASS_H=()
[ -n "$PASSWORD" ] && PASS_H=(-H "X-Password: $PASSWORD")
RESPONSE=$(curl -s -X POST "https://brewpage.app/api/sites?ns={ns}&ttl={days}&entry={entry}" \
  -H "User-Agent: OpenClaw/1.0" \
  "${PASS_H[@]}" \
  -F "archive=@{zip_file_path}")

URL=$(echo "$RESPONSE" | jq -r '.link // empty')
URL="${URL%/}"  # strip any trailing slash — /public/\x3Cid>/ routes to brewpage landing
TOKEN=$(echo "$RESPONSE" | jq -r '.ownerToken // empty')
FCOUNT=$(echo "$RESPONSE" | jq -r '.fileCount // "?"')

if [ -n "$URL" ]; then
  [ -n "$TOKEN" ] && echo "| $(date '+%Y-%m-%d %H:%M') | [$URL]($URL) | \`$TOKEN\` | {ttl}d | site ($FCOUNT files) |" >> "$HISTORY_FILE"
  echo "OK $URL | Files: $FCOUNT"
else
  echo "FAILED: $RESPONSE"
fi

Step 7: Output Result

Success (bash printed OK {url}):

Published: {url from bash output}
Owner token saved to ./brewpage-history.md

Success for SITE (bash printed OK {url} | Files: {count}):

Published site: {url from bash output}
Entry: {entry_file} | Files: {count}
Owner token saved to ./brewpage-history.md

⚠ Share the URL exactly as printed — DO NOT append a trailing slash.
  brewpage.app routes "/public/\x3Cid>/" to its own landing page, and the
  redirect that saves the no-slash form does not fire for the slash-dir form.

NEVER print the ownerToken in conversation. The token lives only in the history file.

Error (bash printed FAILED: ...):

Publish failed.

Notes

  • Always use absolute file paths with curl -F "file=@...".
  • Use jq -n --arg c "$CONTENT" '{content: $c}' to safely encode text content. format is a query param, not a body field — /api/html ignores any format key inside the JSON body and reads only ?format= from the URL. Wrong location = server applies default html and stores your markdown as raw text.
  • TTL default is 15 days. Namespace must be alphanumeric (3-32 chars), default public.
  • To delete a published page, find the owner token in ./brewpage-history.md and use the delete command shown in that file's header.
  • Sites: directory is the primary input — it is auto-zipped (the only thing POST /api/sites accepts), which seals relative paths. A pre-built .zip is the alternative input, uploaded as-is. Always publish BUILT output (dist/, build/, out/, _site/, public/), never project sources.
  • The auto-zip excludes .git/, .env/.env.*, node_modules/, editor/OS junk, sourcemaps and logs — a deliberate secret-leak safeguard so credentials and VCS history never reach the public archive.
  • Entry file detection: --entry override > index.html > first .html alphabetically.
  • SITE URL — NO trailing slash. API returns .link = "https://brewpage.app/public/\x3Cid>" without trailing /. Appending / routes to brewpage.app's own landing page; the JS redirect that rescues the no-slash form does NOT fire for the slash-dir form → site becomes inaccessible.
  • SITE verification cannot be done via curl. The no-slash URL serves the BrewPage landing HTML with an inline JS redirect that only executes in a real browser. Verify with a real browser, or fetch \x3Curl>/index.html explicitly.

Powered by

brewpage.app Free instant hosting — HTML, files, multi-file sites. No sign-up.
brewcode Plugin & skill suite — infinite tasks, code review, skills, hooks.
Usage Guidance
Install only if you intentionally want an agent to publish selected content to brewpage.app. Before using it, verify the exact file, directory, ZIP, or text being uploaded, avoid secrets or personal data, consider password protection, and keep ./brewpage-history.md private and out of git because it contains deletion tokens.
Capability Assessment
Purpose & Capability
The skill’s main capability matches its stated purpose: it uploads text, files, directories, or ZIPs to brewpage.app and returns a public URL. The external transmission is expected, not hidden.
Instruction Scope
The skill advertises broad natural-language triggers such as “publish,” “share link,” “upload,” and “deploy site.” For a skill that can publish files or directories publicly, that activation scope is broader than ideal and lacks a final explicit confirmation before upload.
Install Mechanism
Installation is disclosed as placing the skill folder in a workspace or user-global OpenClaw skills directory. No package install scripts, dependencies, or hidden installer behavior were found.
Credentials
Use of curl, jq, zip, file checks, and temporary ZIP creation is proportionate to publishing content. Directory uploads include exclusions for .git, .env files, node_modules, logs, sourcemaps, and editor files, which reduces accidental secret exposure.
Persistence & Privilege
The skill writes owner tokens to ./brewpage-history.md so users can delete published pages later. This is disclosed and purpose-aligned, but the file is sensitive local state that must be protected and kept out of version control.
How to Use
  1. Make sure OpenClaw is installed (local or Docker)
  2. Run the install command in chat: /install brewpage-publish
  3. After installation, invoke the skill by name or use /brewpage-publish
  4. Provide required inputs per the skill's parameter spec and get structured output
Version History
v1.0.0
Initial publish
Metadata
Slug brewpage-publish
Version 1.0.0
License MIT-0
All-time Installs 0
Active Installs 0
Total Versions 1
Frequently Asked Questions

What is BrewPage Publish?

Publish content to brewpage.app — text, markdown, any file, or multi-file site. Asks namespace and password, returns public URL. Triggers: publish, share lin... It is an AI Agent Skill for Claude Code / OpenClaw, with 23 downloads so far.

How do I install BrewPage Publish?

Run "/install brewpage-publish" in the OpenClaw or Claude Code chat to install it in one step — no extra setup required.

Is BrewPage Publish free?

Yes, BrewPage Publish is completely free, licensed under MIT-0. You can download, install and use it at no cost.

Which platforms does BrewPage Publish support?

BrewPage Publish is cross-platform and runs anywhere OpenClaw / Claude Code is available (cross-platform).

Who created BrewPage Publish?

It is built and maintained by kochetkov-ma (@kochetkov-ma); the current version is v1.0.0.

💬 Comments