アーキテクチャ
全体俯瞰
┌─────────────────────────────────────────────────────────────────────────┐
│ Claude Code (slash commands) │
│ /substack-draft /substack-from-url /substack-from-pdf /substack-... │
└─────────────────────────────────────────────────────────────────────────┘
│ subprocess
▼
┌─────────────────────────────────────────────────────────────────────────┐
│ substack-skill CLI (typer) │
│ src/substack_skill/cli.py │
└─────────────────────────────────────────────────────────────────────────┘
│ orchestrate
▼
┌──────────────────────────────────────────────────────────────────────┐
│ Pipeline orchestrator (Article IR を中心とした責務分離) │
│ │
│ ┌───────────┐ ┌──────────────┐ ┌───────────┐ ┌────────────────┐ │
│ │ sources/ │→ │ generators/ │→ │ assets/ │→ │ publishers/ │ │
│ │ - topic │ │ - article │ │ - image │ │ - substack │ │
│ │ - url │ │ - title │ │ - diagram │ │ draft │ │
│ │ - pdf │ │ - tags │ │ - cache │ │ │ │
│ │ - markdown│ │ │ │ │ │ │ │
│ └───────────┘ └──────────────┘ └───────────┘ └────────────────┘ │
│ │
│ 共通: ir/model.py (Article IR), errors.py, retry.py │
└──────────────────────────────────────────────────────────────────────┘
│
▼
┌──────────────────────────────────────────────────────────────────────┐
│ External services │
│ - Anthropic Claude API - Higgsfield API - OpenAI API │
│ - Substack (unofficial) - Excalidraw renderer (Node CLI) │
└──────────────────────────────────────────────────────────────────────┘出力パイプライン詳細(DESIGN §4.4 / Codex C1 反映)
Article(IR + ImageBlock(local_path) + DiagramBlock(rendered_png))
│
▼
auth.load_cookies() → resolve_identity() → SubstackClient
│ ↑ /user/profile/self を叩き publication_url / user_id を確定
▼
images.upload_all(article)
│ # local_path → /api/v1/image に POST して cdn_url + cdn_meta を書き戻す
│ # === ここで 04_uploaded.json を保存 ===
▼
prosemirror.convert(article) → dict (ProseMirror JSON)
│ # アップロード済 cdn_url を src に埋め込む必要があるため、必ず upload 後
│ # === ここで 05_prosemirror.json を保存 ===
▼
draft.create_or_update(article, prosemirror_doc, *, slug_lock=...) → draft_id
│ # === ここで 06_draft.json を保存(draft_id 含む)===
▼
preview.urls(draft_id) → (edit_url, preview_url)中間成果物(再開可能性)
| ファイル | 内容 | 作成タイミング |
|---|---|---|
01_source.json | SourceMeta + 原文 | 入力ソースロード後 |
02_article.json | Article IR(本文完成、アセット未挿入) | 生成層完了後 |
02b_asset_plan.json | AssetPlan(画像・図候補と挿入位置) | 統合フェーズ後 |
03_with_assets.json | Article IR(ローカルパスのみ、CDN 未) | アセット生成完了後 |
04_uploaded.json | Article IR(CDN URL 確定) | CDN アップロード後 |
05_prosemirror.json | ProseMirror JSON | 変換後 |
06_draft.json | draft_id + 編集URL | Substack 下書き作成後 |
--resume <run_id> で続きから実行可能。
モジュール構成
src/substack_skill/
├── cli.py # typer エントリ
├── errors.py # 例外階層
├── ir/
│ ├── model.py # Article / Block / ImageRef / ...
│ └── serialize.py # JSON round-trip
├── infra/
│ ├── keychain.py # keyring ラッパ
│ ├── log.py # マスキング Formatter
│ ├── retry.py # 指数バックオフ + Retry-After
│ └── workdir.py # output/.work/<run_id>/
├── sources/ # [Phase B]
├── generators/ # [Phase B]
├── assets/
│ ├── image/ # [Phase C]
│ └── diagram/ # [Phase D]
└── publishers/substack/ # [Phase E]x-news-auto アーキテクチャ(Track 3)
[Claude routine cron] ── 毎朝6:00 ──┐
▼
[x-news-auto pipeline]
│
┌──────────────────────┼─────────────────────────┐
▼ ▼ ▼
X timeline X List/Search Grok 連携 (任意)
│ │ │
└────────────┬─────────┴──────────────────────────┘
▼
AI関連度フィルタ (LLM judge)
│
▼
重複検出・集約
│
▼
[substack-skill 既存パイプライン]
Article gen / Image / Diagram
│
▼
Substack 下書き作成 (E)
│
▼
[Slack レビューゲート (Block Kit)]
┌────┬────┬─────┐
│承認│却下│修正 │
▼ ▼ ▼
[自動公開] archive AI再生成 → 再レビュー
│
▼
Slack 通知 + 統計設計原則(DESIGN §1.1)
- Article IR を唯一のハブ — 入力源・出力先を疎結合に
- 副作用は publishers/ 配下に集約 — 生成系は pure に近づける
- 外部API呼び出しは1モジュールに隔離 — 仕様変更時の影響範囲を局所化
- 失敗しても再開可能 — 中間成果物を
output/.work/<run_id>/に保存 - 秘密情報は Keychain のみ — .env 平文禁止
- 公開行動を絶対に取らない — substack-skill 本体では
publishを実装すらしない - 個人情報・著作物を不要に保持しない — 7日 retention、
--no-source-cacheで完全無効化可能