このシステムでできること

JCBカード 楽天カード PayPayカード 三井住友カード
ヒント:メール通知サービスがあれば他のカードにも対応できます。Claude Code と対話しながらカスタマイズしてみてください。

1

LINEボット作成(LINE Developers)

LINEにメッセージを自動送信するための専用ボットアカウントを作成します。

  1. 「LINE developer」で検索、またはアクセスして普段のLINEアカウントでログイン
    ブラウザ
    https://developers.line.biz/
  2. 「プロバイダーを作成」→ 名前を入力(例:kakeibo-demo)→ 作成
  3. 「Messaging API channel」を選択 → 「Create a LINE Official Account」をクリック
  4. アカウント情報を入力:
    • アカウント名・メールアドレスなど必要事項を記入
    • 業種は「個人」を選択
  5. 緑色のボタンを押して完了 → アカウントマネージャーに進む → 緑ボタンを押す
  6. ホーム画面 → 右上「設定」→「トークへの参加」を 許可 に変更
  7. 左メニュー「Messaging API」→ 最初に作ったプロバイダを選択 → 同意 → OK
  8. Developersコンソールに戻る → プロバイダ → アカウントをクリック
    下にスクロールして Channel secret をメモ
  9. 「Messaging API」タブ → スクロール → 長期アクセストークンを 発行 → メモ
完了:Channel secret長期アクセストークン の2つをメモできたらSTEP 1完了です。
2

Google Cloud 設定

プログラムを動かすサーバーレス環境とGmail連携の認証情報を用意します。

  1. Google Cloud Console にアクセス → 普段のGmailアカウントでログイン
    ブラウザ
    https://console.cloud.google.com/
  2. 「新しいプロジェクト」を作成(プロジェクト名は任意、例:kakeibo-bot
  3. 作成後、プロジェクトID をメモ(プロジェクト名と異なる場合あり)
  4. 作成したプロジェクトに切り替え
  1. 左メニュー「APIとサービス」→「APIとサービスを有効にする」をクリック
  2. 以下を順に検索して「有効にする」を押す
    • Gmail API
    • Cloud Run API
    • Cloud Scheduler API
    • Secret Manager API
    • Artifact Registry API
注意:Cloud Run API を有効にする際、請求先(クレジットカード)の登録を求められます。今回の用途は無料枠内に収まりますが、登録は必要です。
  1. 左メニュー「APIとサービス」→「認証情報」に進む
  2. 「認証情報を作成」→「OAuthクライアントID」を選択
  3. 「同意画面を構成」→「開始」をクリック
  4. アプリ情報を入力:
    • アプリ名・サポートメールアドレス(任意)
    • ユーザーの種類「外部」を選択
    • 連絡先に自分のGmailアドレスを入力
  5. 同意して作成
  6. アプリケーションの種類「デスクトップアプリ」を選択 → 作成
  7. JSONをダウンロード → 保存(後で使います)
  1. 左メニュー「対象」をクリック → 下にスクロール
  2. 「テストユーザー」欄に、カード会社からの通知メールを受け取るGmailアドレスを登録
完了:プロジェクトIDJSONファイル(credentials.json) が手元にあればSTEP 2完了です。
3

設定ファイルの作成

作業フォルダを作り、取得した認証情報をまとめた設定ファイルを用意します。

  1. 作業フォルダを作成(フォルダ名は任意)
    フォルダ名の例
    C:\kakeibo-demo
  2. ダウンロードしたJSONファイルを作業フォルダに移動し、以下のファイル名にリネーム
    リネーム後のファイル名
    credentials.json
  3. メモ帳を開き、下記の内容を入力
    保存方法:「名前を付けて保存」→ ファイルの種類「すべてのファイル」→ ファイル名:
    ファイル名
    .env.local
.env.local の内容
# Google Cloud
GCP_PROJECT_ID=【ここにプロジェクトIDをコピペ】
GCP_REGION=asia-northeast1

# LINE Messaging API
LINE_CHANNEL_ACCESS_TOKEN=【ここに長期アクセストークンをコピペ】
LINE_CHANNEL_SECRET=【ここにチャンネルシークレットを貼る】

# LINE Group ID(起動後にWebhook経由で自動設定されるため初期値は空でよい)
LINE_GROUP_ID=
重要:このファイルは絶対に外部に公開しないでください。SNSへの投稿・GitHubへのアップロードは厳禁です。
完了:作業フォルダに credentials.json.env.local の2ファイルが揃えばSTEP 3完了です。
4

Claude Code で実装

プロンプトを渡すだけで、必要なプログラムをすべて自動生成します。

注意:Claude Code の Max プラン(月額 $22〜) が必要です。概要欄のリンクから登録できます。
  1. PowerShell を開く
  2. 作業フォルダに移動して Claude Code を起動
    PowerShell
    cd C:\kakeibo-demo
    claude
  3. 下記のプロンプトをまるごとコピーして貼り付け → Enter
Claude Code プロンプト(全文)
以下の指示に従い、システムを一から構築してください。

## 概要
Gmailに届くクレジットカードの利用通知メールを自動で集計し、
毎週・毎月の支出レポートをLINEグループに送信するシステムを作ります。

## 前提
- 作業ディレクトリに credentials.json(Gmail OAuth認証情報・デスクトップアプリ型)が置かれている
- 作業ディレクトリに .env.local が置かれており、以下の変数をすべて含む:
    GCP_PROJECT_ID=...
    GCP_REGION=...
    LINE_CHANNEL_ACCESS_TOKEN=...
    LINE_CHANNEL_SECRET=...
- Docker インストール済み(未インストールでも構わない。deploy.ps1 が自動インストールする)
- Python 3.11 以上
- PowerShell 5.1 以上(Windows 標準搭載のもので可)
- Google Cloud SDK・gcloud ログイン・ADC は **未設定でも構わない**(deploy.ps1 が自動でセットアップする)

## 技術スタック
- Python (Flask)
- Gmail API (OAuth2) — カード通知メールの読み取り
- LINE Messaging API — グループへのレポート送信
- Google Cloud Run — ホスティング
- Google Cloud Scheduler — 定期実行
- Google Secret Manager — 認証情報の安全な保管
- Google Artifact Registry — Dockerイメージの保管

## 対応カードのメール解析
以下のカードの利用通知メールを解析できるようにしてください。
対応できないカードのメールは無視します。
- JCBカード(件名に「ご利用のお知らせ」を含む)
- 楽天カード(件名に「楽天カード利用お知らせ」を含む)
- PayPayカード(件名に「PayPayカード ご利用のお知らせ」を含む)
- 三井住友カード(件名に「クレジットカードご利用のお知らせ」を含む)

各メールから抽出する情報:
- 利用日
- 利用金額(円)
- 利用店舗名(取れる場合)

### 件名判定の順序(重要)
件名の部分一致で誤判定しないよう、必ず以下の順序で判定してください:
1. PayPayカード(件名に「PayPayカード」を含むか)
2. 楽天カード(件名に「楽天カード利用お知らせ」を含むか)
3. 三井住友カード(件名に「クレジットカードご利用のお知らせ」を含むか)
4. JCBカード(件名に「ご利用のお知らせ」を含むか)
先に判定したカードのパーサーで処理し、残りは無視すること。
JCB の「ご利用のお知らせ」は三井住友・PayPay の件名とも部分一致するため、順序を守ることが必須。

## ファイル構成

全ファイルは作業ディレクトリ(credentials.json や .env.local が置かれているディレクトリ)の直下に作成すること。
サブディレクトリは作らない。

作業ディレクトリ/
├── app.py               # Flask アプリ本体 + Webhook受信
├── gmail_reader.py      # Gmail API でメール読み取り・解析
├── line_sender.py       # LINE Messaging API でメッセージ送信
├── scheduler.py         # 週次・月次レポート生成ロジック
├── auth.py              # Gmail OAuth 認証フロー(初回のみ)
├── requirements.txt
├── Dockerfile
├── .dockerignore        # credentials.json / .env.local / token.json をイメージから除外
└── deploy.ps1           # Cloud Run デプロイ一括スクリプト(PowerShell)

## 動作フロー

1. Cloud Scheduler が週次(毎週日曜 20:00 JST)・月次(毎月1日 09:00 JST)に
   Cloud Run の /report エンドポイントを呼び出す
2. app.py が Gmail から過去7日分(週次)または30日分(月次)の
   カード通知メールを取得
3. 金額を集計してレポート文を作成し、LINE グループに送信する
4. LINE Bot が /webhook でメッセージを受信したときにグループIDを自動取得・更新する

## セキュリティ対策(必須)

以下をすべて実装すること。

### /report エンドポイントの保護
Cloud Scheduler は /report を呼び出す際、`X-CloudScheduler-JobName` ヘッダーを自動付与する。
このヘッダーは GCP インフラが設定するものであり、外部の一般リクエストには存在しない。
/report の冒頭でこのヘッダーの有無を確認し、存在しない場合は 403 を返して処理を中断すること:

  if not request.headers.get('X-CloudScheduler-JobName'):
      return jsonify({'status': 'forbidden'}), 403

これにより URL を知っている第三者が /report を直接叩いて LINE グループに
スパムを送ったり、Gmail API クォータを浪費する攻撃を防ぐ。

### エラー詳細の隠蔽
/report・/webhook のエラーレスポンスに例外メッセージを含めてはならない。
例外の詳細(プロジェクトID・内部パス等)が外部に漏れるのを防ぐため、
レスポンスは汎用メッセージに留め、詳細はサーバーログにのみ記録すること:

  # 禁止
  return jsonify({'status': 'error', 'message': str(exc)}), 500

  # 正しい実装
  logger.error('Error: %s', exc, exc_info=True)   # ログには残す
  return jsonify({'status': 'error', 'message': 'Internal server error'}), 500

### /webhook の署名検証(実装方法を厳守)
/webhook の LINE 署名検証は手書き HMAC ではなく、必ず line-bot-sdk v3 の
WebhookParser を使うこと。手書き HMAC は Secret Manager から取得した
channel_secret に末尾改行・空白が混入した場合に不一致となり 403 になる。

正しい実装:

  from linebot.v3.webhook import WebhookParser
  from linebot.v3.exceptions import InvalidSignatureError

  @app.route("/webhook", methods=["POST"])
  def webhook():
      try:
          project_id = get_project_id()
          channel_secret = _get_secret(project_id, "line-channel-secret").strip()
          signature = request.headers.get("X-Line-Signature", "")
          body = request.get_data(as_text=True)
          parser = WebhookParser(channel_secret)
          try:
              events = parser.parse(body, signature)
          except InvalidSignatureError:
              logger.warning("Invalid LINE signature")
              return jsonify({"status": "forbidden"}), 403
          for event in events:
              group_id = getattr(event.source, "group_id", None)
              if group_id:
                  from line_sender import save_group_id
                  save_group_id(project_id, group_id)
                  logger.info("Updated LINE group ID: %s", group_id)
          return jsonify({"status": "ok"})
      except Exception as exc:
          logger.error("Error in /webhook: %s", exc, exc_info=True)
          return jsonify({"status": "error", "message": "Internal server error"}), 500

また、Secret Manager から取得する文字列値(channel_secret / channel_access_token /
group_id)には必ず .lstrip('\ufeff').strip() を適用し、BOM・改行・空白を除去すること。
PowerShell の [System.Text.Encoding]::UTF8 で書いたファイルを Secret Manager に登録すると
先頭に UTF-8 BOM(\xEF\xBB\xBF = \ufeff)が混入する。.strip() だけでは BOM は除去されない。
_get_secret の実装は必ず以下とすること:
  return response.payload.data.decode("utf-8").lstrip('\ufeff').strip()

### その他(以下は設計上すでに満たすこと)
- 認証情報(LINE token・Gmail token・グループID)はすべて Secret Manager で管理し、
  コードや環境変数に直書きしない
- credentials.json / .env.local / token.json は .dockerignore で Docker イメージから除外する
- Gmail API のスコープは `gmail.readonly` のみとし、送信・削除権限を持たせない

## LINE グループID の自動取得
- /webhook エンドポイントで LINE からのイベントを受信する
- グループからメッセージが届いたとき、そのグループIDを Secret Manager に保存する
- グループIDが未設定の状態でレポート送信が走った場合は送信をスキップしてログに記録する

## レポートのフォーマット(週次)

📊 週次支出レポート(〇月〇日〜〇月〇日)
━━━━━━━━━━━━━━━━━
合計: ¥XX,XXX

支出TOP5:
1. 〇〇 ¥X,XXX
2. 〇〇 ¥X,XXX
3. 〇〇 ¥X,XXX
4. 〇〇 ¥X,XXX
5. 〇〇 ¥X,XXX

集計件数: XX件

## 実行場所とパス規約(重要・厳守)
auth.py と deploy.ps1 はどちらも作業ディレクトリ内で実行する。
ファイルのパスは以下の規約で統一すること:
- credentials.json: 作業ディレクトリ(カレントディレクトリ)に配置
  auth.py は credentials.json を自動で探す。
  見つからない場合は client_secret_*.json も検索する(Google Cloud Console が生成するデフォルト名に対応するため)
- .env.local: 作業ディレクトリ(./.env.local)に配置
- token.json: 作業ディレクトリ(./token.json)に生成・参照する
  auth.py の TOKEN_FILE と deploy.ps1 の TOKEN_FILE のパスは必ず一致させること

## 認証フロー (auth.py)
deploy.ps1 より先に実行する。作業ディレクトリ内で `python auth.py` として実行。
- 実行前に以下を自動チェックする:
  1. credentials.json に "installed" キーが含まれることを確認する
     "web" キーの場合は「デスクトップアプリ型のcredentials.jsonが必要です」と
     エラーメッセージを出して終了する
- InstalledAppFlow を使い flow.run_local_server(port=0) で認証する
  (port=0 で空きポートを自動割り当て。GCP側のリダイレクトURI設定不要)
- ブラウザが自動で開くので Google アカウントでログインして許可する
- 認証完了後、token.json を作業ディレクトリ(カレントディレクトリ)に生成する
- token.json は Secret Manager に「gmail-token」としてアップロードする
  この時点では gcloud / ADC が未設定のためアップロードは失敗することが多いが、
  エラーで終了せず手動アップロード用コマンドを表示してスキップすること
  (Secret Manager への登録は deploy.ps1 の Step 5 が確実に行うため問題ない)
  アップロード失敗時のエラーハンドリング:
  - FileNotFoundError(WinError 2)の場合は gcloud が PATH にないことを意味するため、
    「gcloud is not installed yet — deploy.ps1 will handle the upload in Step 5.」と表示する
  - それ以外の例外は発生した例外メッセージをそのまま表示してスキップする

## デプロイ手順 (deploy.ps1)
auth.py で token.json を生成した後に、作業ディレクトリ内で `.\deploy.ps1` として実行する。
PowerShell スクリプト(.ps1)として作成すること。
以下をすべて自動で行うスクリプトを作成してください:
0. Google Cloud SDK の自動インストール・ログイン・ADC 設定
   a. gcloud コマンドが PATH に存在するか確認する
      存在しない場合は以下の既知パスに gcloud.cmd が存在するか Test-Path で確認し、
      存在すれば $env:PATH に追加する:
        $env:LOCALAPPDATA\Google\Cloud SDK\google-cloud-sdk\bin
        $env:ProgramFiles\Google\Cloud SDK\google-cloud-sdk\bin
   b. それでも gcloud が見つからない場合、インストーラーを自動ダウンロード・サイレントインストールする
      - ダウンロード URL: https://dl.google.com/dl/cloudsdk/channels/rapid/GoogleCloudSDKInstaller.exe
      - $env:TEMP にダウンロードしてサイレントインストール(/S オプション)
      - インストール完了後、$env:LOCALAPPDATA\Google\Cloud SDK\google-cloud-sdk\bin を
        現在のセッションの PATH に追加する
      - インストール完了の確認は Get-Command ではなく Test-Path で行うこと
        (Get-Command はセッション内でコマンド検索結果をキャッシュするため、
        PATH 追加後も古いキャッシュを返して gcloud を見つけられない場合がある)
        具体的には "$installBin\gcloud.cmd" が存在するか Test-Path で確認する
      - インストーラーは内部で子プロセスを使って非同期にインストールを行うため、
        Start-Process -Wait で親プロセスの終了を待っても子プロセスがまだ動いている場合がある
        そのため、gcloud.cmd が見つからない場合は 5 秒待機して再確認する処理を
        最大 12 回(計60秒)繰り返すリトライループを実装すること
        60 秒待っても見つからない場合は「インストールに失敗しました。
        https://cloud.google.com/sdk/docs/install から手動でインストールしてください」と
        メッセージを出してスクリプトを終了する
   c. gcloud auth list --format="value(account)" でログイン済みアカウントを確認する
      出力が空またはエラーの場合は gcloud auth login を実行する(ブラウザが開く)
   d. gcloud auth application-default print-access-token で ADC 設定済みか確認する
      エラーの場合は gcloud auth application-default login を実行する(ブラウザが開く)
      【重要】2>$null だけでは不十分。必ず $ErrorActionPreference を SilentlyContinue に下げること。
      理由:Windows の gcloud は PowerShell ラッパー(gcloud.ps1)であり、
      内部で起動する python.exe が stderr に書いた内容を、gcloud.ps1 が
      NativeCommandError として PowerShell のエラーストリームに「再発行」する。
      $ErrorActionPreference = "Stop" 環境では、この NativeCommandError が
      終端エラーに昇格してから 2>$null に到達するため、リダイレクトが効かない。
      正しい対処は呼び出し前後で $ErrorActionPreference を SilentlyContinue に下げること。
      この問題は ADC チェックに限らず、stderr を出力しうる全 gcloud 呼び出しで発生する。
      そのため、スクリプト内の全 gcloud 呼び出しを以下のヘルパー関数で統一すること:

        function Invoke-Gcloud {
            param([string[]]$Arguments, [switch]$CaptureOutput)
            $saved = $ErrorActionPreference
            $ErrorActionPreference = "SilentlyContinue"
            if ($CaptureOutput) {
                $out = & gcloud @Arguments 2>$null
            } else {
                & gcloud @Arguments 2>$null
                $out = $null
            }
            $ec = $LASTEXITCODE
            $ErrorActionPreference = $saved
            return [PSCustomObject]@{ ExitCode = $ec; Output = $out }
        }

      この関数を通じて gcloud を呼び出し、戻り値の .ExitCode で成否を判定する。
      直接 & gcloud ... を書かないこと。

      【Invoke-Gcloud の改善点】
      上記の基本形に加え、以下の点を改善すること:
      - 失敗時(ExitCode != 0)に $out の内容を Write-Host で出力する。
        2>$null でエラーを抑制するため、失敗理由が全く表示されなくなるのを防ぐため。
      - 具体的には return の直前に以下を追加する:
          if ($ec -ne 0 -and $out) { Write-Host ($out | Out-String) }

      【対話操作(ブラウザが開く処理)の呼び出し方】
      gcloud auth login / application-default login はブラウザを開く対話操作なので、
      Invoke-Gcloud ではなく直接 & gcloud ... で呼ぶ。
      ただしこの場合も $ErrorActionPreference = "Stop" のままだとクラッシュするため、
      必ず以下のように SilentlyContinue で包んで呼び出すこと(2>$null は付けない):
        $saved = $ErrorActionPreference; $ErrorActionPreference = "SilentlyContinue"
        & gcloud auth login
        $ErrorActionPreference = $saved

      ADC チェックの書き方:
        $r = Invoke-Gcloud -Arguments @("auth","application-default","print-access-token") -CaptureOutput
        if ($r.ExitCode -ne 0) {
            $saved = $ErrorActionPreference; $ErrorActionPreference = "SilentlyContinue"
            & gcloud auth application-default login
            $ErrorActionPreference = $saved
        }
1. .env.local(.\.env.local)から環境変数を読み込む
   GCP_PROJECT_ID・GCP_REGION・LINE_CHANNEL_ACCESS_TOKEN・LINE_CHANNEL_SECRET が
   すべて設定されていることを検証し、欠落があればエラー終了
2. 必要な GCP API を有効化する
   (run / artifactregistry / secretmanager / cloudscheduler / gmail / compute)
   compute.googleapis.com を有効化することで、Cloud Run が使用するデフォルト Compute SA
   (${PROJECT_NUMBER}-compute@developer.gserviceaccount.com)が確実に作成される
   新規プロジェクトでは compute API が有効化されるまで Compute SA が存在せず、
   Step 6 の Cloud Run デプロイが失敗するため、必ず有効化リストに含めること
3. Artifact Registry にリポジトリを作成(なければ)
4. Docker Desktop のインストール確認・起動確認とイメージのビルド・push
   ① WSL2 が使用可能か確認する。Docker Desktop の WSL2 バックエンドに必要なため。
      確認方法:まず Get-Command で wsl.exe が存在するか確認する。
      存在する場合は `wsl --status` を実行し、終了コード 0 なら WSL2 は正常動作中として次へ進む。
      理由:Windows 11 では WSL2 が Microsoft Store アプリとして提供されており、
      Get-WindowsOptionalFeature では正しく検出できないケースがある(既存の WSL2 ユーザーでも
      State: Enabled が返らず誤って dism が走り、不要な再起動を求めてしまう)。
      exe ベースの確認が確実。
      wsl.exe が存在しない、または `wsl --status` が失敗する場合のみ、以下を実行して有効化する:
        dism.exe /online /enable-feature /featurename:Microsoft-Windows-Subsystem-Linux /all /norestart
        dism.exe /online /enable-feature /featurename:VirtualMachinePlatform /all /norestart
      有効化後は必ず以下のメッセージを表示してスクリプトを終了する:
        "WSL2 features have been enabled. Please RESTART your PC, then re-run .\deploy.ps1"
      ※ dism.exe は管理者権限が必要。権限不足でエラーになった場合は
        "Please re-run this script as Administrator (right-click -> Run as Administrator)" と表示して終了する
      ※ 再実行時(再起動後)はこのチェックを通過して次の手順に進む
   ② docker が PATH に存在するか確認する(SilentlyContinue の外で実施)
      存在しない場合は以下の既知パスを Test-Path で確認し、あれば $env:PATH に追加する:
        C:\Program Files\Docker\Docker\resources\bin
      それでも見つからない場合は Docker Desktop を自動インストールする:
      - ダウンロード URL: https://desktop.docker.com/win/main/amd64/Docker%20Desktop%20Installer.exe
      - $env:TEMP にダウンロードして以下のコマンドでサイレントインストール:
          Start-Process -FilePath $installerPath -ArgumentList "install","--quiet","--accept-license" -Wait
      - インストール完了後、C:\Program Files\Docker\Docker\resources\bin を PATH に追加する
      - インストール完了の確認は Get-Command ではなく Test-Path で行うこと
        (gcloud と同様の理由でキャッシュの影響を受けるため)
        具体的には "C:\Program Files\Docker\Docker\resources\bin\docker.exe" を Test-Path で確認する
      - インストーラーは子プロセスで非同期動作するため、5 秒待機 × 最大 12 回リトライすること
        60 秒待っても見つからない場合は「Docker Desktop installation failed.
        Please install manually from https://docs.docker.com/desktop/install/windows-install/」
        とメッセージを出して終了する
   ③ docker が見つかったら Docker Desktop を起動する:
        $dockerDesktop = "C:\Program Files\Docker\Docker\Docker Desktop.exe"
        if (Test-Path $dockerDesktop) { Start-Process $dockerDesktop }
      起動後、docker info が成功するまで 5 秒待機 × 最大 24 回(計 2 分)リトライする。
      2 分待っても失敗する場合は「Docker Desktop did not start in time. Please start it manually and re-run.」
      とメッセージを出して終了する
   ① ② ③ を必ず docker build より前に実施すること
5. Secret Manager に以下を登録する。
   登録ロジックは「シークレットが存在しない場合は create、存在する場合は versions add」とする。
   再デプロイ時に .env.local の値が更新されても古い値が使われ続けるのを防ぐため、
   line-channel-access-token・line-channel-secret・gmail-token は常に最新バージョンを追加すること。
   (Cloud Run は "versions/latest" を参照するため、新バージョン追加で自動的に最新に切り替わる)
   - line-channel-access-token(LINE_CHANNEL_ACCESS_TOKEN の値)← 常に新バージョン追加
   - line-channel-secret(LINE_CHANNEL_SECRET の値)← 常に新バージョン追加
   - line-group-id(初期値 "placeholder" で作成、Webhook 経由で後から実際のグループIDに更新される)
     ← シークレット自体が存在しない場合のみ create。既存の場合はスキップ(Webhook が管理するため)
     Secret Manager はペイロード 0 バイトを受け付けないため、空文字ではなく
     "placeholder" など 1 文字以上の値で初期作成すること
   - gmail-token(token.json の内容)← 常に新バージョン追加
   token.json(.\token.json)が存在しない場合はエラーメッセージを出してスクリプトを終了する
   (token.json なしでデプロイしても Secret Manager に gmail-token が登録されず動作しないため)
   実装パターン(line-channel-secret を例に):
     $r = Invoke-Gcloud -Arguments @("secrets","describe","line-channel-secret","--project",$PROJECT_ID) -CaptureOutput
     if ($r.ExitCode -ne 0) {
         Invoke-Gcloud -Arguments @("secrets","create","line-channel-secret","--data-file",$tmpFile,"--project",$PROJECT_ID)
     } else {
         Invoke-Gcloud -Arguments @("secrets","versions","add","line-channel-secret","--data-file",$tmpFile,"--project",$PROJECT_ID)
     }
6. Cloud Run にデプロイする
   - `--allow-unauthenticated` を必ず指定する(LINE Webhook は外部から無認証で呼ばれるため)
   - 環境変数 GCP_PROJECT_ID をセット
   - サービスアカウントの email は `gcloud iam service-accounts list` で検索するのではなく、
     `gcloud projects describe` でプロジェクト番号を取得し
     "${PROJECT_NUMBER}-compute@developer.gserviceaccount.com" の形式で確定的に組み立てる
   - `gcloud projects describe` の出力には末尾に改行や空白が入ることがあるため、
     プロジェクト番号を取得した直後に .Trim() で余分な空白を除去すること
7. Cloud Run のサービスアカウントに Secret Manager の読み取り権限を付与する
   `gcloud projects add-iam-policy-binding` で
   roles/secretmanager.secretAccessor を付与する(既に付与済みでもエラーにならない)
   `--condition=None` を必ず付けること。
   付けないと GCP の新しいバージョンで条件の入力を対話的に求めてくる場合があり、
   スクリプトが止まってしまう。
8. Cloud Scheduler を設定する(週次・月次、Asia/Tokyo、POST メソッド)
   認証オプション(--oidc-service-account-email 等)は指定しないこと。
   Cloud Run に --allow-unauthenticated を設定しているため OIDC は不要であり、
   OIDC を指定するとサービスアカウントへのトークン生成権限が必要になって失敗することがある。
   シンプルに HTTP POST のみ(認証なし)で呼び出す。
9. デプロイ完了後、サービスの URL と LINE Developers で設定する Webhook URL
   (${SERVICE_URL}/webhook)を表示する

### deploy.ps1 実装上の注意
- スクリプト冒頭に `$ErrorActionPreference = "Stop"` を設定し、エラー時に即終了させること
- gcloud・docker などの外部コマンドはすべて PowerShell から直接呼び出す(bash 不要)
- .env.local の読み込みは `Get-Content` で各行をパースして環境変数に展開する
- Secret Manager にバイナリ/テキストを渡す際は、`gcloud secrets create --data-file=` に
  一時ファイルを経由する方法を使うこと(PowerShell のパイプでは文字コードが崩れる場合がある)
  【重要・BOM禁止】一時ファイルへの書き込みには必ず BOM なし UTF-8 を使うこと。
  [System.Text.Encoding]::UTF8 は BOM 付き UTF-8 を出力するため、Secret Manager に登録した
  シークレット先頭に \xEF\xBB\xBF(UTF-8 BOM)が混入し、LINE 署名検証(HMAC)が必ず失敗する。
  正しい書き方(この形式を厳守すること):
    $utf8NoBom = New-Object System.Text.UTF8Encoding($false)
    [System.IO.File]::WriteAllText($tmpFile, $Value, $utf8NoBom)
  [System.Text.Encoding]::UTF8 を WriteAllText の第3引数に渡してはならない。
- **ファイルパスは必ず $PSScriptRoot で絶対パスに解決してから使うこと**
  `[System.IO.File]::ReadAllText(".\token.json")` のように .NET の静的メソッドに相対パスを
  渡してはならない。PowerShell の「現在のディレクトリ」($PWD)と .NET ランタイムの
  「カレントディレクトリ」(System.Environment.CurrentDirectory)は別物であり、
  .NET の相対パスはユーザーのホームディレクトリ(例: C:\Users\username)を起点に解決される。
  その結果、`.\deploy.ps1` をスクリプトのあるディレクトリで実行しても
  `[System.IO.File]::ReadAllText(".\token.json")` は C:\Users\username\token.json を探して
  FileNotFoundException になる。
  token.json を読む箇所は必ず以下のように書くこと:
    $TOKEN_PATH = Join-Path $PSScriptRoot "token.json"
    if (-not (Test-Path $TOKEN_PATH)) { ... exit 1 }
    $tokenContent = [System.IO.File]::ReadAllText($TOKEN_PATH, [System.Text.Encoding]::UTF8)
  同様に、.NET の静的メソッド(File::WriteAllText 等)に渡すパスはすべて
  Join-Path $PSScriptRoot "ファイル名" で絶対パス化すること。
  ただし一時ファイル([System.IO.Path]::GetTempFileName() の戻り値)は既に絶対パスなので不要。
- gcloud のサイレントインストール後の完了確認は Get-Command ではなく Test-Path を使うこと
  Get-Command はキャッシュの影響で新規インストールしたコマンドを見つけられない場合がある
  インストーラーは子プロセスで非同期動作するため Start-Process -Wait だけでは不十分
  "$installBin\gcloud.cmd" を Test-Path で確認し、見つからなければ 5 秒 × 最大 12 回リトライすること
- gcloud auth login / application-default login はブラウザが開くインタラクティブな操作であり、
  subprocess や非表示プロセスとして実行せず、ユーザーが操作できる通常のプロセスとして起動すること
- **全ての gcloud 呼び出しは必ず上記の Invoke-Gcloud ヘルパー関数を通じて行うこと**
  `& gcloud ...` を直接書いてはならない。
  対話的なログイン操作(gcloud auth login / application-default login)だけは
  ヘルパーを使わず直接呼び出す(ブラウザが開く必要があるため)
- **外部コマンドの存在確認は必ず Get-Command で行い、SilentlyContinue ブロックの外で実施すること**
  理由:`$ErrorActionPreference = "SilentlyContinue"` の状態で存在しないコマンドを
  `& commandName ...` で呼び出すと、CommandNotFoundException が無視される。
  加えて $LASTEXITCODE はコマンドが「実行された」場合のみ更新されるため、
  コマンドが見つからなかった場合は直前の成功したコマンドの終了コード(多くの場合 0)が
  残り続け、エラー検出が完全に無効化される。
  docker 呼び出し前後での SilentlyContinue の切り替えは、コマンドが存在すると
  確認できた後の実行時のみ適用すること。
- **deploy.ps1 の Write-Host などすべての文字列は英語(ASCII)のみで記述すること**
  日本語を含む UTF-8 文字列を BOM なし UTF-8 の .ps1 ファイルに書くと、
  PowerShell 5.1 が日本語 Windows の既定エンコード(CP932/Shift-JIS)でファイルを読み込むため、
  UTF-8 マルチバイト列の一部が `}` (0x7D) と同じバイト値になり、
  「ステートメントブロックに終わりの '}' が存在しません」という構文エラーが発生する

## requirements.txt の注意点
- gunicorn を必ず含めること(Dockerfile の CMD で `python -m gunicorn` を使うため)
- requests はバージョンを固定せず `requests>=2.31.0` とすること
  (line-bot-sdk が requests==2.31.0 を要求するため、厳密な固定は競合を起こす)
- auth.py の docstring やコメントに Windows パス(バックスラッシュ区切り)を書かないこと
  (Python が \k \t などを無効エスケープシーケンスとして警告する)
- auth.py に ADC のチェックや自動セットアップ処理は実装しないこと
  (gcloud auth application-default login はインタラクティブな CLI 操作であり、
  subprocess から呼び出すと PATH・シェルの環境依存で失敗するため、deploy.ps1 の Step 0 が担う)
- deploy.sh(bash スクリプト)は作成しないこと。デプロイスクリプトは deploy.ps1(PowerShell)のみ作成する
- app.py の /report・/webhook のエラーハンドラでは `str(exc)` をレスポンスに含めないこと
  (「## セキュリティ対策」セクションの「エラー詳細の隠蔽」を参照)

## ファイル作成後に自動実行すること
全ファイルを作成したら、続けて以下を自動で実行すること:

### Python / pip の確認とインストール
`pip install -r requirements.txt` を実行する前に、以下の順で実行環境を確認・整備すること:

1. `python --version` を実行し、Python 3.11 以上が使えるか確認する
   - 失敗した場合は `python3 --version` を試す
   - どちらも失敗した場合は、以下の方法で Python をインストールする:
     - Windows: `winget install Python.Python.3.11` を実行する
       winget が使えない場合は `https://www.python.org/downloads/` からインストーラーをダウンロードするよう案内する
     - インストール後、新しいシェルセッションで再度 `python --version` を確認する
2. `pip --version` を実行し pip が使えるか確認する
   - 失敗した場合は `python -m pip --version` を試す
   - それも失敗した場合は `python -m ensurepip --upgrade` を実行して pip を有効化する
3. 上記で確認できた python / pip コマンドを使って `pip install -r requirements.txt` を実行する
   (auth.py の実行に必要な依存パッケージを事前にインストールするため)

## 起動手順(ユーザーが手動で実行する手順)
上記が完了した後、ユーザーは以下の順番で実行する:
1. PowerShell を開き、作業ディレクトリに移動する
   初回のみ、スクリプト実行ポリシーを許可する(管理者権限不要):
   `Set-ExecutionPolicy -ExecutionPolicy RemoteSigned -Scope CurrentUser`
2. `python auth.py`    ← Gmail OAuth 認証・token.json 生成(ブラウザが開く)
3. `.\deploy.ps1`      ← gcloud 自動インストール・ログイン・ADC 設定・Cloud Run への全自動デプロイ
                          (gcloud 未インストールの場合は自動インストール後にブラウザが2回開く)

## 注意点
- .env.local・credentials.json・token.json は絶対に外部に公開しないこと
- Dockerfile に credentials.json・.env.local・token.json を含めないこと(.dockerignore で除外)
- LINE_GROUP_ID は起動後に Webhook 経由で自動設定されるため、初期値は空でよい
- Cloud Run のサービス名は kakeibo-bot とする
- Cloud Scheduler のタイムゾーンは Asia/Tokyo を指定すること
ヒント:プログラム生成が完了すると、続けて以下が自動で実行されます。完了するまでそのままお待ちください。
自動実行
pip install -r requirements.txt
5

デプロイ・動作確認

生成されたプログラムをGoogleクラウドに載せて、LINEと接続します。

PowerShell
Set-ExecutionPolicy -ExecutionPolicy RemoteSigned -Scope CurrentUser

ブラウザが自動で開きます。Googleアカウントでログインしてアクセスを許可してください。

PowerShell
python auth.py

Google Cloudへのログインのためブラウザがあと2回開くことがあります。数分かかります。

PowerShell
.\deploy.ps1
  1. デプロイ完了後に表示されるURLをコピー(例)
    Webhook URL の形式
    https://xxxx-xxxxxxxxxx-an.a.run.app/webhook
  2. LINE Developers → チャンネル →「Messaging API」設定タブ
  3. Webhook URL に貼り付け →「検証」ボタンで疎通確認 →「Webhook の利用」を有効化
  1. 家族LINEに通知したい場合は、グループに Bot を招待する
  2. Bot(またはBotを招待したグループ)にLINEメッセージを1通送る
ヒント:メッセージを送ることで、システムがグループIDを自動で認識します。以降、自動でこのトークルームに通知が届くようになります。

約1分後にLINEに通知が届けば完成です。

PowerShell
gcloud scheduler jobs run kakeibo-weekly --location=asia-northeast1
完了:LINEに通知が届いたら構築完了です。毎週日曜20時・毎月1日9時に自動でレポートが届きます。
カスタマイズについて
ヒント:このページのプロンプトをベースに、カード会社のメール形式に合わせてカスタマイズしてみてください。エラーが出たらメッセージをそのまま Claude Code に伝えれば大丈夫です。