PLUGIN brewdoc / Skills

publish

haiku model-invocable

Shares 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

FieldValue
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
Modelhaiku
InvocationType the command, or ask Claude (“publish this”, “share a link”). Claude may start it itself; headless and dontAsk runs need Skill(brewdoc:publish *) allowed
PermissionsNo Bash or Write prompts: one scoped rule, scripts/brewpage-run.sh, covers every step
Requiresjq, curl; site uploads also zip, unzip, node

When to use

GoalCommand
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

InputTypeEndpoint
Text or markdown stringHTMLPOST /api/html?format=markdown
Existing .md fileMARKDOWNPOST /api/html, rendered page, not a raw download
Starts with { or [JSONPOST /api/json
Existing local fileFILEPOST /api/files (multipart)
Directory or .zipSITEPOST /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 publicationDefaultExplicit choice
Public HTML/Markdown, JSON or filePromotion (path)path or subdomain, also with a password
Site, any namespaceSubdomainsubdomain; path is rejected
Non-site outside publicSubdomainsubdomain; 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 public can appear in the gallery and be indexed; everything else is noindex. 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 successful OK line, lines starting with NOTICE: 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 limits command prints the current values.
  • Do not publish .env files 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.

ResponseMeaning
403Owner authorization failed
400Invalid mode or version, or HOSTING_MODE_LOCKED (old or forced-mode identity; sites have no PATCH)
409 HOSTING_VERSION_CONFLICTReload current settings, retry
503Hosting 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).

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