Skip to content

アーキテクチャ

全体俯瞰

┌─────────────────────────────────────────────────────────────────────────┐
│  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.jsonSourceMeta + 原文入力ソースロード後
02_article.jsonArticle IR(本文完成、アセット未挿入)生成層完了後
02b_asset_plan.jsonAssetPlan(画像・図候補と挿入位置)統合フェーズ後
03_with_assets.jsonArticle IR(ローカルパスのみ、CDN 未)アセット生成完了後
04_uploaded.jsonArticle IR(CDN URL 確定)CDN アップロード後
05_prosemirror.jsonProseMirror JSON変換後
06_draft.jsondraft_id + 編集URLSubstack 下書き作成後

--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)

  1. Article IR を唯一のハブ — 入力源・出力先を疎結合に
  2. 副作用は publishers/ 配下に集約 — 生成系は pure に近づける
  3. 外部API呼び出しは1モジュールに隔離 — 仕様変更時の影響範囲を局所化
  4. 失敗しても再開可能 — 中間成果物を output/.work/<run_id>/ に保存
  5. 秘密情報は Keychain のみ — .env 平文禁止
  6. 公開行動を絶対に取らない — substack-skill 本体では publish を実装すらしない
  7. 個人情報・著作物を不要に保持しない — 7日 retention、--no-source-cache で完全無効化可能

substack-skill 内部設計書 — 公開非対象