はてなブログの記事をローカルのMarkdownファイルとして管理し、AtomPub APIで同期するCLIツールです。
go install github.com/hirano00o/hb/cmd/hb@latestまたはリポジトリをクローンしてビルドします:
git clone https://github.com/hirano00o/hb
cd hb
make build# ビルドして ./result/bin/hb に配置
nix build github:hirano00o/hb
# インストールせず直接実行
nix run github:hirano00o/hb -- --helphb config init -g対話形式で Hatena ID・Blog ID・API キーを入力します。API キー入力時はターミナル上でマスキングされます。
設定は ~/.config/hb/config.yaml に保存されます。
グローバル設定の値をプロジェクトごとに上書きしたい場合は、プロジェクトルートで実行します:
hb config init対話形式で各フィールドを入力します。空 Enter でスキップしたフィールドはファイルに書き込まれず、グローバル設定が使われます。
設定は .hb/config.yaml に保存されます。
コンフィグファイルよりも環境変数が優先されます。CI/CD などでの利用に便利です。
| 環境変数 | 説明 |
|---|---|
HB_HATENA_ID |
Hatena ID |
HB_BLOG_ID |
Blog ID |
HB_API_KEY |
API キー |
HB_CONCURRENCY |
pull の並列実行数(デフォルト: 5) |
HB_MAX_PAGES |
pull のページ取得上限(デフォルト: 0 = 無制限) |
HB_TIMEOUT_SEC |
タイムアウト秒数(正の整数、デフォルト: 30) |
環境変数 > プロジェクト設定(.hb/config.yaml)> グローバル設定(~/.config/hb/config.yaml)
hatena_id: yourhatenaId
blog_id: yourblog.hateblo.jp
api_key: your_api_key
concurrency: 10 # pull の並列実行数(デフォルト: 5)
max_pages: 5 # pull のページ取得上限(デフォルト: 0 = 無制限)
timeout_sec: 30 # タイムアウト秒数(正の整数、デフォルト: 30)プロジェクトローカル設定を対話形式で初期化します。空 Enter でスキップしたフィールドはファイルに書き込まれません。
-g フラグでグローバル設定を初期化します(全フィールドの入力が必須)。
現在の有効な設定値を表示します(グローバル設定 → プロジェクト設定 → 環境変数の優先順でマージ済みの値)。
hb config showAPI キーは末尾4文字のみ表示され、残りは * でマスクされます。未設定のフィールドはデフォルト値とともに表示されます。
hatena_id: yourid
blog_id: yourblog.hateblo.jp
api_key: *********1234
concurrency: 5 (default)
max_pages: unlimited
timeout_sec: 30 (default)
全サブコマンドで使用できるフラグです。
| フラグ | 説明 |
|---|---|
--version / -v |
バージョン情報を表示(例: hb version v0.1.0。ローカルビルドは dev) |
--verbose |
スキップされたファイルの警告を詳細表示する。省略時は読み取りエラーのみ件数サマリーを表示 |
リモートの全記事をローカルのMarkdownファイルとして取得します。
hb pull [--force|-f] [--dir <directory>] [--from <date>] [--to <date>]--force/-f: ファイル名が衝突したときに確認なしで自動リネーム(連番suffix付与:_1,_2, …)--dir: 保存先ディレクトリ(デフォルト: カレントディレクトリ)--from: 指定日以降に投稿された記事のみ取得(形式:YYYY-mm-dd,YYYY/mm/dd,YYYYmmdd)--to: 指定日以前に投稿された記事のみ取得(形式:YYYY-mm-dd,YYYY/mm/dd,YYYYmmdd)
ファイル名衝突時(--force なし)は、カスタム名入力 / Enter(連番suffix自動付与)/ s(スキップ)から選択します。
既に editUrl が一致するローカルファイルがある記事は自動的にスキップされます。pull は新規記事の取得専用で、取得済みファイルの更新は行いません。取得済みファイルをリモートの最新内容に追従させるには hb sync を使います。
. 始まりのディレクトリ(.git、.hb 等)はローカルファイル収集の対象から除外されます。
指定したローカルファイルをリモートの最新内容で上書きします(リモート → ローカルの単方向)。差分を表示してから上書き確認します。逆方向(ローカルの変更をリモートへ反映)は hb push です。
hb sync [--yes|-y] [--all] [<file> ...]--yes/-y: 確認プロンプトをスキップして上書き--all: カレントディレクトリ以下の全.mdファイルを対象にする(ファイル引数と同時指定不可)
hb sync 20260301_my-article.mdローカルファイルをリモートへ送信します。
editUrlが frontmatter にない場合 → 新規投稿(POST)editUrlが frontmatter にある場合 → 差分表示後に確認してから更新(PUT)
hb push [--yes|-y] [--draft] [--all] [<file> ...]--yes/-y: 確認プロンプトをスキップ--draft: 下書きとして投稿。frontmatter のdraft値と異なる場合は確認プロンプトを表示--all: カレントディレクトリ以下の全.mdファイルを対象にする(ファイル引数と同時指定不可)
hb push 20260301_my-article.mdfrontmatter の editUrl が指すリモート記事を削除します。実行前に削除対象の URL を表示して確認します。
hb delete [--yes|-y] [--remove-local] <file>--yes/-y: 確認プロンプトをスキップ--remove-local: リモート削除後にローカルファイルも削除
editUrl のないファイル(未 push の記事)はエラーになります。
指定した記事を公開状態にします。draft を false に設定し、ファイル名の draft_ プレフィックスを除去します。scheduledAt が設定されている場合はクリアします(予約時刻ではなく即時公開の意図とみなします)。
hb publish <file> [--undo] [--push|-p]--undo: 記事を下書き状態に戻します。draftをtrueに設定し、ファイル名にdraft_プレフィックスを付与します。scheduledAtが設定されている場合はクリアします(残したままだと次回 push で予約投稿として再登録されるため)--push/-p: 変更後にリモートへ即座に反映
記事のタイトルを変更し、frontmatter の title を更新するとともに、ファイル名を新しいタイトルに合わせてリネームします。
hb rename <file> --title <title> [--force]--title: 新しい記事タイトル(必須)--force: リネーム先ファイルが既に存在する場合でも上書き
記事の予約投稿日時を設定またはクリアします。
hb schedule <file> <datetime>
hb schedule --clear <file><datetime>: 予約投稿日時。RFC3339形式(2026-04-01T12:00:00+09:00)またはYYYY-MM-DD HH:MM:SS形式で指定--clear:scheduledAtをクリア。<datetime>と同時に指定できません
成功時は設定で Scheduled: <file> (<UTC の RFC3339>)、クリアで Unscheduled: <file> を出力します。
注意:
YYYY-MM-DD HH:MM:SS形式(タイムゾーンなし)は UTC として解釈されます。ローカル時刻で指定したい場合は RFC3339 形式でタイムゾーンを明示してください。
ローカルファイルとリモートのunified diffを表示します。
hb diff [--all] [<file> ...]--all: カレントディレクトリ以下の全.mdファイルを対象にする(ファイル引数と同時指定不可)
hb diff 20260301_my-article.mdeditUrl のないファイルはエラーにならず、未公開の新規記事である旨のメッセージを表示します。
注意: ローカル画像参照(
のようにhttp/httpsで始まらないパス)を含む記事では、hb pushでアップロードが完了するまで画像行に差分が表示されます。このとき stderr にnote: this file contains local images; ...が出力されます。
フロントマター付きのMarkdownファイルをカレントディレクトリに新規作成します。
hb new --title|-t <title> [--draft] [--push|-p] [--body|-b <body>]--title/-t: 記事タイトル(必須)--draft: 下書きとして作成。ファイル名にdraft_プレフィックスを付与し、frontmatter にdraft: trueを設定--push/-p: ファイル作成後にリモートへ新規投稿(POST)。editUrl・url・dateをローカルに書き戻し--body/-b: 作成するファイルの本文を指定。省略時は stdin がパイプなら自動で読み込む(\nは変換しない)
同名ファイルが既に存在する場合はエラーで中断します。先にファイルをリネームしてから再実行してください。
# タイトルを指定してファイルを作成
hb new -t "はじめての記事"
# → 20260306_はじめての記事.md を作成
# 作成と同時にリモートへ投稿
hb new --push -t "公開記事"ローカルの記事一覧をテーブル形式で表示します。日付の降順(新しい記事が上)でソートされます。
hb list [--dir <directory>] [--draft] [--published] [--scheduled] [--category <name>] [--categories] [--query|-q <keyword>]--dir: スキャン先ディレクトリ(デフォルト: カレントディレクトリ)--draft: 下書きのみ表示--published: 公開記事のみ表示--category <name>: 指定カテゴリの記事のみ表示--categories: 全カテゴリを一覧表示--scheduled: 予約投稿記事のみ表示--query <keyword>/-q: タイトルまたは本文にキーワードを含む記事のみ表示(大文字小文字を区別しない)。他のフィルタとは AND で組み合わせられます
--draft と --published は同時に指定できません。--scheduled は --draft、--published と同時に指定できません。--categories は --draft、--published、--category、--scheduled、--query と同時に指定できません。
. 始まりのディレクトリ(.git、.hb 等)は走査対象から除外されます。フロントマターのないファイルはサイレントにスキップされます。読み取りに失敗したファイルはデフォルトで件数サマリーを stderr に表示し、--verbose で詳細表示できます。
指定したローカルファイルの公開URLをデフォルトブラウザで開きます。
hb open [--edit|-e] <file>--edit/-e: はてなブログの編集ページ(https://blog.hatena.ne.jp/{user}/{blog}/edit?entry={id})を開く
hb open 20260301_my-article.mdfrontmatter に url(または --edit 時は editUrl)が設定されていない(未公開の)記事ではエラーになります。
指定した記事を $EDITOR(未設定の場合は vi)で開きます。保存後に差分を表示します。
hb edit <file> [--push]--push: 編集後に差分を表示してリモートへ確認なしで push
hb edit 20260301_my-article.mdローカル記事とリモートの同期状態を表示します。
hb status [--dir <directory>]--dir: スキャン先ディレクトリ(デフォルト: カレントディレクトリ)
. 始まりのディレクトリ(.git、.hb 等)は走査対象から除外されます。読み取りに失敗したファイルは hb list と同様に --verbose で詳細表示できます。
各ローカルファイルを以下の 3 つに分類して表示します。
- Modified:
editUrlがあり、ローカルとリモートの内容が異なる - Untracked:
editUrlがない(未公開記事) - Up to date: ローカルとリモートが一致している
max_pages を設定している場合、上限を超えたリモート記事は取得されないため Untracked と誤分類されることがあります。このとき stderr に注記が出力されます。
注意: ローカル画像参照(
のようにhttp/httpsで始まらないパス)を含む記事は、hb pushでアップロードが完了するまで Modified と表示される場合があります。このとき stderr にnote: <file> contains local images; ...が出力されます。
Modified (2):
20260301_Article1.md
...
Untracked (1):
draft_NewPost.md
Up to date (3):
20260201_OldPost.md
...
ローカル記事の統計情報を表示します。
hb stats [--dir <directory>] [--months <n>]--dir: スキャン先ディレクトリ(デフォルト: カレントディレクトリ)--months: 月別統計の表示月数(デフォルト: 6、0 で全期間)
合計・公開・下書き・予約投稿の件数、カテゴリ別件数、月別件数を表示します。
ファイルの変更を監視し、変更を検知すると自動的に push します。Ctrl+C で停止します。
hb watch [<file>] [--dir <directory>] [--debounce <duration>]<file>: 監視対象ファイル(省略時はディレクトリ全体を監視)--dir: 監視対象ディレクトリ(デフォルト: カレントディレクトリ)--debounce: 変更検知から push までの遅延時間(デフォルト: 500ms)
<file> と --dir は同時に指定できません。
自動 push の対象は editUrl を持つ(=一度 push 済みの)記事のみです。editUrl のないファイルは新規公開を防ぐためスキップされ、stderr に通知が出ます。先に hb push で一度公開してから watch してください。
---
title: 記事タイトル
date: 2026-03-01T12:00:00+09:00
draft: false
category:
- Go
- CLI
url: https://example.hateblo.jp/entry/2026/03/01/120000
editUrl: https://blog.hatena.ne.jp/user/example.hateblo.jp/atom/entry/123456
customUrlPath: my-custom-path
scheduledAt: 2026-04-01T12:00:00+09:00
---| フィールド | 説明 |
|---|---|
title |
記事タイトル |
date |
投稿日時(RFC3339形式) |
draft |
下書きフラグ |
category |
カテゴリ(複数可) |
url |
公開URL(pullで自動設定) |
editUrl |
AtomPub編集URL(pullで自動設定) |
customUrlPath |
カスタムURLパス(オプション) |
scheduledAt |
予約投稿日時(RFC3339形式、オプション)。設定すると draft が false でも下書きとして投稿される |
[draft_]<YYYYmmdd>_<title>.md
例: 20260301_My Article Title.md / draft_20260301_未公開記事.md
make test # 全テスト実行
make build # バイナリビルド(VERSION は git describe --tags から自動設定)
make lint # go vet 実行Nix を使う場合は、go / gopls / golangci-lint が揃った開発シェルを起動できます:
nix develop