Skip to content

Instantly share code, notes, and snippets.

@fumikito
Created September 16, 2025 06:08
Show Gist options
  • Save fumikito/c1a4ea6f5285aeb56bea2176820a0b41 to your computer and use it in GitHub Desktop.
Save fumikito/c1a4ea6f5285aeb56bea2176820a0b41 to your computer and use it in GitHub Desktop.
Local by FlyWheelのWordPressサイト(Docker)に生成AIが直接アクセスできるようにするCLIのショートハンドスクリプト
#!/usr/bin/env bash
# local-wp — Run commands inside a Local by Flywheel site shell non-interactively.
#
# About:
# このシェルスクリプトはClaude Codeなどの生成AIツールがLocalのDocker内でCLIコマンドを実行するためのヘルパースクリプトです。
# CLAUDE.mdなどに書いておくと、Vibe Codingが捗ります。
# ChatGPT 5と一緒に作りました。
#
# Installation:
# 1. パスの通った場所にインストールしてください。たとえば、 `~/bin/local-wp` として保存します。
# 2. 実行権限を付与します。 `chmod +x ~/bin/local-wp`
#
# Usage:
# local-wp <site_name> -- <command ...>
# local-wp --list
# local-wp --help
#
set -Eeuo pipefail
SSH_DIR="$HOME/Library/Application Support/Local/ssh-entry"
usage() {
cat <<'EOF'
Usage:
local-wp <site_name> -- <command ...>
local-wp --list
local-wp --help
Notes:
- 各サイトで一度は GUI の「Open Site Shell」を実行しておくと ssh-entry が作成されます。
- サイトは Local.app で起動中である必要があります。
- <site_name> は「Local Sites/<site_name>」のフォルダ名です(空白可)。
EOF
}
die() { echo "✗ $*" >&2; exit 1; }
# どんな保存場所でもOK: ssh-entryの中から "/.../app/public" を拾ってサイト名を推定
extract_site_name() {
local f="$1" raw path site_root site_name
# 末尾が /app/public の絶対パス(両側の'や"はあってもなくてもOK)を1つ抜く
raw="$(grep -m1 -Eo '["'"'"']?/[A-Za-z0-9._~/: -]*/app/public["'"'"']?' "$f" || true)" || true
[[ -n "$raw" ]] || return 1
raw="${raw%\"}"; raw="${raw%\'}"; raw="${raw#\"}"; raw="${raw#\'}"
path="$raw"
site_root="${path%/app/public}"
site_name="${site_root##*/}" # 例: /…/minicome → minicome
printf '%s\n' "$site_name"
}
list_sites() {
local f name
shopt -s nullglob
for f in "$SSH_DIR"/*.sh; do
name="$(extract_site_name "$f" || true)"
[[ -n "$name" ]] && printf '%s\n' "$name"
done | sort -u
}
find_script_for_site() {
local target="$1" lc_target lc_name f name best=""
lc_target="$(printf '%s' "$target" | tr '[:upper:]' '[:lower:]')"
shopt -s nullglob
# ★ 空白を壊さない:lsやコマンド置換は使わず、そのままループ
for f in "$SSH_DIR"/*.sh; do
name="$(extract_site_name "$f" || true)" || continue
lc_name="$(printf '%s' "$name" | tr '[:upper:]' '[:lower:]')"
if [[ "$lc_name" == "$lc_target" ]]; then
printf '%s\n' "$f"
return 0
fi
[[ -z "$best" && "$lc_name" == *"$lc_target"* ]] && best="$f"
done
[[ -n "$best" ]] && { printf '%s\n' "$best"; return 0; }
return 1
}
main() {
[[ "${1:-}" == "--help" ]] && { usage; exit 0; }
[[ "${1:-}" == "--list" ]] && { list_sites; exit 0; }
[[ -d "$SSH_DIR" ]] || die "ssh-entry が見つかりません: $SSH_DIR"
[[ $# -ge 1 ]] || { usage; exit 1; }
local site="$1"; shift || true
[[ "${1:-}" == "--" ]] && shift || true
[[ $# -ge 1 ]] || die "実行コマンドがありません。例: local-wp \"$site\" -- wp plugin list"
# サイト名に空白があっても OK(grep マッチは文字列完全一致)
script="$(find_script_for_site "$site" || true)"
[[ -n "${script:-}" ]] || die "対象サイトの ssh-entry が見つかりません: $site
ヒント: 1) Local でサイトを起動 2) 一度「Open Site Shell」を実行 3) site 名の綴りを確認"
# 引数を安全にクォートして 1 行に整形(bash 3.2 互換)
local Q=""; printf -v Q '%q ' "$@"; Q="${Q% }"
# 1) ssh-entryから対話起動系を除去した一時ファイルを作る
tmp="$(mktemp -t localwp.XXXXXX)"
# exec $SHELL と 「Launching shell: …」の行を削除
sed -E '/^exec[[:space:]]+\$SHELL[[:space:]]*$/d;/Launching shell:/d' "$script" > "$tmp"
# 2) “ほぼ空”の環境でサブシェル起動し、無害化したssh-entryをsource → コマンド実行
/usr/bin/env -i HOME="$HOME" USER="$USER" LOGNAME="${LOGNAME:-$USER}" \
PATH="/usr/bin:/bin:/usr/local/bin" TERM="${TERM:-xterm-256color}" SHELL="/bin/bash" \
/bin/bash -lc "unset PROMPT_COMMAND BASH_ENV ENV; source \"$tmp\" >/dev/null 2>&1; ${Q}"
status=$?
# 3) 後始末
rm -f "$tmp"
exit "$status"
}
main "$@"
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment