# Excalibur Brothers Kingdom — Agent Skill

**App**: Excalibur Brothers Kingdom (skincare / healthspan tracking)  
**Base URL**: `https://app.excaliburbrothers.com`  
**Brand site**: `https://www.excaliburbrothers.com`  
**SSO**: GFAVIP Wallet (`https://wallet.gfavip.com`)  
**Service id**: `excalibur-kingdom`

This skill tells AI agents how to authenticate headlessly and call Kingdom APIs.

## Skill discovery URLs (raw markdown)

Fetch with a normal HTTP client (preferred) — not only the in-page browser:

| URL | Notes |
|-----|--------|
| `https://app.excaliburbrothers.com/skill.md` | Canonical |
| `https://app.excaliburbrothers.com/skill` | Alias |
| `https://app.excaliburbrothers.com/.well-known/agent-skill.md` | Well-known alias |

```bash
curl -sS https://app.excaliburbrothers.com/skill.md
```

CORS is open for these paths (`Access-Control-Allow-Origin: *`).  
If a **browser-based** agent host blocks loading `.md` files, install this skill by pasting this document, or fetch it server-side / with `curl`.

---

## 1. What this app is

- **Not** a chat app. No Matrix.
- Tracks product use, skin sensations, and progress over time.
- Default privacy: **no face photos** on the server. Daily logs are text + optional metrics.
- Optional future path: marketing photo share with explicit consent (schema reserved; not required for tracking).

---

## 2. Authentication (aligned with wallet.gfavip.com/docs/headless)

Kingdom supports the standard GFAVIP **Bearer token** model from [Headless / AI Agent Access](https://wallet.gfavip.com/headless_sso.md) and [Wallet skill / PowerLobster SSO](https://wallet.gfavip.com/skill.md).

### 2a. Headless agents (no browser) — **required path for agents**

1. Obtain a GFAVIP session token from Wallet (PowerLobster identity flow or PAT), per Wallet docs:
   - Identity: `POST https://powerlobster.com/api/agent/identity-token`
   - Exchange: `POST https://wallet.gfavip.com/api/auth/powerlobster` → `sso_token`
2. Call Kingdom with:
   ```http
   Authorization: Bearer <GFAVIP_SESSION_TOKEN>
   ```
3. Kingdom validates every protected call against:
   ```http
   GET/POST https://wallet.gfavip.com/api/auth/validate
   Authorization: Bearer <GFAVIP_SESSION_TOKEN>
   ```

**App job** (done): accept Bearer + validate with Wallet.  
**Agent job**: get the token from Wallet; do not invent browser redirect logins.

### 2b. Human browser login (flow=code)

For browsers only (not agents):

1. Redirect:
   ```
   https://wallet.gfavip.com/api/auth/sso/authorize?redirect_uri=https%3A%2F%2Fapp.excaliburbrothers.com%2Fcallback&service=excalibur-kingdom&flow=code
   ```
2. Callback receives `?code=` only.
3. App server exchanges:
   ```http
   POST https://app.excaliburbrothers.com/api/auth/sso/callback
   Content-Type: application/json

   { "code": "<one-time-code>" }
   ```
   Internally the app calls `POST https://wallet.gfavip.com/api/auth/sso/exchange`.

Agents should **not** use the browser code flow.

### 2c. Register / sync user on Kingdom

Before logging data, ensure a local user row exists:

```http
POST https://app.excaliburbrothers.com/api/users/sync
Authorization: Bearer <GFAVIP_SESSION_TOKEN>
Content-Type: application/json

{}
```

Or call any authenticated endpoint that upserts on first use (`POST /api/logs` also ensures the user exists when product-used logging runs).

---

## 3. API reference

All authenticated endpoints require:

```http
Authorization: Bearer <GFAVIP_SESSION_TOKEN>
```

### Public

| Method | Path | Description |
|--------|------|-------------|
| `GET` | `/api/health` | Health + storage mode + schema map + QR config flags |
| `GET` | `/api/products` | Product catalog (agent-readable) |
| `GET` | `/api/products/verify?token=<token>` | Authenticity / ingredients (signed `eb1.*` or catalog id) |
| `POST` | `/api/products/qr/mint` | Mint signed packaging token (ops; `X-QR-Mint-Secret`) |
| `GET` | `/skill.md` | This document |

### Authenticated

| Method | Path | Description |
|--------|------|-------------|
| `POST` | `/api/users/sync` | Upsert GFAVIP user + accept household invites |
| `GET` | `/api/me` | Current user + `households` |
| `GET` | `/api/households` | Households you belong to |
| `GET` | `/api/households/:id` | Members + pending invites (owner sees invites) |
| `POST` | `/api/households/:id/members` | Invite by username `{ "username", "role": "agent"\|"member" }` |
| `DELETE` | `/api/households/:id/members/:userId` | Remove member (owner) |
| `GET` | `/api/logs` | Shared household logs (`?householdId=` optional) |
| `POST` | `/api/logs` | Create log (writes into household you can access) |
| `PATCH` | `/api/logs/:id` | Update log (any household member/agent) |
| `DELETE` | `/api/logs/:id` | Delete log (any household member/agent) |
| `POST` | `/api/auth/sso/callback` | Browser code exchange only (`{ "code" }`) |

### Product QR verification (signed)

**Human / IT ops runbook** (mint, void, Railway secrets, packaging): see repo  
`docs/signed-qr-ops.md` on GitHub  
`https://github.com/shadstoneofficial/excaliburbrothers-kingdom-app/blob/main/docs/signed-qr-ops.md`

Packaging scratch-off codes use **HMAC-SHA256** tokens:

```
eb1.<payload_b64url>.<sig_b64url>
```

Payload JSON (compact keys):

| Key | Meaning |
|-----|---------|
| `v` | `1` |
| `p` | Catalog product id (e.g. `demo-face-serum-v1`) |
| `u` | Unique unit / package id (void list key) |
| `l` | Lot (optional) |
| `e` | Expiry unix seconds (optional) |
| `k` | Key id for rotation (optional, default `1`) |

Signature = HMAC-SHA256 over the **payload_b64url string**, secret from `QR_SIGNING_SECRET`.

Human + agent verify:

```http
GET https://app.excaliburbrothers.com/api/products/verify?token=<token>
```

Also works with bare catalog ids (unsigned pilot demos).

Response highlights:

```json
{
  "valid": true,
  "authenticity": "authentic",
  "tokenKind": "signed",
  "signatureValid": true,
  "productId": "demo-face-serum-v1",
  "unitId": "UNIT-42",
  "lot": "2026-08",
  "ingredients": ["Aqua", "..."],
  "agentReadable": true
}
```

`authenticity` may be `authentic` | `invalid` | `unknown` | `revoked`.  
Voided units: env `QR_VOIDED_UNITS` (comma-separated) plus built-in `VOID-DEMO-001`.

Mint locally for packaging:

```bash
npm run qr:mint -- --product demo-face-serum-v1 --unit UNIT-42 --lot 2026-08
```

Ops mint on Railway (when `QR_MINT_SECRET` is set):

```http
POST https://app.excaliburbrothers.com/api/products/qr/mint
X-QR-Mint-Secret: <secret>
Content-Type: application/json

{ "productId": "demo-face-serum-v1", "unitId": "UNIT-42", "lot": "2026-08" }
```

Encode the returned `verifyUrl` into the physical QR.

### Update log (`PATCH /api/logs/:id`)

Same fields as create (all optional). Example:

```json
{
  "timeOfDay": "both",
  "sensation": "calmer than morning",
  "notes": "Night application done"
}
```

### Multi-user / agents

Logs belong to a **household**, not only the person who typed them.

1. **Owner** (human, e.g. Mike) opens https://app.excaliburbrothers.com/household and invites `pl-arthur-blaze` as **agent**.
2. If Arthur already signed into Kingdom once, he is added immediately and can `GET /api/logs` to see the owner's progress.
3. If not yet registered, invite stays **pending** until his next `POST /api/users/sync` (username match).

Agent flow after invite:

```bash
# 1. Sync (accepts invites if pending)
curl -sS -X POST https://app.excaliburbrothers.com/api/users/sync \
  -H "Authorization: Bearer $SSO_TOKEN" -H "Content-Type: application/json" -d '{}'

# 2. List shared logs (owner's household included once invited)
curl -sS https://app.excaliburbrothers.com/api/logs \
  -H "Authorization: Bearer $SSO_TOKEN"
```

### Create log body (`POST /api/logs`)

```json
{
  "date": "2026-08-09",
  "productUsed": true,
  "productIds": ["demo-face-serum-v1"],
  "timeOfDay": "both",
  "sensation": "tingling, calm",
  "notes": "Factory sample day 1 — AM + night"
}
```

| Field | Required | Notes |
|-------|----------|--------|
| `date` | yes | `YYYY-MM-DD` |
| `productUsed` | no | default false |
| `productIds` | if productUsed | catalog ids; at least one when productUsed true |
| `timeOfDay` | recommended for twice-daily products | `morning` \| `evening` \| `both` \| `unspecified` |
| `sensation` | no | max 500 chars |
| `notes` | no | max 2000 chars |
| `metrics` | no | `{ tone?, texture?, redness? }` 0–100 — **not** photos |

**Response** `201`: `{ "log": { ... } }`

---

## 4. Product identity (pilot catalog)

| id | Name | Directions |
|----|------|------------|
| `demo-face-serum-v1` | Face Serum | **Twice daily — morning and night** |
| `sample-night-balm-v1` | Night Recovery Balm | Once daily evening |
| `sample-cleanser-v1` | Gentle Daily Cleanser | Morning and night as needed |

Fetch live catalog: `GET /api/products`.

---

## 5. Example: agent logs a twice-daily serum use

```bash
# Assume SSO_TOKEN already obtained from Wallet (PowerLobster path)

curl -sS -X POST https://app.excaliburbrothers.com/api/users/sync \
  -H "Authorization: Bearer $SSO_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{}'

curl -sS -X POST https://app.excaliburbrothers.com/api/logs \
  -H "Authorization: Bearer $SSO_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "date": "2026-08-09",
    "productUsed": true,
    "productIds": ["demo-face-serum-v1"],
    "timeOfDay": "both",
    "sensation": "tingling",
    "notes": "Sample pilot — morning and night as directed"
  }'

curl -sS https://app.excaliburbrothers.com/api/logs \
  -H "Authorization: Bearer $SSO_TOKEN"
```

---

## 6. Privacy rules for agents

- Do **not** upload face images to Kingdom APIs (not supported on tracking endpoints).
- **On-device progress photos** (`/progress` in the web UI) live in the user's **browser IndexedDB only**. Agents cannot list, fetch, or sync them via API. Do not invent upload endpoints.
- Do not invent server photo storage; marketing share is a future explicit-consent path only.
- Prefer structured `productIds` + `timeOfDay` over free-text only.

---

## 7. Related GFAVIP docs

- Wallet agent SSO: https://wallet.gfavip.com/skill.md  
- Headless Bearer contract: https://wallet.gfavip.com/headless_sso.md  
- Wallet docs UI: https://wallet.gfavip.com/docs/headless-sso  
- Browser code-exchange pattern: same Wallet docs (`flow=code`)

---

## 8. Support

- Human product support: support@excaliburbrothers.com  
- GFAVIP identity / SSO: wallet.gfavip.com ecosystem  
