ops-marketing について
ops-marketingは、メール、広告、分析、SEO、ソーシャルのマーケティング状況を確認し、キャンペーンを管理するClaudeスキルです。チャネル別のレポートを提供し、依頼を対応するダッシュボードや管理ワークフローに振り分けます。
主な機能
- 01Klaviyoのメール指標
- 02Meta Adsの実績確認と管理
- 03Google Adsのダッシュボードとキャンペーン
- 04GA4分析とアトリビューション
- 05Search Consoleとソーシャルのレポート
活用シーン
- 01購読者数、メールキャンペーン、フローの確認
- 02広告費とキャンペーン実績の調査
- 03サイトのセッション、コンバージョン、検索指標の分析
- 04チャネル別のマーケティング業務の管理
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ davepoon/ buildwithclaude --skill ops-marketingインストール後、Claude Code で/ops-marketingを入力して読み込まれたか確認します。
詳細
OPS ► マーケティングコマンドセンター
ランタイムコンテキスト
実行前に、利用可能なコンテキストを読み込みます。
-
設定:
${CLAUDE_PLUGIN_DATA_DIR:-$HOME/.claude/plugins/data/ops-ops-marketplace}/preferences.jsonを読み込みますtimezone— すべてのタイムスタンプを正しく表示しますklaviyo_private_key、meta_ads_token、meta_ad_account_id、ga4_property_id、google_search_console_site— 環境変数より先に userConfig のキーを確認しますgoogle_ads_developer_token、google_ads_client_id、google_ads_client_secret、google_ads_refresh_token、google_ads_customer_id、google_ads_login_customer_id— Google Ads の認証情報
-
デーモンの状態:
${CLAUDE_PLUGIN_DATA_DIR}/daemon-health.jsonを読み込みますaction_neededが null でない場合 → チャネルのクエリを実行する前に、その内容を通知します
-
シークレット:userConfig → 環境変数 → Doppler MCP(
mcp__doppler__*)→ Doppler CLI のフォールバックの順に API キーを解決します(以下の「認証情報の解決」セクションを参照)
CLI/API リファレンス
Klaviyo REST API
| エンドポイント | メソッド | 説明 |
|---|---|---|
https://a.klaviyo.com/api/lists/?fields[list]=name,id,profile_count | GET | すべてのリストと登録者数 |
https://a.klaviyo.com/api/campaigns/?filter=equals(messages.channel,'email')&sort=-created_at | GET | 最近のキャンペーン |
https://a.klaviyo.com/api/flows/?filter=equals(status,'live') | GET | アクティブなフロー |
https://a.klaviyo.com/api/metrics/ | GET | 利用可能なメトリクス |
認証ヘッダー:Authorization: Klaviyo-API-Key ${KLAVIYO_KEY} | リビジョンヘッダー:revision: 2024-10-15
Meta Graph API
| エンドポイント | メソッド | 説明 |
|---|---|---|
https://graph.facebook.com/v18.0/${META_ACCOUNT}/insights?fields=spend,...&date_preset=last_7d | GET | アカウントレベルの広告費 |
https://graph.facebook.com/v18.0/${META_ACCOUNT}/campaigns?fields=name,status,insights{...} | GET | キャンペーンの内訳 |
https://graph.facebook.com/v18.0/me/accounts?fields=instagram_business_account | GET | リンク済みの Instagram アカウント |
認証ヘッダー:Authorization: Bearer ${META_TOKEN}
Google Analytics 4(Data API)
| エンドポイント | メソッド | 説明 |
|---|---|---|
https://analyticsdata.googleapis.com/v1beta/properties/${GA4_PROPERTY}:runReport | POST | カスタムレポートを実行 |
認証:gcloud ADC — GA4_TOKEN=$(gcloud auth application-default print-access-token)
Google Search Console
| エンドポイント | メソッド | 説明 |
|---|---|---|
https://searchconsole.googleapis.com/webmasters/v3/sites/${GSC_SITE_ENCODED}/searchAnalytics/query | POST | 検索パフォーマンスデータ |
認証:GA4 と同じ gcloud ADC トークン
Agent Teams のサポート
CLAUDE_CODE_EXPERIMENTAL_AGENT_TEAMS=1 が設定されている場合、チャネルデータを並行して収集する際に Agent Teams を使用します。これにより、次のことが可能になります。
- エージェントがコンテキストを共有し、処理の途中で連携できます
- 優先順位をリアルタイムで調整できます
- エージェントは完了次第、進捗を報告します
チームのセットアップ(フラグが有効な場合のみ):
TeamCreate("marketing-team")
Agent(team_name="marketing-team", name="email-metrics", prompt="Pull Klaviyo subscriber counts, campaign stats, and flow metrics")
Agent(team_name="marketing-team", name="ads-metrics", prompt="Pull Meta Ads spend, ROAS, and campaign breakdown")
Agent(team_name="marketing-team", name="analytics-metrics", prompt="Pull GA4 sessions, conversions, and traffic sources")
Agent(team_name="marketing-team", name="seo-metrics", prompt="Pull Search Console clicks, impressions, and top queries")
フラグが設定されていない場合は、標準のファイア・アンド・フォーゲット方式のサブエージェントを使用します。
認証情報の解決
各サービスの認証情報は、次の順序で解決します。
Klaviyo
KLAVIYO_KEY="${KLAVIYO_PRIVATE_KEY:-$(claude plugin config get klaviyo_private_key 2>/dev/null)}"
if [ -z "$KLAVIYO_KEY" ]; then
KLAVIYO_KEY="$(doppler secrets get KLAVIYO_PRIVATE_KEY --plain 2>/dev/null)"
fi
Meta Ads
META_TOKEN="${META_ADS_TOKEN:-$(claude plugin config get meta_ads_token 2>/dev/null)}"
META_ACCOUNT="${META_AD_ACCOUNT_ID:-$(claude plugin config get meta_ad_account_id 2>/dev/null)}"
if [ -z "$META_TOKEN" ]; then
META_TOKEN="$(doppler secrets get META_ADS_TOKEN --plain 2>/dev/null)"
fi
GA4
GA4_PROPERTY="${GA4_PROPERTY_ID:-$(claude plugin config get ga4_property_id 2>/dev/null)}"
# GA4 uses gcloud application default credentials — check if configured:
gcloud auth application-default print-access-token 2>/dev/null
Google Search Console
GSC_SITE="${GOOGLE_SEARCH_CONSOLE_SITE:-$(claude plugin config get google_search_console_site 2>/dev/null)}"
# Uses same gcloud ADC as GA4
Google Ads
GADS_API_VERSION="v23"
GADS_DEV_TOKEN="${GOOGLE_ADS_DEVELOPER_TOKEN:-$(claude plugin config get google_ads_developer_token 2>/dev/null)}"
GADS_CLIENT_ID="${GOOGLE_ADS_CLIENT_ID:-$(claude plugin config get google_ads_client_id 2>/dev/null)}"
GADS_CLIENT_SECRET="${GOOGLE_ADS_CLIENT_SECRET:-$(claude plugin config get google_ads_client_secret 2>/dev/null)}"
GADS_REFRESH_TOKEN="${GOOGLE_ADS_REFRESH_TOKEN:-$(claude plugin config get google_ads_refresh_token 2>/dev/null)}"
GADS_CUSTOMER_ID="${GOOGLE_ADS_CUSTOMER_ID:-$(claude plugin config get google_ads_customer_id 2>/dev/null)}"
GADS_LOGIN_CUSTOMER_ID="${GOOGLE_ADS_LOGIN_CUSTOMER_ID:-$(claude plugin config get google_ads_login_customer_id 2>/dev/null)}"
# Doppler fallback
if [ -z "$GADS_REFRESH_TOKEN" ]; then
GADS_REFRESH_TOKEN="$(doppler secrets get GOOGLE_ADS_REFRESH_TOKEN --plain 2>/dev/null)"
fi
if [ -z "$GADS_DEV_TOKEN" ]; then
GADS_DEV_TOKEN="$(doppler secrets get GOOGLE_ADS_DEVELOPER_TOKEN --plain 2>/dev/null)"
fi
# Strip dashes from customer ID (API requires no dashes)
GADS_CUSTOMER_ID="${GADS_CUSTOMER_ID//-/}"
# Refresh access token (expires in ~1 hour — always refresh before API calls)
GADS_ACCESS_TOKEN=$(curl -s -X POST https://oauth2.googleapis.com/token \
--data "client_id=${GADS_CLIENT_ID}" \
--data "client_secret=${GADS_CLIENT_SECRET}" \
--data "refresh_token=${GADS_REFRESH_TOKEN}" \
--data "grant_type=refresh_token" | jq -r '.access_token')
# Common headers for all Google Ads API calls
GADS_HEADERS=(-H "Content-Type: application/json" -H "Authorization: Bearer ${GADS_ACCESS_TOKEN}" -H "developer-token: ${GADS_DEV_TOKEN}")
if [ -n "$GADS_LOGIN_CUSTOMER_ID" ]; then
GADS_HEADERS+=(-H "login-customer-id: ${GADS_LOGIN_CUSTOMER_ID}")
fi
サブコマンドの振り分け
$ARGUMENTS を以下の適切なセクションに振り分けます。
| 入力 | アクション |
|---|---|
| (空)、dashboard | マーケティングダッシュボード全体を実行 |
| email、klaviyo | Klaviyo のメール指標 |
| ads、meta | Meta Ads のパフォーマンス(読み取り専用の概要) |
| meta-manage、meta create-campaign、meta target、meta creative、meta rules、meta audiences、meta advantage | Meta Ads のキャンペーン管理(## meta-manage セクションを参照) |
| google-ads、gads | Google Ads のダッシュボードとキャンペーン管理(## google-ads セクションを参照) |
| analytics、ga4 | GA4 のセッションとコンバージョン |
| ga4 realtime、ga4 funnel、ga4 cohort、ga4 audience、ga4 pivot | GA4 の高度な分析(## ga4-advanced セクションを参照) |
| seo、gsc | Search Console の指標 |
| social | ソーシャルメディアの集計 |
| instagram、instagram post、instagram reel、instagram story、instagram insights、instagram demographics | Instagram の投稿とインサイト(## instagram セクションを参照) |
| campaigns | クロスチャネルのキャンペーン概要(全プラットフォーム) |
| optimize | クロスプラットフォーム広告最適化エージェント |
| attribution | 統合アトリビューションテーブル(Meta + Google + Klaviyo + GA4) |
| setup | API キーを設定 |
email / klaviyo
過去30日間の Klaviyo 指標を取得します。
登録者数
curl -s "https://a.klaviyo.com/api/lists/?fields[list]=name,id,profile_count" \
-H "Authorization: Klaviyo-API-Key ${KLAVIYO_KEY}" \
-H "revision: 2024-10-15" | jq '.data[] | {name: .attributes.name, id: .id, count: .attributes.profile_count}'
最近のキャンペーン(直近10件)
curl -s "https://a.klaviyo.com/api/campaigns/?filter=equals(messages.channel,'email')&sort=-created_at&page[size]=10&fields[campaign]=name,status,created_at,send_time" \
-H "Authorization: Klaviyo-API-Key ${KLAVIYO_KEY}" \
-H "revision: 2024-10-15" | jq '.data[] | {name: .attributes.name, status: .attributes.status, sent: .attributes.send_time}'
フローメトリクス(有効なフロー)
curl -s "https://a.klaviyo.com/api/flows/?filter=equals(status,'live')&fields[flow]=name,status,created,trigger_type" \
-H "Authorization: Klaviyo-API-Key ${KLAVIYO_KEY}" \
-H "revision: 2024-10-15" | jq '.data[] | {name: .attributes.name, trigger: .attributes.trigger_type}'
メールの主要指標(開封、クリック、収益。指標集計を使用)
# Get metric IDs first
curl -s "https://a.klaviyo.com/api/metrics/" \
-H "Authorization: Klaviyo-API-Key ${KLAVIYO_KEY}" \
-H "revision: 2024-10-15" | jq '.data[] | select(.attributes.name | test("Opened Email|Clicked Email|Placed Order")) | {name: .attributes.name, id: .id}'
出力形式
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
EMAIL (KLAVIYO) — last 30d
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
Lists: [list_name] — [N] subscribers
Campaigns: [N sent] | [N drafts]
Active Flows: [N]
RECENT CAMPAIGNS
[name] [status] sent [date]
...
ads / meta
設定済みの広告アカウントの Meta Ads インサイトを取得します。
アカウントレベルの広告費(過去7日間)
curl -s "https://graph.facebook.com/v18.0/${META_ACCOUNT}/insights?fields=spend,impressions,clicks,ctr,cpc,actions,action_values&date_preset=last_7d&level=account" \
-H "Authorization: Bearer ${META_TOKEN}" | jq '{spend: .data[0].spend, impressions: .data[0].impressions, clicks: .data[0].clicks, ctr: .data[0].ctr, cpc: .data[0].cpc}'
キャンペーンの内訳(過去7日間)
curl -s "https://graph.facebook.com/v18.0/${META_ACCOUNT}/campaigns?fields=name,status,daily_budget,lifetime_budget,insights{spend,impressions,clicks,actions,action_values}&date_preset=last_7d" \
-H "Authorization: Bearer ${META_TOKEN}" | jq '.data[] | {name: .name, status: .status, spend: .insights.data[0].spend}'
ROAS の計算
action_values 配列から、action_type == "purchase" の値を抽出し、広告費で割ります。
パフォーマンス上位の広告(過去7日間)
curl -s "https://graph.facebook.com/v18.0/${META_ACCOUNT}/ads?fields=name,adset_id,insights{spend,impressions,clicks,actions,action_values,ctr,cpc}&date_preset=last_7d&limit=10" \
-H "Authorization: Bearer ${META_TOKEN}" | jq '.data | sort_by(.insights.data[0].spend | tonumber) | reverse | .[0:5] | .[] | {name: .name, spend: .insights.data[0].spend, ctr: .insights.data[0].ctr}'
出力形式
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
META ADS — last 7d
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
Spend: $[X]
ROAS: [X]x
Purchases: [N] ($[X] revenue)
Impressions: [N] CTR: [X]%
CPC: $[X]
CAMPAIGNS
[name] [status] $[spend] [roas]x ROAS
...
TOP ADS (by spend)
[name] $[spend] [ctr]% CTR
meta-manage
Meta Ads のキャンペーン管理全般を行います。読み取り専用の ads セクションと同じ META_TOKEN および META_ACCOUNT の認証情報を使用します。
認証情報の確認:META_TOKEN が空の場合は、Meta Ads not configured. Run /ops:marketing setup. と出力して停止します。
meta-manage 内で $ARGUMENTS を振り分けます。
| 入力 | アクション |
|---|---|
| create-campaign | 新しいキャンペーンを作成(常に一時停止状態) |
| target <ADSET_ID> | 広告セットのターゲティングを設定 |
| creative <CAMPAIGN_ID> | 画像をアップロードし、広告文を添えて広告を作成 |
| rules | 自動化ルールを一覧表示/作成 |
| audiences | カスタムオーディエンスまたは類似オーディエンスを作成 |
| advantage | Advantage+ AI 最適化キャンペーンを作成 |
create-campaign
AskUserQuestion を使用して以下を収集します(1回の呼び出しにつき選択肢は最大4つ)。
- キャンペーンの目的 —
[OUTCOME_TRAFFIC, OUTCOME_SALES, OUTCOME_LEADS, OUTCOME_AWARENESS] - 1日あたりの予算(ドル)(自由入力)
- キャンペーン名(自由入力)
次に、AskUserQuestion で確認します:"Create Meta campaign '<NAME>' with $<BUDGET>/day budget?" 選択肢は [Create, Cancel]
BUDGET_CENTS=$(awk "BEGIN {printf \"%d\", ${BUDGET_DOLLARS} * 100}")
curl -s -X POST "https://graph.facebook.com/v20.0/${META_ACCOUNT}/campaigns" \
-H "Authorization: Bearer ${META_TOKEN}" \
-F "name=${CAMPAIGN_NAME}" \
-F "objective=${OBJECTIVE}" \
-F "status=PAUSED" \
-F "special_ad_categories=[]" \
-F "daily_budget=${BUDGET_CENTS}" | jq '{id: .id, error: .error.message}'
次のように出力します:Campaign "${CAMPAIGN_NAME}" created (ID: <ID>, status: PAUSED, budget: $<BUDGET>/day). Enable via Meta Ads Manager or add ad sets first.
エラーが発生した場合は、エラーメッセージを出力します。
target <ADSET_ID>
既存の広告セットのターゲティングを設定します。AskUserQuestion を使用して以下を収集します。
- ターゲットとする国(カンマ区切りの ISO コード。例:
US,CA,GB)— 自由入力 - 年齢範囲:
[18-34, 25-54, 35-65, 18-65] - 性別:
[All, Men only, Women only, Skip]
# Build geo_locations JSON
GEO_JSON=$(echo "$COUNTRIES" | tr ',' '\n' | jq -Rc '.' | jq -sc '{"countries": .}')
# Build targeting spec
TARGETING_JSON=$(jq -n \
--argjson geo "$GEO_JSON" \
--arg age_min "$AGE_MIN" \
--arg age_max "$AGE_MAX" \
'{
geo_locations: $geo,
age_min: ($age_min | tonumber),
age_max: ($age_max | tonumber)
}')
# Add gender filter if requested
if [ "$GENDER" = "Men only" ]; then
TARGETING_JSON=$(echo "$TARGETING_JSON" | jq '. + {"genders": [1]}')
elif [ "$GENDER" = "Women only" ]; then
TARGETING_JSON=$(echo "$TARGETING_JSON" | jq '. + {"genders": [2]}')
fi
curl -s -X POST "https://graph.facebook.com/v20.0/${ADSET_ID}" \
-H "Authorization: Bearer ${META_TOKEN}" \
-F "targeting=${TARGETING_JSON}" | jq '{success: .success, error: .error.message}'
次のように出力します:Ad set ${ADSET_ID} targeting updated: ${COUNTRIES}, ages ${AGE_MIN}-${AGE_MAX}${GENDER_LABEL}.
creative <CAMPAIGN_ID>
画像をアップロードして広告を作成します。AskUserQuestion を使用して以下を収集します。
- 画像のファイルパスまたは URL(自由入力)
- 広告を追加する広告セット ID(自由入力)
- メインテキスト(広告文。自由入力。125文字以内を推奨)
次に、2回目の AskUserQuestion で見出しを収集します(自由入力、40文字以内)。
# Step 1: Upload image
if [[ "$IMAGE_INPUT" == http* ]]; then
# Upload by URL
UPLOAD_RESP=$(curl -s -X POST "https://graph.facebook.com/v20.0/${META_ACCOUNT}/adimages" \
-H "Authorization: Bearer ${META_TOKEN}" \
-F "url=${IMAGE_INPUT}")
else
# Upload by file (multipart)
UPLOAD_RESP=$(curl -s -X POST "https://graph.facebook.com/v20.0/${META_ACCOUNT}/adimages" \
-H "Authorization: Bearer ${META_TOKEN}" \
-F "filename=@${IMAGE_INPUT}")
fi
IMAGE_HASH=$(echo "$UPLOAD_RESP" | jq -r '.images | to_entries[0].value.hash // empty')
if [ -z "$IMAGE_HASH" ]; then
echo "Image upload failed: $(echo "$UPLOAD_RESP" | jq -r '.error.message // "unknown error"')"
exit 0
fi
# Resolve the Facebook Page ID. Meta's `object_story_spec.page_id` requires a
# real Page ID — the ad account ID (with `act_` stripped) is NOT a Page ID and
# the API call will fail. Require META_PAGE_ID in env or plugin prefs.
META_PAGE_ID="${META_PAGE_ID:-$(claude plugin config get meta_page_id 2>/dev/null || echo "")}"
if [ -z "$META_PAGE_ID" ]; then
echo "META_PAGE_ID is required to create an ad creative. Set it via:"
echo " claude plugin config set meta_page_id <your_fb_page_id>"
echo "Find your Page ID at https://www.facebook.com/<your-page>/about_profile_transparency"
exit 0
fi
# Step 2: Create ad creative
CREATIVE_RESP=$(curl -s -X POST "https://graph.facebook.com/v20.0/${META_ACCOUNT}/adcreatives" \
-H "Authorization: Bearer ${META_TOKEN}" \
-F "name=Creative for ${AD_NAME}" \
-F "object_story_spec={\"page_id\": \"${META_PAGE_ID}\", \"link_data\": {\"image_hash\": \"${IMAGE_HASH}\", \"message\": \"${PRIMARY_TEXT}\", \"name\": \"${HEADLINE}\"}}")
CREATIVE_ID=$(echo "$CREATIVE_RESP" | jq -r '.id // empty')
if [ -z "$CREATIVE_ID" ]; then
echo "Creative creation failed: $(echo "$CREATIVE_RESP" | jq -r '.error.message // "unknown error"')"
exit 0
fi
# Step 3: Create ad (status PAUSED — Rule 5)
curl -s -X POST "https://graph.facebook.com/v20.0/${META_ACCOUNT}/ads" \
-H "Authorization: Bearer ${META_TOKEN}" \
-F "name=${AD_NAME}" \
-F "adset_id=${ADSET_ID}" \
-F "creative={\"creative_id\": \"${CREATIVE_ID}\"}" \
-F "status=PAUSED" | jq '{id: .id, error: .error.message}'
次のように出力します:Ad created (ID: <ID>, creative: <CREATIVE_ID>, status: PAUSED). Enable via Meta Ads Manager when ready.
ルール
既存のルールを一覧表示するか、新しい自動化ルールを作成します。
ルールを一覧表示:
curl -s "https://graph.facebook.com/v20.0/${META_ACCOUNT}/adrules_library?fields=name,status,evaluation_spec,execution_spec" \
-H "Authorization: Bearer ${META_TOKEN}" | jq '.data[] | {id: .id, name: .name, status: .status}'
ルールを作成(AskUserQuestion でプロンプトを表示):
- ルールタイプ:
[低パフォーマンスを一時停止, 勝ちパターンを拡大, 予算を増額, 予算を減額]
「低パフォーマンスを一時停止」の場合:
# Pause ads where CPA > $50 and spend > $20 in last 7 days
curl -s -X POST "https://graph.facebook.com/v20.0/${META_ACCOUNT}/adrules_library" \
-H "Authorization: Bearer ${META_TOKEN}" \
-H "Content-Type: application/json" \
-d '{
"name": "Pause high CPA ads",
"schedule_spec": {"schedule_type": "SEMI_HOURLY"},
"evaluation_spec": {
"evaluation_type": "SCHEDULE",
"filters": [
{"field": "cost_per_result", "value": [50], "operator": "GREATER_THAN"},
{"field": "spent", "value": [20], "operator": "GREATER_THAN"},
{"field": "entity_type", "value": ["AD"], "operator": "EQUAL"},
{"field": "time_preset", "value": ["LAST_7_DAYS"], "operator": "EQUAL"}
]
},
"execution_spec": {
"execution_type": "PAUSE"
},
"status": "ENABLED"
}' | jq '{id: .id, error: .error.message}'
「勝ちパターンを拡大」の場合:
⚠️ 対象をプロスペクティング広告セットに限定してください。 単純な
purchase_roas > 3フィルターでは、リターゲティング広告セットも自動的にスケールされます。リターゲティングの ROAS は、増分成長ではなく、ウォームオーディエンスの需要獲得(いずれにせよ発生していたコンバージョン)によって水増しされています。無条件にスケールすると、すでに獲得済みの需要に予算を注ぎ込み、プロスペクティングを圧迫し、1 か月後にはファネルが縮小します。リターゲティング/リマーケティングを除外する広告セット名/オーディエンスフィルターを追加するか、ルールをプロスペクティング広告セットに限定し、ROAS だけを根拠にスケールする前に、ホールドアウトで成果の向上を確認してください。
# Increase budget 20% for ad sets with ROAS > 3x in last 7 days
# NOTE: restrict to prospecting ad sets — see caveat above
curl -s -X POST "https://graph.facebook.com/v20.0/${META_ACCOUNT}/adrules_library" \
-H "Authorization: Bearer ${META_TOKEN}" \
-H "Content-Type: application/json" \
-d '{
"name": "Scale winning ad sets",
"schedule_spec": {"schedule_type": "DAILY"},
"evaluation_spec": {
"evaluation_type": "SCHEDULE",
"filters": [
{"field": "purchase_roas", "value": [3], "operator": "GREATER_THAN"},
{"field": "entity_type", "value": ["ADSET"], "operator": "EQUAL"},
{"field": "time_preset", "value": ["LAST_7_DAYS"], "operator": "EQUAL"}
]
},
"execution_spec": {
"execution_type": "INCREASE_BUDGET",
"execution_options": [{"field": "budget_value", "value": "20", "operator": "PERCENTAGE"}]
},
"status": "ENABLED"
}' | jq '{id: .id, error: .error.message}'
出力:ルールを作成しました(ID: <ID>)。30 分ごとに実行され、CPA が $50 を超える広告を自動的に一時停止します。
オーディエンス
カスタムオーディエンスまたは類似オーディエンスを作成します。
AskUserQuestion でプロンプトを表示:
- オーディエンスタイプ:
[カスタム — ウェブサイト, カスタム — 顧客リスト, 類似, スキップ]
カスタム — ウェブサイト(ピクセルベース):
curl -s -X POST "https://graph.facebook.com/v20.0/${META_ACCOUNT}/customaudiences" \
-H "Authorization: Bearer ${META_TOKEN}" \
-H "Content-Type: application/json" \
-d '{
"name": "Website visitors — last 30 days",
"subtype": "WEBSITE",
"retention_days": 30,
"rule": {"inclusions": {"operator": "or", "rules": [{"event_sources": [{"id": "<PIXEL_ID>", "type": "pixel"}], "retention_seconds": 2592000, "filter": {"operator": "and", "filters": [{"field": "event", "operator": "eq", "value": "PageView"}]}}]}}
}' | jq '{id: .id, name: .name, error: .error.message}'
注:<PIXEL_ID> を Meta Events Manager の実際のピクセル ID に置き換えてください。
類似オーディエンス(一致するプロフィールが 100 件以上の元オーディエンスが必要です):
# Prompt for origin audience ID via AskUserQuestion (free text)
curl -s -X POST "https://graph.facebook.com/v20.0/${META_ACCOUNT}/customaudiences" \
-H "Authorization: Bearer ${META_TOKEN}" \
-H "Content-Type: application/json" \
-d "{
\"name\": \"Lookalike — ${ORIGIN_AUDIENCE_NAME} 1%\",
\"subtype\": \"LOOKALIKE\",
\"origin_audience_id\": \"${ORIGIN_AUDIENCE_ID}\",
\"lookalike_spec\": {
\"country\": \"US\",
\"ratio\": 0.01,
\"type\": \"similarity\"
}
}" | jq '{id: .id, name: .name, error: .error.message}'
出力:類似オーディエンスを作成しました(ID: <ID>)。反映には通常 1〜6 時間かかります。
Advantage
Advantage+ ショッピングキャンペーン(AI 最適化)を作成します。
AskUserQuestion で以下を入力します:
- 1 日あたりの予算(ドル、自由入力)
- キャンペーン名(自由入力)
次に確認します:「Advantage+ キャンペーン '<NAME>' を $<BUDGET>/日で作成しますか?」 選択肢 [作成, キャンセル]
BUDGET_CENTS=$(awk "BEGIN {printf \"%d\", ${BUDGET_DOLLARS} * 100}")
curl -s -X POST "https://graph.facebook.com/v20.0/${META_ACCOUNT}/campaigns" \
-H "Authorization: Bearer ${META_TOKEN}" \
-H "Content-Type: application/json" \
-d "{
\"name\": \"${CAMPAIGN_NAME}\",
\"objective\": \"OUTCOME_SALES\",
\"status\": \"PAUSED\",
\"special_ad_categories\": [],
\"daily_budget\": ${BUDGET_CENTS},
\"smart_promotion_type\": \"AUTOMATED_SHOPPING_ADS\"
}" | jq '{id: .id, error: .error.message}'
出力:Advantage+ キャンペーン "${CAMPAIGN_NAME}" を作成しました(ID: <ID>、ステータス: 一時停止)。有効にすると、Meta AI がターゲティングとクリエイティブ配信を最適化します。
analytics / ga4
gcloud ADC を使用して Data API 経由で GA4 データを取得します。
アクセストークンを取得
GA4_TOKEN=$(gcloud auth application-default print-access-token 2>/dev/null)
セッション数 + コンバージョン数(過去 7 日間)
curl -s -X POST "https://analyticsdata.googleapis.com/v1beta/properties/${GA4_PROPERTY}:runReport" \
-H "Authorization: Bearer ${GA4_TOKEN}" \
-H "Content-Type: application/json" \
-d '{
"dateRanges": [{"startDate": "7daysAgo", "endDate": "today"}],
"metrics": [
{"name": "sessions"},
{"name": "totalUsers"},
{"name": "conversions"},
{"name": "totalRevenue"},
{"name": "bounceRate"},
{"name": "averageSessionDuration"}
]
}' | jq '.rows[0].metricValues | {sessions: .[0].value, users: .[1].value, conversions: .[2].value, revenue: .[3].value, bounce_rate: .[4].value}'
トラフィックソース(過去 7 日間)
curl -s -X POST "https://analyticsdata.googleapis.com/v1beta/properties/${GA4_PROPERTY}:runReport" \
-H "Authorization: Bearer ${GA4_TOKEN}" \
-H "Content-Type: application/json" \
-d '{
"dateRanges": [{"startDate": "7daysAgo", "endDate": "today"}],
"dimensions": [{"name": "sessionDefaultChannelGrouping"}],
"metrics": [{"name": "sessions"}, {"name": "conversions"}],
"orderBys": [{"metric": {"metricName": "sessions"}, "desc": true}],
"limit": 8
}' | jq '.rows[] | {channel: .dimensionValues[0].value, sessions: .metricValues[0].value, conversions: .metricValues[1].value}'
上位ページ(過去 7 日間)
curl -s -X POST "https://analyticsdata.googleapis.com/v1beta/properties/${GA4_PROPERTY}:runReport" \
-H "Authorization: Bearer ${GA4_TOKEN}" \
-H "Content-Type: application/json" \
-d '{
"dateRanges": [{"startDate": "7daysAgo", "endDate": "today"}],
"dimensions": [{"name": "pagePath"}],
"metrics": [{"name": "screenPageViews"}, {"name": "averageSessionDuration"}],
"orderBys": [{"metric": {"metricName": "screenPageViews"}, "desc": true}],
"limit": 10
}' | jq '.rows[] | {page: .dimensionValues[0].value, views: .metricValues[0].value}'
GA4_TOKEN が空、または gcloud が利用できない場合は、GA4 を設定していません — /ops:marketing setup を実行するか、gcloud ADC を設定してください と出力します。
出力形式
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
ANALYTICS (GA4) — last 7d
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
Sessions: [N] Users: [N]
Conversions: [N] CVR: [X]%
Revenue: $[X]
Bounce Rate: [X]% Avg Session: [Xm Xs]
TRAFFIC SOURCES
[channel] [N sessions] [N conversions]
...
TOP PAGES
[path] [N views]
ga4-advanced
高度な GA4 分析:リアルタイム、ファネル、コホート、オーディエンスエクスポート、ピボットレポート。
認証情報の確認:GA4_TOKEN が空、または GA4_PROPERTY が設定されていない場合は、GA4 を設定していません — /ops:marketing setup を実行するか、gcloud ADC を設定してください と出力して終了します。
ga4-advanced 内で $ARGUMENTS をルーティングします(ga4 <sub> パターンに一致):
| 入力 | アクション |
|---|---|
| realtime | 現在のアクティブユーザー数(過去 30 分間) |
| funnel | ステップを視覚化したコンバージョンファネル |
| cohort | デバイス別のコホート維持分析 |
| audience | 非同期オーディエンスセグメントエクスポート |
| pivot | 多次元ピボットレポート |
realtime
GA4_TOKEN=$(gcloud auth application-default print-access-token 2>/dev/null)
RESULT=$(curl -s -X POST "https://analyticsdata.googleapis.com/v1beta/properties/${GA4_PROPERTY}:runRealtimeReport" \
-H "Authorization: Bearer ${GA4_TOKEN}" \
-H "Content-Type: application/json" \
-d '{
"minuteRanges": [{"startMinutesAgo": 29, "endMinutesAgo": 0}],
"dimensions": [
{"name": "unifiedScreenName"},
{"name": "deviceCategory"}
],
"metrics": [{"name": "activeUsers"}],
"orderBys": [{"metric": {"metricName": "activeUsers"}, "desc": true}],
"limit": 10
}')
TOTAL=$(echo "$RESULT" | jq '[.rows[]?.metricValues[0].value | tonumber] | add // 0')
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
echo " GA4 REALTIME — Last 30 Minutes"
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
echo ""
echo "Active Users Right Now: ${TOTAL}"
echo ""
echo "Top Pages:"
printf "| %-40s | %-8s | %-7s |\n" "Page" "Device" "Users"
printf "|%s|%s|%s|\n" "------------------------------------------" "----------" "---------"
echo "$RESULT" | jq -r '.rows[]? | [.dimensionValues[0].value, .dimensionValues[1].value, .metricValues[0].value] | @tsv' 2>/dev/null | \
while IFS=$'\t' read -r page device users; do
printf "| %-40s | %-8s | %-7s |\n" "${page:0:40}" "$device" "$users"
done
funnel
AskUserQuestion でファネルのステップをユーザーに尋ねます(自由入力)。デフォルトのテンプレートは session_start → page_view → purchase を使用します。
# NOTE: Uses v1alpha — breaking changes possible per Google's versioning policy
GA4_TOKEN=$(gcloud auth application-default print-access-token 2>/dev/null)
# Prompt for funnel type first
# AskUserQuestion: "Funnel mode?" options [Closed funnel, Open funnel]
IS_OPEN=$([ "$FUNNEL_MODE" = "Open funnel" ] && echo "true" || echo "false")
RESULT=$(curl -s -X POST "https://analyticsdata.googleapis.com/v1alpha/properties/${GA4_PROPERTY}:runFunnelReport" \
-H "Authorization: Bearer ${GA4_TOKEN}" \
-H "Content-Type: application/json" \
-d "{
\"dateRanges\": [{\"startDate\": \"30daysAgo\", \"endDate\": \"today\"}],
\"funnel\": {
\"isOpenFunnel\": ${IS_OPEN},
\"steps\": [
{
\"name\": \"Session Start\",
\"filterExpression\": {\"funnelEventFilter\": {\"eventName\": \"session_start\"}}
},
{
\"name\": \"Page View\",
\"filterExpression\": {\"funnelEventFilter\": {\"eventName\": \"page_view\"}}
},
{
\"name\": \"Purchase\",
\"filterExpression\": {\"funnelEventFilter\": {\"eventName\": \"purchase\"}}
}
]
},
\"funnelBreakdown\": {
\"breakdownDimension\": {\"name\": \"deviceCategory\"},
\"limit\": 4
}
}")
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
echo " GA4 FUNNEL — Last 30 Days (${FUNNEL_MODE})"
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
echo ""
echo "$RESULT" | jq -r '
.funnelTable.rows[]? |
"Step: \(.dimensionValues[0].value) Users: \(.metricValues[0].value) Completion: \(.metricValues[1].value)% Abandoned: \(.metricValues[2].value)"
' 2>/dev/null || echo "No funnel data — ensure purchase events are firing in GA4."
cohort
過去 1 か月に獲得したユーザーを対象に、デバイス別の週次コホート維持率を表示します。
GA4_TOKEN=$(gcloud auth application-default print-access-token 2>/dev/null)
START_DATE=$(date -v-30d +%Y-%m-%d 2>/dev/null || date -d '30 days ago' +%Y-%m-%d)
END_DATE=$(date +%Y-%m-%d)
RESULT=$(curl -s -X POST "https://analyticsdata.googleapis.com/v1beta/properties/${GA4_PROPERTY}:runReport" \
-H "Authorization: Bearer ${GA4_TOKEN}" \
-H "Content-Type: application/json" \
-d "{
\"dimensions\": [
{\"name\": \"cohort\"},
{\"name\": \"cohortNthWeek\"},
{\"name\": \"deviceCategory\"}
],
\"metrics\": [
{\"name\": \"cohortActiveUsers\"},
{\"name\": \"cohortRetentionFraction\"}
],
\"cohortSpec\": {
\"cohorts\": [{
\"dimension\": \"firstSessionDate\",
\"dateRange\": {\"startDate\": \"${START_DATE}\", \"endDate\": \"${END_DATE}\"}
}],
\"cohortsRange\": {
\"granularity\": \"WEEKLY\",
\"startOffset\": 0,
\"endOffset\": 5
}
}
}")
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
echo " GA4 COHORT RETENTION — Last 30 Days"
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
echo ""
printf "| %-12s | %-6s | %-8s | %-10s | %-10s |\n" "Cohort" "Week" "Device" "Users" "Retention%"
printf "|%s|%s|%s|%s|%s|\n" "--------------" "--------" "----------" "------------" "------------"
echo "$RESULT" | jq -r '.rows[]? | [
.dimensionValues[0].value,
.dimensionValues[1].value,
.dimensionValues[2].value,
.metricValues[0].value,
(.metricValues[1].value | tonumber * 100 | tostring | split(".")[0])
] | @tsv' 2>/dev/null | \
while IFS=$'\t' read -r cohort week device users retention; do
printf "| %-12s | %-6s | %-8s | %-10s | %-10s |\n" "$cohort" "$week" "$device" "$users" "${retention}%"
done
audience
非同期オーディエンスエクスポート:作成 → ACTIVE になるまでポーリング → ユーザー数を表示します。
GA4_TOKEN=$(gcloud auth application-default print-access-token 2>/dev/null)
# Step 1: List available audiences so user can pick one
AUDIENCES=$(curl -s "https://analyticsadmin.googleapis.com/v1alpha/properties/${GA4_PROPERTY}/audiences" \
-H "Authorization: Bearer ${GA4_TOKEN}")
echo "Available audiences:"
echo "$AUDIENCES" | jq -r '.audiences[]? | "\(.name | split("/") | last): \(.displayName)"' 2>/dev/null
# AskUserQuestion: "Enter audience ID from list above:" (free text)
# Step 2: Create export
EXPORT_RESP=$(curl -s -X POST \
"https://analyticsdata.googleapis.com/v1beta/properties/${GA4_PROPERTY}/audienceExports" \
-H "Authorization: Bearer ${GA4_TOKEN}" \
-H "Content-Type: application/json" \
-d "{
\"audience\": \"properties/${GA4_PROPERTY}/audiences/${AUDIENCE_ID}\",
\"dimensions\": [
{\"dimensionName\": \"deviceId\"},
{\"dimensionName\": \"isAdsPersonalizationAllowed\"}
]
}")
EXPORT_NAME=$(echo "$EXPORT_RESP" | jq -r '.name // empty')
if [ -z "$EXPORT_NAME" ]; then
echo "Failed to create export: $(echo "$EXPORT_RESP" | jq -r '.error.message // "unknown error"')"
exit 0
fi
echo "Export created: ${EXPORT_NAME}"
echo "Polling for completion (small audiences: ~30s, large: up to 15 min)..."
# Step 3: Poll until ACTIVE or FAILED
ATTEMPTS=0
while [ $ATTEMPTS -lt 60 ]; do
STATUS_RESP=$(curl -s "https://analyticsdata.googleapis.com/v1beta/${EXPORT_NAME}" \
-H "Authorization: Bearer ${GA4_TOKEN}")
STATE=$(echo "$STATUS_RESP" | jq -r '.state // "UNKNOWN"')
PCT=$(echo "$STATUS_RESP" | jq -r '.percentageCompleted // 0')
if [ "$STATE" = "ACTIVE" ]; then break; fi
if [ "$STATE" = "FAILED" ]; then
echo "Export failed. Try again or check GA4 audience configuration."
exit 0
fi
echo " State: ${STATE} (${PCT}% complete)..."
sleep 10
ATTEMPTS=$((ATTEMPTS + 1))
done
# Step 4: Query results
QUERY_RESP=$(curl -s -X POST \
"https://analyticsdata.googleapis.com/v1beta/${EXPORT_NAME}:query" \
-H "Authorization: Bearer ${GA4_TOKEN}")
ROW_COUNT=$(echo "$QUERY_RESP" | jq '.rowCount // 0')
echo ""
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
echo " GA4 AUDIENCE EXPORT"
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
echo " Audience ID: ${AUDIENCE_ID}"
echo " Users exported: ${ROW_COUNT}"
echo " Ads-eligible: $(echo "$QUERY_RESP" | jq '[.audienceRows[]? | select(.dimensionValues[1].value == "true")] | length') users"
echo ""
echo "Export ready. Use this audience for retargeting in Meta or Google Ads."
pivot
多次元ピボット:チャネルグループ × デバイスカテゴリ × コンバージョン数。
GA4_TOKEN=$(gcloud auth application-default print-access-token 2>/dev/null)
RESULT=$(curl -s -X POST "https://analyticsdata.googleapis.com/v1beta/properties/${GA4_PROPERTY}:runPivotReport" \
-H "Authorization: Bearer ${GA4_TOKEN}" \
-H "Content-Type: application/json" \
-d '{
"dateRanges": [{"startDate": "30daysAgo", "endDate": "today"}],
"dimensions": [
{"name": "sessionDefaultChannelGrouping"},
{"name": "deviceCategory"}
],
"metrics": [
{"name": "sessions"},
{"name": "conversions"},
{"name": "totalRevenue"}
],
"pivots": [
{
"fieldNames": ["sessionDefaultChannelGrouping"],
"limit": 6
},
{
"fieldNames": ["deviceCategory"],
"limit": 3
}
]
}')
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
echo " GA4 PIVOT — Channel × Device (Last 30 Days)"
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
echo ""
printf "| %-20s | %-8s | %-10s | %-11s | %-10s |\n" "Channel" "Device" "Sessions" "Conversions" "Revenue"
printf "|%s|%s|%s|%s|%s|\n" "----------------------" "----------" "------------" "-------------" "------------"
echo "$RESULT" | jq -r '.rows[]? | [
.dimensionValues[0].value,
.dimensionValues[1].value,
.metricValues[0].value,
.metricValues[1].value,
(.metricValues[2].value | tonumber | . * 100 | round / 100 | tostring)
] | @tsv' 2>/dev/null | \
while IFS=$'\t' read -r channel device sessions convs revenue; do
printf "| %-20s | %-8s | %-10s | %-11s | \$%-9s |\n" "${channel:0:20}" "$device" "$sessions" "$convs" "$revenue"
done
seo / gsc
Google Search Console のデータを取得します。
アクセストークンを取得(同じ gcloud ADC)
GSC_TOKEN=$(gcloud auth application-default print-access-token 2>/dev/null)
GSC_SITE_ENCODED=$(python3 -c "import urllib.parse; print(urllib.parse.quote('${GSC_SITE}', safe=''))" 2>/dev/null || echo "${GSC_SITE}" | sed 's|:|%3A|g; s|/|%2F|g')
検索パフォーマンス(過去 28 日間)
curl -s -X POST "https://searchconsole.googleapis.com/webmasters/v3/sites/${GSC_SITE_ENCODED}/searchAnalytics/query" \
-H "Authorization: Bearer ${GSC_TOKEN}" \
-H "Content-Type: application/json" \
-d '{
"startDate": "'$(date -v-28d +%Y-%m-%d 2>/dev/null || date -d '28 days ago' +%Y-%m-%d)'",
"endDate": "'$(date +%Y-%m-%d)'",
"dimensions": [],
"rowLimit": 1
}' | jq '{clicks: .rows[0].clicks, impressions: .rows[0].impressions, ctr: .rows[0].ctr, position: .rows[0].position}'
上位クエリ(過去 28 日間)
curl -s -X POST "https://searchconsole.googleapis.com/webmasters/v3/sites/${GSC_SITE_ENCODED}/searchAnalytics/query" \
-H "Authorization: Bearer ${GSC_TOKEN}" \
-H "Content-Type: application/json" \
-d '{
"startDate": "'$(date -v-28d +%Y-%m-%d 2>/dev/null || date -d '28 days ago' +%Y-%m-%d)'",
"endDate": "'$(date +%Y-%m-%d)'",
"dimensions": ["query"],
"rowLimit": 20,
"dimensionFilterGroups": []
}' | jq '.rows[] | {query: .keys[0], clicks: .clicks, impressions: .impressions, position: (.position | floor)}'
クリック数が多い上位ページ
curl -s -X POST "https://searchconsole.googleapis.com/webmasters/v3/sites/${GSC_SITE_ENCODED}/searchAnalytics/query" \
-H "Authorization: Bearer ${GSC_TOKEN}" \
-H "Content-Type: application/json" \
-d '{
"startDate": "'$(date -v-28d +%Y-%m-%d 2>/dev/null || date -d '28 days ago' +%Y-%m-%d)'",
"endDate": "'$(date +%Y-%m-%d)'",
"dimensions": ["page"],
"rowLimit": 10
}' | jq '.rows[] | {page: .keys[0], clicks: .clicks, impressions: .impressions, position: (.position | floor)}'
GSC が設定されていない場合は、Search Console を設定していません — /ops:marketing setup を実行してください と出力します。
出力形式
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
SEO (SEARCH CONSOLE) — last 28d
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
Clicks: [N]
Impressions: [N]
CTR: [X]%
Avg Position: [X]
TOP QUERIES
[query] [clicks] clicks pos [N]
...
TOP PAGES
[url] [clicks] clicks [impressions] impr
social
利用可能なソーシャルメディア指標を集計します。設定済みのサービスを確認してください。
Instagram(Meta Graph API 経由 — Meta Ads と同じトークン)
# Get Instagram Business Account ID linked to the ad account
curl -s "https://graph.facebook.com/v18.0/me/accounts?fields=instagram_business_account" \
-H "Authorization: Bearer ${META_TOKEN}" | jq '.data[].instagram_business_account.id' 2>/dev/null
# Then pull media insights
curl -s "https://graph.facebook.com/v18.0/${IG_ACCOUNT_ID}?fields=followers_count,media_count,profile_views" \
-H "Authorization: Bearer ${META_TOKEN}" | jq '{followers: .followers_count, posts: .media_count, profile_views: .profile_views}'
YouTube(gcloud 経由で設定されている場合)
YT_TOKEN=$(gcloud auth application-default print-access-token 2>/dev/null)
curl -s "https://www.googleapis.com/youtube/v3/channels?part=statistics&mine=true" \
-H "Authorization: Bearer ${YT_TOKEN}" | jq '.items[0].statistics | {subscribers: .subscriberCount, views: .viewCount, videos: .videoCount}'
設定されていないチャネルはエラーにせず、[未設定] と表示します。
出力形式
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
SOCIAL MEDIA
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
Instagram: [N followers] [N posts] [N profile views]
YouTube: [N subscribers] [N total views]
TikTok: [not configured] — set TIKTOK_ACCESS_TOKEN
Instagram Graph API を使用した Instagram の投稿とインサイト(Meta Ads と同じ META_TOKEN を使用します)。
前提条件:
META_TOKENが設定されていること(Meta Ads と同じ)- Instagram ビジネスアカウントが Facebook ページにリンクされていること
IG_ACCOUNT_IDは次の方法で解決します:curl "https://graph.facebook.com/v21.0/me/accounts?fields=instagram_business_account" -H "Authorization: Bearer ${META_TOKEN}"→data[0].instagram_business_account.id
レート制限: アプリごとに 1 時間あたり 200 回の API 呼び出し。デモグラフィック情報には 48 時間のレポート遅延が必要です。メディアインサイトには、フォロワーが 1,000 人を超えるアカウントが必要です。
instagram の呼び出しごとに、最初に IG アカウント ID を解決します:
IG_ACCOUNT_ID=$(claude plugin config get instagram_account_id 2>/dev/null)
if [ -z "$IG_ACCOUNT_ID" ]; then
IG_ACCOUNT_ID=$(curl -s "https://graph.facebook.com/v21.0/me/accounts?fields=instagram_business_account" \
-H "Authorization: Bearer ${META_TOKEN}" | jq -r '.data[0].instagram_business_account.id // empty')
# Cache it
[ -n "$IG_ACCOUNT_ID" ] && claude plugin config set instagram_account_id "$IG_ACCOUNT_ID" 2>/dev/null
fi
if [ -z "$IG_ACCOUNT_ID" ]; then
echo "Instagram Business account not linked to your Meta token. Ensure your Facebook Page has an Instagram Business account connected."
exit 0
fi
instagram 内で $ARGUMENTS をルーティングします:
| 入力 | アクション |
|---|---|
| post <IMAGE_URL> | フィードに画像投稿を公開 |
| reel <VIDEO_URL> | リールを公開 |
| story <IMAGE_URL|VIDEO_URL> | ストーリーを公開 |
| insights <MEDIA_ID> | 投稿ごとの指標 |
| account-insights [days] | アカウント全体のリーチとインプレッション |
| demographics | オーディエンスの年齢/性別/地域 |
post
画像投稿を公開します(2 段階:コンテナを作成 → 公開)。
AskUserQuestion で次の情報を収集します:
- 画像 URL(一般公開されている HTTPS URL)— 自由入力
- キャプション — 自由入力
# Step 1: Create media container
CONTAINER=$(curl -s -X POST "https://graph.facebook.com/v21.0/${IG_ACCOUNT_ID}/media" \
-H "Authorization: Bearer ${META_TOKEN}" \
-F "image_url=${IMAGE_URL}" \
-F "caption=${CAPTION}" \
-F "media_type=IMAGE")
CONTAINER_ID=$(echo "$CONTAINER" | jq -r '.id // empty')
if [ -z "$CONTAINER_ID" ]; then
echo "Failed to create media container: $(echo "$CONTAINER" | jq -r '.error.message // "unknown error"')"
exit 0
fi
# Step 2: Publish
PUBLISH=$(curl -s -X POST "https://graph.facebook.com/v21.0/${IG_ACCOUNT_ID}/media_publish" \
-H "Authorization: Bearer ${META_TOKEN}" \
-F "creation_id=${CONTAINER_ID}")
MEDIA_ID=$(echo "$PUBLISH" | jq -r '.id // empty')
if [ -n "$MEDIA_ID" ]; then
echo "Post published (Media ID: ${MEDIA_ID}). View at https://www.instagram.com/ — may take 1-2 min to appear."
else
echo "Publish failed: $(echo "$PUBLISH" | jq -r '.error.message // "unknown error"')"
fi
reel
リールを公開します。動画は HTTPS URL である必要があります(MP4、H.264、最大 15 分、幅は最低 500px)。
AskUserQuestion で次の情報を収集します:
- 動画 URL(HTTPS)— 自由入力
- キャプション — 自由入力
# Step 1: Create video container (async — must poll for status)
CONTAINER=$(curl -s -X POST "https://graph.facebook.com/v21.0/${IG_ACCOUNT_ID}/media" \
-H "Authorization: Bearer ${META_TOKEN}" \
-F "media_type=REELS" \
-F "video_url=${VIDEO_URL}" \
-F "caption=${CAPTION}" \
-F "share_to_feed=true")
CONTAINER_ID=$(echo "$CONTAINER" | jq -r '.id // empty')
if [ -z "$CONTAINER_ID" ]; then
echo "Failed to create Reel container: $(echo "$CONTAINER" | jq -r '.error.message // "unknown error"')"
exit 0
fi
echo "Reel uploading... polling for ready status."
# Poll until status is FINISHED
ATTEMPTS=0
while [ $ATTEMPTS -lt 30 ]; do
STATUS=$(curl -s "https://graph.facebook.com/v21.0/${CONTAINER_ID}?fields=status_code" \
-H "Authorization: Bearer ${META_TOKEN}" | jq -r '.status_code // "UNKNOWN"')
[ "$STATUS" = "FINISHED" ] && break
[ "$STATUS" = "ERROR" ] && echo "Reel processing failed." && exit 0
sleep 10
ATTEMPTS=$((ATTEMPTS + 1))
done
# Step 2: Publish
PUBLISH=$(curl -s -X POST "https://graph.facebook.com/v21.0/${IG_ACCOUNT_ID}/media_publish" \
-H "Authorization: Bearer ${META_TOKEN}" \
-F "creation_id=${CONTAINER_ID}")
MEDIA_ID=$(echo "$PUBLISH" | jq -r '.id // empty')
if [ -n "$MEDIA_ID" ]; then
echo "Reel published (Media ID: ${MEDIA_ID}). Reach and plays metrics available after 24-48h."
else
echo "Reel publish failed: $(echo "$PUBLISH" | jq -r '.error.message // "unknown error"')"
fi
story
ストーリーを公開します(画像または動画、24 時間で期限切れ)。
AskUserQuestion で次の情報を収集します:
- コンテンツ URL(HTTPS の画像または動画)— 自由入力
- コンテンツの種類:
[Image story, Video story]
if [ "$CONTENT_TYPE" = "Video story" ]; then
MEDIA_TYPE="VIDEO"
URL_FIELD="video_url"
else
MEDIA_TYPE="IMAGE"
URL_FIELD="image_url"
fi
CONTAINER=$(curl -s -X POST "https://graph.facebook.com/v21.0/${IG_ACCOUNT_ID}/media" \
-H "Authorization: Bearer ${META_TOKEN}" \
-F "media_type=STORIES" \
-F "${URL_FIELD}=${CONTENT_URL}")
CONTAINER_ID=$(echo "$CONTAINER" | jq -r '.id // empty')
if [ -z "$CONTAINER_ID" ]; then
echo "Failed to create Story container: $(echo "$CONTAINER" | jq -r '.error.message // "unknown error"')"
exit 0
fi
PUBLISH=$(curl -s -X POST "https://graph.facebook.com/v21.0/${IG_ACCOUNT_ID}/media_publish" \
-H "Authorization: Bearer ${META_TOKEN}" \
-F "creation_id=${CONTAINER_ID}")
MEDIA_ID=$(echo "$PUBLISH" | jq -r '.id // empty')
if [ -n "$MEDIA_ID" ]; then
echo "Story published (Media ID: ${MEDIA_ID}). Expires after 24 hours."
else
echo "Story publish failed: $(echo "$PUBLISH" | jq -r '.error.message // "unknown error"')"
fi
insights <MEDIA_ID>
投稿ごとの指標です。注:リール以外の動画ではリーチ、保存数、シェア数は廃止されています。リール/動画では再生数のみ利用できます。
METRICS="reach,saved,shares,comments_count,like_count,impressions"
# For Reels, add plays: detect via media_type field
MEDIA_TYPE=$(curl -s "https://graph.facebook.com/v21.0/${MEDIA_ID}?fields=media_type" \
-H "Authorization: Bearer ${META_TOKEN}" | jq -r '.media_type // "IMAGE"')
[ "$MEDIA_TYPE" = "VIDEO" ] || [ "$MEDIA_TYPE" = "REEL" ] && METRICS="${METRICS},plays"
RESULT=$(curl -s "https://graph.facebook.com/v21.0/${MEDIA_ID}/insights?metric=${METRICS}&period=lifetime" \
-H "Authorization: Bearer ${META_TOKEN}")
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
echo " INSTAGRAM POST INSIGHTS — ${MEDIA_ID}"
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
echo ""
echo "$RESULT" | jq -r '.data[]? | " \(.name): \(.values[0].value)"' 2>/dev/null || \
echo "No insights data — account must have >1,000 followers for insights access."
account-insights [days]
アカウント全体のリーチとインプレッションです。デフォルト:直近 7 日間。
DAYS="${DAYS:-7}"
END_DATE=$(date +%Y-%m-%d)
START_DATE=$(date -v-${DAYS}d +%Y-%m-%d 2>/dev/null || date -d "${DAYS} days ago" +%Y-%m-%d)
RESULT=$(curl -s "https://graph.facebook.com/v21.0/${IG_ACCOUNT_ID}/insights?metric=reach,impressions,profile_views&period=day&since=${START_DATE}&until=${END_DATE}" \
-H "Authorization: Bearer ${META_TOKEN}")
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
echo " INSTAGRAM ACCOUNT INSIGHTS — Last ${DAYS} Days"
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
echo " Note: Data reflects 24-48h reporting delay"
echo ""
REACH=$(echo "$RESULT" | jq '[.data[]? | select(.name == "reach") | .values[]?.value | tonumber] | add // 0')
IMPRESSIONS=$(echo "$RESULT" | jq '[.data[]? | select(.name == "impressions") | .values[]?.value | tonumber] | add // 0')
PROFILE_VIEWS=$(echo "$RESULT" | jq '[.data[]? | select(.name == "profile_views") | .values[]?.value | tonumber] | add // 0')
echo " Reach: ${REACH}"
echo " Impressions: ${IMPRESSIONS}"
echo " Profile Views: ${PROFILE_VIEWS}"
demographics
年齢/性別別のオーディエンス内訳と上位の地域です。期間には lifetime が必要です(48 時間の遅延)。
RESULT=$(curl -s "https://graph.facebook.com/v21.0/${IG_ACCOUNT_ID}/insights?metric=audience_gender_age,audience_city,audience_country&period=lifetime" \
-H "Authorization: Bearer ${META_TOKEN}")
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
echo " INSTAGRAM DEMOGRAPHICS"
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
echo " Note: Top 45 segments shown. Data has 48h delay."
echo ""
echo "AGE / GENDER BREAKDOWN:"
echo "$RESULT" | jq -r '.data[]? | select(.name == "audience_gender_age") | .values[0].value | to_entries[] | " \(.key): \(.value)%"' 2>/dev/null | head -20
echo ""
echo "TOP CITIES:"
echo "$RESULT" | jq -r '.data[]? | select(.name == "audience_city") | .values[0].value | to_entries | sort_by(-.value) | .[0:10][] | " \(.key): \(.value)%"' 2>/dev/null
echo ""
echo "TOP COUNTRIES:"
echo "$RESULT" | jq -r '.data[]? | select(.name == "audience_country") | .values[0].value | to_entries | sort_by(-.value) | .[0:10][] | " \(.key): \(.value)%"' 2>/dev/null
google-ads
認証情報の確認:解決後に GADS_DEV_TOKEN または GADS_REFRESH_TOKEN が空の場合、次を表示します:
Warning: Google Ads not configured. Run /ops:setup marketing to set up credentials.
その後、停止します。
トークンの更新:google-ads の呼び出しごとに、最初に(上記の認証情報の解決にある)アクセストークン更新の curl を実行します。GADS_ACCESS_TOKEN が null または "null" の場合、次を表示します:
Warning: Google Ads token refresh failed. Check client_id/client_secret/refresh_token in /ops:setup.
その後、停止します。
google-ads セクション内で $ARGUMENTS をルーティングします:
| 入力 | アクション |
|---|---|
| (empty), dashboard, overview | キャンペーンのパフォーマンスダッシュボード(直近 7 日間) |
| search-terms, terms | 除外キーワード候補を含む検索語句レポート(直近 30 日間) |
| budget-recs, recommendations, recs | Google による予算最適化の推奨事項 |
| campaigns, manage | キャンペーン管理 — 一覧表示、作成、一時停止、有効化、予算調整 |
| keywords, kw, keyword-planner | キーワード プランナー — 検索ボリュームと入札データを含むキーワードを検出 |
| ad-groups, ag | 広告グループ管理 — 一覧表示、作成、キーワードの追加/削除、入札額の調整 |
ダッシュボード(デフォルト — 引数なし、dashboard、overview)
# Campaign performance — last 7 days
CAMPAIGNS=$(curl -s -X POST \
"https://googleads.googleapis.com/${GADS_API_VERSION}/customers/${GADS_CUSTOMER_ID}/googleAds:searchStream" \
"${GADS_HEADERS[@]}" \
--data-binary '{
"query": "SELECT campaign.id, campaign.name, campaign.status, campaign_budget.amount_micros, metrics.cost_micros, metrics.impressions, metrics.clicks, metrics.conversions, metrics.conversions_value FROM campaign WHERE segments.date DURING LAST_7_DAYS AND campaign.status != REMOVED ORDER BY metrics.cost_micros DESC LIMIT 20"
}')
# Check for API error
GADS_ERROR=$(echo "$CAMPAIGNS" | jq -r '.[0].error.message // empty' 2>/dev/null)
if [ -n "$GADS_ERROR" ]; then
echo "Google Ads API error: ${GADS_ERROR}"
echo "Check credentials with /ops:marketing setup."
exit 0
fi
# Check for empty results
CAMPAIGN_COUNT=$(echo "$CAMPAIGNS" | jq '[.[].results[]?] | length' 2>/dev/null || echo "0")
if [ "$CAMPAIGN_COUNT" -eq 0 ]; then
echo "No active campaigns found in the last 7 days."
exit 0
fi
# Compute totals
TOTAL_SPEND=$(echo "$CAMPAIGNS" | jq '[.[].results[]?.metrics.costMicros // "0" | tonumber] | add / 1000000' 2>/dev/null)
TOTAL_CONVERSIONS=$(echo "$CAMPAIGNS" | jq '[.[].results[]?.metrics.conversions // "0" | tonumber] | add' 2>/dev/null)
TOTAL_VALUE=$(echo "$CAMPAIGNS" | jq '[.[].results[]?.metrics.conversionsValue // "0" | tonumber] | add' 2>/dev/null)
OVERALL_ROAS=$(awk "BEGIN { if (${TOTAL_SPEND:-0} > 0) printf \"%.2f\", ${TOTAL_VALUE:-0} / ${TOTAL_SPEND:-1}; else print \"—\" }")
TOTAL_SPEND_FMT=$(awk "BEGIN { printf \"%.2f\", ${TOTAL_SPEND:-0} }")
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
echo " GOOGLE ADS — Last 7 Days"
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
echo ""
echo "Total Spend: \$${TOTAL_SPEND_FMT}"
echo "Total Conversions: ${TOTAL_CONVERSIONS}"
echo "Overall ROAS: ${OVERALL_ROAS}"
echo ""
# Print table header
printf "| %-30s | %-8s | %-10s | %-10s | %-8s | %-8s | %-6s | %-6s | %-6s |\n" \
"Campaign" "Status" "Budget/day" "Spend" "Impr" "Clicks" "CTR" "Conv" "ROAS"
printf "|%s|%s|%s|%s|%s|%s|%s|%s|%s|\n" \
"--------------------------------" "----------" "------------" "------------" "----------" "----------" "--------" "--------" "--------"
# Print each campaign row
echo "$CAMPAIGNS" | jq -r '.[].results[]? | [
.campaign.name,
.campaign.status,
(.campaignBudget.amountMicros // "0" | tonumber / 1000000),
(.metrics.costMicros // "0" | tonumber / 1000000),
(.metrics.impressions // "0" | tonumber),
(.metrics.clicks // "0" | tonumber),
(.metrics.conversions // "0" | tonumber),
(.metrics.conversionsValue // "0" | tonumber),
(.metrics.costMicros // "0" | tonumber)
] | @tsv' 2>/dev/null | while IFS=$'\t' read -r name status budget_raw spend_raw impr_raw clicks_raw conv_raw value_raw cost_raw; do
BUDGET=$(awk "BEGIN { printf \"%.2f\", ${budget_raw:-0} }")
SPEND=$(awk "BEGIN { printf \"%.2f\", ${spend_raw:-0} }")
CTR=$(awk "BEGIN { if (${impr_raw:-0} > 0) printf \"%.2f\", ${clicks_raw:-0} / ${impr_raw:-0} * 100; else print \"0.00\" }")
ROAS=$(awk "BEGIN { if (${spend_raw:-0} > 0) printf \"%.2f\", ${value_raw:-0} / ${spend_raw:-1}; else print \"—\" }")
printf "| %-30s | %-8s | \$%-9s | \$%-9s | %-8s | %-8s | %-5s%% | %-6s | %-6s |\n" \
"${name:0:30}" "$status" "$BUDGET" "$SPEND" "$impr_raw" "$clicks_raw" "$CTR" "$conv_raw" "$ROAS"
done
検索語句(search-terms、terms)
SEARCH_TERMS=$(curl -s -X POST \
"https://googleads.googleapis.com/${GADS_API_VERSION}/customers/${GADS_CUSTOMER_ID}/googleAds:searchStream" \
"${GADS_HEADERS[@]}" \
--data-binary '{
"query": "SELECT search_term_view.search_term, search_term_view.status, campaign.name, ad_group.name, metrics.impressions, metrics.clicks, metrics.cost_micros, metrics.conversions FROM search_term_view WHERE segments.date DURING LAST_30_DAYS AND metrics.impressions > 0 ORDER BY metrics.impressions DESC LIMIT 100"
}')
# Check for API error
GADS_ST_ERROR=$(echo "$SEARCH_TERMS" | jq -r '.[0].error.message // empty' 2>/dev/null)
if [ -n "$GADS_ST_ERROR" ]; then
echo "Google Ads API error: ${GADS_ST_ERROR}"
echo "Check credentials with /ops:marketing setup."
exit 0
fi
TERM_COUNT=$(echo "$SEARCH_TERMS" | jq '[.[].results[]?] | length' 2>/dev/null || echo "0")
if [ "$TERM_COUNT" -eq 0 ]; then
echo "No search term data found for the last 30 days."
exit 0
fi
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
echo " SEARCH TERMS REPORT — Last 30 Days"
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
echo ""
printf "| %-30s | %-10s | %-20s | %-15s | %-6s | %-6s | %-7s | %-6s |\n" \
"Search Term" "Status" "Campaign" "Ad Group" "Impr" "Clicks" "Cost" "Conv"
printf "|%s|%s|%s|%s|%s|%s|%s|%s|\n" \
"--------------------------------" "------------" "----------------------" "-----------------" "--------" "--------" "---------" "--------"
# Status mapping and table rows
echo "$SEARCH_TERMS" | jq -r '.[].results[]? | [
.searchTermView.searchTerm,
.searchTermView.status,
.campaign.name,
.adGroup.name,
(.metrics.impressions // "0" | tostring),
(.metrics.clicks // "0" | tostring),
(.metrics.costMicros // "0" | tonumber / 1000000 | tostring),
(.metrics.conversions // "0" | tostring)
] | @tsv' 2>/dev/null | while IFS=$'\t' read -r term status campaign adgroup impr clicks cost_raw conv; do
case "$status" in
ADDED) STATUS_LABEL="✓ Added" ;;
EXCLUDED) STATUS_LABEL="✗ Excluded" ;;
*) STATUS_LABEL="○ New" ;;
esac
COST=$(awk "BEGIN { printf \"%.2f\", ${cost_raw:-0} }")
printf "| %-30s | %-10s | %-20s | %-15s | %-6s | %-6s | \$%-6s | %-6s |\n" \
"${term:0:30}" "$STATUS_LABEL" "${campaign:0:20}" "${adgroup:0:15}" "$impr" "$clicks" "$COST" "$conv"
done
echo ""
echo "Negative keyword candidates (high spend, zero conversions):"
echo "$SEARCH_TERMS" | jq -r '.[].results[]? | select(
(.metrics.conversions // "0" | tonumber) == 0 and
(.metrics.costMicros // "0" | tonumber) > 1000000
) | " • \(.searchTermView.searchTerm) — $\(.metrics.costMicros | tonumber / 1000000 | tostring | split(".") | .[0] + "." + (.[1] // "00")[0:2])"' 2>/dev/null || echo " (none found)"
予算の推奨事項(budget-recs、recommendations、recs)
RECS=$(curl -s -X POST \
"https://googleads.googleapis.com/${GADS_API_VERSION}/customers/${GADS_CUSTOMER_ID}/googleAds:searchStream" \
"${GADS_HEADERS[@]}" \
--data-binary '{
"query": "SELECT recommendation.resource_name, recommendation.type, recommendation.campaign, recommendation.impact, recommendation.campaign_budget_recommendation FROM recommendation WHERE recommendation.type IN (CAMPAIGN_BUDGET, MOVE_UNUSED_BUDGET, MARGINAL_ROI_CAMPAIGN_BUDGET, FORECASTING_CAMPAIGN_BUDGET)"
}')
# Check for API error
GADS_REC_ERROR=$(echo "$RECS" | jq -r '.[0].error.message // empty' 2>/dev/null)
if [ -n "$GADS_REC_ERROR" ]; then
echo "Google Ads API error: ${GADS_REC_ERROR}"
echo "Check credentials with /ops:marketing setup."
exit 0
fi
REC_COUNT=$(echo "$RECS" | jq '[.[].results[]?] | length' 2>/dev/null || echo "0")
if [ "$REC_COUNT" -eq 0 ]; then
echo "No budget recommendations available. Google needs campaign data to generate recommendations."
exit 0
fi
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
echo " BUDGET RECOMMENDATIONS"
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
echo ""
printf "| %-22s | %-25s | %-15s | %-13s | %-20s |\n" \
"Type" "Campaign" "Current Budget" "Recommended" "Impact"
printf "|%s|%s|%s|%s|%s|\n" \
"------------------------" "---------------------------" "-----------------" "---------------" "----------------------"
echo "$RECS" | jq -r '.[].results[]? | [
.recommendation.type,
.recommendation.campaign,
(.recommendation.campaignBudgetRecommendation.currentBudgetAmountMicros // "0" | tonumber / 1000000 | tostring),
(.recommendation.campaignBudgetRecommendation.recommendedBudgetAmountMicros // "0" | tonumber / 1000000 | tostring),
(.recommendation.impact.baseMetrics.impressions // "0" | tonumber | tostring),
(.recommendation.impact.potentialMetrics.impressions // "0" | tonumber | tostring)
] | @tsv' 2>/dev/null | while IFS=$'\t' read -r rec_type campaign current_raw recommended_raw base_impr_raw pot_impr_raw; do
case "$rec_type" in
CAMPAIGN_BUDGET) TYPE_LABEL="Increase Budget" ;;
MOVE_UNUSED_BUDGET) TYPE_LABEL="Move Unused Budget" ;;
MARGINAL_ROI_CAMPAIGN_BUDGET) TYPE_LABEL="Marginal ROI" ;;
FORECASTING_CAMPAIGN_BUDGET) TYPE_LABEL="Forecasting" ;;
*) TYPE_LABEL="$rec_type" ;;
esac
CURRENT=$(awk "BEGIN { printf \"%.2f\", ${current_raw:-0} }")
RECOMMENDED=$(awk "BEGIN { printf \"%.2f\", ${recommended_raw:-0} }")
IMPACT=$(awk "BEGIN {
base = ${base_impr_raw:-0}; pot = ${pot_impr_raw:-0}
if (base > 0) printf \"+%.0f%% impressions\", (pot - base) / base * 100
else print \"—\"
}")
printf "| %-22s | %-25s | \$%-14s | \$%-12s | %-20s |\n" \
"$TYPE_LABEL" "${campaign:0:25}" "$CURRENT" "$RECOMMENDED" "$IMPACT"
done
キャンペーン管理(campaigns、manage)
キャンペーンの一覧表示:
curl -s -X POST \
"https://googleads.googleapis.com/${GADS_API_VERSION}/customers/${GADS_CUSTOMER_ID}/googleAds:searchStream" \
"${GADS_HEADERS[@]}" \
--data-binary '{
"query": "SELECT campaign.id, campaign.name, campaign.status, campaign.advertising_channel_type, campaign_budget.amount_micros FROM campaign WHERE campaign.status != REMOVED ORDER BY campaign.name LIMIT 50"
}'
テーブル形式で出力します:
| # | Campaign ID | Name | Status | Channel | Budget/day |
キャンペーンの作成(campaigns create):
AskUserQuestion を使って、ユーザーから以下の情報を一度に1つずつ収集します(自由入力):
- キャンペーン名
- 1日の予算(ドル単位。マイクロ単位に変換します:
BUDGET_MICROS=$(awk "BEGIN {printf \"%d\", $DOLLARS * 1000000}")) - チャネルタイプ — 以下の選択肢を指定して AskUserQuestion で尋ねます:[Search, Display, Shopping, Video]
API の値にマッピングします:
SEARCH、DISPLAY、SHOPPING、VIDEO
変更処理は2段階です:
ステップ1 — 予算を作成します:
BUDGET_RESP=$(curl -s -X POST \
"https://googleads.googleapis.com/${GADS_API_VERSION}/customers/${GADS_CUSTOMER_ID}/campaignBudgets:mutate" \
"${GADS_HEADERS[@]}" \
--data-binary "{
\"operations\": [{
\"create\": {
\"name\": \"Budget for ${CAMPAIGN_NAME}\",
\"deliveryMethod\": \"STANDARD\",
\"amountMicros\": \"${BUDGET_MICROS}\"
}
}]
}")
BUDGET_RESOURCE=$(echo "$BUDGET_RESP" | jq -r '.results[0].resourceName')
ステップ2 — キャンペーンを作成します(安全のため、常に PAUSED 状態にします):
curl -s -X POST \
"https://googleads.googleapis.com/${GADS_API_VERSION}/customers/${GADS_CUSTOMER_ID}/campaigns:mutate" \
"${GADS_HEADERS[@]}" \
--data-binary "{
\"operations\": [{
\"create\": {
\"name\": \"${CAMPAIGN_NAME}\",
\"campaignBudget\": \"${BUDGET_RESOURCE}\",
\"advertisingChannelType\": \"${CHANNEL_TYPE}\",
\"status\": \"PAUSED\",
\"manualCpc\": {},
\"networkSettings\": {
\"targetGoogleSearch\": true,
\"targetSearchNetwork\": true,
\"targetContentNetwork\": false
}
}
}]
}"
次のように出力します:✓ Campaign "${CAMPAIGN_NAME}" created (status: PAUSED, budget: $XX.XX/day). Enable it with: /ops:marketing google-ads campaigns enable <ID>
エラーが発生した場合は、応答から error.message を解析して表示します。
キャンペーンの一時停止(campaigns pause <ID>):
⚠ ルール5 — 一時停止前に確認:AskUserQuestion を使い、"Pause campaign <NAME> (ID: <ID>)?" と尋ねます。選択肢は [Pause, Cancel] です。
curl -s -X POST \
"https://googleads.googleapis.com/${GADS_API_VERSION}/customers/${GADS_CUSTOMER_ID}/campaigns:mutate" \
"${GADS_HEADERS[@]}" \
--data-binary "{
\"operations\": [{
\"update\": {
\"resourceName\": \"customers/${GADS_CUSTOMER_ID}/campaigns/${CAMPAIGN_ID}\",
\"status\": \"PAUSED\"
},
\"updateMask\": \"status\"
}]
}"
次のように出力します:✓ Campaign <NAME> paused.
キャンペーンの有効化(campaigns enable <ID>):
一時停止の場合と同様ですが、"status": "ENABLED" にします。確認は不要です(有効化は破壊的な操作ではありません)。
curl -s -X POST \
"https://googleads.googleapis.com/${GADS_API_VERSION}/customers/${GADS_CUSTOMER_ID}/campaigns:mutate" \
"${GADS_HEADERS[@]}" \
--data-binary "{
\"operations\": [{
\"update\": {
\"resourceName\": \"customers/${GADS_CUSTOMER_ID}/campaigns/${CAMPAIGN_ID}\",
\"status\": \"ENABLED\"
},
\"updateMask\": \"status\"
}]
}"
次のように出力します:✓ Campaign <NAME> enabled.
予算の調整(campaigns budget <ID> <AMOUNT>):
まず、キャンペーンの現在の予算リソース名を取得します:
CAMPAIGN_DATA=$(curl -s -X POST \
"https://googleads.googleapis.com/${GADS_API_VERSION}/customers/${GADS_CUSTOMER_ID}/googleAds:searchStream" \
"${GADS_HEADERS[@]}" \
--data-binary "{\"query\": \"SELECT campaign.campaign_budget, campaign_budget.amount_micros FROM campaign WHERE campaign.id = ${CAMPAIGN_ID}\"}")
BUDGET_RESOURCE=$(echo "$CAMPAIGN_DATA" | jq -r '.[0].results[0].campaign.campaignBudget // empty')
CURRENT_BUDGET_MICROS=$(echo "$CAMPAIGN_DATA" | jq -r '.[0].results[0].campaignBudget.amountMicros // "0"')
CURRENT_BUDGET=$(awk "BEGIN { printf \"%.2f\", ${CURRENT_BUDGET_MICROS:-0} / 1000000 }")
AskUserQuestion を使って、"Change budget from $<CURRENT> to $<NEW>/day?" と確認します。選択肢は [Confirm, Cancel] です。
次に更新します:
NEW_BUDGET_MICROS=$(awk "BEGIN {printf \"%d\", ${NEW_AMOUNT} * 1000000}")
curl -s -X POST \
"https://googleads.googleapis.com/${GADS_API_VERSION}/customers/${GADS_CUSTOMER_ID}/campaignBudgets:mutate" \
"${GADS_HEADERS[@]}" \
--data-binary "{
\"operations\": [{
\"update\": {
\"resourceName\": \"${BUDGET_RESOURCE}\",
\"amountMicros\": \"${NEW_BUDGET_MICROS}\"
},
\"updateMask\": \"amountMicros\"
}]
}"
次のように出力します:✓ Budget updated: $<OLD>/day → $<NEW>/day
キーワードプランナー(keywords、kw、keyword-planner)
# Collect seed keywords from user via AskUserQuestion (free text)
# "Enter seed keywords (comma-separated):"
# Split into JSON array for the request: KEYWORDS_JSON_ARRAY=$(echo "$SEED_KEYWORDS" | sed 's/,/","/g' | sed 's/^/"/;s/$/"/')
curl -s -X POST \
"https://googleads.googleapis.com/${GADS_API_VERSION}/customers/${GADS_CUSTOMER_ID}:generateKeywordIdeas" \
"${GADS_HEADERS[@]}" \
--data-binary "{
\"language\": \"languageConstants/1000\",
\"geoTargetConstants\": [\"geoTargetConstants/2840\"],
\"includeAdultKeywords\": false,
\"keywordPlanNetwork\": \"GOOGLE_SEARCH\",
\"keywordSeed\": {
\"keywords\": [${KEYWORDS_JSON_ARRAY}]
}
}"
言語 1000 は英語、地域 2840 は米国です。これらはデフォルト値です。ユーザーの環境設定にロケールが設定されている場合は、そちらを使用してください。その他の一般的な値:英国=2826、カナダ=2124、オーストラリア=2036。
出力形式:
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
KEYWORD IDEAS
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
Seeds: keyword1, keyword2
| Keyword | Avg Monthly Searches | Competition | Low Bid | High Bid |
|---------|---------------------|-------------|---------|----------|
avgMonthlySearchesはそのまま表示します(整数)。competitionはそのまま表示します(LOW、MEDIUM、HIGH)。lowTopOfPageBidMicrosとhighTopOfPageBidMicrosは 1,000,000 で割り、$X.XXの形式で表示します。- 出力は
avgMonthlySearchesの降順で並べ替えます。
結果がない場合は、次のように出力します:No keyword ideas found for these seeds. Try different or broader keywords.
広告グループ管理(ad-groups、ag)
キャンペーンの広告グループを一覧表示(ad-groups list <CAMPAIGN_ID>):
curl -s -X POST \
"https://googleads.googleapis.com/${GADS_API_VERSION}/customers/${GADS_CUSTOMER_ID}/googleAds:searchStream" \
"${GADS_HEADERS[@]}" \
--data-binary "{\"query\": \"SELECT ad_group.id, ad_group.name, ad_group.status, ad_group.cpc_bid_micros FROM ad_group WHERE campaign.id = ${CAMPAIGN_ID} AND ad_group.status != REMOVED ORDER BY ad_group.name\"}"
次の形式で出力します:
| # | Ad Group ID | Name | Status | CPC Bid |
広告グループの作成(ad-groups create <CAMPAIGN_ID>):
AskUserQuestion を使って、以下の情報を収集します:
- 広告グループ名(自由入力)
- デフォルトの CPC 入札額(ドル単位。自由入力し、マイクロ単位に変換します)
BID_MICROS=$(awk "BEGIN {printf \"%d\", ${BID_DOLLARS} * 1000000}")
curl -s -X POST \
"https://googleads.googleapis.com/${GADS_API_VERSION}/customers/${GADS_CUSTOMER_ID}/adGroups:mutate" \
"${GADS_HEADERS[@]}" \
--data-binary "{
\"operations\": [{
\"create\": {
\"name\": \"${AD_GROUP_NAME}\",
\"campaign\": \"customers/${GADS_CUSTOMER_ID}/campaigns/${CAMPAIGN_ID}\",
\"status\": \"ENABLED\",
\"type\": \"SEARCH_STANDARD\",
\"cpcBidMicros\": \"${BID_MICROS}\"
}
}]
}"
次のように出力します:✓ Ad group "${AD_GROUP_NAME}" created in campaign ${CAMPAIGN_ID} (CPC bid: $X.XX)
広告グループのキーワードを一覧表示(ad-groups keywords <AD_GROUP_ID>):
curl -s -X POST \
"https://googleads.googleapis.com/${GADS_API_VERSION}/customers/${GADS_CUSTOMER_ID}/googleAds:searchStream" \
"${GADS_HEADERS[@]}" \
--data-binary "{\"query\": \"SELECT ad_group_criterion.criterion_id, ad_group_criterion.keyword.text, ad_group_criterion.keyword.match_type, ad_group_criterion.cpc_bid_micros, ad_group_criterion.status FROM ad_group_criterion WHERE ad_group.id = ${AD_GROUP_ID} AND ad_group_criterion.type = KEYWORD AND ad_group_criterion.status != REMOVED\"}"
次の形式で出力します:
| # | Keyword | Match Type | CPC Bid | Status |
広告グループへのキーワードの追加(ad-groups add-keyword <AD_GROUP_ID>):
AskUserQuestion を使って、以下の情報を収集します:
- キーワードのテキスト(自由入力)
- マッチタイプ — 以下の選択肢を指定して AskUserQuestion で尋ねます:[Broad, Phrase, Exact]
次のようにマッピングします:
BROAD、PHRASE、EXACT - CPC 入札額(ドル単位。自由入力し、マイクロ単位に変換します)— 省略可能です。指定がない場合は広告グループのデフォルト値を使用します。
curl -s -X POST \
"https://googleads.googleapis.com/${GADS_API_VERSION}/customers/${GADS_CUSTOMER_ID}/adGroupCriteria:mutate" \
"${GADS_HEADERS[@]}" \
--data-binary "{
\"operations\": [{
\"create\": {
\"adGroup\": \"customers/${GADS_CUSTOMER_ID}/adGroups/${AD_GROUP_ID}\",
\"status\": \"ENABLED\",
\"keyword\": {
\"text\": \"${KEYWORD_TEXT}\",
\"matchType\": \"${MATCH_TYPE}\"
}
${BID_MICROS:+,\"cpcBidMicros\": \"${BID_MICROS}\"}
}
}]
}"
次のように出力します:✓ Keyword "${KEYWORD_TEXT}" (${MATCH_TYPE}) added to ad group ${AD_GROUP_ID}
注:キーワードは作成後に変更できません。マッチタイプまたはテキストを変更するには、削除して再作成してください。入札額のみを変更するには、ad-groups update-bid を使用してください。
キーワードの削除(ad-groups remove-keyword <AD_GROUP_ID> <CRITERION_ID>):
⚠ ルール5 — 削除前に確認:AskUserQuestion を使い、"Remove keyword <TEXT> from ad group <NAME>?" と尋ねます。選択肢は [Remove, Cancel] です。
curl -s -X POST \
"https://googleads.googleapis.com/${GADS_API_VERSION}/customers/${GADS_CUSTOMER_ID}/adGroupCriteria:mutate" \
"${GADS_HEADERS[@]}" \
--data-binary "{
\"operations\": [{
\"remove\": \"customers/${GADS_CUSTOMER_ID}/adGroupCriteria/${AD_GROUP_ID}~${CRITERION_ID}\"
}]
}"
次のように出力します:✓ Keyword removed from ad group.
キーワードの入札額の更新(ad-groups update-bid <AD_GROUP_ID> <CRITERION_ID> <BID>):
NEW_BID_MICROS=$(awk "BEGIN {printf \"%d\", ${NEW_BID} * 1000000}")
curl -s -X POST \
"https://googleads.googleapis.com/${GADS_API_VERSION}/customers/${GADS_CUSTOMER_ID}/adGroupCriteria:mutate" \
"${GADS_HEADERS[@]}" \
--data-binary "{
\"operations\": [{
\"update\": {
\"resourceName\": \"customers/${GADS_CUSTOMER_ID}/adGroupCriteria/${AD_GROUP_ID}~${CRITERION_ID}\",
\"cpcBidMicros\": \"${NEW_BID_MICROS}\"
},
\"updateMask\": \"cpcBidMicros\"
}]
}"
次のように出力します:✓ Keyword bid updated to $X.XX
campaigns
クロスチャネルキャンペーンの概要 — 設定済みのすべてのチャネル(Klaviyo、Meta Ads、Google Ads)で実施中のキャンペーンを統合して表示します。
並列で実行します:
- Klaviyo の実施中キャンペーン(ステータス:draft + scheduled + sending)
- Meta Ads の実施中キャンペーン(ステータス ACTIVE)—
META_TOKEN/META_ACCOUNTを再利用します - Google Ads の実施中キャンペーン(ステータス ENABLED)— 設定されている場合は
GADS_*の認証情報を再利用します - Google Ads:先にアクセストークンを更新します(Credential Resolution を参照)
# Meta Ads campaigns
META_CAMPAIGNS=$(curl -s "https://graph.facebook.com/v20.0/${META_ACCOUNT}/campaigns?fields=name,status,daily_budget,lifetime_budget,objective&filtering=[{\"field\":\"effective_status\",\"operator\":\"IN\",\"value\":[\"ACTIVE\"]}]" \
-H "Authorization: Bearer ${META_TOKEN}" 2>/dev/null)
# Klaviyo campaigns
KLAVIYO_CAMPAIGNS=$(curl -s "https://a.klaviyo.com/api/campaigns/?filter=equals(messages.channel,'email')&sort=-created_at&page[size]=10" \
-H "Authorization: Klaviyo-API-Key ${KLAVIYO_KEY}" \
-H "revision: 2024-10-15" 2>/dev/null)
# Google Ads campaigns (if configured)
GADS_CAMPAIGNS=""
if [ -n "$GADS_ACCESS_TOKEN" ] && [ "$GADS_ACCESS_TOKEN" != "null" ]; then
GADS_CAMPAIGNS=$(curl -s -X POST \
"https://googleads.googleapis.com/${GADS_API_VERSION}/customers/${GADS_CUSTOMER_ID}/googleAds:searchStream" \
"${GADS_HEADERS[@]}" \
--data-binary '{"query": "SELECT campaign.id, campaign.name, campaign.status, campaign_budget.amount_micros, metrics.cost_micros FROM campaign WHERE campaign.status = ENABLED ORDER BY metrics.cost_micros DESC LIMIT 10"}' 2>/dev/null)
fi
出力形式
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
CROSS-CHANNEL CAMPAIGNS — active
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
EMAIL (Klaviyo)
[campaign name] [status] [send date or "scheduled for X"]
PAID — META ADS
[campaign name] [status] $[daily_budget]/day [objective]
PAID — GOOGLE ADS
[campaign name] [status] $[budget]/day $[spend 7d]
FLOWS (Always-on automation)
[flow name] [trigger type] [status: live/draft]
設定されていないチャネルには、[not configured — /ops:marketing setup] と表示します。
optimize
クロスプラットフォームの広告最適化エージェントです。Meta Ads と Google Ads のデータを読み込み、統合 ROAS を計算し、予算の移動先を提案します。
マーケティング最適化エージェントを起動します:
Agent(prompt="Run the marketing optimizer agent. Read ops-marketing-dash data for Meta Ads and Google Ads spend/conversions/ROAS. Compute blended ROAS across platforms. Identify the highest-ROAS platform. Recommend budget shifts with specific dollar amounts. List top 3 actions by expected impact. Use the marketing-optimizer.md agent instructions.", model="claude-sonnet-4-5")
Agent Teams が利用可能な場合(CLAUDE_CODE_EXPERIMENTAL_AGENT_TEAMS=1):
TeamCreate("optimizer")
Agent(team_name="optimizer", name="marketing-optimizer", prompt="You are the marketing optimizer. Read ops-marketing-dash pre-gathered data. Compute blended ROAS for Meta + Google. Recommend budget reallocation. Show unified attribution table.")
出力形式
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
AD OPTIMIZATION REPORT
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
Blended ROAS: [X]x (Meta: [X]x Google: [X]x)
Total Spend: $[X] Total Revenue: $[X]
RECOMMENDATIONS
1. [Action] Expected impact: [+X% ROAS / +$X revenue]
2. [Action] ...
3. [Action] ...
BUDGET REALLOCATION
Move $[X]/day from [Platform A] → [Platform B]
Rationale: [X]x ROAS vs [X]x ROAS
attribution
設定済みのすべてのプラットフォームについて、広告費、コンバージョン、収益、ROAS を並べて表示する統合アトリビューションテーブルです。
# Gather all platform data in parallel
# Meta Ads — last 7d
META_DATA=$(curl -s "https://graph.facebook.com/v20.0/${META_ACCOUNT}/insights?fields=spend,actions,action_values&date_preset=last_7d&level=account" \
-H "Authorization: Bearer ${META_TOKEN}" 2>/dev/null)
META_SPEND=$(echo "$META_DATA" | jq -r '.data[0].spend // "0"')
META_CONV=$(echo "$META_DATA" | jq '[.data[0].actions[]? | select(.action_type == "purchase") | .value | tonumber] | add // 0' 2>/dev/null)
META_REV=$(echo "$META_DATA" | jq '[.data[0].action_values[]? | select(.action_type == "purchase") | .value | tonumber] | add // 0' 2>/dev/null)
META_ROAS=$(awk "BEGIN { if (${META_SPEND:-0} > 0) printf \"%.2f\", ${META_REV:-0} / ${META_SPEND:-1}; else print \"—\" }")
# Google Ads — last 7d
GADS_DATA=""
GADS_SPEND="0"; GADS_CONV="0"; GADS_REV="0"; GADS_ROAS="—"
if [ -n "$GADS_ACCESS_TOKEN" ] && [ "$GADS_ACCESS_TOKEN" != "null" ]; then
GADS_DATA=$(curl -s -X POST \
"https://googleads.googleapis.com/${GADS_API_VERSION}/customers/${GADS_CUSTOMER_ID}/googleAds:searchStream" \
"${GADS_HEADERS[@]}" \
--data-binary '{"query": "SELECT metrics.cost_micros, metrics.conversions, metrics.conversions_value FROM customer WHERE segments.date DURING LAST_7_DAYS"}' 2>/dev/null)
GADS_SPEND=$(echo "$GADS_DATA" | jq '[.[].results[]?.metrics.costMicros // "0" | tonumber] | add / 1000000 // 0' 2>/dev/null || echo "0")
GADS_CONV=$(echo "$GADS_DATA" | jq '[.[].results[]?.metrics.conversions // "0" | tonumber] | add // 0' 2>/dev/null || echo "0")
GADS_REV=$(echo "$GADS_DATA" | jq '[.[].results[]?.metrics.conversionsValue // "0" | tonumber] | add // 0' 2>/dev/null || echo "0")
GADS_ROAS=$(awk "BEGIN { if (${GADS_SPEND:-0} > 0) printf \"%.2f\", ${GADS_REV:-0} / ${GADS_SPEND:-1}; else print \"—\" }")
fi
# Klaviyo attributed revenue
KLAVIYO_REV="—"
# (Pull from metric aggregates if needed — complex, show as note)
# GA4 conversions + revenue
GA4_DATA=$(curl -s -X POST "https://analyticsdata.googleapis.com/v1beta/properties/${GA4_PROPERTY}:runReport" \
-H "Authorization: Bearer ${GA4_TOKEN}" \
-H "Content-Type: application/json" \
-d '{"dateRanges": [{"startDate": "7daysAgo", "endDate": "today"}], "metrics": [{"name": "conversions"}, {"name": "totalRevenue"}]}' 2>/dev/null)
GA4_CONV=$(echo "$GA4_DATA" | jq -r '.rows[0].metricValues[0].value // "—"')
GA4_REV=$(echo "$GA4_DATA" | jq -r '.rows[0].metricValues[1].value // "—"')
TOTAL_AD_SPEND=$(awk "BEGIN { printf \"%.2f\", ${META_SPEND:-0} + ${GADS_SPEND:-0} }")
TOTAL_AD_REV=$(awk "BEGIN { printf \"%.2f\", ${META_REV:-0} + ${GADS_REV:-0} }")
BLENDED_ROAS=$(awk "BEGIN { if (${TOTAL_AD_SPEND:-0} > 0) printf \"%.2f\", ${TOTAL_AD_REV:-0} / ${TOTAL_AD_SPEND:-1}; else print \"—\" }")
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
echo " UNIFIED ATTRIBUTION — Last 7 Days"
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
printf "| %-14s | %-10s | %-12s | %-12s | %-8s |\n" "Platform" "Spend" "Conversions" "Revenue" "ROAS"
printf "|%s|%s|%s|%s|%s|\n" "----------------" "------------" "--------------" "--------------" "----------"
printf "| %-14s | \$%-9s | %-12s | \$%-11s | %-8s |\n" "Meta Ads" "$META_SPEND" "$META_CONV" "$META_REV" "${META_ROAS}x"
printf "| %-14s | \$%-9s | %-12s | \$%-11s | %-8s |\n" "Google Ads" "$(printf "%.2f" ${GADS_SPEND})" "$GADS_CONV" "$(printf "%.2f" ${GADS_REV})" "${GADS_ROAS}x"
printf "| %-14s | %-10s | %-12s | %-12s | %-8s |\n" "Klaviyo" "—" "—" "${KLAVIYO_REV}" "—"
printf "| %-14s | %-10s | %-12s | %-12s | %-8s |\n" "GA4 (organic)" "—" "$GA4_CONV" "\$${GA4_REV}" "—"
printf "|%s|%s|%s|%s|%s|\n" "----------------" "------------" "--------------" "--------------" "----------"
printf "| %-14s | \$%-9s | %-12s | \$%-11s | %-8s |\n" "TOTAL (ads)" "$TOTAL_AD_SPEND" "—" "$TOTAL_AD_REV" "${BLENDED_ROAS}x"
dashboard (デフォルト — 引数なし)
すべてのセクションを並列で実行し、その後、統合ダッシュボードを表示します。
# Run the pre-gathered data script
"${CLAUDE_PLUGIN_ROOT}/bin/ops-marketing-dash" 2>/dev/null
JSON 出力を解析して、次の内容を表示します:
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
MARKETING DASHBOARD — [date]
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
Email (Klaviyo) [N] subs | [X]% open rate | $[X] attributed
Paid (Meta) $[X] spent | [X]x ROAS | [N] purchases
Paid (Google) $[X] spent | [X]x ROAS | [N] conversions
Organic (GA4) [N] sessions | [X]% CVR | $[X] revenue
SEO (GSC) [N] clicks | [N] impressions | [X] avg pos
Instagram [N] followers | [N] reach (7d)
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
Marketing Health Score: [N]/100 ([status: Healthy/Warning/Critical])
Blended ROAS: [X]x Top channel: [channel]
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
マーケティング健全性スコアの計算(0〜100。ダッシュボードの下部に表示):
- 統合 ROAS が 3 倍以上:+30 点、1〜3 倍:+15 点、1 倍未満:+0 点
- メール開封率が 20% 以上:+20 点、10〜20%:+10 点、10% 未満:+0 点
- チャネルの多様性(3 つ以上のプラットフォームが稼働中):+20 点、2 つのプラットフォーム:+10 点、1 つ:+0 点
- GA4 の CVR が 2% 以上:+20 点、1〜2%:+10 点、1% 未満:+0 点
- オーガニック SEO のクリック数が月 1,000 超:+10 点、100〜1,000:+5 点、100 未満:+0 点
ステータスの基準:70 以上 = Healthy、40〜69 = Warning、40 未満 = Critical。
認証情報がないチャネルには、[not configured — /ops:marketing setup] と表示します。
setup
何かを尋ねる前に、既存の認証情報がないか、すべてのソースを自動スキャンします。単一のバックグラウンドバッチで実行します:
# Env vars
printenv KLAVIYO_API_KEY KLAVIYO_PRIVATE_KEY META_ACCESS_TOKEN FACEBOOK_ACCESS_TOKEN META_AD_ACCOUNT_ID GA4_PROPERTY_ID GA_MEASUREMENT_ID 2>/dev/null
printenv GOOGLE_ADS_DEVELOPER_TOKEN GOOGLE_ADS_CLIENT_ID GOOGLE_ADS_CLIENT_SECRET GOOGLE_ADS_REFRESH_TOKEN GOOGLE_ADS_CUSTOMER_ID 2>/dev/null
# Shell profiles
grep -h 'KLAVIYO\|META_\|FACEBOOK\|GA4\|GA_MEASUREMENT\|GOOGLE_ADS' ~/.zshrc ~/.bashrc ~/.zprofile ~/.envrc 2>/dev/null | grep -v '^#'
# Doppler — ALL projects, ALL configs
for proj in $(doppler projects --json 2>/dev/null | jq -r '.[].slug'); do
for cfg in dev stg prd; do
doppler secrets --project "$proj" --config "$cfg" --json 2>/dev/null | \
jq -r --arg proj "$proj" --arg cfg "$cfg" 'to_entries[] | select(.key | test("KLAVIYO|META|FACEBOOK|GA4|GOOGLE|GOOGLE_ADS"; "i")) | "\(.key)=\(.value.computed) (doppler:\($proj)/\($cfg))"'
done
done
# Dashlane — check for tokens in password entries
dcli password klaviyo --output json 2>/dev/null | jq -r '.[] | select(.password != null and .password != "") | "\(.title): token found"'
dcli password facebook --output json 2>/dev/null | jq -r '.[] | select(.password != null and .password != "") | "\(.title): token found"'
dcli password meta --output json 2>/dev/null | jq -r '.[] | select(.password != null and .password != "") | "\(.title): token found"'
dcli password "google ads" --output json 2>/dev/null | jq -r '.[] | select(.password != null and .password != "") | "\(.title): token found"'
# Keychain
security find-generic-password -s "klaviyo-api-key" -w 2>/dev/null
security find-generic-password -s "meta-ads-token" -w 2>/dev/null
security find-generic-password -s "google-ads-refresh-token" -w 2>/dev/null
# gcloud ADC (for GA4 + Search Console)
gcloud auth application-default print-access-token 2>/dev/null | head -c 10 && echo "...gcloud-ok"
# Chrome history — reveals account identity
sqlite3 ~/Library/Application\ Support/Google/Chrome/Default/History \
"SELECT DISTINCT url FROM urls WHERE url LIKE '%klaviyo.com%' OR url LIKE '%analytics.google.com%' OR url LIKE '%business.facebook.com%' OR url LIKE '%search.google.com/search-console%' OR url LIKE '%ads.google.com%' ORDER BY last_visit_time DESC LIMIT 15" 2>/dev/null
# Existing prefs + userConfig
jq -r '.marketing // empty' "$PREFS_PATH" 2>/dev/null
何かを尋ねる前に、すべての検出結果を提示します。どのソースからも見つからなかった値のみを尋ねます。すべてのスモークテストは run_in_background: true で実行します。
Klaviyo: KLAVIYO_PRIVATE_KEY または ck_* キーを含む Dashlane エントリが見つかった場合は、そのまま使用します。注:Klaviyo のプライベートキーは ck_(旧形式)または pk_(新形式)で始まります。スモークテスト:curl -s -H "Authorization: Klaviyo-API-Key $KEY" -H "revision: 2024-10-15" "https://a.klaviyo.com/api/lists?page[size]=1"。
Meta Ads: Doppler で見つかった場合は、そのまま使用します。META_ACCESS_TOKEN と META_AD_ACCOUNT_ID の両方が必要です。スモークテスト:graph.facebook.com/v20.0/$AD_ACCOUNT_ID/campaigns?limit=1。
GA4: 必要なのはプロパティ ID と gcloud ADC のみです。gcloud ADC が未設定の場合は、バックグラウンドで gcloud auth application-default login を実行します(ブラウザーが開きます)。Chrome の履歴から GA4 のプロパティ URL を確認し、プロパティ ID を自動検出します。
Search Console: 必要なのはサイト URL と gcloud ADC のみです。Chrome の履歴から search.google.com/search-console の URL を確認し、サイトを自動検出します。
Google Ads: 他のマーケティング認証情報より複雑で、次の 3 つが必要です:(1)Google Ads MCC → Tools & Settings → API Center から取得したデベロッパー トークン、(2)Google Cloud Console から取得した OAuth2 クライアント ID とシークレット(デスクトップアプリの種類、Google Ads API が有効)、(3)ブラウザーの OAuth フローで取得した更新トークン。セットアップの流れ:まずデベロッパー トークンとクライアント認証情報を収集し、次にブラウザーの認証 URL を開きます。ユーザーが認証コードを貼り付けたら、curl で更新トークンと交換し、アクセス可能な顧客アカウントを一覧表示して、ユーザーに選択してもらいます。スモークテスト:curl -s -X GET "https://googleads.googleapis.com/v23/customers:listAccessibleCustomers" -H "Authorization: Bearer $TOKEN" -H "developer-token: $DEV_TOKEN" — resourceNames 配列を含む JSON が返されることを確認します。
userConfig(推奨)または Doppler を使用して保存します。次の形式で報告します:[service] ✓ connected または [service] ✗ invalid key — [error]。