OPS ► ECOM — Shopify Store Command Center skill

OPS on-demand: This skill should be used when the user asks to "shopify", "orders inventory", or…

by Lifecycle-Innovations-Limited·MIT license·★ 543 Stars on the repo·GitHub ↗

Use now

Inside this skill

SKILL.md · the author's own words
# OPS ► ECOM — Shopify Store Command Center

Load `ops-rules` before acting. Public repo (no personal data). Outbound: one draft → one approval → one send. If `AskUserQuestion` / `Work…

## Runtime Context

Before executing, load available context:

1. **Preferences**: Read `${CLAUDE_PLUGIN_DATA_DIR:-$HOME/.claude/plugins/data/ops-ops-marketplace}/preferences.json`
   - `timezone` — display all timestamps correctly
   - `shopify_store_url`, `shopify_admin_token` — check userConfig keys before env vars
   - `ecom.shopify.sales_channels` — optional last probe cache (publications + agentic/Shop gaps)
   - `ecom.shopify.agentic_storefronts` — optional operator-declared enablement map (channel → status)

2. **Daemon health**: Read `${CLAUDE_PLUGIN_DATA_DIR}/daemon-health.json`
   - If `action_needed` is not null → surface it before running any store operations

Word-for-word excerpt, not a result. Read the full SKILL.md ↓

Files of OPS ► ECOM — Shopify Store Command Center

Lifecycle-Innovations-Limited/main1 file shown
SKILL.md
Show the full text751 lines

OPS ► ECOM — Shopify Store Command Center

Load ops-rules before acting. Public repo (no personal data). Outbound: one draft → one approval → one send. If AskUserQuestion / Workflow are missing, follow Rule 10 in ops-rules (Hermes: numbered options / two-turn Telegram card; delegate_task).

Runtime Context

Before executing, load available context:

  1. Preferences: Read ${CLAUDE_PLUGIN_DATA_DIR:-$HOME/.claude/plugins/data/ops-ops-marketplace}/preferences.json

    • timezone — display all timestamps correctly
    • shopify_store_url, shopify_admin_token — check userConfig keys before env vars
    • ecom.shopify.sales_channels — optional last probe cache (publications + agentic/Shop gaps)
    • ecom.shopify.agentic_storefronts — optional operator-declared enablement map (channel → status)
  2. Daemon health: Read ${CLAUDE_PLUGIN_DATA_DIR}/daemon-health.json

    • If action_needed is not null → surface it before running any store operations
  3. Secrets: Resolve Shopify credentials via userConfig → env vars → Doppler (see Phase 1 below)

Agent Teams support

If CLAUDE_CODE_EXPERIMENTAL_AGENT_TEAMS=1 is set, use Agent Teams when probing store data in parallel. This enables:

  • Agents share context and can coordinate mid-flight
  • You can steer priorities in real-time
  • Agents report progress as they complete

Team setup (only when flag is enabled):

TeamCreate("ecom-team")
Agent(team_name="ecom-team", name="orders-scanner", prompt="Fetch recent orders, compute revenue for today/7d/30d")
Agent(team_name="ecom-team", name="inventory-scanner", prompt="Fetch all products, flag low stock and out-of-stock items")
Agent(team_name="ecom-team", name="fulfillment-scanner", prompt="Fetch unfulfilled orders and ShipBob shipment status")
Agent(team_name="ecom-team", name="analytics-scanner", prompt="Compute revenue analytics, AOV, and top products for 30d")

If the flag is NOT set, use standard fire-and-forget subagents.

Phase 1 — Resolve credentials

Resolve Shopify credentials in this order:

# 1. Plugin userConfig
SHOPIFY_STORE="${user_config.shopify_store_url}"
SHOPIFY_TOKEN="${user_config.shopify_admin_token}"
SHIPBOB_TOKEN="${user_config.shipbob_access_token}"

# 2. Environment variables (override userConfig if set)
[ -n "$SHOPIFY_STORE_URL" ] && SHOPIFY_STORE="$SHOPIFY_STORE_URL"
[ -n "$SHOPIFY_ACCESS_TOKEN" ] && SHOPIFY_TOKEN="$SHOPIFY_ACCESS_TOKEN"
[ -n "$SHIPBOB_ACCESS_TOKEN" ] && SHIPBOB_TOKEN="$SHIPBOB_ACCESS_TOKEN"

# 3. Doppler fallback
if [ -z "$SHOPIFY_TOKEN" ] && command -v doppler &>/dev/null; then
  SHOPIFY_TOKEN=$(doppler secrets get SHOPIFY_ACCESS_TOKEN --plain 2>/dev/null)
fi
if [ -z "$SHOPIFY_STORE" ] && command -v doppler &>/dev/null; then
  SHOPIFY_STORE=$(doppler secrets get SHOPIFY_STORE_URL --plain 2>/dev/null)
fi
if [ -z "$SHIPBOB_TOKEN" ] && command -v doppler &>/dev/null; then
  SHIPBOB_TOKEN=$(doppler secrets get SHIPBOB_ACCESS_TOKEN --plain 2>/dev/null)
fi

If $SHOPIFY_STORE or $SHOPIFY_TOKEN is still empty after all resolution steps, route to setup flow below.

Set base URLs:

SHOPIFY_BASE="https://${SHOPIFY_STORE}/admin/api/2024-10"
SHOPIFY_GQL="https://${SHOPIFY_STORE}/admin/api/2024-10/graphql.json"
SHOPIFY_AUTH="X-Shopify-Access-Token: ${SHOPIFY_TOKEN}"

Phase 2 — Route by $ARGUMENTS

Input Action
(empty) Show store summary
orders, order Orders dashboard
inventory, stock, inv Inventory levels
fulfillment, fulfill, shipbob, shipping Fulfillment status
health, check, status Store health check
products, product, catalog Products manager
customers, customer, crm Customer stats
analytics, revenue, stats, metrics Analytics dashboard
channels, publications, sales-channels Sales channel inventory (publications + gaps)
agentic, agentic-storefronts, ai-channels Agentic storefront health
shop, shop-channel, shop-campaigns Shop channel + Shop Campaigns readiness
setup, configure, init, token Setup flow

ORDERS

Fetch recent orders and compute revenue:

TODAY=$(date -u +"%Y-%m-%dT00:00:00Z")
WEEK_AGO=$(date -u -v-7d +"%Y-%m-%dT00:00:00Z" 2>/dev/null || date -u -d "7 days ago" +"%Y-%m-%dT00:00:00Z")
MONTH_AGO=$(date -u -v-30d +"%Y-%m-%dT00:00:00Z" 2>/dev/null || date -u -d "30 days ago" +"%Y-%m-%dT00:00:00Z")

# Recent orders (last 50)
curl -s -H "$SHOPIFY_AUTH" \
  "${SHOPIFY_BASE}/orders.json?status=any&limit=50&order=created_at+desc" | \
  jq '{
    total: .orders | length,
    today: [.orders[] | select(.created_at >= "'"$TODAY"'")],
    orders: [.orders[:10] | .[] | {
      id: .order_number,
      name: .name,
      status: .financial_status,
      fulfillment: .fulfillment_status,
      total: .total_price,
      currency: .currency,
      customer: (.customer.first_name + " " + .customer.last_name),
      created: .created_at
    }]
  }'

Render:

━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
 OPS ► ECOM ► ORDERS — [store] — [timestamp]
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

REVENUE
  Today    [N orders]   $[amount]
  7 days   [N orders]   $[amount]
  30 days  [N orders]   $[amount]

RECENT ORDERS
  #[id]  [customer]  $[total]  [status] / [fulfillment]  [age]
  ...

──────────────────────────────────────────────────────
 Actions:
 a) View order details for #[id]
 b) Mark order as fulfilled
 c) Export orders CSV
 d) Filter by status (unfulfilled/refunded/paid)
──────────────────────────────────────────────────────

Use AskUserQuestion for action selection.


INVENTORY

Fetch all products and variant inventory:

# Get all products with variants
curl -s -H "$SHOPIFY_AUTH" \
  "${SHOPIFY_BASE}/products.json?limit=250&fields=id,title,status,variants" | \
  jq '[.products[] | {
    id: .id,
    title: .title,
    status: .status,
    variants: [.variants[] | {
      id: .id,
      title: .title,
      sku: .sku,
      inventory_quantity: .inventory_quantity,
      inventory_policy: .inventory_policy
    }]
  }]'

Flag low stock (inventory_quantity < 10) and out-of-stock (inventory_quantity <= 0).

Render:

━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
 OPS ► ECOM ► INVENTORY — [store] — [timestamp]
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

OUT OF STOCK
  [product] — [variant] — SKU: [sku]

LOW STOCK (< 10 units)
  [product] — [variant] — [N] units — SKU: [sku]

ALL PRODUCTS
  [product]
    [variant]  [N] units  SKU: [sku]
  ...

──────────────────────────────────────────────────────
 Actions:
 a) Update inventory for [product]
 b) Export inventory CSV
 c) Set reorder alerts
──────────────────────────────────────────────────────

Use AskUserQuestion for action selection.


FULFILLMENT

Fetch unfulfilled orders and ShipBob status (if token available):

# Unfulfilled orders
curl -s -H "$SHOPIFY_AUTH" \
  "${SHOPIFY_BASE}/orders.json?fulfillment_status=unfulfilled&status=open&limit=50" | \
  jq '[.orders[] | {
    id: .order_number,
    name: .name,
    customer: (.customer.first_name + " " + .customer.last_name),
    total: .total_price,
    created: .created_at,
    items: [.line_items[] | {title: .title, qty: .quantity}]
  }]'

# Shipments with tracking (fulfilled)
curl -s -H "$SHOPIFY_AUTH" \
  "${SHOPIFY_BASE}/orders.json?fulfillment_status=fulfilled&status=any&limit=20&order=updated_at+desc" | \
  jq '[.orders[] | .fulfillments[] | {
    order: .order_id,
    tracking_number: .tracking_number,
    tracking_url: .tracking_url,
    shipment_status: .shipment_status,
    carrier: .tracking_company,
    updated: .updated_at
  }]'

If $SHIPBOB_TOKEN is set, also query ShipBob:

# ShipBob pending shipments
curl -s -H "Authorization: Bearer ${SHIPBOB_TOKEN}" \
  "https://api.shipbob.com/1.0/shipment?Status=Processing&PageSize=20" | \
  jq '[.[] | {
    id: .id,
    status: .status,
    order_id: .reference_id,
    tracking: .tracking_number,
    created: .created_date
  }]'

Render fulfillment dashboard with pending/in-transit/delivered counts.

Use AskUserQuestion for action selection (mark fulfilled, update tracking, etc.).


HEALTH

Run the health check script, then augment with API checks:

${CLAUDE_PLUGIN_ROOT}/bin/ops-ecom-health 2>/dev/null || echo '{"error":"health script unavailable"}'

Also check:

# Active theme
curl -s -H "$SHOPIFY_AUTH" \
  "${SHOPIFY_BASE}/themes.json" | \
  jq '[.themes[] | select(.role == "main") | {id: .id, name: .name, updated: .updated_at}]'

# Store info
curl -s -H "$SHOPIFY_AUTH" \
  "${SHOPIFY_BASE}/shop.json" | \
  jq '.shop | {name: .name, domain: .domain, country: .country_name, plan: .plan_display_name, currency: .currency, timezone: .timezone}'

Render:

━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
 OPS ► ECOM ► HEALTH — [store] — [timestamp]
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

STORE
  Name:      [name]
  Plan:      [plan]
  Currency:  [currency]
  Timezone:  [tz]

API CONNECTIVITY  [OK / FAIL]
ACTIVE THEME      [theme name]
PRODUCT COUNT     [N] active
ORDERS (24h)      [N] orders

ISSUES
  [any warnings from health check]

──────────────────────────────────────────────────────
 Actions:
 a) Check theme assets for errors
 b) Run full SEO audit
 c) View API rate limit status
──────────────────────────────────────────────────────

Use AskUserQuestion for action selection.


PRODUCTS

List, search, and manage products:

# All products
curl -s -H "$SHOPIFY_AUTH" \
  "${SHOPIFY_BASE}/products.json?limit=250&order=updated_at+desc" | \
  jq '[.products[] | {
    id: .id,
    title: .title,
    status: .status,
    handle: .handle,
    price: (.variants[0].price // "N/A"),
    inventory: ([.variants[].inventory_quantity] | add // 0),
    variants: (.variants | length),
    updated: .updated_at
  }]'

If $ARGUMENTS contains a search term (e.g., products shoes), filter by title.

For price updates, use:

# Update variant price
curl -s -X PUT -H "$SHOPIFY_AUTH" -H "Content-Type: application/json" \
  "${SHOPIFY_BASE}/variants/${VARIANT_ID}.json" \
  -d '{"variant":{"id":'${VARIANT_ID}',"price":"'${NEW_PRICE}'"}}'

Use AskUserQuestion before making any product updates. Show before/after prices.


CUSTOMERS

Fetch customer stats:

# Customer count and recent
curl -s -H "$SHOPIFY_AUTH" \
  "${SHOPIFY_BASE}/customers.json?limit=50&order=created_at+desc" | \
  jq '{
    total_shown: (.customers | length),
    recent: [.customers[:10] | .[] | {
      id: .id,
      name: (.first_name + " " + .last_name),
      email: .email,
      orders: .orders_count,
      total_spent: .total_spent,
      currency: .currency,
      created: .created_at
    }]
  }'

# Top customers by spend
curl -s -H "$SHOPIFY_AUTH" \
  "${SHOPIFY_BASE}/customers.json?limit=10&order=total_spent+desc" | \
  jq '[.customers[] | {name: (.first_name + " " + .last_name), orders: .orders_count, spent: .total_spent}]'

Render customer overview with LTV stats and top spenders.


ANALYTICS

Pull revenue and order data for dashboard:

TODAY=$(date -u +"%Y-%m-%dT00:00:00Z")
WEEK_AGO=$(date -u -v-7d +"%Y-%m-%dT00:00:00Z" 2>/dev/null || date -u -d "7 days ago" +"%Y-%m-%dT00:00:00Z")
MONTH_AGO=$(date -u -v-30d +"%Y-%m-%dT00:00:00Z" 2>/dev/null || date -u -d "30 days ago" +"%Y-%m-%dT00:00:00Z")

# Orders for revenue calculation
curl -s -H "$SHOPIFY_AUTH" \
  "${SHOPIFY_BASE}/orders.json?status=any&financial_status=paid&created_at_min=${MONTH_AGO}&limit=250" | \
  jq '{
    month_orders: (.orders | length),
    month_revenue: ([.orders[].total_price | tonumber] | add // 0),
    week_orders: ([.orders[] | select(.created_at >= "'"$WEEK_AGO"'")] | length),
    week_revenue: ([.orders[] | select(.created_at >= "'"$WEEK_AGO"'") | .total_price | tonumber] | add // 0),
    today_orders: ([.orders[] | select(.created_at >= "'"$TODAY"'")] | length),
    today_revenue: ([.orders[] | select(.created_at >= "'"$TODAY"'") | .total_price | tonumber] | add // 0),
    avg_order_value: ([.orders[].total_price | tonumber] | (add // 0) / (length // 1)),
    top_products: ([.orders[].line_items[] | {title: .title, qty: .quantity}] | group_by(.title) | map({title: .[0].title, total_qty: map(.qty) | add}) | sort_by(-.total_qty)[:5])
  }'

Render:

━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
 OPS ► ECOM ► ANALYTICS — [store] — [timestamp]
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

REVENUE
  Today    $[amount]  ([N] orders)
  7 days   $[amount]  ([N] orders)
  30 days  $[amount]  ([N] orders)

AVERAGES
  AOV (30d)  $[amount]

TOP PRODUCTS (30d)
  1. [product]  [N] sold
  2. [product]  [N] sold
  ...

──────────────────────────────────────────────────────
 Actions:
 a) Export revenue report (CSV)
 b) View by product breakdown
 c) Compare to previous period
──────────────────────────────────────────────────────

Use AskUserQuestion for action selection.



CHANNELS

List publications / sales channels from Shopify Admin GraphQL. Prefer live API; fall back to ecom.shopify.sales_channels cache if API fails.

# GraphQL publications (use store's configured API version when set; default 2024-10+)
curl -s -X POST -H "$SHOPIFY_AUTH" -H "Content-Type: application/json" \
  "$SHOPIFY_GQL" \
  -d '{"query":"{ publications(first: 50) { nodes { id name autoPublish app { title handle } } } channels(first: 50) { nodes { name handle app { title handle } } } }"}' | \
  jq '{
    publications: [.data.publications.nodes[]? | {id, name, autoPublish, app: .app.title}],
    channels: [.data.channels.nodes[]? | {name, handle, app: .app.title}]
  }'

Also merge operator prefs when present:

jq -r '.ecom.shopify.sales_channels // empty' "$PREFS_PATH" 2>/dev/null

Render:

━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
 OPS ► ECOM ► CHANNELS — [store] — [timestamp]
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

PUBLICATIONS / CHANNELS (live)
  [name]  handle=[…]  app=[…]  autoPublish=[true|false]
  ...

KNOWN GAPS (prefs / docs — not always in API list)
  Agentic Storefronts app     [present|missing|unknown]
  ChatGPT / OpenAI channel    [present|missing|unknown]
  Google AI Mode / Gemini     [present|missing|unknown]
  Shop channel                [present|missing|unknown]
  Microsoft Copilot           [present|missing|unknown]

──────────────────────────────────────────────────────
 Actions:
 a) Re-probe GraphQL publications
 b) Open agentic health  (/ops:ecom agentic)
 c) Open Shop readiness  (/ops:ecom shop)
──────────────────────────────────────────────────────

Guardrails: publication names vary by locale; match case-insensitively. Do not invent channels. Unknown → unknown. Write-back of probe cache only with operator OK.


AGENTIC

Agentic storefront health: which AI commerce surfaces are enabled and whether products are published to them.

# Reuse CHANNELS probe; optional prefs:
jq -r '.ecom.shopify.agentic_storefronts // empty' "$PREFS_PATH" 2>/dev/null
jq -r '.marketing.projects // {} | to_entries[]? | select(.value.agentic_storefronts != null) | {project: .key, agentic: .value.agentic_storefronts}' "$PREFS_PATH" 2>/dev/null

Render:

━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
 OPS ► ECOM ► AGENTIC — [store] — [timestamp]
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

AI / AGENTIC SURFACES
  Microsoft Copilot     [on|off|unknown]  channel=[name|—]
  ChatGPT / OpenAI      [on|off|unknown]
  Google AI Mode        [on|off|unknown]
  (other detected)      ...

PRODUCT COVERAGE
  Sampled: [N] products  published_to_agentic: [M]  unknown: [K]

DOCS
  https://help.shopify.com/en/manual/online-sales-channels/agentic-storefronts

──────────────────────────────────────────────────────
 Actions:
 a) Re-probe publications
 b) List channels  (/ops:ecom channels)
 c) Shop readiness (/ops:ecom shop)
──────────────────────────────────────────────────────

Guardrails: Read-only. Never install apps or publish products without explicit operator confirmation (Rule 5). If API cannot list agentic apps, say so — do not claim disabled.


SHOP

Shop sales channel + Shop Campaigns readiness (pay-per-sale on Shop and expanded surfaces).

# Detect Shop from publications/channels (name/handle contains shop; avoid false positives)
jq -r '
  .ecom.shopify.shop // empty,
  (.marketing.projects // {} | to_entries[]? | select(.value.shop_campaigns != null) | {project: .key, shop_campaigns: .value.shop_campaigns})
' "$PREFS_PATH" 2>/dev/null

Render:

━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
 OPS ► ECOM ► SHOP — [store] — [timestamp]
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

SHOP CHANNEL
  Publication:  [present|missing|unknown]
  Products:     [sample published count or unknown]

SHOP CAMPAIGNS (prefs + admin)
  Configured in prefs:  [yes|no]
  Status:               [not_configured|stage_only|active|unknown]
  Budget approval:      [required|granted|n/a]

DOCS
  https://help.shopify.com/en/manual/online-sales-channels/shop/shop-campaigns
  https://www.shopify.com/shop-campaigns

──────────────────────────────────────────────────────
 NEVER LEAK MONEY: never create or raise Shop Campaigns
 budgets without explicit owner approval (Rule 5).
──────────────────────────────────────────────────────

SETUP FLOW

Before asking the user for anything, auto-discover store URLs and tokens. Run ALL of these scans in a single batch:

# 1. Env vars
printenv SHOPIFY_ACCESS_TOKEN SHOPIFY_ADMIN_TOKEN SHOPIFY_STORE_URL SHOPIFY_ADMIN_API_ACCESS_TOKEN 2>/dev/null

# 2. Shell profiles
grep -h 'SHOPIFY\|myshopify' ~/.zshrc ~/.bashrc ~/.zprofile ~/.envrc 2>/dev/null | grep -v '^#'

# 3. Doppler — ALL projects, not just default
for proj in $(doppler projects --json 2>/dev/null | jq -r '.[].slug'); do
  doppler secrets --project "$proj" --config prd --json 2>/dev/null | \
    jq -r --arg proj "$proj" 'to_entries[] | select(.key | test("SHOPIFY|STORE"; "i")) | "\(.key)=\(.value.computed) (doppler:\($proj)/prd)"'
done

# 4. Dashlane — URLs reveal store identity
dcli password shopify --output json 2>/dev/null | jq -r '.[].url // empty' | grep -oE '[a-z0-9-]+\.myshopify\.com' | sort -u

# 5. Keychain
security find-generic-password -s "shopify-admin-token" -w 2>/dev/null
security find-generic-password -s "shopify-access-token" -w 2>/dev/null

# 6. Chrome history — reveals store URLs from admin sessions
sqlite3 ~/Library/Application\ Support/Google/Chrome/Default/History \
  "SELECT DISTINCT url FROM urls WHERE url LIKE '%myshopify.com/admin%' OR url LIKE '%admin.shopify.com/store/%' ORDER BY last_visit_time DESC LIMIT 10" 2>/dev/null | \
  grep -oE '[a-z0-9-]+\.myshopify\.com|admin\.shopify\.com/store/[a-z0-9-]+' | sort -u

# 7. Project .env files
grep -rhE 'myshopify\.com|SHOPIFY_STORE|SHOPIFY.*TOKEN' ~/Projects/*/.env* 2>/dev/null | grep -v '^#' | head -5

# 8. Existing prefs + userConfig
jq -r '.ecom.shopify // empty' "$PREFS_PATH" 2>/dev/null

Token acquisition — automate before asking. If store URL found but no token:

  1. Doppler deep scan — check ALL projects/configs (dev, staging, prd)
  2. Shopify CLI — if command -v shopify succeeds: shopify auth login --store <store>.myshopify.com (opens browser OAuth), then generate token
  3. Browser automation — if Kapture/Playwright available, navigate to admin.shopify.com/store/<slug>/settings/apps/development and automate app creation with scopes: read_orders,write_orders,read_products,write_products,read_customers,read_inventory,write_inventory,read_fulfillments,write_fulfillments,read_analytics
  4. Manual fallback — only if all automated approaches fail:
No automated path for <store>.myshopify.com.
  1. Go to https://admin.shopify.com/store/<slug>/settings/apps/development
  2. Create an app → Configure → grant scopes → Install → copy token
  Token starts with "shpat_"

Multi-store: If multiple stores discovered, process each independently. Present all found stores with their token status before asking for input.

Store credentials via: userConfig (preferred) → Doppler → env vars. ShipBob optional — check SHIPBOB_ACCESS_TOKEN in same scan.

Verify connectivity after acquisition:

curl -s -H "X-Shopify-Access-Token: ${PROVIDED_TOKEN}" \
  "https://${PROVIDED_STORE}/admin/api/2024-10/shop.json" | \
  jq '.shop | {name, domain, plan: .plan_display_name}'

If the connectivity check returns valid shop data, confirm success. If it fails with 401/403, explain the token is invalid and re-prompt.


STORE SUMMARY (empty $ARGUMENTS)

When called with no arguments, show a compact store overview:

Competitor activity (gather before rendering)
# Resolve PLUGIN_ROOT: directory containing the ops-ops-marketplace plugin scripts
_COMP_PLUGIN_ROOT="${CLAUDE_PLUGIN_ROOT:-$(dirname "$(dirname "$(realpath "${BASH_SOURCE[0]}" 2>/dev/null || echo "$0")")")}"
_COMP_LIB="$_COMP_PLUGIN_ROOT/scripts/lib/competitor/context.sh"
_COMP_ECOM_SIGNALS="[]"
if [[ -f "$_COMP_LIB" ]]; then
  # shellcheck source=/dev/null
  . "$_COMP_LIB"
  _COMP_ECOM_SIGNALS=$(competitor_vertical_slice ecom --window-days 7 2>/dev/null || echo "[]")
fi

Parse _COMP_ECOM_SIGNALS JSON array. If it is non-empty, append the COMPETITOR ACTIVITY section after FULFILLMENT in the store summary render. If it is [], skip the section entirely.

Render using this shape (mobile mode: plain text, no banners; desktop: with ━━━ rules):

━━━ COMPETITOR ACTIVITY (last 7d) ━━━
APP RELEASES
  • [competitor] — v[version] released this week (rating: [X.X], [N]k reviews)
PRODUCT / PRICING CHANGES
  • [competitor] — pricing page changed ([low/med/high] severity)
  • [competitor] — features page changed ([brief description from snippet])

Mapping rules (apply per event in the JSON array):

  • source == "appstore" → APP RELEASES entry. Extract version, rating, and review count from snippet if present.
  • source == "page-diff" and kind matches features → PRODUCT / PRICING CHANGES entry, labelled as features change.
  • source == "page-diff" and kind == "pricing" → PRODUCT / PRICING CHANGES entry, labelled as pricing change with severity from severity field.

Run orders, inventory, and health checks in parallel (separate Bash calls), then render:

━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
 OPS ► ECOM — [store] — [timestamp]
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

STORE         [name] ([plan])
CURRENCY      [currency]

TODAY         $[revenue]  [N] orders
7 DAYS        $[revenue]  [N] orders
30 DAYS       $[revenue]  [N] orders

INVENTORY     [N] products  |  [N] low stock  |  [N] out of stock
FULFILLMENT   [N] unfulfilled orders pending

[COMPETITOR ACTIVITY section here if non-empty — see above]

──────────────────────────────────────────────────────
 /ops:ops-ecom orders     — order management
 /ops:ops-ecom inventory  — stock levels
 /ops:ops-ecom products   — product catalog
 /ops:ops-ecom customers  — customer stats
 /ops:ops-ecom analytics  — revenue dashboard
 /ops:ops-ecom health     — store health check
 /ops:ops-ecom channels   — sales channel inventory
 /ops:ops-ecom agentic    — agentic storefront health
 /ops:ops-ecom shop       — Shop channel + Shop Campaigns readiness
 /ops:ops-ecom setup      — configure credentials
──────────────────────────────────────────────────────

Windsor.ai (optional live data)

If Windsor.ai is connected (mcp__*Windsor*__* or a windsor_api_key), use it as the live cross-channel source for the GA4 acquisition funnel (sessions → checkout → purchase) and ad-driven traffic layered over store orders. Map accounts per project via registry.json → .projects[].windsor. Prefer blended ROAS (store/analytics revenue ÷ total ad spend) over platform-reported ROAS. See docs/integrations/windsor-ai.md for the full playbook (REST + MCP modes, registry mapping, analysis mandate, and caveats). Data sanity: if Windsor returns only zeros across sources (Meta + Google spend/impressions and Instagram reach all exactly 0 over 30d while accounts are connected), treat the data as unavailable — the plan may be expired (check get_current_user → is_paid) — warn the user, and never present zeros as real metrics. scripts/windsor-data-sanity.sh automates the check.

Windsor is optional. If Windsor is not connected, returns errors, or returns the all-zero pattern, fall back to the free direct libraries: scripts/lib/ad-spend-aggregator.sh (paid), scripts/lib/ga4-data-api.sh (analytics), and scripts/lib/organic-metrics-aggregator.sh (organic + merchant). See docs/integrations/direct-channel-wiring.md. Never present zeros from a dead source as real metrics.

Additional resources

CLI detail: references/cli.md.

1---
2name: ops-ecom
3description: "OPS on-demand: This skill should be used when the user asks to \"shopify\", \"orders inventory\", or…"
4argument-hint: '[orders|inventory|fulfillment|health|products|customers|analytics|channels|agentic|shop|setup]'
5allowed-tools:
6 - Bash
7 - Read
8 - Write
9 - Grep
10 - Glob
11 - Agent
12 - TeamCreate
13 - SendMessage
14 - AskUserQuestion
15 - WebFetch
16 - WebSearch
17effort: medium
18maxTurns: 40
19context: fork
20---
21 
22# OPS ► ECOM — Shopify Store Command Center
23 
24Load `ops-rules` before acting. Public repo (no personal data). Outbound: one draft → one approval → one send. If `AskUserQuestion` / `Workflow` are missing, follow Rule 10 in `ops-rules` (Hermes: numbered options / two-turn Telegram card; `delegate_task`).
25 
26## Runtime Context
27 
28Before executing, load available context:
29 
301. **Preferences**: Read `${CLAUDE_PLUGIN_DATA_DIR:-$HOME/.claude/plugins/data/ops-ops-marketplace}/preferences.json`
31 - `timezone` — display all timestamps correctly
32 - `shopify_store_url`, `shopify_admin_token` — check userConfig keys before env vars
33 - `ecom.shopify.sales_channels` — optional last probe cache (publications + agentic/Shop gaps)
34 - `ecom.shopify.agentic_storefronts` — optional operator-declared enablement map (channel → status)
35 
362. **Daemon health**: Read `${CLAUDE_PLUGIN_DATA_DIR}/daemon-health.json`
37 - If `action_needed` is not null → surface it before running any store operations
38 
393. **Secrets**: Resolve Shopify credentials via userConfig → env vars → Doppler (see Phase 1 below)
40 
41## Agent Teams support
42 
43If `CLAUDE_CODE_EXPERIMENTAL_AGENT_TEAMS=1` is set, use **Agent Teams** when probing store data in parallel. This enables:
44 
45- Agents share context and can coordinate mid-flight
46- You can steer priorities in real-time
47- Agents report progress as they complete
48 
49**Team setup** (only when flag is enabled):
50 
51```
52TeamCreate("ecom-team")
53Agent(team_name="ecom-team", name="orders-scanner", prompt="Fetch recent orders, compute revenue for today/7d/30d")
54Agent(team_name="ecom-team", name="inventory-scanner", prompt="Fetch all products, flag low stock and out-of-stock items")
55Agent(team_name="ecom-team", name="fulfillment-scanner", prompt="Fetch unfulfilled orders and ShipBob shipment status")
56Agent(team_name="ecom-team", name="analytics-scanner", prompt="Compute revenue analytics, AOV, and top products for 30d")
57```
58 
59If the flag is NOT set, use standard fire-and-forget subagents.
60 
61## Phase 1 — Resolve credentials
62 
63Resolve Shopify credentials in this order:
64 
65```bash
66# 1. Plugin userConfig
67SHOPIFY_STORE="${user_config.shopify_store_url}"
68SHOPIFY_TOKEN="${user_config.shopify_admin_token}"
69SHIPBOB_TOKEN="${user_config.shipbob_access_token}"
70 
71# 2. Environment variables (override userConfig if set)
72[ -n "$SHOPIFY_STORE_URL" ] && SHOPIFY_STORE="$SHOPIFY_STORE_URL"
73[ -n "$SHOPIFY_ACCESS_TOKEN" ] && SHOPIFY_TOKEN="$SHOPIFY_ACCESS_TOKEN"
74[ -n "$SHIPBOB_ACCESS_TOKEN" ] && SHIPBOB_TOKEN="$SHIPBOB_ACCESS_TOKEN"
75 
76# 3. Doppler fallback
77if [ -z "$SHOPIFY_TOKEN" ] && command -v doppler &>/dev/null; then
78 SHOPIFY_TOKEN=$(doppler secrets get SHOPIFY_ACCESS_TOKEN --plain 2>/dev/null)
79fi
80if [ -z "$SHOPIFY_STORE" ] && command -v doppler &>/dev/null; then
81 SHOPIFY_STORE=$(doppler secrets get SHOPIFY_STORE_URL --plain 2>/dev/null)
82fi
83if [ -z "$SHIPBOB_TOKEN" ] && command -v doppler &>/dev/null; then
84 SHIPBOB_TOKEN=$(doppler secrets get SHIPBOB_ACCESS_TOKEN --plain 2>/dev/null)
85fi
86```
87 
88If `$SHOPIFY_STORE` or `$SHOPIFY_TOKEN` is still empty after all resolution steps, route to **setup flow** below.
89 
90Set base URLs:
91 
92```bash
93SHOPIFY_BASE="https://${SHOPIFY_STORE}/admin/api/2024-10"
94SHOPIFY_GQL="https://${SHOPIFY_STORE}/admin/api/2024-10/graphql.json"
95SHOPIFY_AUTH="X-Shopify-Access-Token: ${SHOPIFY_TOKEN}"
96```
97 
98---
99 
100## Phase 2 — Route by $ARGUMENTS
101 
102| Input | Action |
103| --------------------------------------- | ------------------- |
104| (empty) | Show store summary |
105| orders, order | Orders dashboard |
106| inventory, stock, inv | Inventory levels |
107| fulfillment, fulfill, shipbob, shipping | Fulfillment status |
108| health, check, status | Store health check |
109| products, product, catalog | Products manager |
110| customers, customer, crm | Customer stats |
111| analytics, revenue, stats, metrics | Analytics dashboard |
112| channels, publications, sales-channels | Sales channel inventory (publications + gaps) |
113| agentic, agentic-storefronts, ai-channels | Agentic storefront health |
114| shop, shop-channel, shop-campaigns | Shop channel + Shop Campaigns readiness |
115| setup, configure, init, token | Setup flow |
116 
117---
118 
119## ORDERS
120 
121Fetch recent orders and compute revenue:
122 
123```bash
124TODAY=$(date -u +"%Y-%m-%dT00:00:00Z")
125WEEK_AGO=$(date -u -v-7d +"%Y-%m-%dT00:00:00Z" 2>/dev/null || date -u -d "7 days ago" +"%Y-%m-%dT00:00:00Z")
126MONTH_AGO=$(date -u -v-30d +"%Y-%m-%dT00:00:00Z" 2>/dev/null || date -u -d "30 days ago" +"%Y-%m-%dT00:00:00Z")
127 
128# Recent orders (last 50)
129curl -s -H "$SHOPIFY_AUTH" \
130 "${SHOPIFY_BASE}/orders.json?status=any&limit=50&order=created_at+desc" | \
131 jq '{
132 total: .orders | length,
133 today: [.orders[] | select(.created_at >= "'"$TODAY"'")],
134 orders: [.orders[:10] | .[] | {
135 id: .order_number,
136 name: .name,
137 status: .financial_status,
138 fulfillment: .fulfillment_status,
139 total: .total_price,
140 currency: .currency,
141 customer: (.customer.first_name + " " + .customer.last_name),
142 created: .created_at
143 }]
144 }'
145```
146 
147Render:
148 
149```
150━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
151 OPS ► ECOM ► ORDERS — [store] — [timestamp]
152━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
153 
154REVENUE
155 Today [N orders] $[amount]
156 7 days [N orders] $[amount]
157 30 days [N orders] $[amount]
158 
159RECENT ORDERS
160 #[id] [customer] $[total] [status] / [fulfillment] [age]
161 ...
162 
163──────────────────────────────────────────────────────
164 Actions:
165 a) View order details for #[id]
166 b) Mark order as fulfilled
167 c) Export orders CSV
168 d) Filter by status (unfulfilled/refunded/paid)
169──────────────────────────────────────────────────────
170```
171 
172Use `AskUserQuestion` for action selection.
173 
174---
175 
176## INVENTORY
177 
178Fetch all products and variant inventory:
179 
180```bash
181# Get all products with variants
182curl -s -H "$SHOPIFY_AUTH" \
183 "${SHOPIFY_BASE}/products.json?limit=250&fields=id,title,status,variants" | \
184 jq '[.products[] | {
185 id: .id,
186 title: .title,
187 status: .status,
188 variants: [.variants[] | {
189 id: .id,
190 title: .title,
191 sku: .sku,
192 inventory_quantity: .inventory_quantity,
193 inventory_policy: .inventory_policy
194 }]
195 }]'
196```
197 
198Flag low stock (inventory_quantity < 10) and out-of-stock (inventory_quantity <= 0).
199 
200Render:
201 
202```
203━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
204 OPS ► ECOM ► INVENTORY — [store] — [timestamp]
205━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
206 
207OUT OF STOCK
208 [product] — [variant] — SKU: [sku]
209 
210LOW STOCK (< 10 units)
211 [product] — [variant] — [N] units — SKU: [sku]
212 
213ALL PRODUCTS
214 [product]
215 [variant] [N] units SKU: [sku]
216 ...
217 
218──────────────────────────────────────────────────────
219 Actions:
220 a) Update inventory for [product]
221 b) Export inventory CSV
222 c) Set reorder alerts
223──────────────────────────────────────────────────────
224```
225 
226Use `AskUserQuestion` for action selection.
227 
228---
229 
230## FULFILLMENT
231 
232Fetch unfulfilled orders and ShipBob status (if token available):
233 
234```bash
235# Unfulfilled orders
236curl -s -H "$SHOPIFY_AUTH" \
237 "${SHOPIFY_BASE}/orders.json?fulfillment_status=unfulfilled&status=open&limit=50" | \
238 jq '[.orders[] | {
239 id: .order_number,
240 name: .name,
241 customer: (.customer.first_name + " " + .customer.last_name),
242 total: .total_price,
243 created: .created_at,
244 items: [.line_items[] | {title: .title, qty: .quantity}]
245 }]'
246 
247# Shipments with tracking (fulfilled)
248curl -s -H "$SHOPIFY_AUTH" \
249 "${SHOPIFY_BASE}/orders.json?fulfillment_status=fulfilled&status=any&limit=20&order=updated_at+desc" | \
250 jq '[.orders[] | .fulfillments[] | {
251 order: .order_id,
252 tracking_number: .tracking_number,
253 tracking_url: .tracking_url,
254 shipment_status: .shipment_status,
255 carrier: .tracking_company,
256 updated: .updated_at
257 }]'
258```
259 
260If `$SHIPBOB_TOKEN` is set, also query ShipBob:
261 
262```bash
263# ShipBob pending shipments
264curl -s -H "Authorization: Bearer ${SHIPBOB_TOKEN}" \
265 "https://api.shipbob.com/1.0/shipment?Status=Processing&PageSize=20" | \
266 jq '[.[] | {
267 id: .id,
268 status: .status,
269 order_id: .reference_id,
270 tracking: .tracking_number,
271 created: .created_date
272 }]'
273```
274 
275Render fulfillment dashboard with pending/in-transit/delivered counts.
276 
277Use `AskUserQuestion` for action selection (mark fulfilled, update tracking, etc.).
278 
279---
280 
281## HEALTH
282 
283Run the health check script, then augment with API checks:
284 
285```bash
286${CLAUDE_PLUGIN_ROOT}/bin/ops-ecom-health 2>/dev/null || echo '{"error":"health script unavailable"}'
287```
288 
289Also check:
290 
291```bash
292# Active theme
293curl -s -H "$SHOPIFY_AUTH" \
294 "${SHOPIFY_BASE}/themes.json" | \
295 jq '[.themes[] | select(.role == "main") | {id: .id, name: .name, updated: .updated_at}]'
296 
297# Store info
298curl -s -H "$SHOPIFY_AUTH" \
299 "${SHOPIFY_BASE}/shop.json" | \
300 jq '.shop | {name: .name, domain: .domain, country: .country_name, plan: .plan_display_name, currency: .currency, timezone: .timezone}'
301```
302 
303Render:
304 
305```
306━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
307 OPS ► ECOM ► HEALTH — [store] — [timestamp]
308━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
309 
310STORE
311 Name: [name]
312 Plan: [plan]
313 Currency: [currency]
314 Timezone: [tz]
315 
316API CONNECTIVITY [OK / FAIL]
317ACTIVE THEME [theme name]
318PRODUCT COUNT [N] active
319ORDERS (24h) [N] orders
320 
321ISSUES
322 [any warnings from health check]
323 
324──────────────────────────────────────────────────────
325 Actions:
326 a) Check theme assets for errors
327 b) Run full SEO audit
328 c) View API rate limit status
329──────────────────────────────────────────────────────
330```
331 
332Use `AskUserQuestion` for action selection.
333 
334---
335 
336## PRODUCTS
337 
338List, search, and manage products:
339 
340```bash
341# All products
342curl -s -H "$SHOPIFY_AUTH" \
343 "${SHOPIFY_BASE}/products.json?limit=250&order=updated_at+desc" | \
344 jq '[.products[] | {
345 id: .id,
346 title: .title,
347 status: .status,
348 handle: .handle,
349 price: (.variants[0].price // "N/A"),
350 inventory: ([.variants[].inventory_quantity] | add // 0),
351 variants: (.variants | length),
352 updated: .updated_at
353 }]'
354```
355 
356If `$ARGUMENTS` contains a search term (e.g., `products shoes`), filter by title.
357 
358For price updates, use:
359 
360```bash
361# Update variant price
362curl -s -X PUT -H "$SHOPIFY_AUTH" -H "Content-Type: application/json" \
363 "${SHOPIFY_BASE}/variants/${VARIANT_ID}.json" \
364 -d '{"variant":{"id":'${VARIANT_ID}',"price":"'${NEW_PRICE}'"}}'
365```
366 
367Use `AskUserQuestion` before making any product updates. Show before/after prices.
368 
369---
370 
371## CUSTOMERS
372 
373Fetch customer stats:
374 
375```bash
376# Customer count and recent
377curl -s -H "$SHOPIFY_AUTH" \
378 "${SHOPIFY_BASE}/customers.json?limit=50&order=created_at+desc" | \
379 jq '{
380 total_shown: (.customers | length),
381 recent: [.customers[:10] | .[] | {
382 id: .id,
383 name: (.first_name + " " + .last_name),
384 email: .email,
385 orders: .orders_count,
386 total_spent: .total_spent,
387 currency: .currency,
388 created: .created_at
389 }]
390 }'
391 
392# Top customers by spend
393curl -s -H "$SHOPIFY_AUTH" \
394 "${SHOPIFY_BASE}/customers.json?limit=10&order=total_spent+desc" | \
395 jq '[.customers[] | {name: (.first_name + " " + .last_name), orders: .orders_count, spent: .total_spent}]'
396```
397 
398Render customer overview with LTV stats and top spenders.
399 
400---
401 
402## ANALYTICS
403 
404Pull revenue and order data for dashboard:
405 
406```bash
407TODAY=$(date -u +"%Y-%m-%dT00:00:00Z")
408WEEK_AGO=$(date -u -v-7d +"%Y-%m-%dT00:00:00Z" 2>/dev/null || date -u -d "7 days ago" +"%Y-%m-%dT00:00:00Z")
409MONTH_AGO=$(date -u -v-30d +"%Y-%m-%dT00:00:00Z" 2>/dev/null || date -u -d "30 days ago" +"%Y-%m-%dT00:00:00Z")
410 
411# Orders for revenue calculation
412curl -s -H "$SHOPIFY_AUTH" \
413 "${SHOPIFY_BASE}/orders.json?status=any&financial_status=paid&created_at_min=${MONTH_AGO}&limit=250" | \
414 jq '{
415 month_orders: (.orders | length),
416 month_revenue: ([.orders[].total_price | tonumber] | add // 0),
417 week_orders: ([.orders[] | select(.created_at >= "'"$WEEK_AGO"'")] | length),
418 week_revenue: ([.orders[] | select(.created_at >= "'"$WEEK_AGO"'") | .total_price | tonumber] | add // 0),
419 today_orders: ([.orders[] | select(.created_at >= "'"$TODAY"'")] | length),
420 today_revenue: ([.orders[] | select(.created_at >= "'"$TODAY"'") | .total_price | tonumber] | add // 0),
421 avg_order_value: ([.orders[].total_price | tonumber] | (add // 0) / (length // 1)),
422 top_products: ([.orders[].line_items[] | {title: .title, qty: .quantity}] | group_by(.title) | map({title: .[0].title, total_qty: map(.qty) | add}) | sort_by(-.total_qty)[:5])
423 }'
424```
425 
426Render:
427 
428```
429━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
430 OPS ► ECOM ► ANALYTICS — [store] — [timestamp]
431━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
432 
433REVENUE
434 Today $[amount] ([N] orders)
435 7 days $[amount] ([N] orders)
436 30 days $[amount] ([N] orders)
437 
438AVERAGES
439 AOV (30d) $[amount]
440 
441TOP PRODUCTS (30d)
442 1. [product] [N] sold
443 2. [product] [N] sold
444 ...
445 
446──────────────────────────────────────────────────────
447 Actions:
448 a) Export revenue report (CSV)
449 b) View by product breakdown
450 c) Compare to previous period
451──────────────────────────────────────────────────────
452```
453 
454Use `AskUserQuestion` for action selection.
455 
456---
457 
458 
459---
460 
461## CHANNELS
462 
463List publications / sales channels from Shopify Admin GraphQL. Prefer live API; fall back to `ecom.shopify.sales_channels` cache if API fails.
464 
465```bash
466# GraphQL publications (use store's configured API version when set; default 2024-10+)
467curl -s -X POST -H "$SHOPIFY_AUTH" -H "Content-Type: application/json" \
468 "$SHOPIFY_GQL" \
469 -d '{"query":"{ publications(first: 50) { nodes { id name autoPublish app { title handle } } } channels(first: 50) { nodes { name handle app { title handle } } } }"}' | \
470 jq '{
471 publications: [.data.publications.nodes[]? | {id, name, autoPublish, app: .app.title}],
472 channels: [.data.channels.nodes[]? | {name, handle, app: .app.title}]
473 }'
474```
475 
476Also merge operator prefs when present:
477 
478```bash
479jq -r '.ecom.shopify.sales_channels // empty' "$PREFS_PATH" 2>/dev/null
480```
481 
482**Render:**
483 
484```
485━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
486 OPS ► ECOM ► CHANNELS — [store] — [timestamp]
487━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
488 
489PUBLICATIONS / CHANNELS (live)
490 [name] handle=[…] app=[…] autoPublish=[true|false]
491 ...
492 
493KNOWN GAPS (prefs / docs — not always in API list)
494 Agentic Storefronts app [present|missing|unknown]
495 ChatGPT / OpenAI channel [present|missing|unknown]
496 Google AI Mode / Gemini [present|missing|unknown]
497 Shop channel [present|missing|unknown]
498 Microsoft Copilot [present|missing|unknown]
499 
500──────────────────────────────────────────────────────
501 Actions:
502 a) Re-probe GraphQL publications
503 b) Open agentic health (/ops:ecom agentic)
504 c) Open Shop readiness (/ops:ecom shop)
505──────────────────────────────────────────────────────
506```
507 
508**Guardrails:** publication names vary by locale; match case-insensitively. Do not invent channels. Unknown → `unknown`. Write-back of probe cache only with operator OK.
509 
510---
511 
512## AGENTIC
513 
514Agentic storefront health: which AI commerce surfaces are enabled and whether products are published to them.
515 
516```bash
517# Reuse CHANNELS probe; optional prefs:
518jq -r '.ecom.shopify.agentic_storefronts // empty' "$PREFS_PATH" 2>/dev/null
519jq -r '.marketing.projects // {} | to_entries[]? | select(.value.agentic_storefronts != null) | {project: .key, agentic: .value.agentic_storefronts}' "$PREFS_PATH" 2>/dev/null
520```
521 
522**Render:**
523 
524```
525━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
526 OPS ► ECOM ► AGENTIC — [store] — [timestamp]
527━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
528 
529AI / AGENTIC SURFACES
530 Microsoft Copilot [on|off|unknown] channel=[name|—]
531 ChatGPT / OpenAI [on|off|unknown]
532 Google AI Mode [on|off|unknown]
533 (other detected) ...
534 
535PRODUCT COVERAGE
536 Sampled: [N] products published_to_agentic: [M] unknown: [K]
537 
538DOCS
539 https://help.shopify.com/en/manual/online-sales-channels/agentic-storefronts
540 
541──────────────────────────────────────────────────────
542 Actions:
543 a) Re-probe publications
544 b) List channels (/ops:ecom channels)
545 c) Shop readiness (/ops:ecom shop)
546──────────────────────────────────────────────────────
547```
548 
549**Guardrails:** Read-only. Never install apps or publish products without explicit operator confirmation (Rule 5). If API cannot list agentic apps, say so — do not claim disabled.
550 
551---
552 
553## SHOP
554 
555Shop sales channel + Shop Campaigns readiness (pay-per-sale on Shop and expanded surfaces).
556 
557```bash
558# Detect Shop from publications/channels (name/handle contains shop; avoid false positives)
559jq -r '
560 .ecom.shopify.shop // empty,
561 (.marketing.projects // {} | to_entries[]? | select(.value.shop_campaigns != null) | {project: .key, shop_campaigns: .value.shop_campaigns})
562' "$PREFS_PATH" 2>/dev/null
563```
564 
565**Render:**
566 
567```
568━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
569 OPS ► ECOM ► SHOP — [store] — [timestamp]
570━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
571 
572SHOP CHANNEL
573 Publication: [present|missing|unknown]
574 Products: [sample published count or unknown]
575 
576SHOP CAMPAIGNS (prefs + admin)
577 Configured in prefs: [yes|no]
578 Status: [not_configured|stage_only|active|unknown]
579 Budget approval: [required|granted|n/a]
580 
581DOCS
582 https://help.shopify.com/en/manual/online-sales-channels/shop/shop-campaigns
583 https://www.shopify.com/shop-campaigns
584 
585──────────────────────────────────────────────────────
586 NEVER LEAK MONEY: never create or raise Shop Campaigns
587 budgets without explicit owner approval (Rule 5).
588──────────────────────────────────────────────────────
589```
590 
591 
592## SETUP FLOW
593 
594**Before asking the user for anything**, auto-discover store URLs and tokens. Run ALL of these scans in a single batch:
595 
596```bash
597# 1. Env vars
598printenv SHOPIFY_ACCESS_TOKEN SHOPIFY_ADMIN_TOKEN SHOPIFY_STORE_URL SHOPIFY_ADMIN_API_ACCESS_TOKEN 2>/dev/null
599 
600# 2. Shell profiles
601grep -h 'SHOPIFY\|myshopify' ~/.zshrc ~/.bashrc ~/.zprofile ~/.envrc 2>/dev/null | grep -v '^#'
602 
603# 3. Doppler — ALL projects, not just default
604for proj in $(doppler projects --json 2>/dev/null | jq -r '.[].slug'); do
605 doppler secrets --project "$proj" --config prd --json 2>/dev/null | \
606 jq -r --arg proj "$proj" 'to_entries[] | select(.key | test("SHOPIFY|STORE"; "i")) | "\(.key)=\(.value.computed) (doppler:\($proj)/prd)"'
607done
608 
609# 4. Dashlane — URLs reveal store identity
610dcli password shopify --output json 2>/dev/null | jq -r '.[].url // empty' | grep -oE '[a-z0-9-]+\.myshopify\.com' | sort -u
611 
612# 5. Keychain
613security find-generic-password -s "shopify-admin-token" -w 2>/dev/null
614security find-generic-password -s "shopify-access-token" -w 2>/dev/null
615 
616# 6. Chrome history — reveals store URLs from admin sessions
617sqlite3 ~/Library/Application\ Support/Google/Chrome/Default/History \
618 "SELECT DISTINCT url FROM urls WHERE url LIKE '%myshopify.com/admin%' OR url LIKE '%admin.shopify.com/store/%' ORDER BY last_visit_time DESC LIMIT 10" 2>/dev/null | \
619 grep -oE '[a-z0-9-]+\.myshopify\.com|admin\.shopify\.com/store/[a-z0-9-]+' | sort -u
620 
621# 7. Project .env files
622grep -rhE 'myshopify\.com|SHOPIFY_STORE|SHOPIFY.*TOKEN' ~/Projects/*/.env* 2>/dev/null | grep -v '^#' | head -5
623 
624# 8. Existing prefs + userConfig
625jq -r '.ecom.shopify // empty' "$PREFS_PATH" 2>/dev/null
626```
627 
628**Token acquisition — automate before asking.** If store URL found but no token:
629 
6301. **Doppler deep scan** — check ALL projects/configs (dev, staging, prd)
6312. **Shopify CLI** — if `command -v shopify` succeeds: `shopify auth login --store <store>.myshopify.com` (opens browser OAuth), then generate token
6323. **Browser automation** — if Kapture/Playwright available, navigate to `admin.shopify.com/store/<slug>/settings/apps/development` and automate app creation with scopes: `read_orders,write_orders,read_products,write_products,read_customers,read_inventory,write_inventory,read_fulfillments,write_fulfillments,read_analytics`
6334. **Manual fallback** — only if all automated approaches fail:
634 
635```
636No automated path for <store>.myshopify.com.
637 1. Go to https://admin.shopify.com/store/<slug>/settings/apps/development
638 2. Create an app → Configure → grant scopes → Install → copy token
639 Token starts with "shpat_"
640```
641 
642**Multi-store**: If multiple stores discovered, process each independently. Present all found stores with their token status before asking for input.
643 
644Store credentials via: userConfig (preferred) → Doppler → env vars. ShipBob optional — check `SHIPBOB_ACCESS_TOKEN` in same scan.
645 
646Verify connectivity after acquisition:
647 
648```bash
649curl -s -H "X-Shopify-Access-Token: ${PROVIDED_TOKEN}" \
650 "https://${PROVIDED_STORE}/admin/api/2024-10/shop.json" | \
651 jq '.shop | {name, domain, plan: .plan_display_name}'
652```
653 
654If the connectivity check returns valid shop data, confirm success. If it fails with 401/403, explain the token is invalid and re-prompt.
655 
656---
657 
658## STORE SUMMARY (empty $ARGUMENTS)
659 
660When called with no arguments, show a compact store overview:
661 
662### Competitor activity (gather before rendering)
663 
664```bash
665# Resolve PLUGIN_ROOT: directory containing the ops-ops-marketplace plugin scripts
666_COMP_PLUGIN_ROOT="${CLAUDE_PLUGIN_ROOT:-$(dirname "$(dirname "$(realpath "${BASH_SOURCE[0]}" 2>/dev/null || echo "$0")")")}"
667_COMP_LIB="$_COMP_PLUGIN_ROOT/scripts/lib/competitor/context.sh"
668_COMP_ECOM_SIGNALS="[]"
669if [[ -f "$_COMP_LIB" ]]; then
670 # shellcheck source=/dev/null
671 . "$_COMP_LIB"
672 _COMP_ECOM_SIGNALS=$(competitor_vertical_slice ecom --window-days 7 2>/dev/null || echo "[]")
673fi
674```
675 
676Parse `_COMP_ECOM_SIGNALS` JSON array. If it is non-empty, append the **COMPETITOR ACTIVITY** section after FULFILLMENT in the store summary render. If it is `[]`, skip the section entirely.
677 
678Render using this shape (mobile mode: plain text, no banners; desktop: with `━━━` rules):
679 
680```
681━━━ COMPETITOR ACTIVITY (last 7d) ━━━
682APP RELEASES
683 • [competitor] — v[version] released this week (rating: [X.X], [N]k reviews)
684PRODUCT / PRICING CHANGES
685 • [competitor] — pricing page changed ([low/med/high] severity)
686 • [competitor] — features page changed ([brief description from snippet])
687```
688 
689Mapping rules (apply per event in the JSON array):
690 
691- `source == "appstore"` → APP RELEASES entry. Extract version, rating, and review count from `snippet` if present.
692- `source == "page-diff"` and `kind` matches `features` → PRODUCT / PRICING CHANGES entry, labelled as features change.
693- `source == "page-diff"` and `kind == "pricing"` → PRODUCT / PRICING CHANGES entry, labelled as pricing change with severity from `severity` field.
694 
695Run orders, inventory, and health checks in parallel (separate Bash calls), then render:
696 
697```
698━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
699 OPS ► ECOM — [store] — [timestamp]
700━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
701 
702STORE [name] ([plan])
703CURRENCY [currency]
704 
705TODAY $[revenue] [N] orders
7067 DAYS $[revenue] [N] orders
70730 DAYS $[revenue] [N] orders
708 
709INVENTORY [N] products | [N] low stock | [N] out of stock
710FULFILLMENT [N] unfulfilled orders pending
711 
712[COMPETITOR ACTIVITY section here if non-empty — see above]
713 
714──────────────────────────────────────────────────────
715 /ops:ops-ecom orders — order management
716 /ops:ops-ecom inventory — stock levels
717 /ops:ops-ecom products — product catalog
718 /ops:ops-ecom customers — customer stats
719 /ops:ops-ecom analytics — revenue dashboard
720 /ops:ops-ecom health — store health check
721 /ops:ops-ecom channels — sales channel inventory
722 /ops:ops-ecom agentic — agentic storefront health
723 /ops:ops-ecom shop — Shop channel + Shop Campaigns readiness
724 /ops:ops-ecom setup — configure credentials
725──────────────────────────────────────────────────────
726```
727 
728## Windsor.ai (optional live data)
729 
730If Windsor.ai is connected (`mcp__*Windsor*__*` or a `windsor_api_key`), use it as the
731live cross-channel source for the GA4 acquisition funnel (sessions → checkout → purchase) and ad-driven traffic layered over store orders.
732Map accounts per project via `registry.json` → `.projects[].windsor`. Prefer **blended
733ROAS** (store/analytics revenue ÷ total ad spend) over platform-reported ROAS. See
734[docs/integrations/windsor-ai.md](../../../docs/integrations/windsor-ai.md) for the full playbook (REST + MCP modes,
735registry mapping, analysis mandate, and caveats).
736Data sanity: if Windsor returns only zeros across sources (Meta + Google spend/impressions and
737Instagram reach all exactly 0 over 30d while accounts are connected), treat the data as unavailable —
738the plan may be expired (check `get_current_user` → `is_paid`) — warn the user, and never present
739zeros as real metrics. `scripts/windsor-data-sanity.sh` automates the check.
740 
741Windsor is optional. If Windsor is not connected, returns errors, or returns the
742all-zero pattern, fall back to the free direct libraries:
743`scripts/lib/ad-spend-aggregator.sh` (paid), `scripts/lib/ga4-data-api.sh`
744(analytics), and `scripts/lib/organic-metrics-aggregator.sh` (organic + merchant).
745See [docs/integrations/direct-channel-wiring.md](../../../docs/integrations/direct-channel-wiring.md).
746Never present zeros from a dead source as real metrics.
747 
748## Additional resources
749 
750CLI detail: `references/cli.md`.
751 

Discussion

Alternatives

Cloudflare BasinBuild and troubleshoot Cloudflare Basin analytics workflows with Basin Pipelines, Basin Catalog, and Basin SQL. Use for streaming data into R2 Iceberg tables, managing catalogs, or querying those tables; also use for requests using the former Data Platform, Pipelines, R2 Data Catalog, or R2 SQL names.Sales & ecommerce · Apache-2.0ShopifyBuild anything on Shopify. One skill covering every Shopify developer surface: Use Shopify CLI, Admin API, ShopifyQL, Storefront GraphQL API, Partner API, Customer Account API, Payments Apps API, Shopify Functions, Polaris App Home, Polaris Admin Extensions, Polaris Checkout Extensions, Polaris Customer Account Extensions, POS UI, Hydrogen, Liquid, Custom Data, Shopify App Pricing, App Store Review, Developer Onboarding, Merchant Onboarding. Use it for any Shopify API, extension, theme, storefront, function, or CLI task — writing or explaining code, looking up operations, fields, components and valid values, validating generated code, and choosing which surface a request belongs to. Also use it when someone asks to make, build, open, or set up a store or shop that sells something, and when a prompt mentions or asks for any of: metafields or metaobjects, make me a store that sells pet supplies, mock.shop reference stores, build a storefront without an account — even without saying Shopify.Sales & ecommerce · MITShopifyBuild and customize Shopify stores — themes with Liquid, Storefront API, custom apps, and headless commerce with Hydrogen. Use when someone asks to build a Shopify store", "Shopify theme", "Liquid templates", "Shopify API", Shopify app", "headless Shopify", "Hydrogen storefront", "customize Shopify", Shopify product management", or "e-commerce with Shopify". Covers Liquid templating, theme development, Storefront/Admin APIs, custom apps, checkout extensions, and Hydrogen (React-based headless).Sales & ecommerce · Apache-2.0Shopify themes, apps & checkoutUse when building or customizing a Shopify store across its three code surfaces — themes (Liquid, Online Store 2.0 sections, blocks and JSON templates), apps (Remix with the versioned GraphQL Admin API and its query-cost model), and checkout (UI extensions, Functions, Web Pixels), plus the CLI flow and checkout-extensibility migration. NOT WooCommerce or PHP stores (that is `wordpress`), NOT the React layer of a headless storefront (that is `nextjs`), NOT non-Shopify payment integrations (that is `stripe`).Sales & ecommerce · MIT