--- name: hermes-shop description: "Use when creating, deploying, provisioning, or updating Hermes Shop: products, product images, site config, Stripe checkout fields, Shippo shipping/labels, and future Spawn shop add-ons." version: 1.1.0 author: Hermes Agent license: MIT metadata: hermes: tags: [hermes, shop, commerce, stripe, shippo, products, images, spawn] related_skills: [vps-app-deployment, hermes-spawn-control-plane, coding-quality-workflow] --- # Hermes Shop ## Overview Hermes Shop is a standalone shop app that Hermes can operate conversationally. It should help a user create products, edit products, upload product images, configure site copy, and manage per-shop Stripe checkout plus Shippo shipping/labels. Important reference: - `references/stripe-onboarding-and-product-model-ui.md` — session learning on per-shop Stripe onboarding, product model visibility, and avoiding Jungle framing. Current test deployment: - App: `/home/avalon/apps/hermes-shop` - URL: `https://shop.apps.poofc.com` - PM2: `hermes-shop` - Port: `4039` - Data: `/home/avalon/apps/hermes-shop/data/shop.json` This skill is for operating Hermes Shop directly and for shaping the future Hermes Spawn optional shop add-on. ## Conversational Rule Be short and practical. If the user did not provide enough information, ask only for the missing fields needed for the next action. For setup/onboarding flows, use one clear next step at a time and avoid long explanatory checklists unless Alex asks for a plan. Good: > I can add it. I need the product name, price, and whether it ships. Good for Stripe: > I can connect Stripe. I need the Stripe account or permission to start a new Connect onboarding link. Bad: > To create a comprehensive commerce object, please provide... ## Product Model A product can include: - `title` — required - `description` - `type` — `physical`, `digital`, or `service` - `status` — `draft`, `active`, or `archived` - `priceCents`, `currency`, `taxable`, `compareAtCents`, `costCents` - `sku`, `barcode`, `inventory` - `tags` - `images[]` — `{ id, url, alt, role }` - `variants[]` — `{ id, title, options, priceCents, sku, inventory, imageUrl, active, stripePriceId }` - `shipping` — package/weight/origin fields - `stripe` — product/price/checkout fields - `shippo` — provider defaults for rates/labels - `fulfillment` — tracking/label/status fields - `metadata` — flexible extra data UI rule: product cards must expose readable model details in the app, not only JSON/API output. At minimum show type, status, SKU, inventory, Stripe product/price state, Shippo state, fulfillment state, and optional raw JSON. ## Required Fields by Action ### Create simple draft product Need: - product title - price - product type, if not obvious If missing, ask: > What should I call it, and what price should it be? ### Publish / activate product Need: - title - price - at least one image or explicit approval to publish without image - Stripe checkout ready, if taking payment now - for physical products: weight/package details or explicit manual shipping choice If missing, ask one compact question listing only missing pieces. ### Upload product image Need: - product id/title - image file, URL, or data URL - optional alt text Use: ```bash curl -fsS -X POST http://127.0.0.1:4039/api/products//images \ -H 'Content-Type: application/json' \ --data-binary '{"dataUrl":"data:image/jpeg;base64,...","alt":"Front of product"}' ``` ### Edit product info Need: - product id/title - fields to change Use `PUT /api/products/:id` with only changed fields. ## API Quick Reference ```bash GET /api/health GET /api/shop GET /api/products GET /api/products/:id PUT /api/site GET /api/stripe/config PUT /api/stripe/config POST /api/stripe/onboarding-link POST /api/products PUT /api/products/:id DELETE /api/products/:id POST /api/products/:id/images ``` Base URL: ```bash http://127.0.0.1:4039 ``` ## Recipes ### Inspect current shop ```bash curl -fsS http://127.0.0.1:4039/api/shop | jq . ``` ### Create or update a product ```bash curl -fsS -X POST http://127.0.0.1:4039/api/products \ -H 'Content-Type: application/json' \ --data-binary '{ "title":"Example Product", "type":"physical", "status":"draft", "priceCents":2500, "currency":"USD", "inventory":5, "shipping":{"requiresShipping":true,"weightValue":8,"weightUnit":"oz"} }' | jq .product ``` ### Archive a product ```bash curl -fsS -X PUT http://127.0.0.1:4039/api/products/ \ -H 'Content-Type: application/json' \ --data-binary '{"status":"archived"}' | jq .product ``` ### Update site config ```bash curl -fsS -X PUT http://127.0.0.1:4039/api/site \ -H 'Content-Type: application/json' \ --data-binary '{"headline":"New headline","statusText":"Live"}' | jq .site ``` ## Site Hero / Storefront Copy The storefront hero is intentionally minimal and image-led. Site config supports: - `name` - `eyebrow` - `headline` - `subheadline` - `statusText` - `ctaLabel` - `heroImage` — optional URL/path for the hero background Current frontend fallback order for the hero background: 1. `site.heroImage` 2. first available product primary image 3. `/shop-hero.svg` For “clean up the hero” requests, prefer one big background image, a short eyebrow/title, and a single product-scroll CTA. Avoid restoring multiple parallel CTAs or verbose descriptive subcopy in the hero unless asked. If Alex asks for “featured product” language, set the hero title to a concise product feature label such as `Feature product one` instead of repeating `Hermes Shop`. ## Shop Navigation and Modal UI Pattern Hermes Shop should separate customer-facing browsing from operator/configuration screens: - Keep the storefront/product grid as the default view. - Put a hamburger menu in the top corner for secondary views. - Move Stripe configuration/onboarding into its own view reachable from the side panel; do not leave it inline between hero and products. - Move raw JSON / edit config tooling into its own view reachable from the side panel; label it clearly as `Edit config` or similar. - Product cards should open a modal/detail overlay instead of expanding inline on the page. Reusable modal direction: - Prefer a small shared React component (e.g. `OverlayModal`) inside Hermes Shop for product details and future shop overlays. - Style it after Hermes Creative overlays: fixed dim/blur backdrop, cream rounded panel, sticky top bar, and black circular close button. - On mobile product/detail modals should be true full-screen app surfaces: no top gap, no side gutters, `inset:0`, `padding:0` on the backdrop, full viewport-height panel, and safe-area padding inside the panel only. - Product modals should show the product image or placeholder, description, price/action, readable model fields, and optional raw JSON. - Keep the modal component generic enough to reuse for other Hermes Shop panels later, rather than hard-coding only product behavior. Performance and motion rules for reusable overlays: - Do not make product/detail modals feel like clunky popups. Opening/closing should be subtle, fast, and component-quality because this pattern will be reused. - Avoid immediate unmount on close. Keep an internal `rendered`/`is-closing` state so the close animation plays, then unmount after the transition. - Animate only cheap compositor properties (`transform`, `opacity`) with a short duration (~180–220ms) and `will-change`; avoid heavy blur/backdrop-filter transitions on modal surfaces. - Preserve the last selected product/content during the closing state so text/media do not disappear before the animation finishes. - Include a `prefers-reduced-motion: reduce` override. - Make the close affordance large enough for mobile use (roughly 54–56px circular button, larger `×`). Product grid sizing rules: - Below the hero, product cards should feel substantial, not tiny. Prefer a two-column grid for the main product shelf rather than switching to three narrow columns at tablet/desktop browser widths. - Use square/large product art (`aspect-ratio: 1/1`) and slightly larger card padding/title sizing on wider viewports, with tighter spacing only on narrow mobile. ## Stripe Direction Stripe is a payment processor, not the catalog source of truth. Each Hermes Shop gets its own Stripe configuration under `integrations.stripe`; never treat Stripe as one global account for all tenant shops. Current config operations: ```bash curl -fsS http://127.0.0.1:4039/api/stripe/config | jq . curl -fsS -X PUT http://127.0.0.1:4039/api/stripe/config \ -H 'Content-Type: application/json' \ --data-binary '{"mode":"test","businessName":"Example Shop","accountId":"acct_...","publishableKey":"pk_test_...","onboardingComplete":true}' | jq . ``` The UI has a Stripe onboarding panel. Stripe Connect Standard OAuth is wired through `POST /api/stripe/onboarding-link` and `GET /api/stripe/oauth/callback`. It requires platform env vars `STRIPE_SECRET_KEY` and `STRIPE_CONNECT_CLIENT_ID` (or `STRIPE_CLIENT_ID`). Current live operations when env is configured: - create Stripe Standard OAuth connect URL per shop/tenant - store connected `acct_...` and publishable key under `integrations.stripe` - sync product → Stripe Product in the connected account - sync price → Stripe Price in the connected account - create Checkout Session in the connected account via `POST /api/checkout/:id` - store Stripe ids under `product.stripe` / `variant.stripePriceId` - never store card data in Hermes Shop - store provider secrets in env/secret storage, not product JSON Do not claim live checkout is enabled unless the Stripe account is connected and a checkout/session endpoint is verified. ## Shippo Direction Shippo handles shipping rates, labels, and tracking. Planned operations: - create shipment from product package + customer address - fetch rates - buy label - store label/tracking fields under `fulfillment` - refresh tracking status - use environment variables for Shippo secrets For physical products, ask for package weight/dimensions before enabling shipping automation. ## Spawn Add-on Direction When this becomes a Spawn add-on: 1. Provision a shop per tenant or a multi-tenant shop service. 2. Seed site config from tenant name/brand. 3. Give tenant Hermes this skill with its own base URL/data path. 4. Keep shop app separate from the Spawn control plane UI. 5. Store provider secrets in tenant env/config, not in product JSON. 6. Let Hermes manage catalog and site config through APIs. ## Scoped Spawn Broker Artifact When packaging a standalone Hermes Shop build for the scoped Spawn deployment broker: 1. Put the compiled `dist/`, tenant `data/shop.json`, product uploads, and a dependency-free `server.js` at the artifact root. 2. The broker starts exactly `node server.js`; if `server.js` uses ESM imports, include a root `package.json` with `{"private":true,"type":"module"}`. 3. Include a root `deployment-manifest.json` with all required identity/runtime fields: ```json { "tenantId": "tenant-id", "appName": "shop-app-name", "runtime": "node", "start": "node server.js", "healthPath": "/healthz", "hostnames": ["shop.example.com"] } ``` 4. The submit request needs matching `appName`, artifact path, SHA-256, `verification.healthPath`, and requested hostnames. Manifest hostnames may be a superset, but every requested hostname must be declared. 5. Verify the checksum after copying the archive into the tenant container, then submit from inside the tenant with `/data/hermes/bin/spawn-deploy `. 6. A successful application deployment may return `dns_pending`; that means PM2/local nginx are live but authoritative DNS/TLS is not ready. Verify `localHealth`, every `localNginx` result, PM2 status, and authoritative DNS separately. ## Build / Deploy ```bash cd /home/avalon/apps/hermes-shop npm run build pm2 restart /home/avalon/apps/ecosystem.config.js --only hermes-shop --update-env pm2 save npm run smoke curl -fsS https://shop.apps.poofc.com/api/health ``` Pitfall: restarting by bare PM2 process name (`pm2 restart hermes-shop --update-env`) can pick up the caller's current environment instead of the ecosystem file and may drop the configured `PORT=4039`. Use the ecosystem restart form above for deploys. ## Verification Checklist - [ ] `npm run build` passes. - [ ] `npm run smoke` passes. - [ ] PM2 `hermes-shop` is online on port 4039. - [ ] Public health endpoint returns `ok: true`. - [ ] Product/site changes persist in `data/shop.json`. - [ ] For image uploads, returned URL loads publicly under `/uploads/...`. - [ ] Do not claim Stripe or Shippo live behavior unless the endpoint and provider credentials are verified.