PLUGIN brewdoc / Skills
On this page
publish
haiku model-invocableShares text, markdown, JSON, a file, a folder or a ZIP site on brewpage.app (free, no sign-up) and returns the link. Private means unlisted, not locked: anyone with the link can open it unless a password is set. Pick the visibility before uploading; owner tokens go to a local history file and never into the conversation.
Quick reference
| Field | Value |
|---|---|
| Command | /brewdoc:publish [prompt] <text|file_path|directory_path|zip_path> [--ttl N] [--entry filename] [--delivery-mode path|subdomain] |
| Options | --ttl N days (default 15); --entry site entry file; --delivery-mode creation mode |
| Model | haiku |
| Invocation | Type the command, or ask Claude (“publish this”, “share a link”). Claude may start it itself; headless and dontAsk runs need Skill(brewdoc:publish *) allowed |
| Permissions | No Bash or Write prompts: one scoped rule, scripts/brewpage-run.sh, covers every step |
| Requires | jq, curl; site uploads also zip, unzip, node |
When to use
| Goal | Command |
|---|---|
| Share a report | /brewdoc:publish report.md |
| Host a JSON dataset | /brewdoc:publish '{"status":"ok","count":42}' |
| Upload any file, expires in 1 day | /brewdoc:publish screenshot.png --ttl 1 |
| Deploy a static site | /brewdoc:publish ./dist/ --entry index.html |
| Own address for public content | /brewdoc:publish report.md --delivery-mode subdomain |
Example
/brewdoc:publish report.md --ttl 30
The skill asks for a namespace (public or a private slug) and an optional password-file path, then prints the server’s link and where the owner token was saved:
Published: <exact server URL>
Owner token saved to <project-root>/.claude/brewpage-history.md
Modes
| Input | Type | Endpoint |
|---|---|---|
| Text or markdown string | HTML | POST /api/html?format=markdown |
Existing .md file | MARKDOWN | POST /api/html, rendered page, not a raw download |
Starts with { or [ | JSON | POST /api/json |
| Existing local file | FILE | POST /api/files (multipart) |
Directory or .zip | SITE | POST /api/sites |
SITE is checked first. Site entry follows the server rule: --entry, else the root index.html, else the shallowest .html (ties by name). To start on another page, pass --entry.
Upload limits. brewpage.app allows 25 MB per request, 200 files, 5 MB per file and 20 MB in total. Ask the skill to check the limits, or run curl -s https://brewpage.app/api/limits; the skill reads them itself before a big site.
Hosting modes and good to know
--delivery-mode takes path or subdomain. Omitted, the skill sends no X-Delivery-Mode header and the server chooses. It applies to fresh publications with new-v1 routing metadata.
| Fresh publication | Default | Explicit choice |
|---|---|---|
| Public HTML/Markdown, JSON or file | Promotion (path) | path or subdomain, also with a password |
| Site, any namespace | Subdomain | subdomain; path is rejected |
Non-site outside public | Subdomain | subdomain; path is rejected |
Promotion serves from a brewpage.app address with stricter HTML limits (scripts, cookies, embeds). Dedicated subdomain gives its own origin with fewer limits: choose it if Promotion breaks your HTML. The response decides the effective mode, and the server may return an existing publication: share the returned link exactly, trailing slash included. New password-protected links stay clean (never append ?p=).
Good to know
- Eligible, unprotected HTML, sites and files in
publiccan appear in the gallery and be indexed; everything else isnoindex. Eligibility does not guarantee ranking. - Headless runs use the defaults: suggested private namespace, no password.
- A rejected upload prints
FAILED: publish rejected (HTTP <status>: <message>)with the server’s reason. After a successfulOKline, lines starting withNOTICE:say what the server skipped or adjusted. - Limits behind the headline numbers: a ZIP expands to at most 40 MB, paths stop at 380 characters (900 bytes) and names at 200 bytes. The skill’s own read-only
limitscommand prints the current values. - Do not publish
.envfiles or secrets: pages are reachable by anyone with the link, and content expires after the TTL.
Flow in detail
Arguments. --ttl N (default 15), --entry <filename>, optional --delivery-mode path|subdomain (other values are rejected before upload). A prompt with no path and no inline text asks once what to publish.
Detect. For a path, brewpage-run.sh probe '<path>' prints TYPE:, the absolute PATH: and size stats. Types are classified SITE, MARKDOWN, FILE, JSON, HTML in that order.
Namespace. Options: public, a suggested slug (3-16 chars named after the content, such as api-docs; never random), or custom. Skip or no answer means the suggested private slug.
Password. Option 2 takes an absolute path to an existing file with at least four characters. Option 1, 3 or no answer ignores every password file, stale ones included. The helper copies the bytes into private header files: never shell literals, curl arguments or chat output; control characters stop the upload.
Publish. Every slot is re-validated by the script (ns, days, entry, delivery_mode, password_file); an invalid value is re-asked, never cleaned up. Commands over 8,000 characters are refused, so long text goes through stage: chunks of up to 6,000 characters, then publish --run <id>. cleanup --run <id> drops a staged but unpublished run.
Result. The script prints one decisive line: OK <url> (sites add | Files: N), CONFIRM: ... (exit 2, nothing uploaded; after you confirm, the same command reruns with --confirmed) or FAILED: ... (response body withheld, since it may hold a token). On OK the skill shows Published: <url> (sites: Published site: <url>). The run directory is removed on every exit.
Security, permissions and history
One script, one rule. Every shell step is bash ${CLAUDE_SKILL_DIR}/scripts/brewpage-run.sh <probe|stage|publish|cleanup> ... with single-quoted values. The single allowed-tools rule matches that exact shape, so nothing prompts, headless included. A quoted script path, cd prefix, && chain or $(...) falls back to the normal permission prompt.
Owner token. Captured inside the script and written to history; the model sees only OK, CONFIRM or FAILED lines.
Run isolation. A private brewpage.XXXXXX directory (mode 700) under $TMPDIR, else /tmp, outside .claude/; only the script creates and removes it, so no Write grant is needed.
History file. <project-root>/.claude/brewpage-history.md, mode 600, git-ignored. Root resolution: CLAUDE_PROJECT_DIR, Git, an upward .git/.claude search (a profile .claude is skipped), or the current directory. Existing rows are never rewritten.
| Date | URL | Owner Token | TTL | Type | Owner API | Identity |
|------|-----|-------------|-----|------|-----------|----------| Delete, update and change hosting
Use the stored Owner API (ownerLink), never a hostname split into namespace and ID. OWNER_API is that URL and TOKEN the stored owner token.
curl -s -X DELETE "$OWNER_API" -H "X-Owner-Token: TOKEN"
curl -s -X PUT "$OWNER_API" -H "X-Owner-Token: TOKEN" -F "archive=@/absolute/path/to/new-bundle.zip"PUT replaces a site’s file set and keeps the host and the delivery mode; do not send X-Delivery-Mode on it.
Only new public non-sites can switch hosting: GET /api/{type}/{ns}/{id}/hosting returns the version, then PATCH it with X-Owner-Token and {"deliveryMode":"subdomain","expectedVersion":1}. Use the version from GET. Issued URLs become safe redirects.
| Response | Meaning |
|---|---|
403 | Owner authorization failed |
400 | Invalid mode or version, or HOSTING_MODE_LOCKED (old or forced-mode identity; sites have no PATCH) |
409 HOSTING_VERSION_CONFLICT | Reload current settings, retry |
503 | Hosting changes unavailable; creation can fail with HOST_ALLOCATION_FAILED |
Passwords and revocation cannot recall downloaded or cached copies.
Site bundles and tests
A directory is packed by scripts/publish.mjs pack --dir <dir> --out <archive.zip>: stale target removed, zip status checked, result verified with unzip -t and a non-zero size; any failure deletes the archive before curl runs.
The pack is an allowlist: only web-asset extensions (.html .css .js .json .svg .png .woff2 .pdf .md and similar) go in. System files (.DS_Store, __MACOSX/, Thumbs.db, desktop.ini) are dropped and listed under SKIPPED, never flagged and never counted toward the server’s file limit. Hidden files and folders (.env, .git/) are dropped too, except the root .well-known/, which ships with its files, extension-less ones included (such as apple-app-site-association); node_modules/ and symlinks are dropped as well. A supplied .zip is inspected, never rewritten: system entries are left alone (the server drops them), hidden entries are flagged, and an entry matching .env, id_rsa, secret, credential, password, token, .pem, .key, .p12, .crt or .map blocks the upload until you confirm.
Tests: bash brewdoc/skills/publish/tests/run.sh (no network, no real upload; they do not prove server rollout).
Related
md-to-pdf
Convert markdown to PDF, then publish the file.
brewpage-mcp
Official MCP server for the BrewPage Sites API: claude mcp add brewpage -- npx -y brewpage-mcp@1.9.3
OpenAPI spec
Full API contract (Scalar UI); REST base URL https://brewpage.app/api.
GitHub source
Source code, SKILL.md and scripts.
Brewdoc overview
All brewdoc skills.
Update: /brewtools:plugin-update, /reload-plugins, then /brewcode:setup-status upgrade - FAQ