infralog

Claude Code から、開いている Chrome の WordPress 編集画面を操作する

Claude CodeAppleScriptWordPress

目次
  1. AppleScript の JS ではページの変数が見えない。script 要素を注入すれば動く
  2. やりたかったこと
  3. 本題
  4. 検討したが採らなかった選択肢
  5. まとめ
  6. 関連記事

AppleScript の JS ではページの変数が見えない。script 要素を注入すれば動く

Claude Code にはブラウザを操作するツールが無い環境でも、macOS なら AppleScript 経由で、いま開いている Chrome のタブに JavaScript を実行できる。 ログイン済みのセッションをそのまま使えるので、管理画面の操作も可能だ。

ただし、AppleScript の execute javascript で実行したコードからは DOM は見えるのに、ページが持つ JS の変数(WordPress の window.wp など)は見えなかった。 <script> 要素をページに差し込んで実行させる形にしたら見えるようになり、ブロックエディタの本文編集と保存、SEO プラグインの説明文設定まで通った。

その手順と、引っかかった箇所を書く。

やりたかったこと

別で運営している WordPress サイトで、新しい記事を公開した。既存の記事から新記事へ内部リンクを2か所張りたい。

作業そのものは、編集画面で該当の語句を選択して ⌘K で URL を貼り、「更新」を押すだけだ。手でやれば1分で終わる。ところが、Claude Code に手順を説明してもらっても、自分ではどこを操作すればいいのか見つけられなかった。そして編集画面は Chrome でもう開いていた。

そこで「開いている編集画面を、Claude Code にそのまま操作させる」ことにした。

前提:

本題

1. まず許可が要る

最初の osascript は、タブの URL は取れたが JS の実行で止まった。

osascript -e 'tell application "Google Chrome" to get URL of active tab of front window'
# https://<サイト>/wp-admin/post.php?post=<ID>&action=edit

osascript -e 'tell application "Google Chrome" to execute active tab of front window javascript "document.title"'
# execution error: Google Chromeでエラーが起きました: AppleScript からの JavaScript の実行がオフになっています。
# オンにするには、メニューバーから [表示] > [デベロッパー] > [Apple Events からの JavaScript を許可] を選択してください。 (12)

エラーメッセージがそのまま手順になっている。ただ、この「表示」は Chrome のウィンドウ内ではなく、画面最上部の macOS のメニューバーにある。ここで一度迷った。

この設定をオンにしている間は、Mac 上のどのスクリプトからでも、ログイン済みのページで JS を実行できる。 作業が終わったらオフに戻す前提で使う。

2. window.wp が見えない

許可を出してから、ブロックエディタの状態を取りに行った。WordPress のブロックエディタは wp.data というストアを公開していて、そこから全ブロックを読める。

// AppleScript の execute javascript で実行
if (!window.wp || !wp.data) return "no wp.data";

結果は no wp.data。

切り分けのために、DOM とグローバル変数を並べて出した。

title    = 投稿を編集 "<記事タイトル>" ‹ <サイト名> — WordPress
typeof window.wp = undefined
iframes  = 0
body.className = wp-admin wp-core-ui js is-fullscreen-mode post-php ... block-editor-page ...

body のクラスを見ればブロックエディタのページであることは明らかで、iframe の中にあるわけでもない。DOM は見えているのに、ページの JS が作ったグローバル変数だけが見えていない。 AppleScript から実行した JS は、ページのスクリプトとは分かれた実行環境で動いていると考えられる。

3. script 要素を差し込んで、ページの環境で動かす

DOM が共有されているなら、<script> 要素を DOM に追加すれば、その中身はページ自身のスクリプトとして実行される。結果は DOM の属性に書かせて、AppleScript 側で読み戻す。

#!/bin/bash
# run.sh: 渡した JS をページの実行環境で動かし、結果を返す
# JS 側は document.documentElement.dataset.claudeOut に結果を書く
python3 - "$1" > wrapped.js <<'PY'
import json, sys
code = open(sys.argv[1]).read()
print("(function(){var s=document.createElement('script');s.textContent=%s;"
      "document.documentElement.appendChild(s);s.remove();"
      "return document.documentElement.dataset.claudeOut||'(no output)';})()" % json.dumps(code))
PY
osascript -e 'set js to read POSIX file "'"$PWD"'/wrapped.js" as «class utf8»' \
          -e 'tell application "Google Chrome" to execute active tab of front window javascript js'

JS を json.dumps で文字列リテラルにしているのは、引用符や改行のエスケープを AppleScript と shell の二重で考えたくないからだ。ファイル経由で渡すのも同じ理由。

これで、対象の段落ブロックが取れた。

2e4a9e79-... | core/paragraph | ... It's the daily expression of <strong>(リンクを張る語句)</strong> ...
99446d84-... | core/paragraph | ... (リンクを張る語句), ...
dirty=false

4. 書き換えて保存する

ブロックの content を文字列置換して、updateBlockAttributes で戻し、savePost で保存する。置換対象が見つからなければ何もせずに止める。

var U = 'https://<サイト>/<新記事のパス>/';
var sel = wp.data.select('core/block-editor'), d = wp.data.dispatch('core/block-editor');
var ca = String(sel.getBlock(a).attributes.content);
var na = ca.replace('<strong>語句</strong>',
                    '<a href="' + U + '"><strong>語句</strong></a>');
if (na === ca) throw 'pattern not found';
d.updateBlockAttributes(a, { content: na });
wp.data.dispatch('core/editor').savePost();

savePost は非同期なので、直後に状態を見ると保存中だ。

直後   : saving=true dirty=true  ok=true
数秒後 : saving=true dirty=false ok=true

保存できたかの最終確認は、エディタの状態ではなく公開ページの HTML で行った。 CDN のキャッシュを避けるため、クエリ文字列を付けて取得する。

curl -s "https://<サイト>/<既存記事のパス>/?nc=$RANDOM" \
  | grep -o '<新記事のパス>/"><strong>[^<]*</strong>'
# <新記事のパス>/"><strong>語句</strong>

5. SEO プラグインの説明文も同じ方法で

ついでに、この記事のメタディスクリプションが未設定で、本文の冒頭が検索結果用の説明文として出ていることが分かった。SEO プラグインの Rank Math も wp.data に専用のストアを登録しているので、同じ方法で中を見た。

post=<ID> desc="" acts=updateDescription,updateFacebookDescription,updateSerpDescription,...

updateDescription で値を入れて savePost したら、公開ページの <meta name="description"> に反映された。

検討したが採らなかった選択肢

手で操作する。 一番確実で、1分で終わる。ただ今回は、手順の説明を受けても自分で操作する場所を見つけられない段階で詰まっていた。編集画面の中身を wp.data から直接読めば、場所の特定で迷うことがなくなる。

ブラウザ操作用のツールを入れる。 Chrome を操作する MCP サーバーなどを入れれば、クリックや入力をもっと素直に書ける。ただ、このセッションには入っておらず、作業のために導入から始めるほどの量ではなかった。

WordPress の REST API を叩く。 本文の更新だけなら、アプリケーションパスワードを発行して curl で POST /wp-json/wp/v2/posts/<ID> すれば済む。ただ認証情報を新しく発行して手元に置くことになり、2か所のリンクのためにやることではない。

Playwright などで新しくブラウザを起動する。 ログインし直しになるうえ、二段階認証やセキュリティ系プラグインの制限にかかる可能性がある。すでにログイン済みのタブを使えることが、AppleScript を使う一番の利点だった。

まとめ

関連記事