ops-ecom · git:20260822.2fde0ca · 2026-08-22 · sha256 c00825ef33f59090

ops-ecom git:20260822.2fde0caA

Immutable. This exact content is served forever at /api/v1/blob/c00825ef33f59090.

---
name: ops-ecom
description: "This skill should be used when the user asks to \"/ops:ops-ecom\", \"run ops-ecom\", or \"use ops-ecom\". Shopify store command center. Orders, inventory, fulfillment, analytics, store health, sales channels, agentic storefronts, and Shop readiness. Works with any Shopify store via Admin API."
argument-hint: '[orders|inventory|fulfillment|health|products|customers|analytics|channels|agentic|shop|setup]'
allowed-tools:
  - Bash
  - Read
  - Write
  - Grep
  - Glob
  - Agent
  - TeamCreate
  - SendMessage
  - AskUserQuestion
  - WebFetch
  - WebSearch
effort: medium
maxTurns: 40
context: fork
---

# 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:

```bash
# 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:

```bash
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:

```bash
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:

```bash
# 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):

```bash
# 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:

```bash
# 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:

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

Also check:

```bash
# 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:

```bash
# 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:

```bash
# 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:

```bash
# 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:

```bash
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.

```bash
# 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:

```bash
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.

```bash
# 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).

```bash
# 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:

```bash
# 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:

```bash
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)

```bash
# 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](../../../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](../../../docs/integrations/direct-channel-wiring.md).
Never present zeros from a dead source as real metrics.

## Additional resources

CLI detail: `references/cli.md`.