mirror of
https://github.com/kochetkov-ma/claude-brewcode.git
synced 2026-09-14 20:16:41 +08:00
feat(skills): add OpenClaw/AgentSkills brewpage-publish variant; bring standalone to parity
- openclaw/brewpage-publish: AgentSkills-format SKILL.md (name/description/homepage/user-invocable only; drops Claude-only model/argument-hint/allowed-tools). Supports HTML, markdown, files, multi-file sites (no JSON/KV). Directory auto-zip is primary with junk/secret excludes (.git/.env/node_modules/...) + built-static guard. - skills/brewpage-publish: brought to parity with brewdoc max version (sites dir+zip, PASS_H password array, archive filters, guard); kept JSON. - README + brewdoc/README: link both portable variants. Security: ownerToken never echoed (history-file only); password via quoted PASS_H array; .env/.git excluded from site archives.
This commit is contained in:
@@ -239,6 +239,15 @@ After `/brewcode:setup`, each task follows the cycle: `spec` -> `plan` -> `start
|
||||
| `/brewui:image-gen` | AI image generation via 5 providers with anti-slop controls |
|
||||
| `/brewui:glm-design-to-code` | GLM vision design-to-code: image/text/HTML/URL to multi-framework code |
|
||||
|
||||
### Portable skills (standalone)
|
||||
|
||||
Self-contained `SKILL.md` folders that ship outside the four plugins -- drop them into any compatible agent runtime. Both publish content to [brewpage.app](https://brewpage.app) (text, markdown, files, multi-file sites) and return a public URL.
|
||||
|
||||
| Skill | Runtime | Path |
|
||||
|-------|---------|------|
|
||||
| `brewpage-publish` | Claude Code | [`skills/brewpage-publish`](skills/brewpage-publish/) |
|
||||
| `brewpage-publish` | OpenClaw / AgentSkills | [`openclaw/brewpage-publish`](openclaw/brewpage-publish/) |
|
||||
|
||||
## Agents (16 total)
|
||||
|
||||
| Agent | Model | Purpose |
|
||||
|
||||
@@ -85,6 +85,8 @@ claude --plugin-dir ./brewdoc
|
||||
| [`/brewdoc:publish`](skills/publish/README.md) | Publish to brewpage.app -- returns public URL | haiku | `<text\|file\|json> [--ttl N]` |
|
||||
| [`/brewdoc:guide`](skills/guide/README.md) | Interactive tutorial for the plugin suite | haiku | `[topic]` |
|
||||
|
||||
> Need a portable, plugin-free version? See the standalone [`brewpage-publish`](../skills/brewpage-publish/) (Claude Code) and [`openclaw/brewpage-publish`](../openclaw/brewpage-publish/) (OpenClaw / AgentSkills) skills.
|
||||
|
||||
## Agent
|
||||
|
||||
| Agent | Model | Purpose |
|
||||
|
||||
@@ -0,0 +1,94 @@
|
||||
# brewpage-publish (OpenClaw skill)
|
||||
|
||||
Publish text, markdown, any file, or a whole multi-file site to [brewpage.app](https://brewpage.app) — get a public URL instantly. No sign-up.
|
||||
|
||||
This is an [AgentSkills](https://docs.openclaw.ai/tools/skills)-standard skill for **OpenClaw**.
|
||||
|
||||
## What It Does
|
||||
|
||||
1. **Detects content type** — a directory/ZIP becomes a multi-file site, a single file becomes a file upload, everything else becomes HTML (markdown rendered)
|
||||
2. **Asks namespace** — interactive prompt for a short, human-readable namespace (URL slug)
|
||||
3. **Asks password** — optional password protection (hides the page from the gallery)
|
||||
4. **Calls API** — sends content to brewpage.app
|
||||
5. **Returns URL** — public link ready to share
|
||||
6. **Saves token** — owner token written to `./brewpage-history.md`
|
||||
|
||||
## API Coverage
|
||||
|
||||
| Content | Type | Endpoint |
|
||||
|---------|------|----------|
|
||||
| Text / markdown | HTML | `POST /api/html?format=markdown` |
|
||||
| Local file | File | `POST /api/files` |
|
||||
| Directory (built static) | Site | `POST /api/sites` (auto-zipped — primary path) |
|
||||
| `.zip` archive | Site | `POST /api/sites` (pre-built) |
|
||||
|
||||
## Install / Placement
|
||||
|
||||
OpenClaw discovers skills from two locations. Drop the `brewpage-publish/` folder (containing `SKILL.md`) into either:
|
||||
|
||||
```
|
||||
<workspace>/skills/brewpage-publish/SKILL.md # project-local skill
|
||||
~/.openclaw/skills/brewpage-publish/SKILL.md # user-global skill
|
||||
```
|
||||
|
||||
The folder/`name` (`brewpage-publish`) drives the slash command and the allowlist key. The name uses lowercase + a hyphen — colon/uppercase forms like `BrewPage::publish` are **invalid** in OpenClaw.
|
||||
|
||||
## How to Invoke
|
||||
|
||||
Via slash command:
|
||||
```
|
||||
/brewpage-publish "Hello, world!"
|
||||
/brewpage-publish report.md
|
||||
/brewpage-publish screenshot.png --ttl 1
|
||||
/brewpage-publish ./dist --entry index.html
|
||||
/brewpage-publish site.zip
|
||||
```
|
||||
|
||||
Or via natural language:
|
||||
```
|
||||
Publish this to brewpage
|
||||
Upload report.md to brewpage.app
|
||||
Deploy this directory as a site
|
||||
```
|
||||
|
||||
The model detects the content type, asks for a namespace and password, calls the brewpage.app API, and returns the public URL.
|
||||
|
||||
## Sites
|
||||
|
||||
`POST /api/sites` accepts **only a `.zip` archive** — there is no raw-folder upload.
|
||||
|
||||
- **Directory (primary):** point at a **built** static directory; the skill auto-zips it and uploads. Archive sealing keeps relative paths intact.
|
||||
- **Pre-built `.zip` (alternative):** uploaded as-is.
|
||||
|
||||
The auto-zip **excludes** `.git/`, `.env`/`.env.*`, `node_modules/`, `.DS_Store`, `Thumbs.db`, `.idea/`, `.vscode/`, `.cache/`, `*.map` and `*.log` — only real built assets ship; secrets and VCS data never leak into the public archive.
|
||||
|
||||
**Built-static guard:** publish build output, not sources. No `.html` in the directory → the skill fails and tells you to build first. A source tree (`package.json` + `src/`, no top-level `.html`) → the skill asks you to point at the build output (`dist/`, `build/`, `out/`, `_site/`, `public/`).
|
||||
|
||||
Entry file: `--entry` override > `index.html` > first `.html` alphabetically.
|
||||
|
||||
**No trailing slash.** The API returns `https://brewpage.app/public/<id>` without a trailing `/`. Appending `/` routes to brewpage.app's landing page and breaks the link. Site URLs cannot be verified with plain `curl` — verify with a real browser or fetch `<url>/index.html`.
|
||||
|
||||
## TTL
|
||||
|
||||
Default time-to-live is **15 days** (max 30). Override with `--ttl N` (days).
|
||||
|
||||
## Owner Token & Privacy
|
||||
|
||||
The owner token is **never printed in conversation** — the skill's shell blocks curl the API, parse the token, and append it to `./brewpage-history.md` directly. Only the public URL is shown.
|
||||
|
||||
`./brewpage-history.md` is a **private file** (keep it out of version control). Use the token to delete a page:
|
||||
|
||||
```bash
|
||||
# html / json / kv
|
||||
curl -X DELETE "https://brewpage.app/api/{ns}/{id}" -H "X-Owner-Token: {token}"
|
||||
# site
|
||||
curl -X DELETE "https://brewpage.app/api/sites/{ns}/{id}" -H "X-Owner-Token: {token}"
|
||||
```
|
||||
|
||||
## Requirements
|
||||
|
||||
`curl`, `jq`, and `zip` available on the host shell.
|
||||
|
||||
## License
|
||||
|
||||
MIT
|
||||
@@ -0,0 +1,272 @@
|
||||
---
|
||||
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. |
|
||||
@@ -1,6 +1,6 @@
|
||||
# Brewpage
|
||||
# Brewpage Publish
|
||||
|
||||
Publish text, markdown, JSON, or files to [brewpage.app](https://brewpage.app) — get a public URL instantly. No sign-up.
|
||||
Publish text, markdown, JSON, files, or whole multi-file sites to [brewpage.app](https://brewpage.app) — get a public URL instantly. No sign-up.
|
||||
|
||||
## Quick Start
|
||||
|
||||
@@ -11,25 +11,28 @@ Publish text, markdown, JSON, or files to [brewpage.app](https://brewpage.app)
|
||||
|
||||
2. Use via slash command:
|
||||
```
|
||||
/brewpage "Hello, world!"
|
||||
/brewpage report.md
|
||||
/brewpage '{"status": "ok"}'
|
||||
/brewpage screenshot.png --ttl 1
|
||||
/brewpage-publish "Hello, world!"
|
||||
/brewpage-publish report.md
|
||||
/brewpage-publish '{"status": "ok"}'
|
||||
/brewpage-publish screenshot.png --ttl 1
|
||||
/brewpage-publish ./my-site --entry index.html
|
||||
/brewpage-publish site.zip
|
||||
```
|
||||
|
||||
Or via natural language prompt:
|
||||
Or via natural language:
|
||||
```
|
||||
Publish this to brewpage
|
||||
Upload report.md to brewpage.app
|
||||
Deploy this directory as a site
|
||||
```
|
||||
|
||||
Claude detects the content type, asks for a namespace and password interactively, calls the brewpage.app API, and returns a public URL. The owner token is saved to `.claude/brewpage-history.md` for later deletion.
|
||||
|
||||
## What It Does
|
||||
|
||||
1. **Detects content type** — text/markdown becomes HTML, objects/arrays become JSON, file paths become file uploads
|
||||
2. **Asks namespace** — interactive prompt for a short namespace (URL slug)
|
||||
3. **Asks password** — optional password protection for the published page
|
||||
1. **Detects content type** — directory/ZIP becomes a multi-file site, single file becomes a file upload, objects/arrays become JSON, everything else becomes HTML (markdown rendered)
|
||||
2. **Asks namespace** — interactive prompt for a short, human-readable namespace (URL slug)
|
||||
3. **Asks password** — optional password protection (hides the page from the gallery)
|
||||
4. **Calls API** — sends content to brewpage.app
|
||||
5. **Returns URL** — public link ready to share
|
||||
6. **Saves token** — owner token written to `.claude/brewpage-history.md`
|
||||
@@ -38,38 +41,52 @@ Claude detects the content type, asks for a namespace and password interactively
|
||||
|
||||
| Content | Type | Endpoint |
|
||||
|---------|------|----------|
|
||||
| Text / markdown | HTML | `POST /api/html` |
|
||||
| Text / markdown | HTML | `POST /api/html?format=markdown` |
|
||||
| JSON object/array | JSON | `POST /api/json` |
|
||||
| Local file | File | `POST /api/files` |
|
||||
| Directory | Site | `POST /api/sites` (zipped on the fly) |
|
||||
| `.zip` archive | Site | `POST /api/sites` |
|
||||
|
||||
## Sites
|
||||
|
||||
`POST /api/sites` accepts **only a `.zip` archive** — there is no raw-folder upload.
|
||||
|
||||
- **Directory (primary):** point at a **built** static directory; the skill auto-zips it and uploads. Archive sealing keeps relative paths intact.
|
||||
- **Pre-built `.zip` (alternative):** uploaded as-is.
|
||||
|
||||
The auto-zip **excludes** `.git/`, `.env`/`.env.*`, `node_modules/`, `.DS_Store`, `Thumbs.db`, `.idea/`, `.vscode/`, `.cache/`, `*.map` and `*.log` — only real built assets ship; secrets and VCS data never leak into the public archive.
|
||||
|
||||
**Built-static guard:** publish build output, not sources. No `.html` in the directory → the skill fails and tells you to build first. A source tree (`package.json` + `src/`, no top-level `.html`) → the skill asks you to point at the build output (`dist/`, `build/`, `out/`, `_site/`, `public/`).
|
||||
|
||||
Entry file: `--entry` override > `index.html` > first `.html` alphabetically.
|
||||
|
||||
**No trailing slash.** The API returns `https://brewpage.app/public/<id>` without a trailing `/`. Appending `/` routes to brewpage.app's landing page and breaks the link. Share the URL exactly as printed. Site URLs cannot be verified with plain `curl` (the no-slash URL serves an inline JS redirect that only runs in a browser) — verify with a real browser or fetch `<url>/index.html`.
|
||||
|
||||
## TTL
|
||||
|
||||
Default time-to-live is **5 days**. Override with `--ttl N` (days):
|
||||
Default time-to-live is **15 days** (max 30). Override with `--ttl N` (days):
|
||||
|
||||
```
|
||||
/brewpage report.md --ttl 1
|
||||
/brewpage '{"data": [1,2,3]}' --ttl 30
|
||||
/brewpage-publish report.md --ttl 1
|
||||
/brewpage-publish '{"data": [1,2,3]}' --ttl 30
|
||||
```
|
||||
|
||||
## Owner Token
|
||||
## Owner Token & Privacy
|
||||
|
||||
Every publish saves an entry to `.claude/brewpage-history.md`:
|
||||
The owner token is **never printed in conversation** — the skill's bash blocks curl the API, parse the token, and append it to `.claude/brewpage-history.md` directly. Only the public URL is shown.
|
||||
|
||||
```markdown
|
||||
| Namespace | ID | URL | Token | Created |
|
||||
|-----------|-----|-----|-------|---------|
|
||||
| my-ns | abc123 | https://brewpage.app/my-ns/abc123 | tok_... | 2026-03-31 |
|
||||
```
|
||||
|
||||
Use the token to delete a page:
|
||||
`.claude/brewpage-history.md` is a **private file** (keep it out of version control). Use the token to delete a page:
|
||||
|
||||
```bash
|
||||
# html / json / kv
|
||||
curl -X DELETE "https://brewpage.app/api/{ns}/{id}" -H "X-Owner-Token: {token}"
|
||||
# site
|
||||
curl -X DELETE "https://brewpage.app/api/sites/{ns}/{id}" -H "X-Owner-Token: {token}"
|
||||
```
|
||||
|
||||
## Part of Brewcode
|
||||
|
||||
This skill is extracted from [brewcode](https://github.com/kochetkov-ma/claude-brewcode) — a development platform for Claude Code with infinite focus tasks, 14 agents, quorum reviews, and knowledge persistence.
|
||||
This skill is part of [brewcode](https://github.com/kochetkov-ma/claude-brewcode) — a development platform for Claude Code with infinite focus tasks, agents, quorum reviews, and knowledge persistence.
|
||||
|
||||
```bash
|
||||
claude plugin marketplace add https://github.com/kochetkov-ma/claude-brewcode
|
||||
|
||||
@@ -1,15 +1,15 @@
|
||||
---
|
||||
name: brewpage-publish
|
||||
description: "Publish content to brewpage.app — text, markdown, JSON, or file. Asks namespace and password, returns public URL. Triggers: publish, share link, upload to brewpage, host page, brewpage."
|
||||
argument-hint: "<text|file_path|json> [--ttl N]"
|
||||
description: "Publish content to brewpage.app — text, markdown, JSON, 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, сделай публичную ссылку, опубликуй."
|
||||
argument-hint: "<text|file_path|directory_path|zip_path> [--ttl N] [--entry filename]"
|
||||
user-invocable: true
|
||||
allowed-tools: Read, Bash, AskUserQuestion, Glob
|
||||
model: haiku
|
||||
---
|
||||
|
||||
# brewpage
|
||||
# brewpage-publish
|
||||
|
||||
Publish content to **brewpage.app** — free instant hosting for HTML pages, JSON documents, and files. No sign-up required.
|
||||
Publish content to **brewpage.app** — free instant hosting for HTML pages, JSON documents, files, and multi-file sites. No sign-up required.
|
||||
|
||||
## Workflow
|
||||
|
||||
@@ -17,24 +17,38 @@ Publish content to **brewpage.app** — free instant hosting for HTML pages, JSO
|
||||
|
||||
Extract from `$ARGUMENTS`:
|
||||
- `--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 path AND file exists (`test -f`) | FILE | `POST /api/files` (multipart) |
|
||||
| `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) |
|
||||
| `content_arg` starts with `{` or `[` | JSON | `POST /api/json` |
|
||||
| Anything else | HTML | `POST /api/html` (format=markdown) |
|
||||
|
||||
For FILE: get file size and MIME type via Bash (`file --mime-type -b`).
|
||||
For TEXT/JSON: count characters.
|
||||
Mode rule: directory/ZIP → SITE. Single file → FILE. `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/JSON: char count.
|
||||
|
||||
### Step 3: Show Pre-Publish Stats
|
||||
|
||||
For HTML/JSON/FILE:
|
||||
```
|
||||
📊 Content: <type description> · <size> · <api endpoint>
|
||||
TTL: <N> days
|
||||
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
|
||||
@@ -93,9 +107,9 @@ Resolution:
|
||||
|
||||
### Step 6: Publish and Save Token (secure)
|
||||
|
||||
> **SECURITY:** The ownerToken MUST never appear in conversation output. The bash block below handles curl, token parsing, and history saving atomically. The LLM only sees the URL.
|
||||
> **SECURITY:** The ownerToken MUST NEVER appear in conversation output. The bash blocks below handle curl + token parsing + history save atomically; the LLM 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.
|
||||
|
||||
**HTML/Markdown text** — **EXECUTE** using Bash tool:
|
||||
**6a. Init history file (run once, before the publish block)** — **EXECUTE** using Bash tool:
|
||||
```bash
|
||||
HISTORY_FILE=".claude/brewpage-history.md"
|
||||
if [ ! -f "$HISTORY_FILE" ]; then
|
||||
@@ -103,123 +117,173 @@ if [ ! -f "$HISTORY_FILE" ]; then
|
||||
cat > "$HISTORY_FILE" <<'HEADER'
|
||||
# brewpage.app — Published Pages
|
||||
|
||||
> Owner tokens allow update/delete. Keep this file private.
|
||||
> Delete: `curl -s -X DELETE "https://brewpage.app/api/{ns}/{id}" -H "X-Owner-Token: TOKEN"`
|
||||
> 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 |
|
||||
|------|-----|-------------|-----|
|
||||
| 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** — **EXECUTE** using Bash tool:
|
||||
```bash
|
||||
HISTORY_FILE=".claude/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" \
|
||||
{password_header} \
|
||||
"${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 |" >> "$HISTORY_FILE"
|
||||
echo "✅ $URL"
|
||||
[ -n "$TOKEN" ] && echo "| $(date '+%Y-%m-%d %H:%M') | [$URL]($URL) | \`$TOKEN\` | {ttl}d | html |" >> "$HISTORY_FILE"
|
||||
echo "OK $URL"
|
||||
else
|
||||
echo "❌ FAILED: $RESPONSE"
|
||||
echo "FAILED: $RESPONSE"
|
||||
fi
|
||||
```
|
||||
|
||||
**JSON** — **EXECUTE** using Bash tool:
|
||||
```bash
|
||||
HISTORY_FILE=".claude/brewpage-history.md"
|
||||
if [ ! -f "$HISTORY_FILE" ]; then
|
||||
mkdir -p "$(dirname "$HISTORY_FILE")"
|
||||
cat > "$HISTORY_FILE" <<'HEADER'
|
||||
# brewpage.app — Published Pages
|
||||
|
||||
> Owner tokens allow update/delete. Keep this file private.
|
||||
> Delete: `curl -s -X DELETE "https://brewpage.app/api/{ns}/{id}" -H "X-Owner-Token: TOKEN"`
|
||||
|
||||
| Date | URL | Owner Token | TTL |
|
||||
|------|-----|-------------|-----|
|
||||
HEADER
|
||||
fi
|
||||
|
||||
PASS_H=()
|
||||
[ -n "$PASSWORD" ] && PASS_H=(-H "X-Password: $PASSWORD")
|
||||
RESPONSE=$(curl -s -X POST "https://brewpage.app/api/json?ns={ns}&ttl={days}" \
|
||||
-H "Content-Type: application/json" \
|
||||
{password_header} \
|
||||
"${PASS_H[@]}" \
|
||||
-d '{original_json}')
|
||||
|
||||
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 |" >> "$HISTORY_FILE"
|
||||
echo "✅ $URL"
|
||||
[ -n "$TOKEN" ] && echo "| $(date '+%Y-%m-%d %H:%M') | [$URL]($URL) | \`$TOKEN\` | {ttl}d | json |" >> "$HISTORY_FILE"
|
||||
echo "OK $URL"
|
||||
else
|
||||
echo "❌ FAILED: $RESPONSE"
|
||||
echo "FAILED: $RESPONSE"
|
||||
fi
|
||||
```
|
||||
|
||||
**File** — **EXECUTE** using Bash tool:
|
||||
```bash
|
||||
HISTORY_FILE=".claude/brewpage-history.md"
|
||||
if [ ! -f "$HISTORY_FILE" ]; then
|
||||
mkdir -p "$(dirname "$HISTORY_FILE")"
|
||||
cat > "$HISTORY_FILE" <<'HEADER'
|
||||
# brewpage.app — Published Pages
|
||||
|
||||
> Owner tokens allow update/delete. Keep this file private.
|
||||
> Delete: `curl -s -X DELETE "https://brewpage.app/api/{ns}/{id}" -H "X-Owner-Token: TOKEN"`
|
||||
|
||||
| Date | URL | Owner Token | TTL |
|
||||
|------|-----|-------------|-----|
|
||||
HEADER
|
||||
fi
|
||||
|
||||
PASS_H=()
|
||||
[ -n "$PASSWORD" ] && PASS_H=(-H "X-Password: $PASSWORD")
|
||||
RESPONSE=$(curl -s -X POST "https://brewpage.app/api/files?ns={ns}&ttl={days}" \
|
||||
{password_header} \
|
||||
"${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 |" >> "$HISTORY_FILE"
|
||||
echo "✅ $URL"
|
||||
[ -n "$TOKEN" ] && echo "| $(date '+%Y-%m-%d %H:%M') | [$URL]($URL) | \`$TOKEN\` | {ttl}d | file |" >> "$HISTORY_FILE"
|
||||
echo "OK $URL"
|
||||
else
|
||||
echo "❌ FAILED: $RESPONSE"
|
||||
echo "FAILED: $RESPONSE"
|
||||
fi
|
||||
```
|
||||
|
||||
Replace `{password_header}` with `-H "X-Password: {pass}"` only when password was set; otherwise remove it entirely.
|
||||
**Site (directory)** — **EXECUTE** using Bash tool:
|
||||
```bash
|
||||
HISTORY_FILE=".claude/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: ClaudeCode/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)** — **EXECUTE** using Bash tool:
|
||||
```bash
|
||||
HISTORY_FILE=".claude/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: ClaudeCode/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 `✅ {url}`):
|
||||
**Success** (bash printed `OK {url}`):
|
||||
```
|
||||
✅ Published!
|
||||
🔗 {url from bash output}
|
||||
📁 Owner token saved to .claude/brewpage-history.md
|
||||
Published: {url from bash output}
|
||||
Owner token saved to .claude/brewpage-history.md
|
||||
```
|
||||
|
||||
**NEVER print ownerToken in conversation.** The token is only in the history file.
|
||||
|
||||
**Error** (bash printed `❌ FAILED: ...`):
|
||||
**Success for SITE** (bash printed `OK {url} | Files: {count}`):
|
||||
```
|
||||
❌ Publish failed.
|
||||
Published site: {url from bash output}
|
||||
Entry: {entry_file} | Files: {count}
|
||||
Owner token saved to .claude/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`.
|
||||
- TTL default is `15` days. Namespace must be alphanumeric (3-32 chars), default `public`.
|
||||
- To **delete** a published page, find the owner token in `.claude/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 Playwright / `browser_navigate`, or fetch `<url>/index.html` explicitly.
|
||||
|
||||
---
|
||||
|
||||
@@ -227,5 +291,5 @@ Replace `{password_header}` with `-H "X-Password: {pass}"` only when password wa
|
||||
|
||||
| | |
|
||||
|-|-|
|
||||
| **[brewpage.app](https://brewpage.app)** | Free instant hosting — HTML, JSON, files, KV. No sign-up. |
|
||||
| **[brewpage.app](https://brewpage.app)** | Free instant hosting — HTML, JSON, files, sites, KV. No sign-up. |
|
||||
| **[brewcode](https://github.com/kochetkov-ma/claude-brewcode)** | Claude Code plugin suite — infinite tasks, code review, skills, hooks. |
|
||||
|
||||
Reference in New Issue
Block a user