尚未偵測到更新。GitHub 發布版本後會顯示在這裡。
個人 GitHub 帳號下公開儲存庫的擁有者可以登入並驗證所有權,自行管理介紹,無需管理員核准。
登入並產生驗證檔案,在 30 分鐘內將其提交至此儲存庫的預設分支。其他分支或分叉儲存庫中的檔案無效。然後在下方檢查檔案,即可無需管理員核准直接管理專案。請保留檔案以維持管理權限。
如需登入,將前往登入頁面。登入後請回到此處,再次點擊所選的驗證按鈕。
在專案資料夾中執行 /nomorevibe verify,即可驗證所有權並自行管理介紹。
一分鐘完成驗證正在載入完整 README。 以下為已儲存的摘錄。
記事テキストを投入すると、AI が自動で対話形式のポッドキャスト音声と RSS フィードを生成するシステム。 • 台本: Gemini 2.5 Flash で Host / CoHost の掛け合い形式(日本語) • 音声: Gemini TTS のマルチスピーカー合成(WAV 形式) • 配信: Supabase Storage の公開バケット(グローバル CDN) 本番環境では pgflow migration が作成する pgflow_ensure_workers cron が craft-episode-worker の死活監視と再起動を行います。檢視完整 README ↗
記事テキストを投入すると、AI が自動で対話形式のポッドキャスト音声と RSS フィードを生成するシステム。
スタック: TypeScript / Deno · Supabase Edge Functions · Supabase Storage · Supabase Postgres + pgflow · Gemini API
外部トリガー(curl / Capture / queued CI)
│
│ POST /functions/v1/ingest {title, content, content_path?}
▼
┌─────────────────────┐
│ ingest │ articles / episodes を作成(Git 本文)
└────────┬────────────┘
│ pgflow.start_flow("craftEpisodeSubmit")
▼
┌─────────────────────┐
│ generate_script │ Gemini Flash でホスト/コホスト台本を JSON 生成
└────────┬────────────┘
▼
┌─────────────────────┐
│ generate_audio_start│ Gemini Batch API で音声生成ジョブを開始
└────────┬────────────┘
▼
┌─────────────────────┐
│ download-monitor │ pending batch を検知し craftEpisodeDownload を起動
└────────┬────────────┘
▼
┌─────────────────────┐
│generate_audio_download│ バッチ状態をポーリングし、完了後に PCM→WAV 変換 → Storage 保存
└────────┬────────────┘
▼
┌─────────────────────┐
│ update_rss │ DB から全エピソードを取得 → RSS 2.0 + iTunes 形式で feed.xml 生成
└─────────────────────┘
│
▼
Supabase Storage (公開バケット `podcast`)
├── feed.xml ← RSS フィード(CDN 配信)
├── audio/<id>.wav ← 音声ファイル(CDN 配信)
└── cover.png ← カバー画像
本番環境では pgflow migration が作成する pgflow_ensure_workers cron が
craft-episode-worker の死活監視と再起動を行います。
podcaster/
├── apps/
│ └── web/ # Next.js + Keystatic (local admin; Vercel は GitHub storage)
├── content/
│ ├── docs/ # Wiki Markdoc(公開サイト /podcast の入力)
│ └── web-clips/ # クリップ & メモ(/podcast/web-clips、要ログイン)
├── chugaku-juken/ # 中学受験の物語(公開サイト /juken)
│ └── sapix/grade5-japanese-history/
│ ├── course.yml
│ └── weeks/<月曜YYYY-MM-DD>/
├── public/
│ └── cover.png # ポッドキャストカバー画像
├── scripts/
│ ├── lib/
│ │ ├── supabase-detect.ts # ローカル / リモート Supabase 接続情報の自動検出
│ │ └── table.ts # CLI 用テーブル表示ヘルパー
│ ├── deploy.ts # pnpm deploy の実体
│ ├── gemini-mock-server.ts # Gemini API 最小 mock サーバー
│ ├── ingest.ts # Git 上の mdoc/md を ingest
│ ├── podcast-cli.ts # パイプライン状態確認 CLI
│ ├── post-test-article.ts # ローカル動作確認用テスト記事投入
│ └── seed-config.ts # cover.png アップロード + podcast_config 初期化
├── supabase/
│ ├── config.toml # Supabase CLI 設定(ポート等)
│ ├── migrations/
│ │ ├── 20260423000001_initial.sql # articles / episodes / Storage バケット
│ │ ├── ...
│ │ ├── 20260507031725_..._pgflow_initial.sql
│ │ └── 20260507031744_..._pgflow_step_conditions.sql
│ ├── flows/
│ │ ├── index.ts # pgflow フローのエクスポート
│ │ ├── craft-episode-submit.ts # 台本生成 + 音声バッチ submit
│ │ └── craft-episode-download.ts # 音声ダウンロード + RSS 更新
│ └── functions/
│ ├── _shared/
│ │ ├── db.ts # Supabase クライアント
│ │ ├── pipeline-stages.ts # 各ステージ共通実装
│ │ ├── config.ts # podcast_config ローダー
│ │ └── types.ts # 共有型定義
│ ├── ingest/ # Webhook 受信
│ ├── craft-episode-worker/ # pgflow 実行ワーカー
│ └── pgflow/ # pgflow ControlPlane(フロー定義配信/管理)
├── .env.example
├── package.json
└── tsconfig.json
| ツール | インストール |
|---|---|
| Docker Desktop | https://www.docker.com/products/docker-desktop/ |
| Supabase CLI | brew install supabase/tap/supabase |
| pnpm | npm install -g pnpm |
| Gemini API キー | https://aistudio.google.com/apikey |
# 1. 依存関係インストール
pnpm install
# 2. pgflow のセットアップ(初回または更新時)
pnpm pgflow:install
# 3. 環境変数ファイルを作成し API キーを記入
cp .env.example .env.local
# → GEMINI_API_KEY を設定
# 4. Supabase ローカルスタックを起動(Docker が必要)
supabase start
# 5. マイグレーション適用
supabase db reset
# 6. カバー画像をアップロードし podcast_config を初期化
# (supabase status から接続情報を自動取得)
pnpm seed:config
# 7. Edge Functions をローカルで起動
pnpm functions:serve &
起動後のアクセス先:
| サービス | URL |
|---|---|
| Supabase Studio | http://localhost:54333 |
| Edge Functions | http://localhost:54331/functions/v1/ |
| RSS フィード | http://localhost:54331/storage/v1/object/public/podcast/feed.xml |
別ターミナルで:
pnpm test:post
(SUPABASE_SERVICE_ROLE_KEY は supabase status から自動取得)
202 が返ったら成功。あとは worker を起動すればフローが進行します。
本番では pgflow_ensure_workers が自動実行しますが、ローカルでは手動で呼べます。
# service_role key を取得
KEY=$(supabase status --json | python3 -c "import sys,json; print(json.load(sys.stdin)['SERVICE_ROLE_KEY'])")
# craft episode submit worker(script + batch submit)
curl -s http://localhost:54331/functions/v1/craft-episode-worker \
-H "Authorization: Bearer $KEY"
# craft episode download worker(download + rss)
curl -s http://localhost:54331/functions/v1/craft-episode-download-worker \
-H "Authorization: Bearer $KEY"
進捗は Studio の Table Editor (episodes テーブルの status 列) か、
Storage の podcast バケットで確認できます。
supabase start
pnpm functions:serve
gemini-provider のモック分岐は廃止し、Gemini API 互換の mock サーバーを scripts/ 配下で起動する方式になりました。
# 1. config.toml の Gemini API 設定を mock に切り替える
# [gemini]
# api_root = "http://127.0.0.1:8099"
# api_path = "/v1beta"
# 2. podcast_config に反映(local 対象)
TARGET=local pnpm seed:config
# 3. mock サーバー + Edge Functions を同時起動
pnpm functions:serve:mock
dev.gemini.mock.toml を編集すると、台本・音声・遅延・token usage を調整できます。
mock サーバーだけ起動する場合は pnpm gemini:mock:serve を使ってください。
pnpm cli でパイプラインの状態をローカルから確認できます。
# ローカル Supabase に接続(supabase start が必要)
TARGET=local pnpm cli <command>
# 本番に接続(デフォルト)
pnpm cli <command>
接続情報は SUPABASE_URL / SUPABASE_SERVICE_ROLE_KEY 環境変数か、
Supabase CLI (supabase status / supabase projects api-keys) から自動取得します。
list episodes最新 N 件のエピソードを一覧表示します。
pnpm cli list episodes # 最新 10 件
pnpm cli list episodes --limit 5 # 最新 5 件
ID Title Status Created At
-------- -------------------------------------------------- ------------ -------------------
a1b2c3d4 Supabase の pgmq でバックグラウンドジョブを実装… audio_ready 2026-04-26 12:34:56
list articles最新 N 件の記事を一覧表示します。
pnpm cli list articles
pnpm cli list articles --limit 20
list audioaudio_files テーブルの最新 N 件を一覧表示します(ストレージパス・MIME タイプ・ステータス)。
pnpm cli list audio
pnpm cli list audio --limit 5
download audio <id>音声ファイルを Storage バケット podcast からダウンロードし、./downloads/ に保存します。
<id> は audio_files.id または audio_files.episode_id のどちらでも指定できます。
pnpm cli download audio a1b2c3d4-...
# → downloads/audio/<episode_id>.wav に保存
status <article_id>1 つの記事に対するパイプライン全体の状態を表示します。
pnpm cli status a1b2c3d4-e5f6-...
Article
-------
ID Title Source Created At
-------- ----------------------- ------ -------------------
a1b2c3d4 Supabase の pgmq で… mem 2026-04-26 12:00:00
Episode
-------
ID Title Status Created At
-------- ----------------------- ----------- -------------------
b2c3d4e5 第42回:キューイングを… audio_ready 2026-04-26 12:05:00
Script
------
ID Episode Status Created At
-------- -------- ------ -------------------
c3d4e5f6 b2c3d4e5 ready 2026-04-26 12:06:00
Audio
-----
ID Episode Storage Path MIME Status Created At
-------- -------- ------------------------ --------- ------ -------------------
d4e5f6a7 b2c3d4e5 audio/b2c3d4e5.wav audio/wav ready 2026-04-26 12:10:00
logs処理ログ(processing_logs テーブル)を新しい順に一覧表示します。ステータス・エピソード ID でフィルタできます。
queue_name は移行期間の暫定として空文字で保存しているため、--queue フィルタは基本的に使いません。
pnpm cli logs # 最新 20 件
pnpm cli logs --limit 50 # 最新 50 件
pnpm cli logs --status failure # 失敗ログのみ
pnpm cli logs --episode b2c3d4e5-... # 特定エピソードのみ
requeue ... <id>パイプラインの特定ステージを再実行します。ジョブが失敗したときや再処理が必要な場合に使います。
実行前にレコードの存在確認を行い、確認プロンプトを表示します(--yes で省略可)。
| サブコマンド | 対象テーブル | 開始ステージ | 送信 payload |
|---|---|---|---|
script | episodes | script | { episodeId, startFrom: "script", trigger: "manual" } |
audio | episodes | audio | { episodeId, startFrom: "audio", trigger: "manual" } |
rss | episodes | rss | { episodeId, startFrom: "rss", trigger: "manual" } |
regenerate-script | episodes | script | { episodeId, startFrom: "script", regenerate: true, trigger: "manual" } |
regenerate-audio | episodes | audio | { episodeId, startFrom: "audio", regenerate: true, trigger: "manual" } |
# 台本生成からやり直す(記事 ID を指定)
pnpm cli requeue script a1b2c3d4-e5f6-...
# 音声生成からやり直す(エピソード ID を指定)
pnpm cli requeue audio b2c3d4e5-f6a7-...
# RSS 更新だけ再実行する(確認プロンプトをスキップ)
pnpm cli requeue rss b2c3d4e5-f6a7-... --yes
# 既存 episode を維持したまま script→audio→rss を再生成する
pnpm cli requeue regenerate-script b2c3d4e5-f6a7-...
# 既存 episode を維持したまま audio→rss を再生成する
pnpm cli requeue regenerate-audio b2c3d4e5-f6a7-...
Episode: 第42回:キューイングを… (b2c3d4e5)
Start flow {"episodeId":"b2c3d4e5-...","startFrom":"script","trigger":"manual"} → script? [y/N] y
Flow started (script): {"episodeId":"b2c3d4e5-...","startFrom":"script","trigger":"manual"}
supabase login
.env.production を作成(AI API キーのみ):GEMINI_API_KEY=<your-gemini-api-key>
pnpm deploy
デプロイの内容:
supabase projects list でプロジェクト自動検出(複数ある場合は SUPABASE_PROJECT_REF を env に設定)supabase projects api-keys でサービスロールキー自動取得supabase link でプロジェクトに接続supabase secrets set で AI API キーを Edge Function に反映supabase db push でマイグレーションを適用supabase_project_id, pgflow_auth_secret) を更新pgflow.track_worker_function('craft-episode-worker') で監視対象を登録supabase functions deploy で Edge Functions(ingest, craft-episode-worker, craft-episode-download-worker, download-monitor, pgflow)をデプロイseed-config.ts で cover.png をアップロードし podcast_config を初期化https://<ref>.supabase.co/storage/v1/object/public/podcast/feed.xml
Apple Podcasts / Overcast / Pocket Casts 等にこの URL を登録して購読できます。
curl -X POST https://<ref>.supabase.co/functions/v1/ingest \
-H "Content-Type: application/json" \
-d '{"title": "タイトル", "content": "本文"}'
本番では pgflow_ensure_workers が worker を自動起動します。
main ブランチへの push で Supabase クラウドへ自動デプロイされます。
GitHub リポジトリの Settings → Secrets and variables → Actions で以下を登録してください。
| Secret | 取得方法 |
|---|---|
SUPABASE_ACCESS_TOKEN | https://supabase.com/dashboard/account/tokens でトークン発行 |
SUPABASE_DB_PASSWORD | プロジェクト作成時に設定したDBパスワード |
SUPABASE_PROJECT_REF | supabase projects list で確認(以下の自動登録スクリプトで設定可) |
GEMINI_API_KEY | .env の値(以下の自動登録スクリプトで設定可) |
SUPABASE_PROJECT_REF / GEMINI_API_KEY は以下で自動登録できます(GitHub CLI が必要):
pnpm setup:gh-secrets
SUPABASE_ACCESS_TOKEN と SUPABASE_DB_PASSWORD は GitHub Settings から手動登録してください。
ポッドキャストの設定は podcast_config テーブル(キー/値形式)で管理します。
Studio → Table Editor → podcast_config から直接編集できます。
| キー | デフォルト値 | 説明 |
|---|---|---|
podcast.title | My AI Podcast | ポッドキャスト名 |
podcast.description | AI が生成するテック系ポッドキャスト | 説明文 |
podcast.cover_url | Storage の cover.png URL | カバー画像 URL |
generator.model | gemini-2.5-flash | 台本生成 LLM モデル |
gemini.api_root | https://generativelanguage.googleapis.com | Gemini API のベースURL |
gemini.api_path | /v1beta | Gemini API のパス(api_root と結合して利用) |
download.monitor_interval_seconds | 60 | download-monitor の監視間隔(cron) |
tts.model | gemini-3.1-flash-tts-preview | TTS モデル |
tts.chunk_max_chars | 200 | 台本をこの文字数(日本語おおよそ 7 文字/秒 → 200 ≈ 30 秒)以下のチャンクに分割し、1つの Batch ジョブ内の別リクエストとして生成する。話者の切り替わりで区切り、1 ターンが長い場合は同じ話者ラベルでさらに分割。3.1 は1回の生成が長いと音量が下がるため。config.toml の [tts] chunk_max_chars から seed 可。省略時もコード既定は 200(100 未満は無視) |
tts.instructions | (自然な会話トーンで…) | TTS への合成指示 |
tts.host.name | Host | ホストのスクリプト上の名前 |
tts.host.voice | Charon | ホストの音声 |
tts.cohost.name | CoHost | コホストのスクリプト上の名前 |
tts.cohost.voice | Achird | コホストの音声 |
利用可能な Gemini TTS 音声: Charon, Achird, Kore, Puck, Leda など
(全一覧)
調査レポート用スキルは 2 つあり、しばらく併用する。どちらも content/docs/(Wiki 記事)に保存して PR を出し、既定は podcast: none(マージだけでは ingest / TTS は走らない)。
/podcast-research <テーマ>
/podcast-research2 <テーマ>
podcast-research(.claude/skills/podcast-research/SKILL.md): 概要→背景→核概念…のサーベイ型podcast-research2(.claude/skills/podcast-research2/SKILL.md): 調査は how-to-survey、構成は survey-report(Phase 1 探索 / Phase 2 検証、前・中・後重心、入門・実務・専門)に従う。指定がなければ入門。概要・歴史・図・主張数は固定しない。保存と PR だけこのリポジトリ向け例:
/podcast-research Supabase の pgmq でバックグラウンドジョブを実装する方法
/podcast-research2 モジュラー曲線と楕円曲線の関係
スキル同士の呼び方は skill-orchestration(hskksk/agent-skills から import。.agents/skills/skill-orchestration/SKILL.md)に従う。呼び出し側は相手のファイルを指さない。
/sapix-grade5-history-weekly
sapix-grade5-history-weekly(.agents/skills/sapix-grade5-history-weekly/SKILL.md): 現在の日付と SAPIX 540 系カリキュラムから該当テキストを選び、月〜金の物語教材を 5 つの Markdown ファイル(chugaku-juken/sapix/grade5-japanese-history/weeks/<月曜>/)として作成。公開サイトは /juken。content/ の Wiki やポッドキャスト ingest とは別。くまぴー・とらぴー・ねこぴー・きつぴーの会話形式。ZIP や物語外クイズは付けないsupabase start でポートが競合する
他のプロジェクトの Supabase スタックが起動中の場合、supabase/config.toml のポートを変更してください(本プロジェクトは 54331〜54337 を使用)。
generateAudio ステージが失敗する(Broken pipe)
Gemini TTS が返す音声は 5 分エピソードで約 14 MB の WAV になります。 ローカルの Docker Storage でメモリ不足が発生する場合は、テスト記事の文字数を減らして試してください。
generateScript ステージが JSON を返さない
Gemini Flash が稀にフォーマットを外れた応答をすることがあります。
responseSchema で構造を強制していますが、それでも失敗した場合は episodes.error 列でエラー内容を確認し、記事の status を script_ready に戻してワーカーを再実行してください。
本番で pg_cron が動かない
pnpm deploy を再実行すると Vault と worker 登録が更新されます。
Studio → Database → Cron Jobs で pgflow_ensure_workers ジョブが有効か、
Studio → Database → Vault で supabase_project_id と pgflow_auth_secret が保存されているか確認してください。