brewpage-publish について
brewpage-publish は、HTML、Markdown、JSON、ファイル、ビルド済みの複数ファイルサイトを brewpage.app に公開するスキルです。コンテンツ判定や名前空間・パスワードの選択を案内し、所有者トークンをローカルの履歴ファイルに保存します。
主な機能
- 01HTML、Markdown、JSON、ファイルの公開
- 02ディレクトリや ZIP からビルド済みサイトをアップロード
- 03TTL、名前空間、パスワードの設定
- 04サイトのエントリーファイル検出
- 05所有者トークンをローカル履歴に保存
活用シーン
- 01静的サイトのビルド成果物をホスト
- 02JSON ドキュメントや単一ファイルを公開
- 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) | SITE | POST /api/sites(ディレクトリは自動で zip 化 — 主要なパス) |
content_arg が .zip で終わり、かつファイルが存在する(test -f) | SITE | POST /api/sites(ビルド済みアーカイブのアップロード) |
content_arg がファイルパスで、かつファイルが存在する(test -f) | FILE | POST /api/files(multipart) |
content_arg が { または [ で始まる | JSON | POST /api/json |
| それ以外 | HTML | POST /api/html(format=markdown) |
モードのルール: ディレクトリ/ZIP → SITE。単一ファイル → FILE。POST /api/sites は multipart の [email protected] のみを受け付けます — 生のフォルダのアップロードは存在しないため、ディレクトリはその場で自動的に zip 化されます(堅牢なデフォルトであり、アーカイブを封じることで相対パスが維持されます)。タイプごとの統計 — SITE(ディレクトリ): HTML の数、合計サイズ、エントリファイル。SITE(ZIP): ファイルサイズ、エントリの上書き。FILE: サイズ + file --mime-type -b による MIME。TEXT/JSON: 文字数。
ステップ 3: 公開前の統計を表示する
HTML/JSON/FILE の場合:
Content: <type description> · <size> · <api endpoint>
TTL: <N> days
SITE の場合: 優先順位に従ってエントリファイルを検出します: 1) --entry フラグ、2) index.html が存在する、3) アルファベット順で最初の .html ファイル。
ビルド済み静的ファイルのガード(zip 化する前に実行)。 公開するのはビルド済みの出力であり、プロジェクトのソースではありません:
- ディレクトリに
.htmlファイルがまったく含まれていない場合 → 明示的なエラーで 失敗 します: 「.htmlが見つかりません — 先にサイトをビルドし、そのビルド出力ディレクトリを指定してください。」エントリを推測しないでください。 - ディレクトリが未ビルドのソースのように見える場合(
package.jsonとsrc/があるが、トップレベルの.htmlがない) → 警告して確認 し、代わりにビルド出力(dist/、build/、out/、_site/、またはpublic/)を指定するようユーザーに求めます。ソースツリーを zip 化しないでください。
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).
自動提案: コンテンツの内容から 意味のある短いスラッグ(3〜16 文字、小文字の英数字 + ハイフン)を生成します:
- ファイル → ファイルのトピック/目的(例:
api-docs、login-page、report-q2) - テキスト/HTML → 主な主題またはタイトル(例:
pricing、team-intro、changelog) - JSON → データ型またはスキーマ名(例:
user-config、metrics) - フォールバック → コンテンツが曖昧な場合はプロジェクト名またはディレクトリ名 ランダムな文字列や切り詰めたファイル名は決して使用しないでください — スラッグは人間が読めるもので、何を公開するのかを説明するものであるべきです。
解決:
2、4、または空 → 提案されたスラッグ1→public3またはその他の文字列 → そのまま使用
ステップ 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 ツールを使用して EXECUTE でランダムパスワードを生成します:
LC_ALL=C tr -dc 'a-z0-9' < /dev/urandom | head -c6 2>/dev/null
解決:
1、4、または空 → パスワードなし2→ 生成されたランダムパスワードを使用3またはカスタムテキスト → そのまま使用
ステップ 6: 公開とトークンの保存 (セキュア)
新規サイトおよび非公開名前空間には専用サブドメイン (subdomain) が必要です。明示的な path は拒否されます。
新規の公開非サイトはデフォルトでプロモーション (path) となり、subdomain を選択できます。パスワード保護されたアップロードも含まれます。
DELIVERY_MODE には検証済みのフラグ値を設定するか、省略時は空にします。送信するのは X-Delivery-Mode のみで、作成された本文のフィールドは決して送信しません。
重複排除は OLD または NEW の勝者を返す場合があります。要求されたモードが異なる場合でも、その識別情報、コホート、モード、および正確なサーバーの link を保持してください。
OLD のルーティングメタデータが欠落している、または null の場合は既存リンクを意味します。その URL からモードを推測しないでください。
セキュリティ: ownerToken は会話の出力に絶対に含めてはいけません。以下の bash ブロックは curl + トークンのパース + 履歴の保存をアトミックに処理します。LLM が見るのは URL だけです。各ブロックは最初に
PASS_Hを設定し (パスワードがない場合は空の配列)、"${PASS_H[@]}"をクォートして使用します。パスワードがコマンドに文字列として展開されることはありません。サイトディレクトリの zip 除外指定 (.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
次に、検出されたタイプに基づいて以下の公開ブロックのいずれかを実行します。いずれも 6a で HISTORY_FILE がすでに作成されていることを前提としています。
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と保存されたアイデンティティメタデータを使用してください。名前空間/ID のために NEW ルートホストを決して解析しないでください。メタデータのない OLD 履歴は、既存の API 識別子を保持します。 - 公開済みサイトを更新するには、置換バンドルを
X-Owner-Tokenとともにその apex オーナー API にPUTしてください。ホストを保持し、通常の PUT/更新ではX-Delivery-Modeを省略してください。 - NEW のパブリック非サイトのみが、現在のアイデンティティメタデータからの
{"deliveryMode":"subdomain","expectedVersion":1}を用いたオーナー専用のPATCH /api/{type}/{ns}/{id}/hostingを許可します。OLD、サイト、非パブリックのアイデンティティにはモードの選択肢がありません。その後、返された URL/バージョンを使用してください。 - NEW のパスワード保護リンクはクリーンなままに保たれます。
?p=を生成したり追加したりしないでください。API リクエストでは引き続きX-Passwordを使用します。OLD のパスワードの動作は変更されません。 - サイト: ディレクトリが主要な入力です — 自動的に zip 圧縮され(
POST /api/sitesが受け付ける唯一の形式)、相対パスが封止されます。事前にビルドされた.zipが代替入力であり、そのままアップロードされます。常にビルド済みの出力(dist/、build/、out/、_site/、public/)を公開し、プロジェクトのソースは決して公開しないでください。 - 自動 zip は
.git/、.env/.env.*、node_modules/、エディタ/OS の不要ファイル、ソースマップ、ログを除外します — これは資格情報や VCS 履歴が公開アーカイブに決して到達しないようにする、意図的なシークレット漏洩防止策です。 - エントリファイルの検出:
--entryの上書き >index.html> アルファベット順で最初の.html。 .linkを正確に保持してください: NEW のサブドメインルートは/を保持し、OLD リンクは既存の形式を保持します。ID からホストを決して再構築しないでください。- NEW のネイティブサイトは、返された正確な URL とアセットパスで検証してください。ブラウザがレンダリングを証明します。OLD サイトは依然としてランディングページのリダイレクトを使用する場合があります。それらはブラウザで検証するか、明示的なエントリアセットを取得してください。
提供
| brewpage.app | 無料のインスタントホスティング — HTML、JSON、ファイル、サイト、KV。サインアップ不要。 |
| brewcode | Claude Code プラグインスイート — 無限タスク、コードレビュー、スキル、フック。 |