shop-app

Hermes 作者 community v0.0.28

Shop.app: product search, order tracking, returns, reorder.

安装 / 下载方式

TotalClaw CLI推荐
totalclaw install hermes:hermes~shop-app
cURL直接下载,无需登录
curl -fsSL https://skills.taituai.com/api/skills/hermes%3Ahermes~shop-app/file -o shop-app.md
# Shop.app — Personal Shopping Assistant

Use this skill when the user wants to **search products across stores, compare prices, find similar items, track an order, manage a return, or re-order a past purchase** through Shop.app's agent API.

No auth required for product search. Auth (device-authorization flow) is required for any per-user operation: orders, tracking, returns, reorder. Store tokens **only in your working memory for the current session** — never write them to disk, never ask the user to paste them.

All endpoints return **plain-text markdown** (including errors, which look like `# Error\n\n{message} ({status})`). Use `curl` via the `terminal` tool; for the try-on feature use the `image_generate` tool.

---

## Product Search (no auth)

**Endpoint:** `GET https://shop.app/agents/search`

| Parameter | Type | Required | Default | Description |
|---|---|---|---|---|
| `query` | string | yes | — | Search keywords |
| `limit` | int | no | 10 | Results 1–10 |
| `ships_to` | string | no | `US` | ISO-3166 country code (controls currency + availability) |
| `ships_from` | string | no | — | ISO-3166 country code for product origin |
| `min_price` | decimal | no | — | Min price |
| `max_price` | decimal | no | — | Max price |
| `available_for_sale` | int | no | 1 | `1` = in-stock only |
| `include_secondhand` | int | no | 1 | `0` = new only |
| `categories` | string | no | — | Comma-delimited Shopify taxonomy IDs |
| `shop_ids` | string | no | — | Filter to specific shops |
| `products_limit` | int | no | 10 | Variants per product, 1–10 |

```
curl -s 'https://shop.app/agents/search?query=wireless+earbuds&limit=10&ships_to=US'
```

**Response format:** Plain text. Products separated by `\n\n---\n\n`.

**Fields to extract per product:**
- **Title** — first line
- **Price + Brand + Rating** — second line (`$PRICE at BRAND — RATING`)
- **Product URL** — line starting with `https://`
- **Image URL** — line starting with `Img: `
- **Product ID** — line starting with `id: `
- **Variant IDs** — in the Variants section or from the `variant=` query param in the product URL
- **Checkout URL** — line starting with `Checkout: ` (contains `{id}` placeholder; replace with a real variant ID)

**Pagination:** none. For more or different results, **vary the query** (different keywords, synonyms, narrower/broader terms). Up to ~3 search rounds.

**Errors:** missing/empty `query` returns `# Error\n\nquery is missing (400)`.

---

## Find Similar Products

Same response format as Product Search.

**By variant ID (GET):**

```
curl -s 'https://shop.app/agents/search?variant_id=33169831854160&limit=10&ships_to=US'
```

The `variant_id` must come from the `variant=` query param in a product URL — the `id:` field from search results is **not** accepted.

**By image (POST):**

```
curl -s -X POST https://shop.app/agents/search \
  -H 'Content-Type: application/json' \
  -d '{"similarTo":{"media":{"contentType":"image/jpeg","base64":"<BASE64>"}},"limit":10}'
```

Requires base64-encoded image bytes. URLs are **not** accepted — download the image first (`curl -o`), then `base64 -w0 file.jpg` to inline.

---

## Authentication — Device Authorization Flow (RFC 8628)

Required for orders, tracking, returns, reorder. Not required for product search.

**Session state (hold in your reasoning context for this conversation only):**

| Key | Lifetime | Description |
|---|---|---|
| `access_token` | until expired / 401 | Bearer token for authenticated endpoints |
| `refresh_token` | until refresh fails | Renews `access_token` without re-auth |
| `device_id` | whole session | `shop-skill--<uuid>` — generate once, reuse for every request |
| `country` | whole session | ISO country code (`US`, `CA`, `GB`, …) — ask or infer |

**Rules:**
- `user_code` is always 8 chars A-Z, formatted `XXXXXXXX`.
- No `client_id`, `client_secret`, or callback needed — the proxy handles it.
- **Never ask the user to paste tokens into chat.**
- Tokens live only for the duration of this conversation. Do not write them to `.env` or any file.

### Flow

**1. Request a device code:**
```
curl -s -X POST https://shop.app/agents/auth/device-code
```
Response includes `device_code`, `user_code`, `sign_in_url`, `interval`, `expires_in`. Present `sign_in_url` (and the `user_code`) to the user.

**2. Poll for the token** every `interval` seconds:
```
curl -s -X POST https://shop.app/agents/auth/token \
  --data-urlencode 'grant_type=urn:ietf:params:oauth:grant-type:device_code' \
  --data-urlencode "device_code=$DEVICE_CODE"
```
Handle errors: `authorization_pending` (keep polling), `slow_down` (add 5s to interval), `expired_token` / `access_denied` (restart flow). Success returns `access_token` + `refresh_token`.

**3. Validate:**
```
curl -s https://shop.app/agents/auth/userinfo \
  -H "Authorization: Bearer $ACCESS_TOKEN"
```

**4. Refresh on 401:**
```
curl -s -X POST https://shop.app/agents/auth/token \
  --data-urlencode 'grant_type=refresh_token' \
  --data-urlencode "refresh_token=$REFRESH_TOKEN"
```
If refresh fails, restart the device flow.

---

## Orders

> **Scope:** Shop.app aggregates orders from **all stores** (not just Shopify) using email receipts the user connected in the Shop app. This skill never touches the user's email directly.

**Status progression:** `paid → fulfilled → in_transit → out_for_delivery → delivered`
**Other:** `attempted_delivery`, `refunded`, `cancelled`, `buyer_action_required`

### Fetch pattern

```
curl -s 'https://shop.app/agents/orders?limit=50' \
  -H "Authorization: Bearer $ACCESS_TOKEN" \
  -H "x-device-id: $DEVICE_ID"
```

Parameters: `limit` (1–50, default 20), `cursor` (from previous response).

**Key fields to extract:**
- **Order UUID** — `uuid: …`
- **Store** — `at …`, `Store domain: …`, `Store URL: …`
- **Price** — line after `Store URL`
- **Date** — `Ordered: …`
- **Status / Delivery** — `Status: …`, `Delivery: …`
- **Reorder eligible** — `Can reorder: yes`
- **Items** — under `— Items —`, each with optional `[product:ID]` `[variant:ID]` and `Img:`
- **Tracking** — under `— Tracking —` (carrier, code, tracking URL, ETA)
- **Tracker ID** — `tracker_id: …`
- **Return URL** — `Return URL: …` (only if eligible)

**Pagination:** if the first line is `cursor: <value>`, pass it back as `?cursor=<value>` for the next page. Keep going until no `cursor:` line appears.

**Filtering:** apply client-side after fetch (by `Ordered:` date, `Delivery:` status, etc.).

**Errors:** on 401 refresh and retry. On 429 wait 10s and retry.

### Tracking detail

Tracking lives under each order's `— Tracking —` section:
```
delivered via UPS — 1Z999AA10123456784
Tracking URL: https://ups.com/track?num=…
ETA: Arrives Tuesday
```

**Stale tracking warning:** if `Ordered:` is months old but delivery is still `in_transit`, tell the user tracking may be stale.

---

## Returns

Two sources:

**1. Order-level return URL** — look for `Return URL: …` in the order data.

**2. Product-level return policy:**
```
curl -s 'https://shop.app/agents/returns?product_id=29923377167' \
  -H "Authorization: Bearer $ACCESS_TOKEN" \
  -H "x-device-id: $DEVICE_ID"
```

Fields: `Returnable` (`yes` / `no` / `unknown`), `Return window` (days), `Return policy URL`, `Shipping policy URL`.

For full policy text, fetch the return policy URL with `web_extract` (or `curl` + strip tags) — it's HTML.

---

## Reorder

1. Fetch orders with `limit=50`, find target by `uuid:` or store/item match.
2. Confirm `Can reorder: yes` — if absent, reorder may not work.
3. Extract `[variant:ID]` and item title from `— Items —`, and the store domain from `Store domain:` or `Store URL:`.
4. Build the checkout URL: `https://{domain}/cart/{variantId}:{quantity}`.

**Example:** `at Allbirds` + `Store domain: allbirds.myshopify.com` + `[variant:789012]` → `https://allbirds.myshopify.com/cart/789012:1`

**Missing variant (e.g. Amazon orders, no `[variant:ID]`):** fall back to a store search link: `https://{domain}/search?q={title}`.

--