先日、私はターミナルから Dev.to の記事を管理できる CLI ツールを探していました。私は月に 4〜5 本の記事を書き、アナリティクスを徹底的に追跡し、git でバックアップされたワークフローを望んでいました。

既存のツールを 9 個見つけました。すべてを試してみた結果は次のとおりです:

  • devto-cli (Node): 最終コミットが 2 年前。インストール時に壊れました。
  • dev-to-git (Node): ローカルへの同期しかできません。逆方向へのプッシュができません。
  • slinkity: 開発が中止されています。
  • forem-cli: 40 以上のエンドポイントのうち 3 つしか実装されていません。

どのツールも同じことをしています:記事の公開。それだけです。プルやタグの検証ができるかもしれません。

一方、Dev.to API には 40 以上のエンドポイント があり、アナリティクス、セマンティック検索、ML 駆動のコンテンツ概念、フォロワーエンゲージメント、トレンド追跡、リーディングリスト管理などが含まれます。誰もそれらを使っていません。

そこで devpub を構築しました。

目次


devpub ができること(他のツールにない機能)

# 基本機能(どのツールもできる)
devpub push -f articles/my-post.md
devpub pull

# ターミナルでのアナリティクス
devpub stats
# Views: 246.5K | Reactions: 4.4K | Comments: 402 | Followers: 18.9K

# トップ記事付きのフルダッシュボード
devpub dashboard

# AI 駆動の検索(キーワードではなくセマンティック)
devpub search "building serverless apps" --semantic

# 今まさにトレンドのもの
devpub trends

# 公開前に問題をキャッチ
devpub validate

Enter fullscreen mode Exit fullscreen mode

違いは一つの機能ではなく、網羅性です。比較は次のとおりです:

機能 devpub 他のツール
記事の公開/更新 Yes Yes
記事のローカルへのプル Yes 一部
アナリティクス(7 エンドポイント) Yes No
セマンティック検索 Yes No
トレンド発見 Yes No
記事のバリデーション Yes No
レート制限(30 req/30s) Yes No
失敗時の再試行ロジック Yes No
Concepts API(ML トピック) Yes No

Dev.to API で発見したこと

devpub の構築中に、どこにも明記されていない API エンドポイントをいくつか見つけました:

1. セマンティック検索 -- Dev.to は Gemini の埋め込み(768 次元ベクトル)と pgvector を使用した完全な埋め込みベースの検索システムを持っています。キーワードではなく意味で記事を検索できます。エンドポイントはコサイン類似度スコアを返します。

2. Concepts API -- これらは ML で生成されたトピック分類で、日次メトリクス(ページビュー、リアクション、コメント、人気スコア)を持ちます。手動タグよりはるかに強力です。

3. V1 Accept ヘッダー -- V1 API には Accept: application/vnd.forem.api-v1+json が必要です。これがないと V0 のレスポンスが返されます。競合ツールのコードにはこれが記載されていませんでした。

4. ネストされたアナリティクスレスポンス -- アナリティクスエンドポイントは {"page_views": {"total": 246454, "average_read_time_in_seconds": 306}} のようなネストオブジェクトを返し、平坦な整数ではありません。調査したツールはアナリティクスを使っていないか、この構造で壊れていました。


開発の経緯(実際に起きたこと)

devpub のコアは 1 日で構築しました。月曜日の午後 2 時に開始し、夕方には動作する CLI が完成しました。実際のタイムラインは次のとおりです:

1〜2 時間目:調査

コードを一行も書く前に、9 つの競合ツールを分析しました。ダウンロードしてソースを読み、各ツールが使用している API エンドポイントをマッピングしました。最も「完全」なツールでも 40 以上のエンドポイントのうち 12 しかカバーしていませんでした。ほとんどは 3〜5 でした。

その後、Forem API ドキュメント全体を読みました。誰もが読む要約ページではなく、完全な V1 仕様です。そこでセマンティック検索、コンセプト、アナリティクスエンドポイントを見つけました。

3 時間目:足場作り

pyproject.toml、src レイアウト、Click CLI エントリポイント。退屈な作業ですが、15 分で devpub --help を動作させました。ここでの重要な決定は、requests ではなく httpx を使用することでした。httpx はコネクションプーリング、適切なタイムアウト、後で変更なしに async オプションを提供します。

4〜5 時間目:API クライアント

ここに最も時間を費やしました。HTTP 呼び出しが難しいからではありません。初日から本番レベルのクライアントにしたかったからです:

  • 実際に動作するレート制限(単なるスリープタイマーではなくスライディングウィンドウ)
  • 429 と 5xx の指数バックオフによる再試行
  • スタックトレースではなく適切なエラーメッセージ

最初のバージョンにはこれらがありませんでした。単に raise_for_status() を呼び出し、醜い httpx.HTTPStatusError 例外をユーザーに投げていました。自分の 86 記事をプルして 30 記事目でレート制限にヒットしたテストでこれを発見しました。すべてがクラッシュしました。

6 時間目:実際のアカウントでのテスト

ここで興味深いことが起こりました。最初の devpub stats 呼び出しが次でクラッシュしました:

TypeError: '>=' not supported between instances of 'dict' and 'int'

Enter fullscreen mode Exit fullscreen mode

アナリティクスエンドポイントが {"page_views": 246454} ではなく {"page_views": {"total": 246454}} を返すことがわかりました。ネストされた辞書です。既存のツールはアナリティクスを使わないため、正しく処理できません。

ヘルスチェックエンドポイントも驚きました。V1 API(Accept ヘッダー付き)では /health_checks/app に認証が必要です。ヘッダーなしでは 401 が返されます。そこでヘルスチェックを /users/me を呼び出すように変更しました。

7 時間目:push --all の恐怖

テスト中に push --all が README.md を Dev.to に公開しそうになりました。元のロジックは:frontmatter に title を持つ .md ファイルを見つけてプッシュする、というものでした。私の README にはタイトル付きの YAML frontmatter があります。

titlepublished の両方のキーを必須にし、既知のディレクトリ(articles/posts/content/drafts/)のみをスキャンするように修正しました。小さなことですが、CONTRIBUTING.md を誤って Dev.to 記事として公開することを想像してみてください。

違っていたらこうする

  1. push ではなく pull から始める。 Pull はデータモデルを構築する前に API レスポンス形式を理解することを強制します。私はドキュメントに基づいてモデルを最初に構築し、実際のレスポンスが異なることがわかったときに修正する必要がありました。

  2. 最初からモックテストを書く。 すべてのコードを先に書いてからテストを書きました。クライアントメソッドと並行して API モックレスポンスを書くべきでした。ネストされた辞書のイシューは即座に発見できたでしょう。

  3. v0.0.1 でレートリミッターをリリースする。 最初は「後で追加する」と思っていました。実際のテストの 30 分以内に制限にヒットしました。最初のコミットからあるべきでした。


アーキテクチャ(コントリビュータ向け)

src/devpub/
  api/        # レート制限と再試行付きの HTTP クライアント
  cli/        # Click コマンド + Rich ターミナル出力  
  core/       # ビジネスロジック(記事、同期、バリデーション、設定)
  templates/  # 記事の足場(5 テンプレート)

Enter fullscreen mode Exit fullscreen mode

主な決定事項:

  • Python + Click + Rich -- ほとんどのコントリビュータに馴染みがあり、優れたターミナル UX
  • httpx -- async 対応、内蔵のタイムアウト処理
  • スライディングウィンドウレートリミッター -- 30 秒間に 30 リクエスト、自動的にスリープ
  • バックオフ付きの 3 回再試行 -- 429 と 5xx をクラッシュせずに処理
  • Frontmatter ベースの追跡 -- 記事 ID は markdown ファイルに保存され、データベースは不要

57 テスト、すべてパス

$ pytest -v
57 passed in 1.08s

Enter fullscreen mode Exit fullscreen mode

テストは respx で HTTP レイヤーをモックします。CI では実際の API 呼び出しは行われません。カバー範囲:

  • API クライアント(すべてのエンドポイント、エラーコード、再試行)
  • 記事モデル(frontmatter の往復、スラッグ生成、タグ解析)
  • 同期ロジック(新規プッシュ、既存の更新、ドライラン、エラーハンドリング)
  • バリデーション(タイトル長、タグ数、本文チェック、正規 URL)

試してみる

pip install devpub
export DEVPUB_API_KEY=your_key_here
devpub doctor

Enter fullscreen mode Exit fullscreen mode

またはソースから:

git clone https://github.com/simplynadaf/devpub.git
cd devpub
pip install -e .

Enter fullscreen mode Exit fullscreen mode

API キーの取得: https://dev.to/settings/extensions


AI エージェントとの連携

すべてのコマンドは構造化された出力を返し、レート制限をサイレントに処理し、--dry-run をサポートします。AI コーディングエージェント(Claude Code、Copilot、Cursor)は devpub を公開レイヤーとして使用できます。エージェントが記事を書き、devpub がバリデーション、プッシュ、パフォーマンス追跡を行います。初期設定後は人間は不要です。


コントリビュート

プロジェクトはベータ版です。PR を歓迎します。助けが必要なこと:

  • パフォーマンス:pull コマンドは記事ごとに 1 回の API 呼び出しを行います。バッチ化できますか?
  • 画像の書き換え:相対パスはプッシュ時に GitHub raw URL に変換されるべきです
  • ターミナルチャート--graph フラグは受け付けられますが、まだ実装されていません
  • Hashnode アダプター:クロスポストの足場は準備できていますが、実装が必要です

good first issue ラベルの issues を確認してください。


GitHub: github.com/simplynadaf/devpub

これで時間を節約できた場合はリポジトリにスターをつけてください。何か壊れている場合は issue を開いてください。機能が欲しい場合は PR を送ってください。

現在の Dev.to ワークフローはどのようなものですか?ブラウザエディタで書いていますか、それともローカル環境がありますか?人々が抱えている痛点は何でしょうか。


📺 YouTube でデモ動画を見る


Built by Sarvar Nadaf - Cloud Architect
Follow me: Dev.to | GitHub | YouTube | LinkedIn