brewpage-publish

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, сделай публичную ссылку, опубликуй.

By kochetkov-ma · 596 installs

npx skills add kochetkov-ma/claude-brewcode --skill brewpage-publish

Source repository · Upstream listing

brewpage publish Publish content to brewpage.app — free instant hosting for HTML pages, JSON documents, files, and multi file sites. No sign up required. Running commands Every shell step is ONE command of the exact shape in the fences below: the single allowed tools rule pre approves exactly that shape, so the skill never prompts, headless included. Fill each '{…}' slot with the raw value and write every ' inside it as '\'' ; nothing else is escaped. Never wrap a value in double quotes. An empty slot stays '' (= omitted). Never quote the script path, prefix or chain the command ( cd , VAR=… , ; , && , , $(…) ): any other shape falls back to the normal permission prompt. Keep every command under 8,000 characters (longer ones are always refused); longer text goes through stage (Step 6). The script prints one decisive line: OK {url} or OK {url} Files: N (then any NOTICE: … lines: what the server skipped or adjusted), CONFIRM: … (exit 2, nothing uploaded) or FAILED: … (server reason HTTP <status : <message ). It removes its private run directory on every exit. Workflow Step 1: Parse Arguments Extract from $ARGUMENTS : ttl N → TTL in days (default: 15 ) entry <filename → entry file for SITE uploads (default: auto detect) delivery mode path subdomain → optional creation header; omitted means server defaults. Reject other values before upload. Remaining text → content arg : a path, or the exact text to publish. Fired by Claude mid task, the arguments are the path or the verbatim text, never a summary of it. Nothing to publish → ask once what to publish. Step 2: Detect Content Type A content arg that names a path probe it (read only; prints TYPE: , the absolute PATH: to use below, and the Step 3 stats) — EXECUTE using Bash tool: Input Type publish type + source API TYPE: site dir SITE site dir + path POST /api/sites (ZIP built from the dir — primary path) TYPE: site zip ( .zip file) SITE site zip + path POST /api/sites (pre built archive upload) TYPE: file or TYPE: markdown (any other file) FILE file + path POST /api/files (multipart) No path ( TYPE: none ), content arg starts with { or [ JSON json + text POST /api/json Anything else HTML html + text POST /api/html (format=markdown) Mode rule: directory/ZIP → SITE. Single file → FILE. POST /api/sites accepts a multipart archive=@file.zip or repeated files and matching paths fields; the script zips a directory to preserve relative paths. Step 3: Show Pre Publish Stats From the probe lines — SITE (dir): HTML , SIZE KB , ENTRY . SITE (ZIP): BYTES , entry override. FILE: BYTES + MIME . Text/JSON: char count. For HTML/JSON/FILE: For SITE (entry = server rule: entry root index.html shallowest .html ; another page → pass entry ; ENTRY: none → fail with an explicit error, never guess): Built static guard (before publishing). Publish BUILT output, never project sources: No .html file in the directory ( ENTRY: none ) → FAIL with an explicit error: "No .html found — build the site first, then point at the build output directory." Do not guess an entry. 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 publish the source tree. These are source tree estimates: report the ARCHIVE MANIFEST that Step 6 prints before upload; its ENTRY: line is the entry scripts/publish.mjs resolved in the built archive. Site limits ( GET https://brewpage.app/api/limits returns the current ones): 25 MB per request; a site holds 200 files, 5 MB per file, 20 MB total uncompressed, a ZIP expands to at most 40 MB; a path is at most 380 characters / 900 bytes, each file or folder name at most 200 bytes. A big site ( FILES or SIZE KB near a limit) → check the current values first, and send many files as one ZIP (a folder is always zipped). EXECUTE using Bash tool: Pre checks, folder or ZIP: system files ( .DS Store , MACOSX/ , Thumbs.db ) never count and are dropped; hidden files and folders are dropped too, except the root .well known/ ; at least one .html file must remain. Step 4: Ask Namespace Use AskUserQuestion : 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 ) JSON → data type or schema name (e.g. user config , metrics ) 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: explicit 2 or 4 → suggested slug 1 → public custom slug → validate it. An empty, unavailable, ambiguous or unanswered choice → stop before upload. Never infer publication consent from silence. Step 5: Ask Password Use AskUserQuestion : Resolution: explicit 1 or 3 → {password file} = '' (no password). 2 + an existing local file path → {password file} = that absolute path: the script moves the bytes via private header files (never shell literals or curl argv) and rejects control characters (header injection). Never Read, generate or print the password contents. An empty, unavailable, ambiguous or unanswered choice → stop before upload. A previous explicit choice for this content/workflow may be reused; never invent one. Step 6: Publish and Save Token (secure) Delivery mode: NEW sites and non public HTML pages (format html ) get a Dedicated subdomain (an explicit path is not refused: the publication lands on its subdomain and the response reports the mismatch); everything else defaults to Promotion ( path ) and may select subdomain , password protected included. Send it only as X Delivery Mode , never in authored JSON, text or multipart fields. A deduplicated OLD or NEW winner may differ: keep its identity, cohort, mode and exact link . Missing/null OLD routing metadata means Existing link; never infer mode from its address. SECURITY: the ownerToken NEVER appears in conversation output: the script does curl, token parsing and history save in one run and prints only the URL; failures print only the server's status and message. Every slot is re validated by the script before anything is written or uploaded: Slot Must match {ns} ^[a z0 9 ]{1,32}$ {days} positive integer; '' = 15 {entry} plain relative file name, no .. ; '' = auto detect; sites only {delivery mode} '' , path or subdomain {password file} '' or an absolute path to an existing regular file A value failing its pattern: re ask; never clean it up or substitute it anyway. A slot error fails before any run is touched, so staged text survives the retry. {type} comes from the Step 2 table. SITE, FILE — {path} is the probe's PATH: — EXECUTE using Bash tool: HTML text, JSON — {content} is the exact text or JSON, verbatim — EXECUTE using Bash tool: Longer text or JSON (the command would pass 8,000 characters): split it at line ends into chunks of at most 6,000 characters. The first stage call passes run '' and prints RUN: brewpage.XXXXXX ; every later chunk and the final publish pass that id. The script rejoins chunks with one newline. EXECUTE using Bash tool, once per chunk: Then EXECUTE using Bash tool: Staged but not published (cancelled) — EXECUTE using Bash tool: Step 7: Output Result Success (the script printed OK {url} ): Success for SITE (the script printed OK {url} Files: {count} ): Mention the sign in line only as optional; never ask the user to sign in. If a NOTICE says no owner capability was saved, omit the token saved line and report only the reader URL plus that notice; existing history stays unchanged. For a private (non public ) namespace, append one short line after the link (skip if the reply must stay ultra brief): Unlisted link — anyone who has it can open it, but it's not in the gallery or search. Want it discoverable? Publish to public . NEVER print the ownerToken in conversation. The token lives only in the history file. Needs confirmation (the script printed CONFIRM: ... , exit 2; nothing uploaded): show the manifest lines, name the flagged entries, ask ONE AskUserQuestion (publish anyway / cancel). Publish anyway re run the identical command with confirmed review sha256 '{review sha256}' appended, using the exact REVIEW SHA256 printed beside the approved manifest. Changed archive bytes produce a fresh manifest and digest: ask again; never silently approve the replacement. No payload is retained between calls. Cancel or no answer stop: the script already removed its own run directory and never touches other runs, user source files or the shared tmp directory. Error (the script printed FAILED: ... ): Status Meaning Do 400 Fixable limit or path problem (file count/size, path length, bad value) Fix what the message names, retry 413 Request over 25 MB, or too many parts Shrink the bundle; many files → one ZIP 422 No servable file left (every file has an unsupported or missing extension) Add the .html /assets 408 Upload interrupted Retry the identical command once Unstorable metadata (tags, names, control characters) is stored as the closest valid value with a notice; a value that still cannot be stored is a 400, not a 409. Notes History writes reject symlinks, hardlinks, tracked history and directories writable by others; failure stops publication. Archive inspection caps 25 MiB compressed, 40 MiB expanded, 200 files, 400 entries, 5 MiB/member and 1 MiB metadata; ZIP64/split archives are refused. Scripts: scripts/brewpage run.sh (the only entry), scripts/brewpage lib.sh (sourced helpers), scripts/publish.mjs (offline SITE bundle build/verify), scripts/history.py (secure atomic history). Requires a POSIX runtime (macOS/Linux or WSL), bash , curl , jq , POSIX python3 with fcntl , and git inside repositories; sites also node , zip , unzip . Native Windows Python is unsupported and fails before upload. format : /api/html takes ?format= or a JSON body format ; a non blank body value wins over the query, neither default html (markdown stored as raw text). The scripts send ?format=markdown with a {content} only body. History: <project root /.claude/brewpage history.md (root: CLAUDE PROJECT DIR → git toplevel → upward .git / .claude walk, profile dirs skipped → PWD ), mode 600 , added to the project .gitignore . Its header holds the delete command for the owner token. Run dirs: private brewpage.XXXXXX under $TMPDIR (else /tmp ), outside the protected .claude/ ; only scripts/brewpage run.sh creates, fills and removes them, so the skill needs no Write / Edit grant. Permissions: model invocable, so allowed tools holds one scoped Bash rule that matches every step above. A skill directory path containing spaces breaks the unquoted command shape. TTL default is 15 days; stored createdAt / expiresAt are authoritative: a deduplicated winner keeps its original expiry, and ordinary PUT/update does not renew it. Use returned ownerLink and stored identity metadata for owner API operations; never parse NEW root hosts for namespace/ID. OLD history without metadata retains its existing API identifiers. To update a published site , PUT the replacement bundle to its apex owner API with X Owner Token ; it replaces the file set and keeps the host. Omit X Delivery Mode on ordinary PUT/update. Old