gmail-access について
gmail-accessは、コマンドラインツールを通じてGmailの閲覧と検索を行うClaude Code向けスキルです。プロジェクト内でメールを扱うユーザー向けに、アカウント確認、OAuth設定案内、引用文の発言者判定手順を備えています。
主な機能
- 01Gmailメールの一覧表示と検索
- 02アカウントとトークンの事前確認
- 03Gmail OAuth設定の案内
- 04下書きの一覧表示と更新
- 05返信メールの発言者を分析
活用シーン
- 01送信者や条件を指定してメールを検索
- 02プロジェクトで使うGmailアカウントを確認
- 03編集後に下書きIDを再確認
- 04返信の要約時に双方の発言を区別
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ terrylica/ cc-skills --skill gmail-accessインストール後、Claude Code で/gmail-accessを入力して読み込まれたか確認します。
同梱ファイル
SKILL.md と一緒にインストールされる参考資料やスクリプト。
references/attribution-parsing.mdenv-setup.mdevolution-log.mdgmail-api-setup.md詳細
Gmail アクセス
Claude Code を介して、プログラムから Gmail を読み取り、検索します。
自己進化型スキル:このスキルは使用するほど改善されます。手順が誤っている、パラメーターが変わった、または回避策が必要だった場合は、後回しにせず、すぐにこのファイルを修正してください。実際に再現できる問題のみ更新してください。
必須の事前確認(Gmail の操作前に実行)
重要:Gmail コマンドを実行する前に、この事前確認のチェックリストを必ず完了してください。手順を省略しないでください。
ステップ 1:CLI バイナリの存在を確認
ls -la "$HOME/.claude/plugins/marketplaces/cc-skills/plugins/gmail-commander/scripts/gmail-cli/gmail" 2>/dev/null || echo "BINARY_NOT_FOUND"
BINARY_NOT_FOUND の場合:先にビルドしてください。
cd ~/.claude/plugins/marketplaces/cc-skills/plugins/gmail-commander/scripts/gmail-cli && bun install --frozen-lockfile && bun run build
ステップ 2:GMAIL_OP_UUID 環境変数を確認
echo "GMAIL_OP_UUID: ${GMAIL_OP_UUID:-NOT_SET}"
NOT_SET の場合:以下のセットアップフローを必ず実行してください。Gmail コマンドに進まないでください。
ステップ 2.5:アカウントのコンテキストを確認(重要)
現在のプロジェクトで正しいメールアカウントにアクセスしていることを必ず確認してください。
# Show current project context
echo "=== Gmail Account Context ==="
echo "Working directory: $(pwd)"
echo "GMAIL_OP_UUID: ${GMAIL_OP_UUID}"
# Check where GMAIL_OP_UUID is defined (plain env: shell startup files, this shell, or inline)
echo ""
echo "=== GMAIL_OP_UUID Source ==="
grep -l "GMAIL_OP_UUID" ~/.zshenv ~/.zshrc 2>/dev/null || echo "Not in shell startup files (set in this shell or passed inline)"
# Quick connectivity test — shows the account email from a real email
echo ""
echo "=== Account Verification ==="
$GMAIL_CLI list -n 1 2>&1 | head -5
先に進む前に停止し、ユーザーに確認してください。
list -n 1の出力に表示されたアカウントの受信トレイを確認し、プロジェクトで使用するメールと一致しているか確かめてください。- 違うアカウントが表示された場合は、適切な UUID をインラインで指定して再実行してください(
GMAIL_OP_UUID=<uuid> $GMAIL_CLI ...)。作業ディレクトリからアカウントが選択されることはありません。 - 一致しない場合はユーザーに伝え、処理を続行しないでください。
複数アカウントの判別(GMAIL_OP_UUID が NOT_SET だがトークンが存在する場合):whoami サブコマンドはありません。各キャッシュ済みトークンの UUID で照会してメールボックスを特定し、プロジェクトに適したものを選択してください。
🔴 list -n 1 --json | jq '.[0].to' でメールボックスを特定しないでください。 これはメールボックスの所有者ではなく、最新メッセージの受信者を読み取ります。最新の項目が送信済みの下書きや送信済みメッセージの場合、宛先として指定した相手が表示されます。2026-08-18 の測定では、<token-uuid-prefix> トークンを照会すると、メールボックスは実際には [email protected] なのに、外部の相手のアドレスが返されました。正しいアカウントで操作するための手順で、確信を持って誤った答えを返すことになります。
代わりに、Gmail に問い合わせてください。users/me/profile が信頼できる情報源です。
# Which accounts are cached, which mailbox does each ACTUALLY own, and which alias is its DEFAULT?
bun "$HOME/.claude/plugins/marketplaces/cc-skills/plugins/gmail-commander/scripts/gmail-accounts.ts"
# one account, machine-readable:
bun "$HOME/.claude/plugins/marketplaces/cc-skills/plugins/gmail-commander/scripts/gmail-accounts.ts" --account <uuid> --json
これは各キャッシュ済みトークンで users/me/profile と settings/sendAs に問い合わせます。トークンは表示しません。トークンファイルを自分で読み取らないでください(jq .access_token、cat、python3)。トークンファイルはメールボックス全体へのベアラー認証情報であるため、gmail-send-guard によって拒否されます。--from を指定する前に、送信元アドレスの一覧が必要です。verificationStatus は accepted でなければなりません。また、DEFAULT のエイリアスが使用したいものでない場合、--from は任意ではなく必須です。From 行を指定せずに Gmail に届いたメッセージは、DEFAULT エイリアスで送信されます。
照会結果が invalid_grant の場合、そのアカウントのリフレッシュトークンは無効です(「invalid_grant の診断」を参照)。プロジェクトに一致するメールボックスの有効な UUID を選び、各コマンドで GMAIL_OP_UUID=<uuid> をインライン指定してください。子プロジェクトでは親プロジェクトと異なるアカウントが必要になることがよくあります。親の UUID をそのまま使わず、必ず確認してください。
ステップ 3:トークンの状態を確認
# Check cached token exists and is not expired
TOKEN_FILE="$HOME/.claude/tools/gmail-tokens/${GMAIL_OP_UUID}.json"
APP_CREDS="$HOME/.claude/tools/gmail-tokens/${GMAIL_OP_UUID}.app-credentials.json"
echo "Token file: $([ -f "$TOKEN_FILE" ] && echo "EXISTS" || echo "MISSING")"
echo "App credentials: $([ -f "$APP_CREDS" ] && echo "CACHED" || echo "MISSING — will need 1Password on first run")"
トークンファイルが見つからない場合:初回実行時に OAuth 同意のためのブラウザーが開きます。これは想定された動作です。アプリの認証情報が見つからない場合:1Password が一度だけ呼び出され、client_id/client_secret がキャッシュされます。その後は呼び出されません。
セットアップフロー(GMAIL_OP_UUID が NOT_SET の場合)
以下の手順を順番どおりに進めてください。判断が必要な箇所では AskUserQuestion を使用してください。
セットアップ手順 1:1Password CLI を確認
command -v op && echo "OP_CLI_INSTALLED" || echo "OP_CLI_MISSING"
OP_CLI_MISSING の場合:停止して、ユーザーに次のように伝えてください。
1Password CLI が必要です。次のコマンドでインストールしてください:
brew install 1password-cli
セットアップ手順 2:1Password 内の Gmail OAuth アイテムを検出
# Try common vaults — "Claude Automation" for service accounts, "Employee" for interactive
for VAULT in "Claude Automation" "Employee" "Personal"; do
ITEMS=$(op item list --vault "$VAULT" --format json 2>/dev/null | jq -r '.[] | select(.title | test("gmail|oauth|google"; "i")) | "\(.id)\t\(.title)"')
[ -n "$ITEMS" ] && echo "=== Vault: $VAULT ===" && echo "$ITEMS"
done
出力を解析し、結果に応じて処理を進めてください。
セットアップ手順 3:ユーザーが OAuth 認証情報を選択する
項目が見つかった場合、見つかった項目を使って AskUserQuestion を使用します。
AskUserQuestion({
questions: [{
question: "Which 1Password item contains your Gmail OAuth credentials?",
header: "Gmail OAuth",
options: [
// POPULATE FROM op item list RESULTS - example:
{ label: "Gmail API - project-f (56peh...)", description: "OAuth client in Employee vault" },
{ label: "Gmail API - personal (abc12...)", description: "Personal OAuth client" },
],
multiSelect: false
}]
})
項目が見つからなかった場合、AskUserQuestion を使用してセットアップ方法を案内します。
AskUserQuestion({
questions: [{
question: "No Gmail OAuth credentials found in 1Password. How would you like to proceed?",
header: "Setup",
options: [
{ label: "Create new OAuth credentials (Recommended)", description: "I'll guide you through Google Cloud Console setup" },
{ label: "I have credentials elsewhere", description: "Help me add them to 1Password" },
{ label: "Skip for now", description: "I'll set this up later" }
],
multiSelect: false
}]
})
- 「新しい OAuth 認証情報を作成する」の場合:references/gmail-api-setup.md を読み、提示します
- 「別の場所に認証情報がある」の場合:必要なフィールドを含めて 1Password に追加するよう案内します
- 「今回はスキップする」の場合:設定が完了するまでスキルは動作しないことを伝えます
セットアップ手順 4:UUID の設定先を確認する
ユーザーが項目(UUID 付き)を選択した後、AskUserQuestion を使用します。
AskUserQuestion({
questions: [{
question: "Where should GMAIL_OP_UUID be set?",
header: "Configure",
options: [
{ label: "This session only (Recommended)", description: "export GMAIL_OP_UUID in the current shell; nothing written to disk" },
{ label: "Per command", description: "Pass GMAIL_OP_UUID=<uuid> inline on every gmail call; safest when projects use different mailboxes" }
],
multiSelect: false
}]
})
「このセッションのみ」の場合:export GMAIL_OP_UUID=<selected-uuid> を実行します。
「コマンドごと」の場合:各呼び出しの前に、たとえば GMAIL_OP_UUID=<selected-uuid> $GMAIL_CLI list -n 1 のように付けます。
このマシンで動作するバックグラウンドボットやダイジェスト用に、環境ファイルへ書き込んではいけません。これらは独自のシークレットストアを備えたプライベートなデプロイ環境で動作しており、置き換え前のノートパソコン上の launchd ジョブは 2026-09-24 に廃止されました。
セットアップ手順 5:確認
echo "GMAIL_OP_UUID: ${GMAIL_OP_UUID:-NOT_SET}"
セットアップ手順 6:接続のテスト
GMAIL_OP_UUID="${GMAIL_OP_UUID}" $HOME/.claude/plugins/marketplaces/cc-skills/plugins/gmail-commander/scripts/gmail-cli/gmail list -n 1
OAuth プロンプトが表示された場合:初回実行時には想定どおりの動作です。ブラウザーが開き、Google の同意画面が表示されます。
Gmail コマンド(事前確認に合格した後のみ)
GMAIL_CLI="$HOME/.claude/plugins/marketplaces/cc-skills/plugins/gmail-commander/scripts/gmail-cli/gmail"
# List recent emails
$GMAIL_CLI list -n 10
# Search emails
$GMAIL_CLI search "from:[email protected]" -n 20
# Search with date range
$GMAIL_CLI search "from:alice after:2026/01/27" -n 10
# Read specific email with full body
$GMAIL_CLI read <message_id>
# Read and download inline images (copy-pasted screenshots in compose)
$GMAIL_CLI read <message_id> --save-images
# Download inline images to a specific directory
$GMAIL_CLI read <message_id> --save-images --image-dir ./attachments/my-folder/
# Shorthand: --image-dir implies --save-images
$GMAIL_CLI read <message_id> --image-dir ./attachments/my-folder/
# JSON output with image metadata and saved paths
$GMAIL_CLI read <message_id> --save-images --json
# Download REAL file attachments (PDF, docx, csv, …) — distinct from inline images
$GMAIL_CLI read <message_id> --save-attachments
# Download attachments to a specific directory (implies --save-attachments)
$GMAIL_CLI read <message_id> --attachment-dir ./files/case-17402939/
# Export search results to JSON (full body + inlineImages + attachments metadata per message)
$GMAIL_CLI export -q "label:inbox" -o emails.json -n 100
# JSON output (for parsing)
$GMAIL_CLI list -n 10 --json
# Create a draft email
$GMAIL_CLI draft --to "[email protected]" --subject "Hello" --body "Message body"
# Create a draft reply (threads into existing conversation)
$GMAIL_CLI draft --to "[email protected]" --subject "Re: Hello" --body "Reply text" --reply-to <message_id>
# Draft with body loaded from a file (multi-paragraph bodies are awkward via --body)
$GMAIL_CLI draft --to "[email protected]" --subject "Report" --body-file ./email-body.txt
# Draft with file attachments (--attach is repeatable; MIME type guessed from extension)
$GMAIL_CLI draft --to "[email protected]" --subject "Q1 Report" \
--body-file ./email-body.txt \
--attach ./report.pdf \
--attach ./screenshot.png
# Draft reply with both body-file and multiple attachments
$GMAIL_CLI draft --to "[email protected]" --subject "Re: Project" \
--reply-to <message_id> \
--body-file ./reply.txt \
--attach ./diagram.pdf
# List drafts with their draft IDs
$GMAIL_CLI drafts -n 10
# Replace a draft in place (delete + recreate — the draft ID CHANGES)
$GMAIL_CLI draft-update <draft_id> --to "[email protected]" --from "[email protected]" \
--subject "Same subject" --body-file ./revised-body.txt
drafts --json のフィールド名
識別子のフィールドは draftId であり、id ではありません。.id セレクターを使ってもエラーにならず、すべての行でひっそりと null が返されるため、それを使った jq パイプラインは正常に動作したように見えて、結果は何も渡しません。
# WRONG — .id does not exist; prints "null" per draft and fails silently
$GMAIL_CLI drafts -n 10 --json | jq -r '.[] | "\(.id)\t\(.subject)"'
# RIGHT
$GMAIL_CLI drafts -n 10 --json | jq -r '.[] | "\(.draftId)\t\(.subject)"'
行全体の形式:date、draftId、from、messageId、snippet、subject、threadId、to。2 種類の識別子がある点に注意してください。draft-update と draft-delete が受け取るのは draftId、read が受け取るのは messageId です。一方の識別子を他方が必要な箇所に渡すと、失敗するか、誤ったレコードが返されます。
draft-update は新しい draftId を返します(下書きを削除して再作成します)。更新すると、それまで控えていた ID はすぐに無効になります。2 回目の更新前には必ず再度一覧表示し、編集をまたいで下書き ID をキャッシュしてはいけません。
🔴 帰属の原則 — 「相手が X と言った」と書く前に、必ずこれを実行する
メールを読むことと、誰がどの行を書いたかを把握することは、別の問題です。 後者はより難しく、通常返されるテキストには答えが含まれていません。間違ってもエラーにはならず、流ちょうで自信に満ちた要約が生成されますが、そこでは自分自身の文が相手の発言として扱われたり、相手が書いた内容の半分がひっそりと抜け落ちたりします。この節に記録されたすべての失敗は、空の結果を返す誤ったクエリを、世界に何も存在しないことの証拠として扱ったという同じ形でした。
これはデフォルトで適用されます。 メッセージが返信であり、引用された履歴を含み、送信者の発言を引用、要約、帰属、判断、または行動に反映する場合は、以下の手順を実行してください。一覧表示、検索、トリアージ、件数の確認には適用されません。
モデル:著者性 ⊃ 色 ⊃ マーカー
3 つのシグナルが、信頼度の高い順に入れ子になっています。2026-08-25 のインシデントでは、主たるシグナルに誤ったものを使ったことで、両方の問題が発生しました。
| シグナル | 対象範囲 | 保存場所 | 信頼度 |
|---|---|---|---|
| 引用の深さ | 完全 — 冒頭部分と引用内の挿入箇所の両方を捉える | text/plain:> が先頭に付いていない行 | 第一選択。ただし注意点を参照 — 一部のクライアントは誤った情報を返します |
| 色 | 部分集合 — 引用領域内の挿入箇所のみを対象 | text/html のみ | 深さが信頼できない箇所を正確に識別します |
型付きマーカー(>>>PT、@@@、[JS]) | 色の部分集合 — ブロックの開始位置を示す | どちらのパートにも存在 | 最も弱い。単独では決して不十分です |
プロトコル
bun scripts/attribution-parse.ts --id <messageId> --token-cmd '<cmd printing an access token>'
実際に存在するシグナル、それを誰のものと判断したか、そして重要な点として、誰のものとは判断できないのかを出力します。シグナルが食い違う場合はCONFLICT、引用された深さにある行に色もマーカーもない場合はUNKNOWNと報告します。その場合、その行は実際にどちらの側のものとも考えられるからです。**UNKNOWNは正しい答えです。**常に判断を下すツールは、ときに自信満々に間違えるツールです。
特定のクライアントに依存せず通用する4つのルール
- **メッセージごとに慣例を確認してください。別のメッセージから推測してはいけません。**同じ相手は、6日間で3種類の異なる慣例を使いました。8月20日はCSSの色付け、8月21日は色なしの
>>>PTマーカー、8月25日は旧式の<font color>です。「青でコメントを入れたので見てください」と書かれたメッセージに、色がまったく含まれていないこともあります。実際に2回確認しています。 - **検出結果が空なら、検出器が間違っている可能性があります。**このファイルで、これが最も大きな損失につながる習慣です。色の範囲がゼロ、マーカーがゼロ、一致する行がゼロの場合、送信者がその操作をしていないと結論づける前に、対象があるときにクエリで検出できることを証明してください。このスキルの別の箇所にある
.[0].toのメールボックスプローブや.idの下書きフィールドと同じ構図です。 - 段落の最後まで読んでください。22件の作成済みセグメントを監査したところ、見逃しの残りは解析が原因ではありませんでした。すべてすでに抽出済みのセグメント内にあり、最初の文だけを読んだために見落とされていました。彼女の構成は一貫していて、要約とは正反対です。決定は1文目、条件は2文目にあり、あなたを縛るのはその条件です。「ひとまずそれで進めましょう」 ……あとで増額してもらえることを期待しています。「私は交渉から降りる覚悟があります」 ……現地で代わりの業者を見つけられ、時間もあれば。
- **自信を持って断定するより、「判断できません」と伝えてください。**質問することは許されます。相手が読む文書で、相手の口から出ていない言葉を捏造することは許されません。
6日間で同じ送信者から得られた、実測に基づく3つの例
| 日付 | 本人が述べた慣例 | ソースに実際に含まれていたもの | 起きた問題 |
|---|---|---|---|
| 8月20日 | 「以下のコメントは青色です」 | css rgb(0,0,255) の span は1つだけ。それ以外は >>>> / >>> でマーク | 色はほとんど役に立ちませんでした。コメントの1つは途中で切れた断片(>>> should be)で、解決するには追加の質問が必要でした |
| 8月21日 | 「以下を >>>PT とともにご確認ください」 | 色はまったくありません。 マーカーのみ | 色を優先するパーサーでは何も見つからず、何も書いていないと判定していたでしょう |
| 8月25日 | 「以下を青色で >>>PT とともにご確認ください」 | 旧式の <font color="#0000ff"> タグが25個、css の color: はゼロ。 作成者の文章は22区間、マーカーは13個 | css のみを検出する仕組みはゼロ件を返し、「色は使われていない」と解釈されました。さらにその過剰な修正、つまり「色が完全な手がかり」という見方では、黒色で、メッセージ内で最も重要な決定が含まれていた彼女の冒頭部分が破棄されていたでしょう |
3行目から得られる教訓こそ、覚えておくべきものです。 バグを見つけることと、モデルを修正することは同じではありません。最初の読み取りではマーカーを信頼したため22区間中9区間を見落とし、修正後の読み取りでは色を信頼したため移行日を見落とすところでした。両方に対応できるのは、入れ子構造のモデルだけです。
要注意の兆候 — いったん止めて手作業で確認する
- 送信者が説明する慣例をパーサーで見つけられない(「青色」とあるのに色が検出されない)。
- 色付きの文章の数とマーカーの数が異なる — 差分はマークされていない続きの文章であり、続きの部分には条件が書かれていることがあります。
text/htmlには色があるのに、対応するテキストがtext/plainにない、またはその逆。両方を解析して和集合を取ってください。片方を優先してはいけません。 クライアントはそれぞれを独立して生成するため、一方のオフセットをもう一方に対応させることはできません。- 2者間のスレッドに、見かけ上3人以上の作成者がいる — 通常は1人の作成者の色表記が分かれているだけです(
blue/#00F/rgb(0,0,255))。そのため、同一性をテストする前に色を正規の三つ組に正規化する必要があります。 - CJK の全角句読点(
>>、:、【PT】)がある。^>は>に一致しないため、全角引用されたスレッドは、最後の返信者による新規テキストが100%として解析されます。
アーカイブする前に、後回しにせず
色は text/html にしかありません。text/plain だけを保存するアーカイブでは、後から「誰が何を言ったか」に答えられません — 証拠がそもそも含まれていないため、本来なら自分の記録で解決できるはずの質問に答えるため、API に戻ることになります。両方の部分を保存してください。read --json はプレーンテキストのみを返します。html を取得するには format=full を指定してください:
curl -s --noproxy '*' -H "Authorization: Bearer $TOK" \
"https://gmail.googleapis.com/gmail/v1/users/me/messages/<id>?format=full" \
| jq -r '[ .. | objects | select(.mimeType? == "text/html") | .body.data ]
| map(select(. != null)) | .[0] // empty' \
| tr '_-' '/+' | base64 -d > msg.html
また、送信者がインラインで返信している場合は、引用履歴を決して削除しないでください — 返信は引用の 内側 にあるため、削除すると返信が消え、署名だけが残ります。
全カタログ
すべてのルールに先立って必要な正規化(quoted-printable、format=flowed、CRLF、色の正規化、エンティティのデコード、NFKC、Word の条件付きコメント)、クライアントごとの事前確率、型付きマーカーの正規表現、および作成者を示すシグナルに見せかけるクライアント生成物については、references/attribution-parsing.md を参照してください。
インライン画像の抽出
メールには、コピーして貼り付けたスクリーンショット(ファイル添付ではなく、HTML 本文に埋め込まれたインライン画像)が含まれていることがよくあります。これらはプレーンテキストでは [image: image.png] のプレースホルダーとして表示されますが、Gmail API からアクセスできる実際の画像データを含んでいます。
主な動作
| フラグ | 効果 |
|---|---|
--save-images | すべてのインライン画像をディスクにダウンロードします(デフォルト:~/.claude/tools/gmail-images/<message_id>/) |
--image-dir <path> | カスタム出力ディレクトリを指定します(--save-images も有効になります) |
| フラグなし | 画像のメタデータ(件数、ファイル名、サイズ)を表示しますが、ダウンロードはしません |
出力セクション(画像がある場合)
--- Inline Images (3) ---
image.png image/png 245.3 KB
image.png image/png 512.1 KB
photo.jpg image/jpeg 89.7 KB
--- Saved to Disk ---
./attachments/01_image.png (251,234 B)
./attachments/02_image.png (524,001 B)
./attachments/03_photo.jpg (91,852 B)
--- Markdown References ---



重要:インライン画像とファイル添付は別物です
これらは互いに独立した 2 つの経路であり、別々に表示およびダウンロードされます。
| 経路 | MIME パーツ | メタデータフィールド | ダウンロードフラグ |
|---|---|---|---|
| インライン画像 | attachmentId を持つ image/*(コピーして貼り付けたスクリーンショット) | inlineImages[] | --save-images / --image-dir |
| ファイル添付 | filename と attachmentId を持つ画像以外のパーツ(PDF、docx、csv など) | attachments[] | --save-attachments / --attachment-dir |
フラグなしで gmail read <id> を実行すると、ダウンロードせずに両方がメタデータブロック(ファイル名、MIME、サイズ)として表示されるため、取得前に PDF が存在することを確認できます。export と read --json のどちらも、JSON に attachments[](および inlineImages[])を含みます。
has:attachment は実際のファイル添付には一致しますが、インライン画像には一致しません。 Gmail の検索にはインライン画像用の演算子がありません。インライン画像を含むメールを見つけるには、メールを読み込んで MIME ツリーを確認する必要があります。
インライン画像を含むメールを見つけるための方法:
# Search by sender/date, then read each to check for images
$GMAIL_CLI search "from:[email protected] after:2026/02/01" -n 10 --json | \
jq -r '.[].id' | while read id; do
COUNT=$($GMAIL_CLI read "$id" --json | jq '.inlineImages | length')
[ "$COUNT" -gt 0 ] && echo "$id has $COUNT inline image(s)"
done
Gmail のスレッド化と画像の重複排除
スレッド(複数の返信メール)から画像をダウンロードする場合、後続の返信には、それまでのすべてのインライン画像が含まれています。スレッドの最後のメールが、通常は画像をすべて含んでいます。
推奨事項:スレッド形式の会話では、重複を避けるため、最新の返信のみから画像をダウンロードしてください。確信が持てない場合は、ファイルサイズを比較してください。
ファイル名の衝突処理
コピーして貼り付けたスクリーンショットは、汎用的なファイル名 image.png を共有していることがよくあります。CLI はゼロ埋めのインデックスを付けてファイル名を区別します:01_image.png、02_image.png など。これらの機械生成名は、通信記録をアーカイブする際に内容を示す名前に変更してください。
ダウンロード後:注釈の文字起こしプロトコル
インライン画像に手書きの注釈(円、矢印、スクリーンショットに重ねて書かれたテキスト)が含まれている場合は、次の2段階で体系的に分析します。
- シーンの説明:スクリーンショットには何が表示されていますか?(例:「求人情報が一覧表示された採用ポータルのメインページ」)
- 注釈の一覧:元の画像にはないマークアップ要素をすべて漏れなく記録します。
- 手描きの図形:円、楕円、矢印、下線、×印など。何を囲んでいるかも記録します。
- 手書きのテキスト:一字一句そのまま引用符で囲んで書き起こし、判読可能かどうかと画像内の位置も記録します。
- 入力されたテスト用テキスト:スクリーンショットに表示されているフォームフィールドに入力されたテキスト
- ハイライトや色付きのマーク:色と、何が強調されているかを記録します。
Markdownでは、各画像の下にブロック引用のキャプションとして注釈を記載します。

> **Annotation transcription**: [Detailed description of visual markup.]
> Handwritten text reads: _"exact transcription here"_
> [Interpretation of what the annotator is requesting.]
注釈の書き起こしを2回目の確認に先送りしないでください。 繰り返し読み直す必要がないよう、画像を最初に確認する際に注釈をすべて記録します。
ファイル添付の抽出
実際のファイル添付(PDF、docx、csvなど)はattachments[]に表示され、--save-attachments/--attachment-dirでダウンロードされます。インライン画像と同じ取得経路を使用しますが、メタデータフィールドは異なります。
# See what a message carries (no download) — both metadata blocks print
$GMAIL_CLI read <id> # → "--- Attachments (1) --- foo.pdf application/pdf 192.5 KB"
# Download to a chosen dir; files are index-prefixed + sanitized
$GMAIL_CLI read <id> --attachment-dir ./files/
# Discover which messages in a corpus actually carry attachments
$GMAIL_CLI search "from:[email protected] has:attachment" -n 20 --json | \
jq -r '.[].id' | while IFS= read -r id; do
N=$($GMAIL_CLI read "$id" --json | jq '.attachments | length')
[ "$N" -gt 0 ] && echo "$id → $N attachment(s)"
done
アーカイブで重要な理由:クライアント、法務、業務に関するメールでは、添付PDF(プロトコル、ベンダーフォーム、署名済みの同意書など)が最も重要な内容であることがよくあります。本文のみをエクスポートすると、添付ファイルは気付かれないまま失われます。メールのやり取りをアーカイブする際は、必ずattachments[]を確認してください。
一括取得とスレッドのアーカイブ
メールのやり取り全体をアーカイブする標準的な手順(27件のメッセージ、15スレッドからなるクライアントのコーパスで検証済み):
- 汎用的なキーワードではなく、関連性の高いクエリで範囲を絞り込みます。 単独のキーワード(
"Acme")では、ニュースレターのようなノイズが大半を占めます。次のような条件を使います。- ドメイン:
vendor-domain.com(組織の送信元/宛先/ccを検索) - 参加者:
from:[email protected] OR to:… - プロジェクトコード:送信者が使用している社内タグ(例:
1233V)
- ドメイン:
search --json(スニペットのみ)からメッセージIDを収集し、ノイズの中から対象範囲に含めるメッセージを選別します。read --jsonのループを使って本文全体を取得します(メッセージごとに1ファイル)。- クライアント側で
threadIdごとにグループ化します。Gmailの一覧/検索APIが返すのはスレッドではなく個別のメッセージであるため、スレッドは自分で再構成します。 - スレッド内を解析済みの
Dateで並べ替え、引用された履歴を取り除きます(>で始まる行と、On … wrote:以降を削除)。これにより、各メッセージの新しい内容を確認しやすくなります。 attachments[]が空でないメッセージについては、添付ファイルを取得します。
# Robust batch fetch. NOTE: in zsh `for id in $VAR` does NOT word-split —
# always loop with `while IFS= read -r` over newline-delimited IDs.
printf '%s\n' $IDS | while IFS= read -r id; do
[ -n "$id" ] && $GMAIL_CLI read "$id" --json > "raw/$id.json"
done
exportは1回の呼び出しで済むショートカットです(修正済み)
gmail export -q "<query>" -o out.json -n Nは、メッセージごとに本文全体、inlineImages[]、attachments[]を含むJSON配列を1つ出力します。単一のクエリで対象範囲を取得できる場合に、一括取得を簡略化できます。(過去の注記:修正前は、exportは"Exported N emails to <path>"と表示するものの、ファイルを出力していませんでした。outputPathは未使用のパラメーターでした。この症状が見られる場合、バイナリが古いため、再ビルドしてください。)exportは添付ファイルのデータをダウンロードしません。メタデータのみを含むため、ファイルを取得するにはメッセージごとにread --save-attachmentsを使用してください。
メールの下書きの作成
draft コマンドを使うと、送信前に確認できるよう、Gmail の「下書き」フォルダーにメールを作成します。
必須オプション:
--to- 受信者のメールアドレス--subject- メールの件名--bodyまたは--body-file- メール本文(どちらか一方)
任意:
--body-file---bodyの代わりに、ファイルから本文を読み込みます。シェルに渡しにくい複数段落の本文に便利です。--bodyとは併用できません。両方が指定された場合は、stderr に警告を表示して--bodyを優先します。--attach- 添付するファイルのパス。複数の添付ファイルを指定する場合は、繰り返し指定できます。MIME タイプは拡張子から推測されます(PDF、PNG、JPEG、DOCX、XLSX、ZIP、MD、JSON など → 対応するタイプにマッピング。不明な場合 →application/octet-stream)。メッセージ全体のサイズは 25 MB 以下である必要があります(Gmail の上限です。超過すると、CLI は役立つヒントとともに 413 を表示します)。--from- 差出人のメールエイリアス(返信時には自動検出されます。下記の「差出人の整合性」を参照)--reply-to- 返信先のメッセージ ID(適切なヘッダーを設定してスレッド形式の返信を作成します)--json- 下書きの詳細を JSON 形式で出力します
差出人の整合性(必須・例外なし)
ユーザーは Gmail で複数の「名前を指定して送信」エイリアスを設定しています。差出人アドレスが正しく一致しないと、受信者には誤った名前からの返信として表示されます。
ルール 1 - 返信(--reply-to を指定):必ず --from も指定してください。
CLI は元のメールの To/Cc/Delivered-To ヘッダーを読み取り、ユーザーの「名前を指定して送信」エイリアスと照合して差出人を自動検出し、選択したアドレスを表示します。
From: [email protected] (auto-detected from original email)
表示されたアドレスが、使用したい差出人であるとは限りません。 自動検出では、元のメールがエイリアス宛てであっても、エイリアスではなく 基盤となるアカウント が選択されます。2026-08-25 の測定では、Jane Doe <[email protected]> 宛てのメッセージから、警告なしに From: [email protected] が生成されました。そのアカウントでは [email protected] が accepted の送信エイリアスとして登録されていたにもかかわらずです。下書きは正常に作成されたように見えました。
エイリアスは単なる見た目のラベルではなく、それ自体が本人のアイデンティティだからです。署名する名前を定めるやり取りのポリシーでは、アカウントとエイリアスは異なる署名者であり、誤るとメッセージ全体が別人として送信されます。また、このアカウントの デフォルト エイリアスは [email protected] で、ポリシーでは業務以外のメールに使用することが禁止されています。そのため、--from を省略したり誤検出したりすると、単に不適切なだけでなく、使用が禁止されたアイデンティティが選ばれる可能性があります。
したがって、新規メールだけでなく返信でも、必ず --from を明示的に指定し、確認メッセージを読み返してから使用してください。 すでに --from を指定せずに下書きを作成してしまった場合は、draft-update <draftId> --from … で正しい内容に作り直せます。ただし、既存の下書きをその場で更新するのではなく、新しく作成し直すため、ほかのフラグもすべて再指定してください。
--cc フラグはありません。 そのため、返信の下書きに含まれるのは To のみです。返信先のスレッドにほかの人が CC されていても同様です。スレッドへの返信情報(In-Reply-To/References)は設定されますが、CC は引き継がれません。クライアントと複数のベンダーが CC されているスレッドで、クライアントに代わって返信すると、全員が気付かないうちに宛先から外れてしまいます。送信前に Gmail の作成ウィンドウで CC を復元するか、引き継ぎ時に CC リストの復元が必要であることを明確に伝えてください。全員への返信だと思い込んだまま下書きを送信してはいけません。
ルール 2 - 新規メール(--reply-to を指定しない): 返信ではなく新しいメールを作成する場合は、下書きを作成する前に必ず AskUserQuestion を使って、どの差出人エイリアスを使用するか確認してください。デフォルトを決めつけてはいけません。
AskUserQuestion({
questions: [{
question: "Which email address should this be sent from?",
header: "Send As",
options: [
// Populate from known aliases or let user specify
{ label: "[email protected]", description: "Personal Gmail" },
{ label: "[email protected]", description: "Work email" },
],
multiSelect: false
}]
})
選択したアドレスを --from に渡します。
$GMAIL_CLI draft --to "[email protected]" --from "[email protected]" --subject "Hello" --body "Message"
ルール 3 - 出力を必ず確認する: 下書きの作成後、出力に From アドレスが表示されていることを確認してください。表示されていない場合や誤っている場合は、下書きを削除して作り直してください。
下書き作成後に必ず行う手順(必須)
下書きを作成した後は毎回、下書きを確認するための Gmail への直接リンクをユーザーに提示する必要があります。送信前に下書きを必ず目視で確認することが重要です。
下書きの作成後は必ず以下を出力してください:
Draft created! Review it here:
https://mail.google.com/mail/u/0/#drafts
From: <sender_address>
**この手順は絶対に省略しないでください。**送信前にユーザーが Gmail を開いて、下書きの内容、送信者、受信者、スレッドを目視で確認できるようにする必要があります。
例:メールに返信する(送信者を自動検出)
# 1. Find the message to reply to
$GMAIL_CLI search "from:[email protected] subject:meeting" -n 5 --json
# 2. Create draft reply - From is auto-detected from original email's To header
$GMAIL_CLI draft \
--to "[email protected]" \
--subject "Re: Meeting tomorrow" \
--body "Thanks for the update. I'll be there at 2pm." \
--reply-to "19c1e6a97124aed8"
# 3. ALWAYS present the review link + From address to user
例:新しいメール(送信者をユーザーに確認する必要があります)
# 1. Ask user which alias to send from (AskUserQuestion)
# 2. Create draft with explicit --from
$GMAIL_CLI draft \
--to "[email protected]" \
--from "[email protected]" \
--subject "Hello" \
--body "Message body"
# 3. ALWAYS present the review link + From address to user
**注:**下書きの作成後、以前に読み取りアクセスのみを許可していたユーザーは再認証が必要です。CLI で OAuth の同意を求められ、gmail.compose スコープが追加されます。
Gmail の検索構文
| クエリ | 説明 |
|---|---|
from:[email protected] | 特定の送信者からのメール |
to:[email protected] | 特定の受信者宛てのメール |
subject:keyword | 件名にキーワードが含まれるメール |
after:2026/01/01 | 指定日以降 |
before:2026/02/01 | 指定日より前 |
label:inbox | ラベルが付いているメール |
is:unread | 未読メール |
has:attachment | ファイルが添付されたメール(インライン画像には一致しません。「インライン画像の抽出」を参照) |
参考:https://support.google.com/mail/answer/7190
環境変数
| 変数 | 必須 | 説明 |
|---|---|---|
GMAIL_OP_UUID | はい | OAuth 認証情報の 1Password アイテム UUID |
GMAIL_OP_VAULT | いいえ | 1Password の保管庫(デフォルト:Employee) |
HTTPS_PROXY | いいえ | 基盤となる gaxios ライブラリで使用されます。ただし、CLI は起動時に *.googleapis.com を NO_PROXY に自動追加するため、企業プロキシによって Gmail の通信が妨げられることはありません。詳細は以下の「プロキシの自動バイパス」を参照してください。 |
Google API ホストのプロキシ自動バイパス
環境変数に HTTPS_PROXY(または HTTP_PROXY)が設定されている場合、CLI はモジュールの読み込み時に、認証や API 呼び出しが行われる前に、次のホストを NO_PROXY に自動で追加します。
.googleapis.com(gmail.googleapis.com、oauth2.googleapis.comなどが対象です).google.comaccounts.google.comoauth2.googleapis.com
この仕組みがある理由:多くの企業ネットワーク(Cloudflare WARP、mitmproxy のローカルインターセプター、ITP スタイルのローカルプロキシなど)では、Google API ホストへの CONNECT トンネルを確立できません。プロキシが失敗すると、応答として HTTP 502 と空のエラーボディが返されます。googleapis ライブラリは .message が空の gaxios エラーをスローし、以前は標準エラー出力に役に立たない空の Error: が表示されていました。
Google ホストへのプロキシを強制的にバイパスすることで、企業プロキシを利用しているエンドユーザーは、コマンドごとに NO_PROXY を手動で設定したり、HTTPS_PROXY を解除したりせずに CLI を実行できます。
冪等性:この追加処理では、NO_PROXY にまだ存在しない項目だけを追加します。NO_PROXY=.googleapis.com を手動で設定している場合、CLI はその設定を変更しません。
残るプロキシ問題の診断(まれなケース):それでも HTTP 5xx エラーが表示される場合、新しい CLI のエラーフォーマッターに完全な URL、応答ボディ、ヒントが表示されます。CLI の起動前に HTTPS_PROXY が設定されていたことを確認してください(環境変数の検出はモジュール読み込み時に一度だけ行われます)。
エラーメッセージ
CLI のトップレベルエラーハンドラーは、不明なエラーを HTTP ステータス、リクエスト URL、応答ボディの抜粋、カテゴリ別のヒントを含む構造化メッセージとして表示します。存在しない下書き ID に対する 404 の例:
Error: HTTP 404 Not Found DELETE https://gmail.googleapis.com/gmail/v1/users/me/drafts/r-doesnotexist123
body: {"error":{"code":404,"message":"Requested entity was not found.",...}}
hint: message / draft ID not found. List existing drafts with `gmail drafts` first.
ヒントのカテゴリ:
| ステータス | ヒント |
|---|---|
| 401 | トークンの有効期限切れまたは取り消し。再認証を強制するには、~/.claude/tools/gmail-tokens/$GMAIL_OP_UUID.json を削除してください。 |
| 403 | OAuth スコープが不十分です(下書き/添付ファイルには gmail.compose が必要です)、または Send As エイリアスが設定されていません。 |
| 404 | メッセージ/下書き ID が見つかりません。まず gmail drafts で既存の下書きを一覧表示してください。 |
| 413 | 添付ファイルが Gmail のメッセージあたり 25 MB の上限を超えています。分割するか、Drive のリンクを使用してください。 |
| 502/503/504 | ゲートウェイエラー — 通常は Google へのトンネルを確立できないプロキシが原因です。自動バイパスで防止できるはずです。CLI の起動前に HTTPS_PROXY が設定されていたことを確認してください。 |
ネットワーク層のエラー(ECONNREFUSED、ENOTFOUND など)は、以前の実装の空の Error: ではなく、エラーコードとともに表示されます。
トークンのアーキテクチャ
保存場所
~/.claude/tools/gmail-tokens/
├── <uuid>.json # OAuth token (access + refresh), refreshed hourly
└── <uuid>.app-credentials.json # client_id + client_secret (static, cached from 1Password)
- 中央の場所に保存します(プラグイン内でもプロジェクト内でもありません)
- 1Password の UUID ごとに整理されます(複数アカウントに対応)
- chmod 600 で作成されます
認証フロー(1Password の利用は初回のみ)
- 初回実行:1Password を呼び出して
client_id/client_secretを取得し、<uuid>.app-credentials.jsonにキャッシュします - 初回実行:Google OAuth の同意画面をブラウザーで開き、トークンを
<uuid>.jsonに保存します - 2 回目以降の実行:キャッシュ済みファイルのみを読み込みます — 1Password の呼び出しもブラウザーも不要です
- 1 時間ごとのリフレッシュ処理(launchd):キャッシュ済みの refresh_token を使って Google のトークンエンドポイントを呼び出し、access_token を有効な状態に保ちます
1Password で新たに検索を実行するには(例:OAuth アプリの認証情報をローテーションした後):
rm ~/.claude/tools/gmail-tokens/<uuid>.app-credentials.json
invalid_grant の診断
**公開ステータスが「Testing」の Google OAuth アプリのリフレッシュトークンは、7 日後に必ず期限切れになります。 1 時間ごとのリフレッシュ処理は アクセス トークンを更新しますが、リフレッシュ トークンの 7 日間の期限は延長しません。そのため、Testing モードのアカウントはおよそ毎週使えなくなり、ブラウザーで再度同意することでしか復旧できません。(「In production」**ステータスのアプリが発行するリフレッシュトークンは長期間有効で、この期限では失効しません。)
復旧(再同意):期限切れのトークンファイルを削除し、ブラウザーで再認証します。
# 1. Back up and remove the expired token
mv ~/.claude/tools/gmail-tokens/<uuid>.json ~/.claude/tools/gmail-tokens/<uuid>.json.expired
# 2. Run any gmail command — browser will open for OAuth consent
# (sign in with the SPECIFIC account that <uuid> maps to — see accounts.json labels)
$GMAIL_CLI list -n 1
# 3. Verify the hourly refresher picks up the new token
~/.claude/automation/gmail-token-refresher/gmail-oauth-token-refresher 2>&1
# 4. Clean up backup
rm ~/.claude/tools/gmail-tokens/<uuid>.json.expired
恒久的な修正(毎週の失効を止める — 「すべての再認証を維持する」):OAuth アプリを Production に公開し、リフレッシュトークンが 7 日間の期限で失効しないようにします。
- あるアカウントが無期限に使える一方で、別のアカウントが毎週使えなくなる場合、別々の OAuth アプリを使用しています(uuid ごとの
accounts.jsonのvaultを確認してください)。使えなくなる方だけが Testing のままです。 - Google Cloud Console → その OAuth クライアントを所有するプロジェクト(
client_idのプレフィックスはプロジェクト番号です。再認証時に CLI が同意 URL 内に完全なclient_idを表示します)。 - APIs & Services → OAuth consent screen → Publishing status → Publish app →「In production」であることを確認します。(External + Production で Gmail スコープを使う場合、新規ユーザーには「unverified」の警告が表示されることがありますが、すでに同意済みのアカウントには長期間有効なリフレッシュトークンが発行されます。完全な Google 認証が必要なのは、一般公開または 100 人を超えるユーザー向けのアプリのみです。)
- 公開後にもう一度再同意します。その後は、1 時間ごとのリフレッシュ処理によってアクセス トークンが常に最新の状態に保たれ、毎週の再認証が不要になります。
複数アカウントのトークンステータス
# Check all accounts at once: mailbox, access-token expiry, scopes, send-as aliases
bun "$HOME/.claude/plugins/marketplaces/cc-skills/plugins/gmail-commander/scripts/gmail-accounts.ts"
参考資料
- env-setup.md -
GMAIL_OP_UUIDの設定元、コンシューマーごとの設定、複数アカウントの使用方法 - gmail-api-setup.md - Google Cloud OAuth の設定ガイド
変更後のチェックリスト
- YAML frontmatter が有効(description にコロンがない)
- トリガーキーワードが最新
- パスパターンでハードコードされたパスではなく $HOME を使用
- 参照先が存在し、リンクされている
変更履歴
-
2026-10-08 — アカウントのデフォルト ID で直接送信される問題が発生したため、トークンの読み取りを単一のスクリプト経由に変更しました。
- 発生原因:エージェントがキャッシュされたトークンファイルを読み取り、From 行を指定せずに Gmail の送信エンドポイントへ POST するスクリプトを作成しました。Gmail は各アカウントのデフォルトの送信元エイリアスを使用しましたが、どちらのアカウントでも正しくない ID でした。メッセージはオペレーター自身のテスト用アドレスにしか届きませんでした。
- 修正:
hooks/gmail-send-guard.tsが直接送信と~/.claude/tools/gmail-tokens/の直接読み取りを拒否するようにしました(docs/gmail-send-guard.mdを参照)。.access_tokenとexpiry_dateをトークンファイルから直接読み取っていた ID の確認処理と複数アカウントのステータス確認処理は、scripts/gmail-accounts.tsに置き換えました。このスクリプトはメールボックス、有効期限、DEFAULT の印が付いたすべての送信元エイリアスを表示し、トークンは一切表示しません。
-
2026-09-26 — セットアップで、廃止済みのノート PC ボットとダイジェストをまだ設定できる状態でした。
- 発生原因:Telegram ボットと定期ダイジェストはプライベートな Restate デプロイメントに移行し、代替されたノート PC の launchd ジョブは 2026-09-24 に廃止されました。セットアップのステップ 4 では、引き続き
GMAIL_OP_UUIDの書き込み先として「The launchd daemons」が提示され、ステップ 2.5 の事前確認では引き続きそのデーモンの環境変数ファイルを grep していました。 - 修正:ステップ 4 では「This session only」と「Per command」を提示するようになり、このマシン上のバックグラウンドジョブ用の環境変数ファイルには UUID を書き込まないよう明記しました。事前確認ではシェルの起動ファイルのみを grep します。ビルドのステップでは、新たにコミットされた
scripts/gmail-cli/bun.lockを使用してbun install --frozen-lockfileを実行します。
- 発生原因:Telegram ボットと定期ダイジェストはプライベートな Restate デプロイメントに移行し、代替されたノート PC の launchd ジョブは 2026-09-24 に廃止されました。セットアップのステップ 4 では、引き続き
-
2026-09-25 — メールボックスのプローブには、どこにも定義されていないヘルパースクリプトが必要だったため、新規セッションでは実行できませんでした。
- きっかけ: あるリポジトリのメール取得ツールには
GMAIL_OP_UUIDが必要でしたが、どのキャッシュ済みトークンなのか記録されておらず、ステップ 2.5 のプローブは${GMAIL_TOKEN_SCRIPT:?…}で停止しました。この変数は、どのシェルファイル、プラグイン、プロジェクトにも設定されていません。その取得ツールがアーカイブするスレッドはその後3週間更新されず、その中の新しいメッセージを見逃しました。 - 修正: ヘルパーが設定されていない場合、プローブはキャッシュ済みの
<uuid>.jsonから.access_tokenを直接読み取るようになりました。毎時のリフレッシャーがこの値を最新に保つため、新たにトークンを発行する必要はありません。測定結果: キャッシュ済みの2つのトークンが1回の処理でそれぞれのメールボックスに解決され、どちらも有効期限まで約59分残っていました。 - 利用者への教訓: リポジトリが特定のメールボックスに依存する場合、そのリポジトリに、UUIDだけでなく
users/me/profileのアドレスを使って、どのメールボックスなのか記録してください。UUIDのプレフィックスは便宜上のものです。確かな根拠となるのはプロフィールのアドレスです。
- きっかけ: あるリポジトリのメール取得ツールには
-
2026-08-25 — 返信の自動検出はエイリアスではなくアカウントを特定し、成功を報告しました。
- きっかけ: クライアントに代わって、ベンダーとのスレッドに返信を下書きしました。元のメールの宛先は
Jane Doe <[email protected]>でしたが、CLI はFrom: [email protected] (auto-detected from original email)と表示して下書きを作成しました。エイリアスは同じアカウントでverificationStatus=acceptedとなっていたため、検出に失敗したわけではありません。検出は成功し、基盤となるアカウントが選択されました。 - 見逃されかけた理由: このファイルには以前、返信について「手動での介入は不要」とあり、
--fromによる指定は「自動検出に失敗した場合」だけに行うよう案内していました。何も失敗していません。成功した処理で誤った署名者が選ばれ、表示された確認行によって、正しく確認済みであるかのように見えていました。 - 見た目だけのヘッダーよりも重要な理由: どの名前で署名するかを通信ポリシーが定めている場合、アカウントとエイリアスは別人です。このアカウントの_デフォルト_エイリアスは職場用の ID で、このクライアント宛てのメールにはポリシー上使用できません。そのため
--fromを省略すると、単に予想外のものが選ばれるだけではなく、禁止されたものが選ばれる可能性があります。以下の 2026-08-18 の項目では、新規メールについてすでにこの危険性を記録していましたが、返信の経路は安全なものとして例外扱いされていました。実際は安全ではありません。 - 修正: ルール 1 に、返信でも
--fromを明示的に指定し、確認行が表示されたことを信用せず、内容を読み返すよう記載しました。既存の下書きはdraft-update <draftId>で修正できますが、下書きは削除して再作成されるため、すべてのフラグを再指定してください。 - 同じセッションで追加記載:
--ccフラグはありません。返信の下書きにはToだけが含まれるため、クライアントと複数のベンダーを CC に含めたスレッドに返信すると、全員が黙って除外されます。スレッドのヘッダーは保持されるので、実際には全員への返信ではないのに、そう見えてしまいます。送信前に Gmail で CC の宛先を戻すか、引き継ぎ時にその旨を明示してください。 - 根拠:
--reply-toを指定し--fromを指定せずに作成した下書きr2992600088433000620→[email protected]。明示的な--fromを指定して再作成した下書きr6764046697684220651→[email protected]。どちらも同じスレッド19f8ccd3370cdf57です。
- きっかけ: クライアントに代わって、ベンダーとのスレッドに返信を下書きしました。元のメールの宛先は
-
2026-08-25 — どの行を誰が書いたのか判別できず、バグはメールではなく検出器にありました。
- きっかけ: 運用担当者から、クライアントのメッセージにある色分けされたインライン返信が実際に解析されたのか質問されました。解析されていませんでした。css の
color:を検索した結果はゼロで、ゼロは「送信者は色を使っていない」という意味だと解釈されました。メッセージには 25 個のレガシーな<font color="#0000ff">タグが含まれていました。前述の.[0].toプローブや.idフィールドと同じく、誤ったクエリの空の結果を、世界に何も存在しない証拠とみなすパターンです。 - 欠陥 2、欠陥 1 の修正中に発見: 修正もまた誤っていました。色を見つけたあと、モデルは「色が完全なシグナルである」と判断しました。これは送信者の冒頭部分を破棄することになります。そこは黒色だからです。そのメッセージの冒頭部分には、移行日、その理由、電話での連絡依頼が記されていました。提示されたルールに従えば、メールの最も重要な内容を破棄することになり、分析はその内容を保持するという自らのルールに気づかないまま違反していました。
- 欠陥 3、構造上の問題: アーカイブには
text/plainしか保存されていませんでした。色は html のプロパティなので、送信者の言葉とこちらの言葉を区別する証拠は、リポジトリにそもそも存在していませんでした。質問に答えるには API に戻る必要があり、それこそアーカイブが取り除くために存在していた依存先でした。 - 修正: 上部に ATTRIBUTION DOCTRINE セクションを追加(入れ子モデル:著者 ⊃ 色 ⊃ マーカー、引用深度を第一の基準とする)、推測ではなく
CONFLICT/UNKNOWNを報告する再利用可能なscripts/attribution-parse.ts、各失敗パターンを固定するscripts/attribution-parse.test.ts、そして色、マーカー、引用構造、クライアント側の痕跡にわたる 108 種の慣習を調査してまとめたカタログreferences/attribution-parsing.md。 - ツールの構築中に測定してわかった、最も重要な教訓: 作成者が書いた 22 のセグメントすべてを監査したところ、残っていた見落としはマーカーと色の解析が原因ではありませんでした。すべて、すでに抽出されていたセグメントの内部にあり、最初の文だけを読むことによって失われていました。判断は 1 文目に、条件は 2 文目に記されており、あなたを拘束するのはその条件です。
- あわせて測定したこと: 実際のメールは CRLF であり、末尾の
\rは正規表現エンジンにとって行終端文字なので、(.*)$パターンはすべての行で失敗し、メッセージ全体のテキストが空になる一方で、「quote depth: yes」と報告し続けます。既知の正解データがある実際のメッセージでパーサーを実行して、初めて発見できました。 - 証拠: 6 日間で、同じ差出人からの 3 通のメッセージに 3 種類の異なる慣習が使われていました。1 通目は css span、次は色なしのマーカー、最後はレガシーな font タグです。最後のメッセージには、色付きの箇所が 22 個、マーカーが 13 個ありました。ユニットテストは 20 件すべて成功しました。
- きっかけ: 運用担当者から、クライアントのメッセージにある色分けされたインライン返信が実際に解析されたのか質問されました。解析されていませんでした。css の
-
2026-08-18 — アカウント検証プローブが誤ったメールボックスを報告し、
GMAIL_OP_UUIDが2つの異なる意味で使われています。- トリガー: 特定の送信者エイリアスから送信する必要がある、クライアント向けのメールを作成する場合です。ステップ2.5は、誤ったアカウントで操作しないように設けられたものですが、確信を持って誤った回答を返しました。
- 不具合1: 識別用のスニペットは
list -n 1 --json | jq '.[0].to'を読み取っていました。これはメールボックスの所有者ではなく、最新メッセージの受信者です。最新の項目は送信済みの下書きだったため、メールボックスが[email protected]であるにもかかわらず、<token-id>トークンは外部の相手のアドレスを報告しました。これを信頼した人は、自分が相手方のアカウントで認証されていると判断してしまいます。 - 修正1: 権威ある情報である
users/me/profile→.emailAddressに置き換え、さらにsettings/sendAsのプローブを追加しました。これにより、--fromを選ぶ前にエイリアスと DEFAULT エイリアスが分かります。このデフォルトは重要です。このアカウントでは[email protected]ですが、クライアント向けメールでは社内ポリシーで禁止されています。そのため、--fromを省略すると禁止されている ID で送信されます。 - 不具合2、未修正:
GMAIL_OP_UUIDが多重に使われています。プロジェクトの環境変数では、1Password アイテムのタイトル(例:"<account>-gmail")が設定されている場合がありますが、gmail-access-token.shとトークンキャッシュではアイテムのUUID(<token-id>.json)が使われます。タイトルを渡すとno token fileになります。コンパイル済みのgmailCLI が1Password経由でタイトルを解決するかどうかは、誤った推測をすると新たな OAuth ブラウザーの同意画面が表示されるため、テストしていません。どちらかを「修正」する前に確認してください。 - 証拠:
users/me/profile→[email protected]、settings/sendAs→[email protected]verificationStatus=accepted、[email protected]isDefault=true。明示的な--fromを指定して下書きr6501695713107519416を作成し、読み戻して、エイリアスが正しく設定され、ヘッダーに実名トークンが含まれていないことを確認しました。
-
2026-08-18 — 本文ガードは
scripts/gmail-draft.tsを対象としておらず、その抜け穴から Markdown が実際の下書きに入りました。- トリガー: Gmail の下書き API への場当たり的な書き込みは
gmail-draft-guard.shによって正しく BLOCKED され、scripts/gmail-draft.tsに誘導されました。このスクリプトは Markdown の--bodyファイルを受け付けますが、gmail draft/draft-updateにマッチするpretooluse-gmail-body-guard.tsの対象ではありません。そのスクリプト経由で Markdown を含むクライアント向け下書きが作成され、送信可能な状態になっていました。 - 症状: 保存された
text/plainにはリテラルの**bold**が11か所、リテラルの##見出しが2か所含まれており、text/html部分には<b>/<strong>がありませんでした。CLI は本文を Markdown としてレンダリングせず、HTML エスケープします。受信者には生のアスタリスクとシャープ記号が見えてしまうところでした。オペレーターが Gmail で下書きを開いて確認したため、発見できました。 - 修正:
draft-updateを使って、装飾のない文章で書き直しました。下書きへのすべての経路に作成ルールが適用されます。ガード対象の経路だけではありません。段落はそれぞれ改行のない1行にし、Markdown は使わず、改行は箇条書きの項目と署名のみにします。 - 未解決: ガードを
gmail-draft.tsにもマッチさせるか、スクリプト側で Markdown を拒否する必要があります。それまでは、ガードが何も通知しないことを根拠にしてはいけません。保存された本文を確認してください:read <id> --json | jq -r '.body' | grep -cE '\*\*|^#{1,6} 'の結果は0になるはずです。 - 証拠: 下書き
r4023096633097304797(gmail-draft.ts経由の Markdown)と、その差し替えr6980772236558441151(draft-update経由の整形済み本文)。同じセッションで提案用に作成した下書きr6501695713107519416は、装飾のない文章で作成され、スコアは0/0でした。
- トリガー: Gmail の下書き API への場当たり的な書き込みは
-
2026-07-22 — PreToolUse 本文ガードが、段落を1行にし、平文で記述することを強制するようになりました。
- きっかけ: 2026-07-10 の修正後も、ベンダーへの連絡メールの下書きが、約100桁でハードラップされ、生の Markdown を含む Markdown ファイルから作成されていました。
toHtmlBody()は作成時のすべての改行を<br>に変換するため、各折り返しがそのまま改行になり、受信者には文の途中で切れた短い行が縦に並んで表示されていました。また、CLI は本文を HTML エスケープするだけで Markdown をレンダリングしないため、**bold**/バッククォート/[text](url)/見出し/表がそのまま表示されていました。 - 修正: PreToolUse ガード(
itp-hooks→pretooluse-gmail-body-guard.ts、検出ロジックの単一の情報源:hooks/lib/gmail-body-detector.ts)により、インラインの--bodyまたは--body-fileの内容に、文の途中で改行された文章(HARD-WRAP)または判別しやすい生の Markdown(RAW MARKDOWN)が含まれるgmail draft/draft-updateを拒否します。回避手段はGMAIL-BODY-OKです。詳細:plugins/itp-hooks/docs/gmail-body-guard.md。 - 作成ルール(変更なし。今回から強制): 各段落を改行のない1行で記述してください。長い行は閲覧者のウィンドウ幅に合わせて折り返されます。意図した改行(リスト項目や
Best,/名前の署名)だけを独立した行にしてください。本文を72/80/100桁で事前に折り返さないでください。また、平文で記述し、レンダリングされることを期待して生の Markdown を貼り付けないでください。
- きっかけ: 2026-07-10 の修正後も、ベンダーへの連絡メールの下書きが、約100桁でハードラップされ、生の Markdown を含む Markdown ファイルから作成されていました。
-
2026-07-10 — 受信者側で下書きがハードラップされる問題に対処するため、HTML(multipart/alternative)に切り替えました。
- きっかけ: 送信済みの下書き(クライアントとのベンダー関連スレッド)が Gmail で短い固定幅の行になり、途中で切れたように見えていました。元の原因は、折り返しの指示がない
text/plainだったため、クライアントが長い段落をハードラップしていたことです。 - 最初の試み(Gmail では誤り)—
format=flowed: RFC 3676 のformat=flowedを使い、72桁でソフト改行を入れていました。Gmail はformat=flowedを確実に再折り返しできず、72桁の改行が Gmail の作成/下書き画面にそのまま表示されるため、下書きは依然として折り返された、または「途中で切れた」ように見えていました。Gmail 宛てのメールにformat=flowedを使わないでください。 - 正しい修正 — HTML
multipart/alternative:buildRawMessageは、text/plain パート(本文は折り返さず、長い行のまま。72桁で分割しません)と text/html パート(toHtmlBody()+escapeHtml())を含むmultipart/alternativeを送信するようになりました。HTML では本文を<div>で囲み、作成者自身が入力した改行だけを<br>に変換します。本文の途中には改行を挿入しないため、ブラウザーは長い段落を閲覧者のウィンドウ幅に合わせて折り返し、短い固定幅の行が表示されることはありません。リスト項目やBest,\nname の署名は、<br>によって改行を維持します。添付ファイルがある場合、代替本文はmultipart/mixedの中にネストされます。 - 検証結果: 下書きを再作成したところ(msg 19f4e01…)、
read --jsonの plain パートには329文字の段落が1行で含まれており(折り返しなし。新しい処理経路が動作したことを確認)、HTML パートは Gmail で再折り返しされました。バイナリを再ビルド(bun run build)し、tsgo のエラーがないことを確認しました。注:gmailバイナリはコンパイル済みです。TypeScript を編集した場合はbun run buildが必要です。修正前に作成した下書きは作り直す必要があります。
- きっかけ: 送信済みの下書き(クライアントとのベンダー関連スレッド)が Gmail で短い固定幅の行になり、途中で切れたように見えていました。元の原因は、折り返しの指示がない
-
2026-05-31 — エクスポートのサイレントな失敗と添付ファイルの取得不可(クライアントのアーカイブ作業)。
- きっかけ: 27件のベンダーとのやり取りをアーカイブする際、
gmail export -o <path>は"Exported N emails to <path>"と表示しましたが、何も書き込みませんでした(exportEmailsは配列を返すだけで、outputPathに書き込んでいませんでした)。また、CLI ではファイル添付が表示されず(inlineImagesのみ)、11件のメッセージに添付された PDF(ベンダーの認証フォームやプロトコル)が気付かれないまま欠落していました。 - 修正: (1)
exportEmailsが JSON をwriteFileするようになりました。(2)extractAttachmentsとformatMessageのattachments[]メタデータ、gmail-images.ts のsaveAttachments()、--save-attachments/--attachment-dirフラグ、およびprintEmailsの添付ファイルメタデータブロックを追加しました。インライン画像と添付ファイルの違い、一括スレッドアーカイブのパイプライン、複数アカウントの UUID→メールボックスの調査、zsh のwhile readバッチループに関する注意点を文書化しました。 - 検証結果:
export -q vendor-domain.com -o /tmp/x.jsonが、本文全体を含むメール3件を書き込むようになりました。read <id> --attachment-dirでベンダーの認証フォームの PDF を取得しました(197,168 B、有効な PDF 1.7、3ページ)。バイナリを再ビルドし、tsc --noEmitのエラーがないことを確認しました。
- きっかけ: 27件のベンダーとのやり取りをアーカイブする際、
実行後の振り返り
このスキルの実行後、タスクを終える前に振り返ります。
- 現在地を確認します。 — 編集する前に、この SKILL.md の正規パスを特定します。
- 何が失敗しましたか? — 原因となった指示を修正します。
- 予想以上にうまくいったことは何ですか? — 推奨プラクティスにします。
- 何にずれがありましたか? — 現状と合わなくなったスクリプト、参照、依存関係を修正します。
- 記録します。 — 発生のきっかけ、修正内容、根拠を含む進化ログのエントリーを追加します。
先延ばしにしないでください。次回の実行では、ここに残した内容が引き継がれます。