Publish content to brewpage.app — text, markdown, any file, or multi-file site. Asks namespace and password, returns public URL. Triggers: publish, share lin...
---
name: 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 link, upload to brewpage, host page, brewpage, publish site, upload site, upload directory, deploy site, сделай публичную ссылку, опубликуй."
homepage: https://brewpage.app
user-invocable: true
---
# 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 <filename>` → 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 `archive=@file.zip` — 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: <type description> · <size> · <api endpoint>
TTL: <N> 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 · <N> files · <total_size> · POST /api/sites
Entry: <entry_file>
TTL: <N> 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:
```bash
LC_ALL=C tr -dc 'a-z0-9' < /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:
```bash
HISTORY_FILE="./brewpage-history.md"
if [ ! -f "$HISTORY_FILE" ]; then
cat > "$HISTORY_FILE" <<'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:
```bash
HISTORY_FILE="./brewpage-history.md"
CONTENT=$(cat <<'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:
```bash
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:
```bash
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/<id>/ 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:
```bash
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/<id>/ 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/<id>/" 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/<id>"` 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 `<url>/index.html` explicitly.
---
## Powered by
| | |
|-|-|
| **[brewpage.app](https://brewpage.app)** | Free instant hosting — HTML, files, multi-file sites. No sign-up. |
| **[brewcode](https://github.com/kochetkov-ma/claude-brewcode)** | Plugin & skill suite — infinite tasks, code review, skills, hooks. |
don't have the plugin yet? install it then click "run inline in claude" again.
publish content to brewpage.app instantly , text, markdown, single files, or multi-file sites. no sign-up required. returns a public url + owner token for later deletion.
use this skill when you need to host html, markdown, text content, or entire static sites on brewpage.app with zero friction. handles namespace selection, optional password protection, and persists owner tokens locally for future takedown. supports three content types: html/text, single files, and site directories. default ttl is 15 days.
brewpage.app API
content source (one of):
.html files for site mode)command-line flags:
--ttl N , time to live in days (default: 15, min: 1, max: typically 365 depending on api)--entry <filename> , entry file for site mode (default: auto-detect via priority: --entry flag > index.html > first .html alphabetically)local filesystem:
./brewpage-history.md , workspace-relative history file for owner tokens. created on first publish. never commit to vcs; treat as secrets.extract from the user's arguments string:
--ttl N and store N as integer. default to 15 if absent.--entry <filename> and store filename. ignore if not present.content_arg (path or string).classify content_arg using this priority:
content_arg is a directory path that exists on disk (test -d), type = SITE. input will be auto-zipped.content_arg ends with .zip and file exists (test -f), type = SITE. input is pre-built zip.content_arg is a file path and file exists (test -f), type = FILE.content_arg as literal text or markdown.html/site safety check (run before proceeding): if type is SITE and directory contains zero .html files, fail immediately with error: "no .html found in directory. build the site first, then point at the build output directory (e.g., dist/, build/, out/, _site/, or public/). do not zip source trees."
warn on source trees: if directory contains package.json + src/ but no top-level .html files, warn the user: "looks like un-built project sources. point at the build output directory instead (e.g., dist/, build/, out/, _site/, or public/) to avoid publishing source code."
for html/text:
Content: text · <char count> chars · POST /api/html
TTL: <N> days
for file:
Content: file · <filename> · <size in bytes or KB> · <MIME type via file(1)> · POST /api/files
TTL: <N> days
for site:
first, auto-detect entry file using priority: 1) --entry flag value, 2) index.html if exists, 3) first .html file alphabetically in directory. compute total file count and total size.
Content: site · <N> files · <total size> · POST /api/sites
Entry: <entry_file>
TTL: <N> days
prompt user:
Namespace determines the URL prefix and gallery visibility on brewpage.app.
Options:
1) public , visible in gallery (default)
2) {auto-suggested 6-16 char slug}
3) Enter custom namespace
4) Skip , use public
Reply with a number or your custom namespace (alphanumeric + hyphens, 3-32 chars).
auto-suggest: generate one meaningful short slug (3-16 chars, lowercase alphanumeric + hyphens only) from content context:
api-docs, login-page, report-q2)pricing, team-intro, changelog)portfolio, docs-site)never use random strings or truncated filenames. slug must be human-readable and describe what's published.
resolution:
1, 4, or blank → use public2 → use suggested slugstore result as {ns}.
prompt user:
Password protection (optional; if set, page hides 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 bash):
LC_ALL=C tr -dc 'a-z0-9' < /dev/urandom | head -c6 2>/dev/null
resolution:
1, 4, or blank → no password. set PASSWORD="".2 → use generated random string.PASSWORD.run once before any publish (run with bash):
HISTORY_FILE="./brewpage-history.md"
if [ ! -f "$HISTORY_FILE" ]; then
cat > "$HISTORY_FILE" <<'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
html/text (run with bash):
HISTORY_FILE="./brewpage-history.md"
CONTENT=$(cat <<'BREWPAGE_EOF'
{content_arg}
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 bash):
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_file_path}")
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 bash):
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, sourcemaps , only publish 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 trailing slash
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 bash):
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 trailing slash
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
success (html/text/file):
Published: {url from bash output}
Owner token saved to ./brewpage-history.md
success (site):
Published site: {url from bash output}
Entry: {entry_file} | Files: {count}
Owner token saved to ./brewpage-history.md
⚠ share the url exactly as printed , no trailing slash.
brewpage.app routes "/public/<id>/" to its own landing page.
the js redirect that rescues no-slash form does not fire for slash-dir form.
error:
Publish failed.
Error: {error details from bash output}
never print the owner token in conversation. token persists only in ./brewpage-history.md.
if content_arg is a directory path that exists → auto-detect entry file (priority: --entry flag > index.html > first .html alphabetically). validate it contains at least one .html file; if zero .html files, fail with "no .html found , build the site first, then point at the build output directory." if directory looks like un-built sources (has package.json + src/ + no top-level .html), warn and ask user to point at build output instead. zip the directory on the fly and use site publish block.
else if content_arg ends with .zip and file exists → treat as pre-built site archive. skip zipping. use site publish block with pre-built zip.
else if content_arg is a file path and file exists → detect mime type via file(1). use file publish block.
else → treat as literal html/text/markdown content string. use html publish block.
if namespace input is 1, 4, or blank → use public.
if namespace input is 2 → use the auto-suggested slug.
if namespace input is custom text → validate alphanumeric + hyphens only, length 3-32 chars. reject invalid; re-ask.
if password input is 1, 4, or blank → no password protection.
if password input is 2 → use the generated 6-char random password.
if password input is custom text → validate length >= 4 chars. reject if too short; re-ask.
if curl response contains error or no .link field → fail with error message from response.
if curl response contains .ownerToken → append token row to ./brewpage-history.md history table (never print in conversation).
if api returns fileCount in site response → display count in output.
on success (html/text/file):
OK {url} where url is https://brewpage.app/public/{id} (no trailing slash).on success (site):
OK {url} | Files: {count} where url is https://brewpage.app/public/{id} (no trailing slash).on error:
FAILED: {response body}.history file format (markdown table, workspace-relative path ./brewpage-history.md):
| Date | URL | Owner Token | TTL | Type |
|------|-----|-------------|-----|------|
| 2025-01-15 14:32 | [https://brewpage.app/public/abc123](https://brewpage.app/public/abc123) | `token_xyz_secret` | 15d | html |
| 2025-01-15 14:35 | [https://brewpage.app/public/def456](https://brewpage.app/public/def456) | `token_uvw_secret` | 30d | site (8 files) |
owner tokens are never exposed in conversation, only persisted in history file.
user knows it worked when:
OK {url} (no FAILED).index.html) without trailing slash. if url is appended with /, browser may redirect to brewpage's own landing page (js redirect does not fire for slash-dir form)../brewpage-history.md history table and can be used with curl DELETE to remove the page later.