supermarket

TotalClaw 作者 totalclaw

搜索杂货产品、查找商店位置、将商品添加到购物车以及查看所有克罗格家族商店的资料 - Kroger、Ralphs、Fred Meyer、Harris Teeter、King Soopers、Fry's、QFC、Mariano's、Pick 'n Save、Metro Market 等。当用户询问杂货、食品购物、商店位置或想要管理杂货购物车时使用。

安装 / 下载方式

TotalClaw CLI推荐
totalclaw install totalclaw:totalclaw~niemesrw-supermarket
cURL直接下载,无需登录
curl -fsSL https://skills.taituai.com/api/skills/totalclaw%3Atotalclaw~niemesrw-supermarket/file -o niemesrw-supermarket.md
## 概述(中文)

搜索杂货产品、查找商店位置、将商品添加到购物车以及查看所有克罗格家族商店的资料 - Kroger、Ralphs、Fred Meyer、Harris Teeter、King Soopers、Fry's、QFC、Mariano's、Pick 'n Save、Metro Market 等。当用户询问杂货、食品购物、商店位置或想要管理杂货购物车时使用。

## 原文

# Supermarket Skill

Search grocery products, find stores, add to cart, and view your profile across all Kroger-family stores (Kroger, Ralphs, Fred Meyer, Harris Teeter, King Soopers, Fry's, QFC, Mariano's, Pick 'n Save, and more) — all through the Kroger API via a hosted OAuth proxy. No API keys or developer accounts needed.

## How This Works (Transparency)

This skill uses a **hosted OAuth proxy** at `us-central1-krocli.cloudfunctions.net` to handle Kroger API authentication. Here's what it does and doesn't do:

**What the proxy handles:**
- Stores the Kroger client_id/client_secret (as Firebase secrets — never exposed to the agent)
- Exchanges authorization codes for tokens during login
- Refreshes expired user tokens

**Privacy guarantees (verifiable in source):**
- User tokens are **deleted from Firestore immediately** after being returned to the agent (`tokenUser.ts:44`)
- Login sessions **expire after 5 minutes** (`callback.ts:10`)
- Firestore rules **deny all direct client access** — only server-side Cloud Functions can read/write
- No tokens are logged — only errors use `console.error`
- The proxy never sees your Kroger username or password (that goes directly to Kroger's OAuth page)

**Full source code:** The proxy is open source at `firebase/functions/src/` in the [krocli repository](https://github.com/BLANXLAIT/krocli). You can audit every function: `authorize.ts`, `callback.ts`, `tokenClient.ts`, `tokenUser.ts`, `tokenRefresh.ts`.

**If you don't trust the hosted proxy**, see "Self-Hosting" at the bottom of this document.

## Architecture

All API calls go through the hosted proxy which handles OAuth credentials. The agent never needs a client_id or client_secret.

**Two token types:**
- **Client token** — for public data (products, locations). Obtained automatically.
- **User token** — for personal data (cart, profile). Requires one-time browser login.

## Getting a Client Token

Before searching products or locations, obtain a client token:

```bash
curl -s -X POST https://us-central1-krocli.cloudfunctions.net/tokenClient
```

Response:
```json
{"access_token": "eyJ...", "expires_in": 1800, "token_type": "bearer"}
```

Cache the `access_token` for subsequent requests. It expires in 30 minutes.

## Searching Products

```bash
curl -s -H "Authorization: Bearer ACCESS_TOKEN" \
  -H "Accept: application/json" \
  "https://api.kroger.com/v1/products?filter.term=milk&filter.limit=10"
```

**Query parameters:**
| Parameter | Required | Description |
|-----------|----------|-------------|
| `filter.term` | Yes | Search term (e.g. "milk", "organic eggs") |
| `filter.locationId` | No | Store ID for local pricing/availability |
| `filter.limit` | No | Max results (default 10, max 50) |

**Response fields to show the user:**
- `data[].productId` — UPC code
- `data[].description` — Product name
- `data[].brand` — Brand name
- `data[].items[].price.regular` — Price (when locationId provided)
- `data[].items[].price.promo` — Sale price (when available)
- `data[].items[].size` — Package size

## Finding Store Locations

```bash
curl -s -H "Authorization: Bearer ACCESS_TOKEN" \
  -H "Accept: application/json" \
  "https://api.kroger.com/v1/locations?filter.zipCode.near=45202&filter.limit=5"
```

**Query parameters:**
| Parameter | Required | Description |
|-----------|----------|-------------|
| `filter.zipCode.near` | Yes | ZIP code to search near |
| `filter.radiusInMiles` | No | Search radius (default 10) |
| `filter.limit` | No | Max results (default 10) |

**Response fields to show the user:**
- `data[].locationId` — Store ID (use for product pricing)
- `data[].name` — Store name
- `data[].address.addressLine1`, `city`, `state`, `zipCode`
- `data[].phone` — Phone number
- `data[].hours` — Operating hours

## User Authentication (for Cart & Profile)

When the user wants to add items to their cart or view their profile, they need to authenticate with Kroger. This is a one-time browser flow.

### Step 1: Generate a session ID and send the login link

Generate a random hex session ID (16-32 characters) and present the login URL to the user as a clickable link:

```
https://us-central1-krocli.cloudfunctions.net/authorize?session_id=SESSION_ID
```

Tell the user: **"Click this link to log in to your Kroger account. Once you see 'Login successful', come back here and let me know."**

### Step 2: Poll for tokens

After the user says they've logged in, poll for their tokens:

```bash
curl -s "https://us-central1-krocli.cloudfunctions.net/tokenUser?session_id=SESSION_ID"
```

- If `{"status": "pending"}` with HTTP 202: user hasn't finished yet. Wait and retry.
- If HTTP 200: tokens are returned. Cache `access_token` and `refresh_token`.

```json
{
  "access_token": "eyJ...",
  "refresh_token": "abc...",
  "expires_in": 1800,
  "token_type": "bearer"
}
```

### Step 3: Use the user token

The user token is needed for cart and profile endpoints.

## Adding to Cart

Requires user token from authentication above.

```bash
curl -s -X PUT \
  -H "Authorization: Bearer USER_ACCESS_TOKEN" \
  -H "Content-Type: application/json" \
  -H "Accept: application/json" \
  "https://api.kroger.com/v1/cart/add" \
  -d '{"items": [{"upc": "0011110838049", "quantity": 1}]}'
```

**Request body:**
```json
{
  "items": [
    {"upc": "PRODUCT_ID", "quantity": 1}
  ]
}
```

HTTP 204 means success (no response body).

## Viewing Profile

Requires user token.

```bash
curl -s -H "Authorization: Bearer USER_ACCESS_TOKEN" \
  -H "Accept: application/json" \
  "https://api.kroger.com/v1/identity/profile"
```

## Refreshing an Expired User Token

If a user token returns 401, refresh it:

```bash
curl -s -X POST \
  -H "Content-Type: application/json" \
  "https://us-central1-krocli.cloudfunctions.net/tokenRefresh" \
  -d '{"refresh_token": "REFRESH_TOKEN"}'
```

Response includes new `access_token` and `refresh_token`. Cache both.

## Token Management Summary

| Token | How to get | Expires | Refresh |
|-------|-----------|---------|---------|
| Client | `POST /tokenClient` | 30 min | Just request a new one |
| User | Browser login flow | 30 min | `POST /tokenRefresh` with refresh_token |

## Error Handling

| HTTP Status | Meaning | Action |
|-------------|---------|--------|
| 401 | Token expired | Refresh or re-obtain token |
| 403 | Forbidden | Token lacks required scope |
| 429 | Rate limited | Wait and retry |
| 400 | Bad request | Check parameters |

## Typical Workflows

### "Search for milk near me"
1. Get client token via `POST /tokenClient`
2. Ask user for ZIP code (or use a previously known one)
3. Find nearest store via locations API
4. Search products with `filter.locationId` for local pricing

### "Add bananas to my Kroger cart"
1. Check if user token is cached; if not, start login flow
2. Search for "bananas" to get the UPC
3. Confirm product with user
4. `PUT /cart/add` with the UPC

### "What Kroger stores are near 90210?"
1. Get client token
2. Search locations with `filter.zipCode.near=90210`
3. Format results with name, address, hours

## Token Persistence

After a successful user login, save the refresh token and preferred ZIP code to your long-term memory so the user doesn't have to log in again next session.

**After login, remember:**
- `kroger_refresh_token`: the refresh_token from the login flow
- `kroger_zip_code`: the user's preferred ZIP code (if provided)
- `kroger_store_id`: the user's preferred store locationId (if identified)

**At the start of a session**, check your memory for a saved `kroger_refresh_token`. If found:
1. Call `POST /tokenRefresh` with the saved refresh_token
2. If it succeeds, use the new access_token — no login needed
3. If it fails (e.g. token revoked), clear the saved token and start a fresh login flow

**Important:** Only save the refresh