$npx -y skills add 40RTY-ai/shopify-admin-skills --skill shopify-admin-frequently-bought-togetherRead-only: mines order history to find product pairs and triplets frequently purchased together, generating cross-sell and bundle recommendations.
| 1 | ## Purpose |
| 2 | Analyzes order history to discover which products are frequently purchased together. Calculates co-occurrence frequency, lift scores, and confidence metrics to generate data-driven cross-sell recommendations and bundle candidates. Read-only — no mutations. |
| 3 | |
| 4 | ## Prerequisites |
| 5 | - Authenticated Shopify CLI session: `shopify store auth --store <domain> --scopes read_orders,read_products` |
| 6 | - API scopes: `read_orders`, `read_products` |
| 7 | |
| 8 | ## Parameters |
| 9 | |
| 10 | | Parameter | Type | Required | Default | Description | |
| 11 | |-----------|------|----------|---------|-------------| |
| 12 | | store | string | yes | — | Store domain | |
| 13 | | days_back | integer | no | 180 | Order lookback window | |
| 14 | | min_support | integer | no | 3 | Minimum co-occurrence count to report a pair | |
| 15 | | max_results | integer | no | 25 | Maximum product pairs to return | |
| 16 | | group_size | integer | no | 2 | Pair size: `2` for pairs, `3` for triplets | |
| 17 | | collection_filter | string | no | — | Limit to products in a specific collection | |
| 18 | | format | string | no | human | Output format: `human` or `json` | |
| 19 | |
| 20 | ## Safety |
| 21 | |
| 22 | > ℹ️ Read-only skill — no mutations are executed. Safe to run at any time. |
| 23 | |
| 24 | ## Workflow Steps |
| 25 | |
| 26 | 1. **OPERATION:** `orders` — query |
| 27 | **Inputs:** `query: "created_at:>='<NOW - days_back days>'"`, `first: 250`, select `lineItems { product { id, title } }`, pagination cursor |
| 28 | **Expected output:** All orders with product-level line items |
| 29 | |
| 30 | 2. For each order with 2+ distinct products, generate all product pair combinations |
| 31 | |
| 32 | 3. Build co-occurrence matrix: |
| 33 | - **Support** = number of orders containing both products |
| 34 | - **Confidence(A→B)** = P(B|A) = support(A,B) / support(A) |
| 35 | - **Lift** = confidence(A→B) / P(B) — lift > 1.0 means positive association |
| 36 | |
| 37 | 4. **OPERATION:** `products` — query (enrichment) |
| 38 | **Inputs:** Product IDs from top pairs for titles, images, prices |
| 39 | **Expected output:** Product details for display |
| 40 | |
| 41 | 5. Rank pairs by lift score (descending), filter by min_support |
| 42 | |
| 43 | ## GraphQL Operations |
| 44 | |
| 45 | ```graphql |
| 46 | # orders:query — validated against api_version 2025-01 |
| 47 | query OrderLineItems($query: String!, $after: String) { |
| 48 | orders(first: 250, after: $after, query: $query) { |
| 49 | edges { |
| 50 | node { |
| 51 | id |
| 52 | lineItems(first: 50) { |
| 53 | edges { |
| 54 | node { |
| 55 | product { id title } |
| 56 | quantity |
| 57 | } |
| 58 | } |
| 59 | } |
| 60 | } |
| 61 | } |
| 62 | pageInfo { hasNextPage endCursor } |
| 63 | } |
| 64 | } |
| 65 | ``` |
| 66 | |
| 67 | ```graphql |
| 68 | # products:query — validated against api_version 2025-01 |
| 69 | query ProductDetails($ids: [ID!]!) { |
| 70 | nodes(ids: $ids) { |
| 71 | ... on Product { |
| 72 | id |
| 73 | title |
| 74 | vendor |
| 75 | productType |
| 76 | priceRangeV2 { |
| 77 | minVariantPrice { amount currencyCode } |
| 78 | maxVariantPrice { amount currencyCode } |
| 79 | } |
| 80 | totalInventory |
| 81 | status |
| 82 | } |
| 83 | } |
| 84 | } |
| 85 | ``` |
| 86 | |
| 87 | ## Session Tracking |
| 88 | |
| 89 | **Claude MUST emit the following output at each stage. This is mandatory.** |
| 90 | |
| 91 | **On start**, emit: |
| 92 | ``` |
| 93 | ╔══════════════════════════════════════════════╗ |
| 94 | ║ SKILL: Frequently Bought Together ║ |
| 95 | ║ Store: <store domain> ║ |
| 96 | ║ Started: <YYYY-MM-DD HH:MM UTC> ║ |
| 97 | ╚══════════════════════════════════════════════╝ |
| 98 | ``` |
| 99 | |
| 100 | **After each step**, emit: |
| 101 | ``` |
| 102 | [N/TOTAL] <QUERY|MUTATION> <OperationName> |
| 103 | → Params: <brief summary of key inputs> |
| 104 | → Result: <count or outcome> |
| 105 | ``` |
| 106 | |
| 107 | **On completion**, emit: |
| 108 | |
| 109 | For `format: human` (default): |
| 110 | ``` |
| 111 | ══════════════════════════════════════════════ |
| 112 | FREQUENTLY BOUGHT TOGETHER (<days_back> days, <n> orders analyzed) |
| 113 | Unique product pairs found: <n> |
| 114 | Pairs meeting min_support: <n> |
| 115 | |
| 116 | TOP PAIRS BY LIFT: |
| 117 | #1 "<product A>" + "<product B>" |
| 118 | Support: <n> orders Lift: <n>x Confidence: <pct>% |
| 119 | |
| 120 | #2 "<product A>" + "<product B>" |
| 121 | Support: <n> orders Lift: <n>x Confidence: <pct>% |
| 122 | |
| 123 | BUNDLE CANDIDATES (high support + high lift): |
| 124 | "<product A>" + "<product B>" → Suggested bundle price: $<n> |
| 125 | |
| 126 | Output: fbt_pairs_<date>.csv |
| 127 | ══════════════════════════════════════════════ |
| 128 | ``` |
| 129 | |
| 130 | ## Output Format |
| 131 | CSV file `fbt_pairs_<YYYY-MM-DD>.csv` with columns: |
| 132 | `product_a_id`, `product_a_title`, `product_b_id`, `product_b_title`, `support`, `confidence_a_to_b`, `confidence_b_to_a`, `lift`, `combined_avg_price` |
| 133 | |
| 134 | ## Error Handling |
| 135 | | Error | Cause | Recovery | |
| 136 | |-------|-------|----------| |
| 137 | | `THROTTLED` | API rate limit exceeded | Wait 2 seconds, retry up to 3 times | |
| 138 | | Single-item orders only | Store with no multi-item orders | Report empty — suggest longer lookback window | |
| 139 | | Too many products | Combinatorial explosion | Limit to top 500 products |