こんにちは、のた(@Nor21011)です。
書いたブログ記事を、Codexに頼むだけでWordPressへ下書き投稿してくれる。そんなツールを自分で作りました。
この記事では、そのツールの中身を、コードも指示書もまとめて全部公開します。コードが読めなくても大丈夫です。むしろ、コードは読めないけれど「AIに自分専用のツールを作ってもらう流れ」を知りたい人に向けて書いています。
難しい話は極力省いて、何をどうしているのかを順番にご紹介していきます。
このツールはCodexに作ってもらいました。とはいえ、すんなり完成したわけではありません。自動化を頼んだはずが、いつのまにか自分が実行係になって4時間溶かす、という遠回りもしています。
どうやってこのツールにたどり着いたのか、その悪戦苦闘のほうは前の記事にまとめてあります。
→ 自動化したかっただけなのに、気づけば自分が実行係になっていた|Codexで4時間溶かしてわかったAIへの任せ方
今回はその続編として、完成したツールの現物をすべてご紹介します。持ち帰ってほしいのは、次の3つです。
post_to_wp.py:動かすコードAGENTS.md:AIにやらせないことを決める指示書wp-md-format.md:投稿用ファイルの作り方ルール
この3ファイルさえあれば、あとはAIに相談しながら自分の環境に合わせていけます。
このツールでできること
普段、私は自分のPCで記事を書き、その内容をWordPressに転記して投稿しています。具体的には、タイトルやスラッグ、本文などを一つずつ管理画面に貼り付けていく作業です。この転記がツール導入でどう変わるのかを比較したものが、下の表です。
| 手作業のころ | ツール導入後 | |
|---|---|---|
| 全体の手間 | 項目を1つずつ管理画面に貼り付け | ✓ Codexに頼むだけ |
| タイトル | 手作業で転記 | ✓ 自動で入る |
| スラッグ | 手作業で転記 | ✓ 自動で入る |
| メタディスクリプション | 手作業で転記 | ✓ 自動で入る |
| カテゴリ | 手で選択 | ✓ 自動で設定される |
| タグ | 手作業で転記 | ✓ 自動で入る(なければ作成) |
| 本文 | カスタムHTMLブロックに貼り付け | ✓ 自動で入る |
| 保存 | 手動で下書き保存 | ✓ 自動で下書き保存まで完了 |
これまで手作業でやっていた転記が、Codexに頼むだけで終わるようになりました。
結果として手に入った価値は、3つあります。
- 待っている間に別の作業ができる。自分が拘束されない
- 「どこを探して、どこに貼るか」を判断しなくていい。脳のリソースも手間も使わない
- コピペミスのようなヒューマンエラーが起きない
「速くなった」というより、「自分がやらなくてよくなった」というほうが、感覚としては近いです。時短ツールではなく、自分の手から作業を外す道具、というイメージで作っています。動機の細かいところは前の記事に書いてあるので、ここでは深掘りしません。
ツールが動く仕組み
投稿用のファイルを用意して、Codexに「これを投稿して」と頼む。ツールが中身を読み取って、WordPressに下書き投稿してくれて、最後に編集画面のリンクが表示される。やっていることは、これだけです。
ここで言う「投稿用ファイル」とは、ひとつの記事に必要な情報を、決まった形にまとめて書いたファイルのことです。
- ファイル名のルール:必ず
_wp.mdで終わる名前にしておく(このルールでツールが対象を見つけられる) - 中身:タイトル、スラッグ、メタディスクリプション、カテゴリ、タグ、本文を、決まった型に流し込んで書く
骨組みはこんな形です。本文の中身は省略していますが、雰囲気だけ掴んでいただければ大丈夫です。
| **タイトル** | サンプル記事のタイトル |
| **スラッグ** | sample-article |
| **メタディスクリプション** | この記事ではサンプルとして〜 |
| **カテゴリ** | ブログ×AI |
| **タグ** | Claude / AIエージェント |
<!-- WP_BODY_HTML_START -->
ここに本文HTML
<!-- WP_BODY_HTML_END -->
機械が読める決まった型に、必要な情報を流し込むイメージです。
この投稿用ファイルも、決まった形さえ伝えればAIに作ってもらえます。手書きで頑張る必要はありません。私の場合は、ブログ本文を書き終えたタイミングで、Claudeに投稿用ファイルもまとめて作ってもらっています。
作り方のルールは、記事末尾の付録に置いてある wp-md-format.md にまとまっています。AIに「このルールに沿って投稿用ファイルを作って」と渡せば、形は揃います。post_to_wp.py と AGENTS.md もあわせて渡せば、投稿用ファイルの形まで含めて理解してくれます。
実際にCodexに頼んだときの流れを再現したものを置いておきます。「このフォルダの _wp.md を投稿して」とお願いすると、対象ファイルを確認したうえで、タイトル・スラッグ・カテゴリ・タグ・抜粋を見せてくれます。こちらが「yes」と返すと、下書き投稿が完了し、編集URLが返ってきます。画面や返答は、使うAIや指示の仕方によって多少変わります。
ここにある _wp.md ファイルを WordPress に下書きで更新してください。
対象ファイル:
articles/drafts/sample-article_wp.md
投稿前の確認情報です。
タイトル: サンプル記事のタイトル
スラッグ: sample-article
カテゴリ: ブログ×AI
タグ: Claude / AIエージェント
抜粋: この記事ではサンプルとして〜
この内容で WordPress に下書き投稿してよければ、「yes」と返してください。
編集URL: https://example.com/wp-admin/post.php?post=123&action=edit
投稿IDは 123 です。カテゴリ・タグ・抜粋も設定されています。新規タグとして AIエージェント が作成されました。
コードの中身(post_to_wp.py)
コード全文だけ欲しい方は、記事末尾の付録にまとめてあります。
正直なところ、私はこのコードを単体ではほとんど読めません。それでも自分のツールとして扱えているのは、中身を「どんな係がいるか」という形で整理しているからです。
細かい書き方は追わなくて大丈夫です。ここでは、このツールの中にどんな係がいるのかだけを見ていきます。post_to_wp.py は、大きく4つの役に分かれています。
「ファイルを読む係」は、投稿用ファイル(_wp.md)を開いて、文字として読み込みます。
「中身を取り出す係」は、読み込んだ文字から、タイトル・スラッグ・タグ・本文などを抜き出します。本文は、決まったマーカー(WP_BODY_HTML_START〜WP_BODY_HTML_END)の間にあるHTMLだけを取り出すようにしています。マーカーの外に何が書いてあっても拾わないので、メモを混ぜても投稿には影響しません。
「WordPressとやりとりする係」は、WordPressに接続して、カテゴリ・タグを調べ、下書きを投稿します。同じ名前のタグがなければ自動で作りますが、カテゴリは勝手に作らない設計にしています。意図しないカテゴリが増えてしまうのを防ぐためです。
そして、全体を進行する main が、この3つを順番に呼び出して、確認表示や「yes / no」の受付、結果報告までやってくれます。
このコードには、安全のための工夫もいくつか入れてあります。
1つ目は、パスワードのような秘密情報を別ファイルにまとめて、作業フォルダの外(ホームフォルダの直下)に置いていることです。コードと同じフォルダに置いておくと、うっかり記事と一緒に公開してしまう恐れがあります。
2つ目は、AIに対して「その秘密情報を見せない・開かせない」というルールを、別の指示書(AGENTS.md)で課していることです。これは次のセクションで詳しく書きます。
3つ目は、通信を安全にするための設定(https)が満たされていないと、ツール自体が動かないようにしてあることです。これは前の記事でClaudeに指摘されて気づいた話の、現物にあたります。
勝手をさせない指示書(AGENTS.md)
Codexにコードを動かしてもらうには、ただ「動かして」とお願いするだけでは少し怖いところがあります。コードのあるフォルダ全体をのぞけるわけで、関係ないファイルまで読みに行ったり、意図しない操作をされたりすると困ります。
そこで、AIに対して「やっていいこと」「やってはいけないこと」をはっきり伝える指示書を一緒に置いてあります。それが AGENTS.md です。
中身は、たとえばこんなことを書いています。
- 秘密情報のファイルは絶対に表示しない・開かない・中身を出力しない
- 投稿ステータスは必ず下書きにする。公開状態にはしない
- 対象フォルダの中に
_wp.mdが2件以上あったら、勝手に投稿せず、候補を見せてユーザーに確認する - WordPressと通信するエラーが出ても、応答そのものを長文で画面に出さない(秘密情報がまぎれて出ないようにするため)
何をさせるかではなく、何をさせないか。そこを言葉にしておくのが、この指示書の役割です。前の記事で「パスワードが丸見え」だと気づいた話の続きで、Codexに実行係として戻ってもらうための、具体的な安全装置にあたります。全文は記事末尾の付録に置いてあるので、流れだけ追えるはずです。
今の運用スタイル
このツールを作ってから、私は毎記事これでWordPressへ投稿しています。
正直に言うと、時短にはなっていません。Codexが動いている時間も、自分で確認する時間も、足し算するとそこそこかかります。それでも私の中での価値は、はっきり変わりました。
感覚としては、「完全に任せられる外注先ができた」に近いです。前の記事で4時間かけて任せ方を学んだ相手が、今は信頼できる外注先になった、というイメージです。
具体的に変わったのは、次の4つです。
- Codexが投稿作業をしている間に、別のことを進められる
- スキマ時間に「これ投稿しておいて」と指示だけ出しておける
- ファイルを開いてコピペするだけの「考えない作業」から解放された
- 転記でうっかりミスをしていないか、という確認の不安がなくなった
プレビュー確認はWordPress側で自分の目で見ています。下書き投稿までを任せて、人間が見るべき最後の確認だけ自分でやる、という分担です。
やりたいことはたくさんあるのに、時間は有限です。だからこそ、自分がやらなくていいことは決めて、外に出していく。その一つひとつの積み重ねが、自分が本当にやりたいこと、やるべきことに集中する時間を作ってくれるのだと思っています。
このコードは叩き台
ここまで読んで「自分も同じことをやってみたい」と思った方へ、ひとつだけ先に書いておきます。
これから紹介するコードや指示書は、私の環境では問題なく動いています。ただ、WordPressの環境やテーマ、投稿用ファイルの作り方は人によって違います。なので、読者の方にとっては調整前提の叩き台です。コピーしてそのまま実行しても、おそらく動きません。
ただ、それでまったく問題ありません。動かないコードでも、自分が使っているAIに「このコードを自分の環境で動くように直してほしい」と相談すれば、必要な書き換えはAIがやってくれます。コードの中身が分からなくても、斜め読みできる程度で大丈夫です。
私がやったのは、コードを全部理解することではありません。「こう動いてほしい」「ここは怖いから触らせたくない」「投稿は下書きで止めたい」。そういう要望をAIに伝えて、使える形になるまで一緒に詰めていったことです。
昔の私なら、自分専用の投稿ツールを作るなんて発想すら持っていませんでした。コードが書けないのだから、選択肢にすら入っていなかったと思います。それが、AIに相談しながらなら形にできてしまう。今まで自分にはできなかったことが、できるようになる。私がAIに一番面白さを感じているのは、こういうところです。
コードが読めない私でも、ひとつ自動化できた。これは小さいけれど確かな成功体験でした。読んでくださっているあなたも、まずは一つ作ってみると、その感覚がつかめるはずです。
まずは全部を理解しようとしなくて大丈夫です。3つのファイルをAIに渡して、「自分のWordPress環境で使えるようにしたい」と相談する。最初の一歩は、それで十分です。次に全文を置いておきます。
なお、このツールはWordPressの認証情報を扱います。試すときは必ず下書き投稿のままで確認し、アプリケーションパスワードなどの秘密情報をAIの画面に貼り付けないようにしてください。今回のツールで AGENTS.md に「見せない・開かない」ルールを書いているのは、まさにこのためです。
まとめ
書いたブログ記事を、Codexに頼むだけでWordPressに下書き投稿してくれるツールを、現物ベースでご紹介してきました。
最後に、ひとつだけ視野を広げておきます。今回は実行役にCodexを使っていますが、これはCodexでなくても構いません。コードを読んで、必要なら直して、実行してくれるAIなら、Claude Codeでも同じことができます。
大事なのは、ツールそのもの、つまり投稿用ファイル(資料)とコードのほうです。そこさえ整っていれば、実行するAIは各自が使い慣れているもので構いません。自分の環境に合うように、ぜひカスタマイズしてみてください。
ここから先は、コード・指示書・投稿用ファイルの作り方ルール、3点の全文を載せておきます。持ち帰り用です。
付録:コード全文(持ち帰り用)
3ファイルを順番に載せておきます。コピーして、ご自分のAIに「自分の環境で動くように直して」と渡せば、たいてい何とかなります。
post_to_wp.py
import os
import re
import sys
import requests
from dotenv import load_dotenv
# ホーム直下の秘密情報ファイルを読み込む
ENV_PATH = os.path.expanduser("~/.wp-auto-post.env")
load_dotenv(ENV_PATH)
WP_SITE_URL = os.getenv("WP_SITE_URL")
WP_USERNAME = os.getenv("WP_USERNAME")
WP_APP_PASSWORD = os.getenv("WP_APP_PASSWORD")
def validate_env():
"""必要な環境変数が設定されているか確認する"""
missing = []
if not WP_SITE_URL:
missing.append("WP_SITE_URL")
if not WP_USERNAME:
missing.append("WP_USERNAME")
if not WP_APP_PASSWORD:
missing.append("WP_APP_PASSWORD")
if missing:
raise ValueError(
"環境変数ファイルに必要な情報がありません。\n"
f"読み込み先: {ENV_PATH}\n"
"不足: " + ", ".join(missing)
)
if not WP_SITE_URL.startswith("https://"):
raise ValueError(
f"WP_SITE_URL は https:// で始まる必要があります。\n"
f"現在の値: {WP_SITE_URL}\n"
"http:// だとアプリケーションパスワードが平文で送信されます。"
)
def clean_file_path(file_path):
"""Macのドラッグ&ドロップ時につくクォートや空白を除去する"""
return file_path.strip().strip("'").strip('"')
def read_text_file(file_path):
"""UTF-8でファイルを読む"""
file_path = clean_file_path(file_path)
if not os.path.exists(file_path):
raise FileNotFoundError(f"ファイルが見つかりません: {file_path}")
with open(file_path, "r", encoding="utf-8") as f:
return f.read()
def extract_meta_table(text):
"""
_wp.mdのメタ情報テーブルから値を抽出する。
想定形式:
| **タイトル** | xxx |
| **スラッグ** | xxx |
| **メタディスクリプション** | xxx |
| **カテゴリ** | xxx |
| **タグ** | xxx |
"""
meta = {}
pattern = r"^\|\s*\*\*(.*?)\*\*\s*\|\s*(.*?)\s*\|$"
for line in text.splitlines():
match = re.match(pattern, line.strip())
if match:
key = match.group(1).strip()
value = match.group(2).strip()
meta[key] = value
return meta
def extract_body_html_between_markers(text):
"""
_wp.md内の本文HTMLマーカーから本文HTMLだけを抽出する。
想定形式:
<!-- WP_BODY_HTML_START -->
ここに本文HTML
<!-- WP_BODY_HTML_END -->
"""
start_marker = "<!-- WP_BODY_HTML_START -->"
end_marker = "<!-- WP_BODY_HTML_END -->"
pattern = rf"{re.escape(start_marker)}\s*\n?(.*?)\n?\s*{re.escape(end_marker)}"
match = re.search(pattern, text, re.DOTALL)
if not match:
raise ValueError(
"本文HTMLマーカーが見つかりませんでした。\n"
"以下の形式で本文HTMLを囲んでください。\n\n"
"<!-- WP_BODY_HTML_START -->\n"
"<p>本文HTML</p>\n"
"<!-- WP_BODY_HTML_END -->"
)
html = match.group(1).strip()
if not html:
raise ValueError(
"本文HTMLマーカー内が空です。\n"
"<!-- WP_BODY_HTML_START --> と <!-- WP_BODY_HTML_END --> の間に本文HTMLを入れてください。"
)
return html
def wrap_as_custom_html_block(content_html):
"""
WordPressのカスタムHTMLブロック形式に包む。
SWELLのHTML構造を壊さないため、本文HTML自体は加工しない。
"""
return f"""<!-- wp:html -->
{content_html}
<!-- /wp:html -->"""
def split_tags(tag_text):
"""
_wp.mdのタグ文字列を分割する。
想定:Claude Cowork / Claude / AIエージェント
"""
if not tag_text:
return []
tags = re.split(r"\s*/\s*|\s*/\s*|\s*,\s*|、", tag_text)
return [tag.strip() for tag in tags if tag.strip()]
def wp_request(method, endpoint, **kwargs):
"""WordPress REST APIへの共通リクエスト"""
validate_env()
url = f"{WP_SITE_URL.rstrip('/')}/wp-json/wp/v2/{endpoint.lstrip('/')}"
response = requests.request(
method,
url,
auth=(WP_USERNAME, WP_APP_PASSWORD),
timeout=30,
**kwargs,
)
if response.status_code not in (200, 201):
raise RuntimeError(
"WordPress APIリクエストに失敗しました。\n"
f"Endpoint: {endpoint}\n"
f"Status: {response.status_code}\n"
f"Response: {response.text[:500]}"
)
return response.json()
def find_category_id_by_name(category_name):
"""
WordPress上のカテゴリ名からIDを取得する。
見つからなければNoneを返す。
カテゴリは自動作成しない。
"""
if not category_name:
return None
categories = wp_request(
"GET",
"categories",
params={
"search": category_name,
"per_page": 100,
},
)
for category in categories:
if category.get("name") == category_name:
return category.get("id")
return None
def find_tag_id_by_name(tag_name):
"""WordPress上のタグ名からIDを取得する。見つからなければNone。"""
if not tag_name:
return None
tags = wp_request(
"GET",
"tags",
params={
"search": tag_name,
"per_page": 100,
},
)
for tag in tags:
if tag.get("name") == tag_name:
return tag.get("id")
return None
def create_tag(tag_name):
"""WordPressにタグを作成してIDを返す"""
result = wp_request(
"POST",
"tags",
json={
"name": tag_name,
},
)
return result.get("id")
def get_or_create_tag_ids(tag_names):
"""
タグ名リストからタグIDリストを作る。
既存タグがあれば使い、なければ自動作成する。
"""
tag_ids = []
created_tags = []
for tag_name in tag_names:
existing_id = find_tag_id_by_name(tag_name)
if existing_id:
tag_ids.append(existing_id)
else:
new_id = create_tag(tag_name)
tag_ids.append(new_id)
created_tags.append(tag_name)
return tag_ids, created_tags
def build_article_from_wp_md(file_path):
"""_wp.mdから投稿用データを作る"""
text = read_text_file(file_path)
meta = extract_meta_table(text)
raw_html = extract_body_html_between_markers(text)
title = meta.get("タイトル")
slug = meta.get("スラッグ")
description = meta.get("メタディスクリプション")
category_name = meta.get("カテゴリ")
tag_text = meta.get("タグ")
if not title:
raise ValueError("メタ情報に「タイトル」がありません。")
if not slug:
raise ValueError("メタ情報に「スラッグ」がありません。")
tag_names = split_tags(tag_text)
content = wrap_as_custom_html_block(raw_html)
return {
"title": title,
"slug": slug,
"description": description,
"category_name": category_name,
"tag_text": tag_text,
"tag_names": tag_names,
"raw_html": raw_html,
"content": content,
}
def save_debug_files(article):
"""
確認用ファイルを保存する。
ローカル確認用。GitHubには上げない。
保存先: ~/wp-auto-post-debug/
"""
debug_dir = os.path.expanduser("~/wp-auto-post-debug")
os.makedirs(debug_dir, exist_ok=True)
raw_html_path = os.path.join(debug_dir, "debug_raw_html.html")
wp_content_path = os.path.join(debug_dir, "debug_wp_content.html")
with open(raw_html_path, "w", encoding="utf-8") as f:
f.write(article["raw_html"])
with open(wp_content_path, "w", encoding="utf-8") as f:
f.write(article["content"])
return raw_html_path, wp_content_path
def create_draft_post(article):
"""WordPressに下書き投稿を作成する"""
validate_env()
category_id = find_category_id_by_name(article.get("category_name"))
tag_ids, created_tags = get_or_create_tag_ids(article.get("tag_names", []))
payload = {
"title": article["title"],
"slug": article["slug"],
"content": article["content"],
"status": "draft",
}
if article.get("description"):
# WordPress標準の「抜粋」欄へ入れる
payload["excerpt"] = article["description"]
if category_id:
payload["categories"] = [category_id]
if tag_ids:
payload["tags"] = tag_ids
result = wp_request(
"POST",
"posts",
json=payload,
)
return result, {
"category_id": category_id,
"tag_ids": tag_ids,
"created_tags": created_tags,
"excerpt_sent": bool(article.get("description")),
}
def print_article_summary(article):
"""投稿前の確認情報を表示する"""
print("\n=== 抽出結果 ===")
print(f"タイトル: {article['title']}")
print(f"スラッグ: {article['slug']}")
if article.get("description"):
print(f"メタディスクリプション / 抜粋: {article['description']}")
else:
print("メタディスクリプション / 抜粋: なし")
if article.get("category_name"):
print(f"カテゴリ: {article['category_name']}")
else:
print("カテゴリ: なし")
if article.get("tag_names"):
print("タグ:")
for tag in article["tag_names"]:
print(f"- {tag}")
else:
print("タグ: なし")
print(f"本文HTML文字数: {len(article['raw_html'])}")
print("本文抽出方式: WP_BODY_HTML_START / WP_BODY_HTML_END マーカー")
print("投稿形式: カスタムHTMLブロック")
print("投稿ステータス: 下書き")
def main():
print("WordPress下書き投稿ツール")
print("※ .env の中身、アプリケーションパスワードは絶対に共有しないでください。")
print(f"※ 認証情報の読み込み先: {ENV_PATH}")
print("※ wp-auto-post はローカル専用運用を推奨します。GitHubに上げる必要はありません。")
file_path = input("\n投稿する _wp.md ファイルのパスを入力してください: ").strip()
try:
article = build_article_from_wp_md(file_path)
raw_html_path, wp_content_path = save_debug_files(article)
print_article_summary(article)
print("\n確認用ファイルを作成しました。")
print(f"- 抽出した本文HTML: {raw_html_path}")
print(f"- WordPress投稿用HTML: {wp_content_path}")
print("\nカテゴリ・タグ・抜粋の扱い:")
print("- カテゴリ: WordPress上に同名カテゴリがある場合のみ設定。なければ未設定。自動作成しません。")
print("- タグ: WordPress上になければ自動作成します。")
print("- メタディスクリプション: WordPress標準の「抜粋」欄に送信します。")
print("\nこの内容をWordPressに下書き投稿します。")
confirm = input("続行しますか? yes / no: ").strip().lower()
if confirm != "yes":
print("投稿をキャンセルしました。")
return
result, post_result = create_draft_post(article)
post_id = result.get("id")
edit_url = f"{WP_SITE_URL.rstrip('/')}/wp-admin/post.php?post={post_id}&action=edit"
print("\nWordPressに下書き投稿しました。")
print(f"タイトル: {article['title']}")
print(f"投稿ID: {post_id}")
print(f"編集URL: {edit_url}")
if post_result.get("category_id"):
print(f"カテゴリID: {post_result['category_id']} を設定しました。")
else:
print("カテゴリ: 未設定です。カテゴリ名が空、またはWordPress上に同名カテゴリが見つかりませんでした。")
if post_result.get("tag_ids"):
print(f"タグID: {post_result['tag_ids']} を設定しました。")
else:
print("タグ: 未設定です。")
if post_result.get("created_tags"):
print("新規作成したタグ:")
for tag in post_result["created_tags"]:
print(f"- {tag}")
if post_result.get("excerpt_sent"):
print("メタディスクリプション: WordPress標準の「抜粋」欄へ送信しました。")
else:
print("メタディスクリプション: 未送信です。")
print("\n次にWordPress側で確認してください。")
print("- 下書きになっているか")
print("- カスタムHTMLブロックになっているか")
print("- プレビュー表示が崩れていないか")
print("- 画像が表示されているか")
print("- リンクが正しいか")
print("- カテゴリが意図通りか")
print("- タグが意図通りか")
print("- 右サイドバー等の「抜粋」欄にメタディスクリプションが入っているか")
print("\n※本文HTMLは加工せず、カスタムHTMLブロックとして投稿しています。")
except Exception as e:
print("\nエラーが発生しました。")
print(str(e))
sys.exit(1)
if __name__ == "__main__":
main()
AGENTS.md
# AGENTS.md
## プロジェクト概要
このプロジェクトは、WordPress へ下書き投稿するためのローカル専用ツールです。
`post_to_wp.py` を使って、指定フォルダ内の `*_wp.md` ファイルを WordPress へ下書き投稿します。
- このプロジェクトを GitHub に上げる必要はありません。
- 投稿ステータスは必ず `draft` にします。
- 本文はカスタム HTML ブロックとして投稿します。
- SWELL の HTML 構造を壊さないように扱います。
## 秘密情報の扱い
秘密情報は絶対に表示・出力しません。
禁止事項:
- `~/.wp-auto-post.env` の中身を表示しない。
- `cat ~/.wp-auto-post.env` を実行しない。
- `printenv` を実行しない。
- `.env` を作成しない。
- `cat .env` を実行しない。
- WordPress のアプリケーションパスワードなど、秘密情報を出力しない。
## 投稿実行ルール
ユーザーが「下書き投稿して」「WP投稿して」「このフォルダを投稿して」などと依頼した場合は、指定フォルダ内から `*_wp.md` を探します。
対象フォルダが明示されていない場合は、勝手に広範囲を検索せず、ユーザーに対象フォルダの指定を求めます。
- `*_wp.md` が 0 件の場合:
- 投稿は実行せず、「対象ファイルなし」と報告します。
- `*_wp.md` が 2 件以上ある場合:
- 投稿は実行せず、候補ファイル一覧を表示してユーザーに確認します。
- `*_wp.md` が 1 件だけの場合:
- そのファイルを投稿対象にします。
投稿実行前に、対象ファイルから抽出される以下の情報だけを確認表示します。
- タイトル
- スラッグ
- カテゴリ
- タグ
- 抜粋
ユーザー確認後、問題なければ `post_to_wp.py` を使って WordPress へ下書き投稿します。
投稿完了後は、WordPress の編集 URL をユーザーに伝えます。
## 許可してよい操作
以下の操作は、このプロジェクトの目的に沿う範囲で許可します。
- `python3 post_to_wp.py` の実行
- WordPress REST API へのネットワークアクセス
- `~/wp-auto-post-debug/` への debug ファイル書き込み
## 止めるべき操作
以下の操作は行いません。
- `cat ~/.wp-auto-post.env`
- `printenv`
- `cat .env`
- `.env` の作成
- 投稿ステータスを `publish` にする処理
## コード変更時のルール
コードを変更する場合は、以下を守ります。
- 勝手に大きなリファクタリングをしません。
- セキュリティ関連の挙動を変更する場合は、事前にユーザーへ確認します。
- WordPress 投稿処理を変更する場合は、必ず `draft` 固定を維持します。
- `response.text` を長文で出力しません。
- `WP_SITE_URL` の HTTPS チェックを維持します。
wp-md-format.md
# WordPress投稿用 `_wp.md` 出力ルール
ブログ記事をWordPressへ下書き投稿するための `_wp.md` ファイルを作成する際のルール。
このファイルは自動投稿プログラム(wp-auto-post)が読み取るため、出力形式を絶対に崩さないこと。
## 目的
以下の情報を、決められた形式で出力する。
- タイトル
- スラッグ
- メタディスクリプション
- カテゴリ
- タグ
- 投稿前チェック
- 本文HTML
## ファイル名ルール
完成ファイル名は必ず以下の形式。
例:`claude-cowork-guide-2026_wp.md`
## 出力全体の構造
必ず以下の順番で出力する。
# WordPress投稿チェックシート|{記事タイトル}
このファイルはWordPress下書き投稿用のチェックシートです。
自動投稿プログラムが読み取るため、形式を変更しないでください。
---
## 1. メタ情報(投稿画面で入力)
| 項目 | 値 |
|---|---|
| **タイトル** | {記事タイトル} |
| **スラッグ** | {slug} |
| **メタディスクリプション** | {120字前後の説明文} |
| **カテゴリ** | {既存カテゴリ名。未定なら空欄} |
| **タグ** | {タグ1 / タグ2 / タグ3} |
---
## 2. 投稿前チェック
- [ ] アイキャッチ画像を設定
- [ ] スラッグを確認
- [ ] カテゴリを確認
- [ ] メタディスクリプションを確認
- [ ] タグを確認
- [ ] 内部リンクを確認
- [ ] 画像の表示を確認
- [ ] リンク切れがないか確認
- [ ] WordPressプレビューで表示崩れがないか確認
- [ ] TODOコメントや仮リンクが残っていないか確認
---
## 3. 本文HTML(自動投稿プログラム用)
<!-- WP_BODY_HTML_START -->
{本文HTML}
<!-- WP_BODY_HTML_END -->
## 厳守ルール
### メタ情報テーブル
必ずこの形式で出力する。
- 項目名の変更禁止
- 項目順の変更禁止
- `**タイトル**` などの太字記法を削除しない
- 表形式以外で出力しない
- タグを箇条書きにしない
### スラッグ
- 英数字とハイフンのみ
- 小文字のみ
- 日本語禁止
- アンダースコア禁止
- スペース禁止
- 数字始まり禁止
- 末尾をハイフンにしない
- 連続ハイフンを使わない
良い例:`claude-cowork-guide-2026`
### タグ表記
タグは必ず `/` 区切り。
良い例:`Claude Cowork / Claude / AIエージェント`
### カテゴリ
既存カテゴリ名を1つだけ入れる。
不明な場合は空欄。
新しいカテゴリ名を勝手に作らない。
### 投稿前チェック
固定項目を必ず全て入れる。削除・変更禁止。
記事固有のチェック項目は固定項目の下に追加してOK。
### 本文HTML
必ず以下のマーカーで囲む。
<!-- WP_BODY_HTML_START -->
本文HTML
<!-- WP_BODY_HTML_END -->
禁止事項:
- ```html のコードブロックで囲むこと
- マーカー外に本文HTMLを書くこと
- <!-- wp:html --> や <!-- /wp:html --> を入れること
- Markdownを混ぜること
### 本文中のコードサンプル
技術記事でコードを載せる場合は、HTMLとして記述する。
良い例:
<pre><code>print("hello")</code></pre>
Markdownのコードフェンスを本文HTML内に入れない。
HTML特殊文字はエスケープする:
<pre><code><div>Hello</div></code></pre>
## 本文HTMLの方針
- SWELLの邪魔をしない
- HTML構造を勝手に変更しない
- WordPress通常ブロック化を前提にしない
- カスタムHTMLブロックに貼り付ける前提で出力する
## 投稿ステータス
このファイルは下書き投稿用。公開処理は人間がWordPress側で確認後に行う。
