mcpField
B
brewpage-publish

brewpage-publish

github.com/kochetkov-ma云与 DevOps39597 次安装今天更新
代码仓库

关于 brewpage-publish

brewpage-publish 是将 HTML、Markdown、JSON、文件和已构建的多文件站点发布到 brewpage.app 的 Skill,涵盖内容识别、命名空间与密码选择,并将所有者令牌保存到本地历史文件。

主要功能

  1. 01发布 HTML、Markdown、JSON 和文件
  2. 02从目录或 ZIP 上传已构建站点
  3. 03设置 TTL、命名空间和密码选项
  4. 04识别站点入口文件
  5. 05将所有者令牌保存到本地历史文件

适用场景

  1. 01托管静态站点构建产物
  2. 02发布 JSON 文档或单个文件
  3. 03通过私有命名空间分享页面

安装

复制下面的命令,在终端中运行。

$npx skills add https://github.com/kochetkov-ma/claude-brewcode --skill brewpage-publish

安装后在 Claude Code 中输入/brewpage-publish确认已加载。

附带文件LICENSEREADME.md

详细介绍

brewpage-publish

将内容发布到 brewpage.app — 免费即时托管 HTML 页面、JSON 文档、文件和多文件站点。无需注册。

工作流

步骤 1:解析参数

从 $ARGUMENTS 中提取:

  • --ttl N → TTL 天数(默认:15)
  • --entry <filename> → SITE 上传的入口文件(默认:自动检测)
  • --delivery-mode path|subdomain → 可选的创建标头;省略时使用服务端默认值。上传前拒绝其他值。
  • 剩余文本 → content_arg
步骤 2:检测内容类型
输入类型API
content_arg 是目录(test -d)SITEPOST /api/sites(目录自动压缩为 ZIP — 首选方式)
content_arg 以 .zip 结尾且文件存在(test -f)SITEPOST /api/sites(上传预构建的归档文件)
content_arg 是文件路径且文件存在(test -f)FILEPOST /api/files(multipart)
content_arg 以 { 或 [ 开头JSONPOST /api/json
其他任何情况HTMLPOST /api/html(format=markdown)

模式规则:目录/ZIP → SITE。单个文件 → FILE。POST /api/sites 仅接受 multipart [email protected] — 不支持直接上传原始文件夹,因此会即时自动将目录压缩为 ZIP(可靠的默认方式;封装归档可保留相对路径)。各类型统计信息 — SITE(目录):HTML 数量、总大小、入口文件。SITE(ZIP):文件大小、入口文件覆盖项。FILE:大小 + MIME 类型(通过 file --mime-type -b 获取)。TEXT/JSON:字符数。

步骤 3:显示发布前统计信息

对于 HTML/JSON/FILE:

Content:  <type description> · <size> · <api endpoint>
TTL:      <N> days

对于 SITE:按以下优先级检测入口文件:1)--entry 标志,2)存在 index.html,3)按字母顺序排列的第一个 .html 文件。

已构建静态内容检查(在压缩前运行)。 发布已构建的输出,而不是项目源文件:

  • 如果目录中完全没有 .html 文件 → 失败并显示明确错误:"未找到 .html — 请先构建站点,然后指定构建输出目录。"不要猜测入口文件。
  • 如果目录看起来是尚未构建的源代码(存在 package.json 和 src/,但顶层没有 .html)→ 警告并询问用户是否改为指定构建输出目录(dist/、build/、out/、_site/ 或 public/)。不要压缩源代码树。
Content:  site · <N> files · <total_size> · POST /api/sites
Entry:    <entry_file>
TTL:      <N> days
步骤 4:询问命名空间

使用 AskUserQuestion:

Namespace controls discovery and appears in publication addresses on brewpage.app.
By default publishing is PRIVATE (unlisted): not in the public gallery and not indexed by search engines. The link is not secret, though — anyone who has it can open it (use a password to restrict access).
Choose `public` for eligible gallery discovery. Eligible Promotion publications may be indexed; Dedicated subdomain hosts use noindex.

Options:
1) public — eligible gallery discovery; eligible Promotion publications may be indexed
2) {auto-suggested 6-8 char slug} — private, link-only (default)
3) Enter custom namespace
4) Skip → use suggested slug (private)

Reply with a number or your custom namespace (1-32 lowercase letters, digits or hyphens).

自动建议:根据内容上下文生成一个有意义的短 slug(3-16 个字符,仅限小写字母数字和连字符):

  • 文件 → 文件的主题/用途(例如 api-docs、login-page、report-q2)
  • 文本/HTML → 主要主题或标题(例如 pricing、team-intro、changelog)
  • JSON → 数据类型或架构名称(例如 user-config、metrics)
  • 后备方案 → 如果内容含义不明确,则使用项目名称或目录名称 不要使用随机字符串或截断的文件名 — slug 应便于人类阅读,并能描述发布的内容。

解析规则:

  • 2、4 或空值 → 建议的 slug
  • 1 → public
  • 3 或任何其他字符串 → 原样使用
步骤 5:询问密码

使用 AskUserQuestion:

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.

使用 Bash 工具执行生成随机密码:

LC_ALL=C tr -dc 'a-z0-9' < /dev/urandom | head -c6 2>/dev/null

解析规则:

  • 1、4 或空值 → 不设置密码
  • 2 → 使用生成的随机密码
  • 3 或自定义文本 → 原样使用
步骤 6:发布并保存令牌(安全)

NEW 站点和非公开命名空间需要 Dedicated 子域名(subdomain);不接受显式 path。 NEW 公开的非站点默认使用 Promotion(path),也可以选择 subdomain,包括受密码保护的上传。 将 DELIVERY_MODE 设为经过验证的标志值;若未提供,则设为空;仅发送 X-Delivery-Mode,绝不发送由用户编写的正文内容字段。 去重可能返回 OLD 或 NEW 的胜出项:保留其身份、群组、模式和服务器返回的准确 link,即使请求的模式有所不同。 OLD 的路由元数据缺失或为 null 时,表示 Existing 链接;绝不根据其 URL 推断模式。

安全:ownerToken 绝不能出现在对话输出中。以下 bash 代码块会原子地处理 curl、令牌解析和历史记录保存;LLM 只能看到 URL。每个代码块都会先设置 PASS_H(没有密码时为空数组),并使用加引号的 "${PASS_H[@]}"——绝不会将密码插值到命令中。站点目录压缩包的排除项(.git/、.env* 等)也有助于防止机密泄露——它们可确保凭据和 VCS 数据不会进入已发布的归档文件。

6a. 初始化历史记录文件(在发布代码块之前运行一次)——使用 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

> PRIVATE FILE — keep this out of version control and never share it.
> Owner tokens allow delete and in-place republish (html/json/kv/sites all 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"`
> Update site (keep same URL): `PUT /api/sites/{ns}/{id}` with `X-Owner-Token: TOKEN` + the new bundle — the uploaded bundle fully replaces the file set (adds new files, removes absent ones, overwrites matching). The link never changes.

| Date | URL | Owner Token | TTL | Type | Owner API | Identity |
|------|-----|-------------|-----|------|-----------|----------|
HEADER
fi

然后根据检测到的类型运行以下发布代码块之一。每个代码块都假定 HISTORY_FILE 已在 6a 中创建。

HTML/Markdown 文本——使用 Bash 工具执行:

HISTORY_FILE=".claude/brewpage-history.md"
CONTENT=$(cat <<'BREWPAGE_EOF'
{content}
BREWPAGE_EOF
)
PAYLOAD=$(jq -n --arg c "$CONTENT" '{content: $c}')
DELIVERY_H=()
case "${DELIVERY_MODE:-}" in
  '') : ;;
  path|subdomain) DELIVERY_H=(-H "X-Delivery-Mode: $DELIVERY_MODE") ;;
  *) echo "FAILED: delivery mode must be path or subdomain"; exit 1 ;;
esac
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[@]}" "${DELIVERY_H[@]}" \
  -d "$PAYLOAD")

URL=$(echo "$RESPONSE" | jq -r '.link // empty')
TOKEN=$(echo "$RESPONSE" | jq -r '.ownerToken // empty')
OWNER_API=$(echo "$RESPONSE" | jq -r '.ownerLink // empty')
IDENTITY=$(echo "$RESPONSE" | jq -c '{id, namespace, type, routingCohort, deliveryMode, modeLocked, hostingVersion, managementLink}')

if [ -n "$URL" ]; then
  [ -n "$TOKEN" ] && echo "| $(date '+%Y-%m-%d %H:%M') | [$URL]($URL) | \`$TOKEN\` | {ttl}d | html | $OWNER_API | \`$IDENTITY\` |" >> "$HISTORY_FILE"
  echo "OK $URL"
else
  echo "FAILED: publish rejected (no .link in response)"
fi

JSON——使用 Bash 工具执行:

HISTORY_FILE=".claude/brewpage-history.md"
DELIVERY_H=()
case "${DELIVERY_MODE:-}" in
  '') : ;;
  path|subdomain) DELIVERY_H=(-H "X-Delivery-Mode: $DELIVERY_MODE") ;;
  *) echo "FAILED: delivery mode must be path or subdomain"; exit 1 ;;
esac
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" \
  "${PASS_H[@]}" "${DELIVERY_H[@]}" \
  -d '{original_json}')

URL=$(echo "$RESPONSE" | jq -r '.link // empty')
TOKEN=$(echo "$RESPONSE" | jq -r '.ownerToken // empty')
OWNER_API=$(echo "$RESPONSE" | jq -r '.ownerLink // empty')
IDENTITY=$(echo "$RESPONSE" | jq -c '{id, namespace, type, routingCohort, deliveryMode, modeLocked, hostingVersion, managementLink}')

if [ -n "$URL" ]; then
  [ -n "$TOKEN" ] && echo "| $(date '+%Y-%m-%d %H:%M') | [$URL]($URL) | \`$TOKEN\` | {ttl}d | json | $OWNER_API | \`$IDENTITY\` |" >> "$HISTORY_FILE"
  echo "OK $URL"
else
  echo "FAILED: publish rejected (no .link in response)"
fi

文件——使用 Bash 工具执行:

HISTORY_FILE=".claude/brewpage-history.md"
DELIVERY_H=()
case "${DELIVERY_MODE:-}" in
  '') : ;;
  path|subdomain) DELIVERY_H=(-H "X-Delivery-Mode: $DELIVERY_MODE") ;;
  *) echo "FAILED: delivery mode must be path or subdomain"; exit 1 ;;
esac
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[@]}" "${DELIVERY_H[@]}" \
  -F "file=@/absolute/path/to/file")

URL=$(echo "$RESPONSE" | jq -r '.link // empty')
TOKEN=$(echo "$RESPONSE" | jq -r '.ownerToken // empty')
OWNER_API=$(echo "$RESPONSE" | jq -r '.ownerLink // empty')
IDENTITY=$(echo "$RESPONSE" | jq -c '{id, namespace, type, routingCohort, deliveryMode, modeLocked, hostingVersion, managementLink}')

if [ -n "$URL" ]; then
  [ -n "$TOKEN" ] && echo "| $(date '+%Y-%m-%d %H:%M') | [$URL]($URL) | \`$TOKEN\` | {ttl}d | file | $OWNER_API | \`$IDENTITY\` |" >> "$HISTORY_FILE"
  echo "OK $URL"
else
  echo "FAILED: publish rejected (no .link in response)"
fi

站点(目录)——使用 Bash 工具执行:

HISTORY_FILE=".claude/brewpage-history.md"
DELIVERY_H=()
case "${DELIVERY_MODE:-}" in
  '') : ;;
  path|subdomain) DELIVERY_H=(-H "X-Delivery-Mode: $DELIVERY_MODE") ;;
  *) echo "FAILED: delivery mode must be path or subdomain"; exit 1 ;;
esac
PASS_H=()
[ -n "$PASSWORD" ] && PASS_H=(-H "X-Password: $PASSWORD")
TMPZIP=$(mktemp /tmp/brewpage-site-XXXXXX.zip) || { echo "FAILED: cannot allocate site archive"; exit 1; }
rm -f "$TMPZIP" || { echo "FAILED: cannot prepare site archive"; exit 1; }
# Exclude VCS, secrets, deps, editor + OS junk and sourcemaps — publish only built static assets.
if ! (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') ||
  [ ! -s "$TMPZIP" ] || ! unzip -tq "$TMPZIP" >/dev/null; then
  rm -f "$TMPZIP"
  echo "FAILED: site archive creation or verification failed"
  exit 1
fi
RESPONSE=$(curl -s -X POST "https://brewpage.app/api/sites?ns={ns}&ttl={days}&entry={entry}" \
  -H "User-Agent: ClaudeCode/1.0" \
  "${PASS_H[@]}" "${DELIVERY_H[@]}" \
  -F "archive=@$TMPZIP")
rm -f "$TMPZIP"

URL=$(echo "$RESPONSE" | jq -r '.link // empty')
TOKEN=$(echo "$RESPONSE" | jq -r '.ownerToken // empty')
OWNER_API=$(echo "$RESPONSE" | jq -r '.ownerLink // empty')
IDENTITY=$(echo "$RESPONSE" | jq -c '{id, namespace, type, routingCohort, deliveryMode, modeLocked, hostingVersion, managementLink}')
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) | $OWNER_API | \`$IDENTITY\` |" >> "$HISTORY_FILE"
  echo "OK $URL | Files: $FCOUNT"
else
  echo "FAILED: publish rejected (no .link in response)"
fi

站点(ZIP 文件)——使用 Bash 工具执行:

HISTORY_FILE=".claude/brewpage-history.md"
DELIVERY_H=()
case "${DELIVERY_MODE:-}" in
  '') : ;;
  path|subdomain) DELIVERY_H=(-H "X-Delivery-Mode: $DELIVERY_MODE") ;;
  *) echo "FAILED: delivery mode must be path or subdomain"; exit 1 ;;
esac
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[@]}" "${DELIVERY_H[@]}" \
  -F "archive=@{zip_file_path}")

URL=$(echo "$RESPONSE" | jq -r '.link // empty')
TOKEN=$(echo "$RESPONSE" | jq -r '.ownerToken // empty')
OWNER_API=$(echo "$RESPONSE" | jq -r '.ownerLink // empty')
IDENTITY=$(echo "$RESPONSE" | jq -c '{id, namespace, type, routingCohort, deliveryMode, modeLocked, hostingVersion, managementLink}')
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) | $OWNER_API | \`$IDENTITY\` |" >> "$HISTORY_FILE"
  echo "OK $URL | Files: $FCOUNT"
else
  echo "FAILED: publish rejected (no .link in response)"
fi
步骤 7:输出结果

成功(bash 输出 OK {url}):

Published: {url from bash output}
Owner token saved to .claude/brewpage-history.md

站点发布成功(bash 输出 OK {url} | Files: {count}):

Published site: {url from bash output}
Entry: {entry_file} | Files: {count}
Owner token saved to .claude/brewpage-history.md

Share the server URL exactly as printed, including any trailing slash.

对于私有(非 public)命名空间,在链接后追加一行简短说明(如果回复必须极其简短,则跳过):未列出的链接——任何拥有该链接的人都可以打开,但它不会出现在图库或搜索结果中。想让它可被发现?请发布到 public。

绝不在对话中输出 ownerToken。 令牌只保存在历史记录文件中。

错误(bash 输出 FAILED: ...):

Publish failed.

说明

  • 使用 curl -F "file=@..." 时,始终使用绝对文件路径。
  • 使用 jq -n --arg c "$CONTENT" '{content: $c}' 安全编码文本内容。format 是查询参数,不是请求体字段——/api/html 会忽略 JSON 请求体中的任何 format 键,只读取 URL 中的 ?format=。位置错误 = 服务端应用默认格式 html,并将你的 Markdown 作为原始文本存储。
  • TTL 默认为 15 天。命名空间必须匹配 ^[a-z0-9-]{1,32}$,默认值:建议的(私有)命名空间。
  • 要删除已发布的页面,请在 .claude/brewpage-history.md 中查找所有者令牌,并使用该文件页眉中显示的删除命令。
  • 对所有者 API 执行操作时,使用返回的 ownerLink 和存储的身份元数据;切勿解析 NEW 根主机名来获取命名空间/ID。缺少元数据的 OLD 历史记录会保留其现有 API 标识符。
  • 要更新已发布的网站,将替换包 PUT 到其顶级域名所有者 API,并使用 X-Owner-Token;常规 PUT/更新时保留主机名并省略 X-Delivery-Mode。
  • 只有 NEW 公共非站点可使用所有者专属的 PATCH /api/{type}/{ns}/{id}/hosting,并从当前身份元数据中传入 {"deliveryMode":"subdomain","expectedVersion":1}。OLD、站点和非公开身份都没有模式可选。之后使用返回的 URL/版本。
  • NEW 受密码保护的链接保持干净:切勿生成或附加 ?p=。API 请求仍使用 X-Password;OLD 密码行为保持不变。
  • 站点:目录是主要输入——系统会自动将其打包为 ZIP(POST /api/sites 唯一接受的内容),从而固定相对路径。预先构建的 .zip 是另一种输入,可原样上传。始终发布构建产物(dist/、build/、out/、_site/、public/),不要发布项目源文件。
  • 自动 ZIP 会排除 .git/、.env/.env.*、node_modules/、编辑器/操作系统杂项、源映射和日志——这是有意设置的防止机密泄露措施,可确保凭据和 VCS 历史记录不会进入公开归档。
  • 入口文件检测顺序:--entry 覆盖项 > index.html > 按字母顺序排列的第一个 .html 文件。
  • 保持 .link 原样:NEW 子域名根路径保留 /;OLD 链接保留其现有形式。切勿根据 ID 重建主机名。
  • 在返回的确切 URL 和资源路径上验证 NEW 原生站点;浏览器可以证明页面是否正常渲染。OLD 站点仍可能使用落地页重定向;请在浏览器中验证,或获取其明确指定的入口资源。

由以下项目提供支持

brewpage.app免费即时托管——HTML、JSON、文件、站点、KV。无需注册。
brewcodeClaude Code 插件套件——无限任务、代码审查、技能、钩子。

评论

支持 Markdown