BrewPage Publish
/install brewpage-publish
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
.htmlfile at all → FAIL with an explicit error: "No.htmlfound — 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/, orpublic/). 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 →public2→ suggested slug3or 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 password2→ use generated random password3or 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_Hfirst (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.formatis a query param, not a body field —/api/htmlignores anyformatkey inside the JSON body and reads only?format=from the URL. Wrong location = server applies defaulthtmland stores your markdown as raw text. - TTL default is
15days. Namespace must be alphanumeric (3-32 chars), defaultpublic. - To delete a published page, find the owner token in
./brewpage-history.mdand 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/sitesaccepts), which seals relative paths. A pre-built.zipis 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:
--entryoverride >index.html> first.htmlalphabetically. - 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.htmlexplicitly.
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. |
- 确保已安装 OpenClaw(本地或 Docker 部署)
- 在对话框中输入安装命令:
/install brewpage-publish - 安装完成后,直接呼叫该 Skill 的名称或使用
/brewpage-publish触发 - 根据 Skill 的参数说明提供必要输入,即可获得结构化输出
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... 它是一个面向 Claude Code / OpenClaw 的 AI Agent Skill 插件,目前累计下载 23 次。
如何安装 BrewPage Publish?
在 OpenClaw 或 Claude Code 对话框中运行命令「/install brewpage-publish」即可一键安装,无需额外配置。
BrewPage Publish 是免费的吗?
是的,BrewPage Publish 完全免费,采用 MIT-0 许可证,可自由下载、安装和使用。
BrewPage Publish 支持哪些平台?
BrewPage Publish 跨平台运行,可在任意部署了 OpenClaw / Claude Code 的环境中使用(cross-platform)。
谁开发了 BrewPage Publish?
由 kochetkov-ma(@kochetkov-ma)开发并维护,当前版本 v1.0.0。