@curless/sinocare-demo 0.56.0 → 0.57.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1 +1 @@
1
- {"version":3,"file":"checkout-card.generated.js","sourceRoot":"","sources":["../src/checkout-card.generated.ts"],"names":[],"mappings":"AAAA,qFAAqF;AACrF,iFAAiF;AACjF,gFAAgF;AAChF,4DAA4D;AAC5D,MAAM,CAAC,MAAM,kBAAkB,GAAW,MAAM,CAAC,IAAI,CACnD,850dAA850d,EAC950d,QAAQ,CACT,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC"}
1
+ {"version":3,"file":"checkout-card.generated.js","sourceRoot":"","sources":["../src/checkout-card.generated.ts"],"names":[],"mappings":"AAAA,qFAAqF;AACrF,iFAAiF;AACjF,gFAAgF;AAChF,4DAA4D;AAC5D,MAAM,CAAC,MAAM,kBAAkB,GAAW,MAAM,CAAC,IAAI,CACnD,s60dAAs60d,EACt60d,QAAQ,CACT,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC"}
package/dist/index.js CHANGED
@@ -229,11 +229,11 @@ const WALLET_FALLBACK_OK = process.env.SINOCARE_WALLET_FALLBACK === '1';
229
229
  const mintSharedPaymentToken = async (amountMinor, currency) => {
230
230
  if (!STRIPE_AGENT_KEY || !SELLER_PROFILE)
231
231
  return null;
232
- // A live key here means a booking charges a REAL card for the full resort
232
+ // A live key here means a booking charges a REAL card for the full product
233
233
  // price (€1,280–2,900) the moment someone clicks pay in the card — with no
234
234
  // confirmation step anywhere. Refuse unless that is explicitly what you want.
235
235
  if (STRIPE_AGENT_SECRET_KEY_IS_LIVE && !WALLET_LIVE_OK) {
236
- throw new Error('refusing to mint with a LIVE Stripe key — a booking would charge a real card for the full resort price. Set SINOCARE_ALLOW_LIVE_WALLET=1 if that is genuinely intended.');
236
+ throw new Error('refusing to mint with a LIVE Stripe key — a booking would charge a real card for the full product price. Set SINOCARE_ALLOW_LIVE_WALLET=1 if that is genuinely intended.');
237
237
  }
238
238
  const post = async (path, body, preview = false) => {
239
239
  const res = await fetch(`https://api.stripe.com/v1/${path}`, {
@@ -500,7 +500,7 @@ let agentBudget = loadBudget();
500
500
  let lastCheckout = null;
501
501
  const completedBookings = new Map();
502
502
  // Persist the card's checkout state (last pick + booking receipts) across restarts
503
- // and conversations, so returning to a paid resort's card shows its receipt
503
+ // and conversations, so returning to a paid product's card shows its receipt
504
504
  // instead of resetting to the picker — and so a fresh "book it again" starts
505
505
  // clean. Keyed to the agent token so a different buyer starts empty. Override the
506
506
  // path with SINOCARE_DEMO_STATE_FILE.
@@ -556,12 +556,12 @@ if (tokenFp) {
556
556
  // It exists for one case: a card self-healing with no arguments, where
557
557
  // the server replays the last pick so the widget fills instead of
558
558
  // hanging. Restored from disk it does that to a card belonging to a
559
- // DIFFERENT resort — restart the app, ask for a new hotel, and the card
559
+ // DIFFERENT product — restart the app, ask for a new hotel, and the card
560
560
  // renders the previous booking's receipt, which reads as having booked
561
561
  // something you did not.
562
562
  //
563
563
  // `bookings` is safe to restore because it is keyed by sku: it is only
564
- // ever consulted for a resort someone actually asked about.
564
+ // ever consulted for a product someone actually asked about.
565
565
  for (const [k, v] of saved.bookings ?? [])
566
566
  completedBookings.set(k, v);
567
567
  }
@@ -597,7 +597,7 @@ const finishBooking = (sku, value) => {
597
597
  }).catch(() => { });
598
598
  completedBookings.set(sku, {
599
599
  sku,
600
- name: String(value.resort ?? ''),
600
+ name: String(value.product ?? ''),
601
601
  protocol: value.protocol,
602
602
  total: (value.total ?? value.paidUsdc ?? value.paidRlusd ?? value.priceEur),
603
603
  status: value.status,
@@ -616,7 +616,7 @@ const finishBooking = (sku, value) => {
616
616
  return text(value);
617
617
  };
618
618
  // The 'booked' card view: a receipt shown when a rehydrated checkout resolves to
619
- // an already-booked resort. Carries the pick fields too so the card's "再订一次"
619
+ // an already-booked product. Carries the pick fields too so the card's "再订一次"
620
620
  // button can drop straight back into the picker without another round-trip.
621
621
  const bookedView = (pick, r) => ({
622
622
  view: 'booked',
@@ -645,7 +645,7 @@ const bookedView = (pick, r) => ({
645
645
  // corrected data and the old JS draws the old picture. Three widget changes
646
646
  // went out today without a bump, and the receipt/picker fix appeared to have
647
647
  // no effect at all because of it.
648
- const CHECKOUT_URI = 'ui://sinocare/card-v24.html';
648
+ const CHECKOUT_URI = 'ui://sinocare/card-v25.html';
649
649
  const CARD_MIME = 'text/html;profile=mcp-app';
650
650
  const CHECKOUT_UI_META = { ui: { resourceUri: CHECKOUT_URI }, 'ui/resourceUri': CHECKOUT_URI };
651
651
  const TOOLS = [
@@ -666,7 +666,7 @@ const TOOLS = [
666
666
  },
667
667
  {
668
668
  name: 'get_sinocare_product',
669
- description: 'Get one Sinocare resort by its sku (from list_sinocare_products).',
669
+ description: 'Get one Sinocare product by its sku (from list_sinocare_products).',
670
670
  inputSchema: {
671
671
  type: 'object',
672
672
  required: ['sku'],
@@ -675,17 +675,17 @@ const TOOLS = [
675
675
  },
676
676
  {
677
677
  name: 'start_sinocare_checkout',
678
- description: "THE DEFAULT WAY TO BOOK. Open a visual checkout card for a Sinocare resort so the user PICKS a payment method by clicking (instead of typing) — Card (ACP) / UCP / USDC (x402) / RLUSD (XRP Ledger) in-conversation, or a Payment Link hosted page. When the user wants to book, CALL THIS — do not list the payment methods as text and ask which one; the card is where the user chooses and pays. ALWAYS prefer this over buy_sinocare_product_*. YOU MUST identify the resort: pass its `sku` OR a `query` name (e.g. 'Val d'Isère', 'Saint-Moritz') — do NOT call this with no resort (the card can't render without one). If one resort matches, open its card directly rather than asking to confirm.",
678
+ description: "THE DEFAULT WAY TO BUY. Open a visual checkout card for a Sinocare product so the user PICKS a payment method by clicking (instead of typing) — Card (ACP) / UCP / USDC (x402) / RLUSD (XRP Ledger) in-conversation, or a Payment Link hosted page. When the user wants to buy, CALL THIS — do not list the payment methods as text and ask which one; the card is where the user chooses and pays. ALWAYS prefer this over buy_sinocare_product_*. YOU MUST identify the product: pass its `sku` OR a `query` name (e.g. 'Safe-Accu', 'Safe AQ Smart') — do NOT call this with no product (the card can't render without one). To show the whole catalog instead, call list_sinocare_products: it renders as a card too, and every row opens that product's checkout. If one product matches, open its card directly rather than asking to confirm.",
679
679
  inputSchema: {
680
680
  type: 'object',
681
681
  properties: {
682
682
  sku: {
683
683
  type: 'string',
684
- description: "the resort's exact sku (from list_sinocare_products)",
684
+ description: "the product's exact sku (from list_sinocare_products)",
685
685
  },
686
686
  query: {
687
687
  type: 'string',
688
- description: "the resort NAME (e.g. 'Val d'Isère') — used if you don't have the exact sku; resolved server-side. Pass `sku` OR `query`.",
688
+ description: "the product NAME (e.g. 'Safe AQ Smart') — used if you don't have the exact sku; resolved server-side. Pass `sku` OR `query`.",
689
689
  },
690
690
  quantity: { type: 'integer', description: 'number of guests/weeks (default 1)' },
691
691
  },
@@ -694,7 +694,7 @@ const TOOLS = [
694
694
  },
695
695
  {
696
696
  name: 'create_sinocare_payment_link',
697
- description: "Create a Payment Link for a resort — a PUBLIC hosted checkout page (like a Stripe Payment Link) the user opens in a browser to pay (test-mode card). Returns the URL. Mostly invoked by the checkout card's 'Payment Link' button; the external-link payment path.",
697
+ description: "Create a Payment Link for a product — a PUBLIC hosted checkout page (like a Stripe Payment Link) the user opens in a browser to pay (test-mode card). Returns the URL. Mostly invoked by the checkout card's 'Payment Link' button; the external-link payment path.",
698
698
  inputSchema: {
699
699
  type: 'object',
700
700
  required: ['sku'],
@@ -716,7 +716,7 @@ const TOOLS = [
716
716
  },
717
717
  {
718
718
  name: 'buy_sinocare_product_wallet',
719
- description: "Book a Sinocare resort over ACP, paying from the buyer's own Curless wallet: the wallet mints a credential from the card THEY bound, capped by the limits THEY set, scoped to this seller and this amount for 15 minutes. If nobody is signed in, this returns a sign-in link — show it to the person; the card polls on its own and pays once they finish in the browser. Do NOT ask them for a password in the chat. If the wallet refuses because the booking is over their limit, tell the person it is their own limit and offer to raise it — do not retry with another payment method.",
719
+ description: "Book a Sinocare product over ACP, paying from the buyer's own Curless wallet: the wallet mints a credential from the card THEY bound, capped by the limits THEY set, scoped to this seller and this amount for 15 minutes. If nobody is signed in, this returns a sign-in link — show it to the person; the card polls on its own and pays once they finish in the browser. Do NOT ask them for a password in the chat. If the wallet refuses because the booking is over their limit, tell the person it is their own limit and offer to raise it — do not retry with another payment method.",
720
720
  inputSchema: {
721
721
  type: 'object',
722
722
  required: ['sku'],
@@ -787,7 +787,7 @@ const TOOLS = [
787
787
  },
788
788
  {
789
789
  name: 'buy_sinocare_product_acp',
790
- description: 'Book a Sinocare resort over ACP (Agentic Commerce Protocol) — Sinocare prices the resort and opens a merchant-quoted checkout session; this agent completes it with a delegated card credential, settling on the card rail (test mode). One of THREE ways to pay: ACP (card) / UCP (card) / x402 (USDC). The card rail is an implementation detail, not a separate payment choice. Returns the booked order.',
790
+ description: 'Book a Sinocare product over ACP (Agentic Commerce Protocol) — Sinocare prices the product and opens a merchant-quoted checkout session; this agent completes it with a delegated card credential, settling on the card rail (test mode). One of THREE ways to pay: ACP (card) / UCP (card) / x402 (USDC). The card rail is an implementation detail, not a separate payment choice. Returns the booked order.',
791
791
  inputSchema: {
792
792
  type: 'object',
793
793
  required: ['sku'],
@@ -799,7 +799,7 @@ const TOOLS = [
799
799
  },
800
800
  {
801
801
  name: 'buy_sinocare_product_ucp',
802
- description: 'Book a Sinocare resort over UCP (Universal Commerce Protocol, ucp.dev) — Sinocare prices it and opens a UCP checkout session. UCP "supports x402", and x402 is chain-agnostic, so the SAME UCP session settles FOUR ways (pass `pay`): `card` (default) → the agent authorizes with an AP2 PaymentMandate bound to the cart, settled on the card rail (test mode, same as ACP); `usdc` → the gateway freezes an x402 challenge and the agent pays with a signed EIP-3009 X-PAYMENT (USDC on Base/EVM); `usdc-sol` → same, on Solana — the agent partially signs an SPL transfer the facilitator co-signs + submits (USDC on Solana, buyer needs no SOL); `rlusd` → an XRPL x402 challenge, a signed XRPL Payment (Ed25519, no gas), on the XRP Ledger. The three stablecoin ways all settle on-chain over the stablecoin rail — currency + chain just pick the rail. The standing authorization (authorize_agent_budget) is attached on the card path. Returns the booked order.',
802
+ description: 'Book a Sinocare product over UCP (Universal Commerce Protocol, ucp.dev) — Sinocare prices it and opens a UCP checkout session. UCP "supports x402", and x402 is chain-agnostic, so the SAME UCP session settles FOUR ways (pass `pay`): `card` (default) → the agent authorizes with an AP2 PaymentMandate bound to the cart, settled on the card rail (test mode, same as ACP); `usdc` → the gateway freezes an x402 challenge and the agent pays with a signed EIP-3009 X-PAYMENT (USDC on Base/EVM); `usdc-sol` → same, on Solana — the agent partially signs an SPL transfer the facilitator co-signs + submits (USDC on Solana, buyer needs no SOL); `rlusd` → an XRPL x402 challenge, a signed XRPL Payment (Ed25519, no gas), on the XRP Ledger. The three stablecoin ways all settle on-chain over the stablecoin rail — currency + chain just pick the rail. The standing authorization (authorize_agent_budget) is attached on the card path. Returns the booked order.',
803
803
  inputSchema: {
804
804
  type: 'object',
805
805
  required: ['sku'],
@@ -844,7 +844,7 @@ const TOOLS = [
844
844
  },
845
845
  {
846
846
  name: 'buy_sinocare_product_x402',
847
- description: 'Book a Sinocare resort and pay in USDC over x402. `chain` picks the USDC chain: `base` (default) → EVM, a signed EIP-3009 authorization; `solana` → an SVM transfer the buyer partially signs and the facilitator co-signs (feePayer) + submits (buyer needs no SOL). The merchant receives USDC at its address on that chain. Returns the booking + the USDC amount paid (+ the Solana tx on the solana path).',
847
+ description: 'Book a Sinocare product and pay in USDC over x402. `chain` picks the USDC chain: `base` (default) → EVM, a signed EIP-3009 authorization; `solana` → an SVM transfer the buyer partially signs and the facilitator co-signs (feePayer) + submits (buyer needs no SOL). The merchant receives USDC at its address on that chain. Returns the booking + the USDC amount paid (+ the Solana tx on the solana path).',
848
848
  inputSchema: {
849
849
  type: 'object',
850
850
  required: ['sku'],
@@ -866,7 +866,7 @@ const TOOLS = [
866
866
  },
867
867
  {
868
868
  name: 'buy_sinocare_product_rlusd',
869
- description: "Book a Sinocare resort and pay in RLUSD (Ripple USD) over x402 on the XRP Ledger. Sinocare opens a merchant-quoted RLUSD session; this agent signs a standard XRPL Payment (Ed25519, no gas — the buyer pays the tiny XRPL fee) and Curless's self-hosted facilitator submits it on-ledger. Same x402 protocol as the USDC tool, different chain. Returns the booking + the RLUSD amount paid + the XRPL tx.",
869
+ description: "Book a Sinocare product and pay in RLUSD (Ripple USD) over x402 on the XRP Ledger. Sinocare opens a merchant-quoted RLUSD session; this agent signs a standard XRPL Payment (Ed25519, no gas — the buyer pays the tiny XRPL fee) and Curless's self-hosted facilitator submits it on-ledger. Same x402 protocol as the USDC tool, different chain. Returns the booking + the RLUSD amount paid + the XRPL tx.",
870
870
  inputSchema: {
871
871
  type: 'object',
872
872
  required: ['sku'],
@@ -878,7 +878,7 @@ const TOOLS = [
878
878
  },
879
879
  {
880
880
  name: 'buy_sinocare_product_skyfire',
881
- description: "Book a Sinocare resort over Skyfire KYAPay (agent-native JWT payments). The buyer mints a Skyfire-SIGNED PAY token (via Skyfire, authorizing a small USDC charge to Sinocare — unlike the crypto rails the buyer can't self-sign, Skyfire signs it) and presents it to the Sinocare merchant backend, which charges it via the merchant SDK; agentbank verifies it against Skyfire's live JWKS — proving the agent's identity (Know Your Agent) AND the payment authorization — and records the USDC payment intent. PRODUCTION Skyfire (real token, real verify). Needs SKYFIRE_BUYER_API_KEY + SKYFIRE_SELLER_SERVICE_ID (to mint). Returns the verification + the recorded payment intent (agentbank verifies + records the authorization; it does not auto-capture).",
881
+ description: "Book a Sinocare product over Skyfire KYAPay (agent-native JWT payments). The buyer mints a Skyfire-SIGNED PAY token (via Skyfire, authorizing a small USDC charge to Sinocare — unlike the crypto rails the buyer can't self-sign, Skyfire signs it) and presents it to the Sinocare merchant backend, which charges it via the merchant SDK; agentbank verifies it against Skyfire's live JWKS — proving the agent's identity (Know Your Agent) AND the payment authorization — and records the USDC payment intent. PRODUCTION Skyfire (real token, real verify). Needs SKYFIRE_BUYER_API_KEY + SKYFIRE_SELLER_SERVICE_ID (to mint). Returns the verification + the recorded payment intent (agentbank verifies + records the authorization; it does not auto-capture).",
882
882
  inputSchema: {
883
883
  type: 'object',
884
884
  required: ['sku'],
@@ -890,7 +890,7 @@ const TOOLS = [
890
890
  },
891
891
  {
892
892
  name: 'buy_sinocare_product_mpp',
893
- description: "Book a Sinocare resort over MPP (Machine Payments Protocol) settled on the Tempo payments chain. agentbank answers the unpaid request with an HTTP-402 Payment challenge whose SIGNED terms carry the on-chain payment (TIP-20 pathUSD token, merchant recipient, memo=challenge binding); the buyer's Tempo wallet pays it with transferWithMemo; the credential proves it by tx HASH and agentbank VERIFIES the on-chain transfer read-only, then captures the payment intent. Real Tempo testnet, on-chain stablecoin — no card, no gateway signing. Needs AGENTBANK_AGENT_TOKEN + TEMPO_BUYER_PRIVATE_KEY (a funded Tempo testnet key).",
893
+ description: "Book a Sinocare product over MPP (Machine Payments Protocol) settled on the Tempo payments chain. agentbank answers the unpaid request with an HTTP-402 Payment challenge whose SIGNED terms carry the on-chain payment (TIP-20 pathUSD token, merchant recipient, memo=challenge binding); the buyer's Tempo wallet pays it with transferWithMemo; the credential proves it by tx HASH and agentbank VERIFIES the on-chain transfer read-only, then captures the payment intent. Real Tempo testnet, on-chain stablecoin — no card, no gateway signing. Needs AGENTBANK_AGENT_TOKEN + TEMPO_BUYER_PRIVATE_KEY (a funded Tempo testnet key).",
894
894
  inputSchema: {
895
895
  type: 'object',
896
896
  required: ['sku'],
@@ -907,7 +907,7 @@ const TOOLS = [
907
907
  },
908
908
  {
909
909
  name: 'list_my_orders',
910
- description: 'List my Sinocare bookings (orders) — newest first, with id, resort, amount, and status. Renders an interactive card: each settled booking has a 申请退款 (request refund) button. Use the order id with request_refund to ask for a refund.',
910
+ description: 'List my Sinocare bookings (orders) — newest first, with id, product, amount, and status. Renders an interactive card: each settled booking has a 申请退款 (request refund) button. Use the order id with request_refund to ask for a refund.',
911
911
  inputSchema: { type: 'object', properties: {} },
912
912
  _meta: CHECKOUT_UI_META,
913
913
  },
@@ -936,7 +936,7 @@ const text = (value) => ({
936
936
  // non-UI fallback only.
937
937
  const INSTRUCTIONS = [
938
938
  'Sinocare storefront (buyer side).',
939
- 'When the user wants to book a resort any phrasing like "book …", "预订…", "reserve " — call start_sinocare_checkout (pass the resort `sku`, or a `query` name such as "Saint-Moritz" / "Val d\'Isère") to open the visual checkout card. The user picks a payment method and pays by clicking IN THE CARD.',
939
+ 'When the user wants to buy something — "buy", "order", "我要买…", "再来一盒试纸" — call start_sinocare_checkout (pass the product `sku`, or a `query` name such as "Safe-Accu") to open the visual checkout card. To show the whole catalog, call list_sinocare_products — it renders as a card too, and every row opens that product\'s checkout. The user picks a payment method and pays by clicking IN THE CARD.',
940
940
  'Do NOT enumerate the payment methods (ACP / UCP / x402 / RLUSD / Payment Link) as text and ask which one — that is what the card is for. Only fall back to describing options in prose if the checkout card genuinely cannot be shown (a non-UI host).',
941
941
  'If the product is ambiguous or unknown, show the catalog card with list_sinocare_products and let the person pick a row. If exactly one product matches, open its card directly rather than asking to confirm.',
942
942
  'Call the buy_sinocare_product_* tools directly only when the card is unavailable or the user explicitly asked for a specific rail in a plain-text flow.',
@@ -956,7 +956,7 @@ server.setRequestHandler(CallToolRequestSchema, async (req) => {
956
956
  try {
957
957
  if (req.params.name === 'list_sinocare_products') {
958
958
  // `products`, and a `view` the card dispatches on. This returned
959
- // `{currency, count, resorts}` — the resort storefront's shape, left
959
+ // `{currency, count, products}` — the product storefront's shape, left
960
960
  // behind by a rename that changed identifiers and not payloads. The card
961
961
  // reads `products`, got undefined, and drew nothing.
962
962
  const category = typeof args.category === 'string' ? args.category.trim().toLowerCase() : '';
@@ -973,23 +973,23 @@ server.setRequestHandler(CallToolRequestSchema, async (req) => {
973
973
  }
974
974
  if (req.params.name === 'get_sinocare_product') {
975
975
  const sku = String(args.sku ?? '');
976
- const resort = await backend(`/catalog/${encodeURIComponent(sku)}`);
977
- return text(resort);
976
+ const product = await backend(`/catalog/${encodeURIComponent(sku)}`);
977
+ return text(product);
978
978
  }
979
- // Visual checkout: return the MCP App card (payment picker) + the resort as
979
+ // Visual checkout: return the MCP App card (payment picker) + the product as
980
980
  // structuredContent. The card's buttons call the book_* /
981
981
  // create_sinocare_payment_link tools; the card renders their results.
982
982
  if (req.params.name === 'start_sinocare_checkout') {
983
- // Accept a `sku` (exact) OR a `query`/`resort`/`name` (e.g. "Val d'Isère")
983
+ // Accept a `sku` (exact) OR a `query`/`product`/`name` (e.g. "Safe AQ Smart")
984
984
  // so the agent doesn't need the exact sku to open the card.
985
- const idOrName = String(args.sku ?? args.query ?? args.resort ?? args.name ?? '').trim();
985
+ const idOrName = String(args.sku ?? args.query ?? args.product ?? args.name ?? '').trim();
986
986
  const quantity = Math.max(Number(args.quantity) || 1, 1);
987
987
  // The card sets __rehydrate when it re-invokes to fill itself (reload /
988
988
  // self-heal), never on a fresh agent-initiated checkout. Only on rehydrate
989
989
  // do we show a completed booking's receipt instead of the picker — so an
990
990
  // explicit "book X again" from the agent still opens the picker.
991
991
  const rehydrate = args.__rehydrate === true || args.__rehydrate === 1 || args.__rehydrate === '1';
992
- // No resort given = the card self-healing (empty-arg re-invoke) — replay the
992
+ // No product given = the card self-healing (empty-arg re-invoke) — replay the
993
993
  // last pick so the widget fills in instead of hanging on "Loading…". If that
994
994
  // last checkout has since been booked, show its receipt on rehydrate.
995
995
  if (!idOrName) {
@@ -1009,7 +1009,7 @@ server.setRequestHandler(CallToolRequestSchema, async (req) => {
1009
1009
  type: 'text',
1010
1010
  text: lastCheckout
1011
1011
  ? `Checkout for ${lastCheckout.name}.`
1012
- : 'Pick a resort first (list_sinocare_products), then open its checkout.',
1012
+ : 'Pick a product first (list_sinocare_products), then open its checkout.',
1013
1013
  },
1014
1014
  ],
1015
1015
  structuredContent: lastCheckout ?? {
@@ -1023,20 +1023,20 @@ server.setRequestHandler(CallToolRequestSchema, async (req) => {
1023
1023
  };
1024
1024
  }
1025
1025
  // Resolve: try exact sku first, then a name search over the catalog.
1026
- let resort = null;
1026
+ let product = null;
1027
1027
  try {
1028
- resort = await backend(`/catalog/${encodeURIComponent(idOrName)}`);
1028
+ product = await backend(`/catalog/${encodeURIComponent(idOrName)}`);
1029
1029
  }
1030
1030
  catch {
1031
1031
  const { items } = await backend('/catalog');
1032
1032
  const q = idOrName.toLowerCase();
1033
- resort =
1033
+ product =
1034
1034
  items.find((r) => r.sku.toLowerCase() === q) ??
1035
1035
  items.find((r) => r.name.toLowerCase() === q) ??
1036
1036
  items.find((r) => r.name.toLowerCase().includes(q)) ??
1037
1037
  null;
1038
1038
  }
1039
- if (!resort) {
1039
+ if (!product) {
1040
1040
  return {
1041
1041
  content: [
1042
1042
  {
@@ -1047,8 +1047,8 @@ server.setRequestHandler(CallToolRequestSchema, async (req) => {
1047
1047
  isError: true,
1048
1048
  };
1049
1049
  }
1050
- const priceEur = resort.priceDisplay ?? `€${((resort.price ?? 0) / 100).toLocaleString('en-IE')}`;
1051
- // Rehydrating an already-BOOKED resort's card → show its receipt, not the
1050
+ const priceEur = product.priceDisplay ?? `€${((product.price ?? 0) / 100).toLocaleString('en-IE')}`;
1051
+ // Rehydrating an already-BOOKED product's card → show its receipt, not the
1052
1052
  // picker (fixes: pay, leave the conversation, come back, and the card had
1053
1053
  // reset to the payment selection). The card self-heals by re-invoking with
1054
1054
  // its ORIGINAL args (the sku) + __rehydrate. EXCEPTION: a deliberate "book
@@ -1058,30 +1058,30 @@ server.setRequestHandler(CallToolRequestSchema, async (req) => {
1058
1058
  // receipts are kept intact (never deleted), so #6's cross-conversation
1059
1059
  // records — and OTHER booked cards in the same conversation — still
1060
1060
  // rehydrate to their receipts.
1061
- // Is THIS checkout paid? Not "has this resort ever been bought".
1061
+ // Is THIS checkout paid? Not "has this product ever been bought".
1062
1062
  //
1063
- // The receipt used to be looked up by sku, which treats a resort as a
1063
+ // The receipt used to be looked up by sku, which treats a product as a
1064
1064
  // purchase. It is not: opening a checkout for somewhere you booked last
1065
- // week is a NEW order. With 42 of 44 resorts recorded, that lookup made
1065
+ // week is a NEW order. With 42 of 44 products recorded, that lookup made
1066
1066
  // almost every card render as an already-paid receipt the moment it
1067
1067
  // self-healed — no payment involved — and hid the payment picker behind
1068
1068
  // a booking that had happened days ago.
1069
1069
  //
1070
1070
  // So the question is answered by the checkout in hand: the pick we just
1071
- // had open, for this same resort, that was actually paid. A fresh open
1071
+ // had open, for this same product, that was actually paid. A fresh open
1072
1072
  // replaces it with an unpaid one (below), which is what makes "book it
1073
1073
  // again" resolve to the picker.
1074
- const rehydratedReceipt = rehydrate && lastCheckout?.sku === resort.sku && lastCheckout.booked
1075
- ? completedBookings.get(resort.sku)
1074
+ const rehydratedReceipt = rehydrate && lastCheckout?.sku === product.sku && lastCheckout.booked
1075
+ ? completedBookings.get(product.sku)
1076
1076
  : undefined;
1077
1077
  lastCheckout = {
1078
1078
  view: 'pick',
1079
- sku: resort.sku,
1080
- name: resort.name,
1079
+ sku: product.sku,
1080
+ name: product.name,
1081
1081
  priceEur,
1082
1082
  quantity,
1083
- imageUrl: resort.imageUrl,
1084
- externalShopify: resort.source === 'shopify',
1083
+ imageUrl: product.imageUrl,
1084
+ externalShopify: product.source === 'shopify',
1085
1085
  // Preserve booked only when rehydrating a paid pick; a fresh open is a
1086
1086
  // new, unpaid order (booked stays falsy → the card shows the picker).
1087
1087
  booked: rehydratedReceipt ? true : undefined,
@@ -1090,7 +1090,7 @@ server.setRequestHandler(CallToolRequestSchema, async (req) => {
1090
1090
  const receipt = rehydratedReceipt;
1091
1091
  if (receipt) {
1092
1092
  return {
1093
- content: [{ type: 'text', text: `Booked ${resort.name} (${priceEur}).` }],
1093
+ content: [{ type: 'text', text: `Booked ${product.name} (${priceEur}).` }],
1094
1094
  structuredContent: bookedView(lastCheckout, receipt),
1095
1095
  _meta: CHECKOUT_UI_META,
1096
1096
  };
@@ -1099,7 +1099,7 @@ server.setRequestHandler(CallToolRequestSchema, async (req) => {
1099
1099
  content: [
1100
1100
  {
1101
1101
  type: 'text',
1102
- text: `Checkout for ${resort.name} (${priceEur}). Pick a payment method in the card — Card (ACP) / UCP / USDC (x402) / RLUSD (XRP Ledger), or a Payment Link (hosted page).`,
1102
+ text: `Checkout for ${product.name} (${priceEur}). Pick a payment method in the card — Card (ACP) / UCP / USDC (x402) / RLUSD (XRP Ledger), or a Payment Link (hosted page).`,
1103
1103
  },
1104
1104
  ],
1105
1105
  structuredContent: lastCheckout,
@@ -1220,7 +1220,7 @@ server.setRequestHandler(CallToolRequestSchema, async (req) => {
1220
1220
  }
1221
1221
  const sku = String(args.sku ?? '');
1222
1222
  const quantity = Math.max(Number(args.quantity) || 1, 1);
1223
- // 1. Sinocare prices the resort + opens a merchant-quoted ACP session.
1223
+ // 1. Sinocare prices the product + opens a merchant-quoted ACP session.
1224
1224
  const session = await backend('/checkout-acp', {
1225
1225
  method: 'POST',
1226
1226
  body: JSON.stringify({ sku, quantity, ...(BUYER ? { buyer: BUYER } : {}) }),
@@ -1290,7 +1290,7 @@ server.setRequestHandler(CallToolRequestSchema, async (req) => {
1290
1290
  const order = (await completeCheckout(acp(session.merchantId), session.sessionId, token));
1291
1291
  return finishBooking(sku, {
1292
1292
  booked: order.status === 'completed',
1293
- resort: session.name,
1293
+ product: session.name,
1294
1294
  quantity,
1295
1295
  total: session.amountDisplay,
1296
1296
  protocol: 'acp',
@@ -1327,7 +1327,7 @@ server.setRequestHandler(CallToolRequestSchema, async (req) => {
1327
1327
  merchantId: session.merchantId,
1328
1328
  amount,
1329
1329
  currency,
1330
- resort: session.name,
1330
+ product: session.name,
1331
1331
  total: session.amountDisplay,
1332
1332
  // Spells out the sign-in step. Without it the model calls
1333
1333
  // wallet_pay_credential, gets "nobody is signed in", and has to invent
@@ -1357,7 +1357,7 @@ server.setRequestHandler(CallToolRequestSchema, async (req) => {
1357
1357
  // idea it had been bought, so it rehydrated to the picker.
1358
1358
  return finishBooking(bought?.sku ?? '', {
1359
1359
  booked,
1360
- resort: bought?.name,
1360
+ product: bought?.name,
1361
1361
  quantity: bought?.quantity,
1362
1362
  protocol: 'acp',
1363
1363
  payer: 'curless-wallet (separate connector)',
@@ -1383,10 +1383,10 @@ server.setRequestHandler(CallToolRequestSchema, async (req) => {
1383
1383
  message: '在浏览器里打开这个链接,看清金额再批准。这里还没有扣任何款。',
1384
1384
  payUrl: `${API_BASE}/buyer/pay?session=${encodeURIComponent(session.sessionId)}`,
1385
1385
  sessionId: session.sessionId,
1386
- resort: session.name,
1386
+ product: session.name,
1387
1387
  total: session.amountDisplay,
1388
1388
  // Say plainly that nothing has been paid yet. A tool that returns a
1389
- // link and a resort name reads like a receipt if it does not.
1389
+ // link and a product name reads like a receipt if it does not.
1390
1390
  paid: false,
1391
1391
  merchantId: session.merchantId,
1392
1392
  next: 'show the person this link. Nothing has been charged yet — they approve it in their browser. Then call get_sinocare_order_status with this sessionId.',
@@ -1408,7 +1408,7 @@ server.setRequestHandler(CallToolRequestSchema, async (req) => {
1408
1408
  paid,
1409
1409
  sessionId,
1410
1410
  status: session.status,
1411
- resort: bought?.name,
1411
+ product: bought?.name,
1412
1412
  quantity: bought?.quantity,
1413
1413
  protocol: 'acp',
1414
1414
  payer: 'curless-wallet (hosted approval)',
@@ -1488,7 +1488,7 @@ server.setRequestHandler(CallToolRequestSchema, async (req) => {
1488
1488
  const paidUsdc = fmtUsdc(BigInt(session.amount));
1489
1489
  return finishBooking(sku, {
1490
1490
  booked: paid.status === 'completed',
1491
- resort: session.name,
1491
+ product: session.name,
1492
1492
  quantity,
1493
1493
  protocol: 'ucp',
1494
1494
  payer: 'agent-key (UCP)',
@@ -1571,7 +1571,7 @@ server.setRequestHandler(CallToolRequestSchema, async (req) => {
1571
1571
  const paidUsdc = `${(need / 1e6).toFixed(6)} USDC`;
1572
1572
  return finishBooking(sku, {
1573
1573
  booked: paid.status === 'completed',
1574
- resort: session.name,
1574
+ product: session.name,
1575
1575
  quantity,
1576
1576
  protocol: 'ucp',
1577
1577
  payer: 'agent-key (UCP)',
@@ -1652,7 +1652,7 @@ server.setRequestHandler(CallToolRequestSchema, async (req) => {
1652
1652
  }
1653
1653
  return finishBooking(sku, {
1654
1654
  booked: paid.status === 'completed',
1655
- resort: session.name,
1655
+ product: session.name,
1656
1656
  quantity,
1657
1657
  protocol: 'ucp',
1658
1658
  payer: 'agent-key (UCP)',
@@ -1668,7 +1668,7 @@ server.setRequestHandler(CallToolRequestSchema, async (req) => {
1668
1668
  });
1669
1669
  }
1670
1670
  // CARD path (default).
1671
- // 1. Sinocare prices the resort + opens a merchant-quoted UCP session,
1671
+ // 1. Sinocare prices the product + opens a merchant-quoted UCP session,
1672
1672
  // returning a merchant-signed AP2 CartMandate for it.
1673
1673
  const session = await backend('/checkout-ucp', {
1674
1674
  method: 'POST',
@@ -1713,10 +1713,10 @@ server.setRequestHandler(CallToolRequestSchema, async (req) => {
1713
1713
  return text({
1714
1714
  booked: false,
1715
1715
  blockedBy: 'standing-authorization',
1716
- resort: session.name,
1716
+ product: session.name,
1717
1717
  total: session.amountDisplay,
1718
1718
  authorizedCap: `€${agentBudget?.maxAmountEur.toLocaleString('en-IE')}`,
1719
- message: `Blocked: ${session.name} (${session.amountDisplay}) is over your standing authorization (cap €${agentBudget?.maxAmountEur}, or it expired). Raise it with authorize_agent_budget, or book a cheaper resort.`,
1719
+ message: `Blocked: ${session.name} (${session.amountDisplay}) is over your standing authorization (cap €${agentBudget?.maxAmountEur}, or it expired). Raise it with authorize_agent_budget, or book a cheaper product.`,
1720
1720
  });
1721
1721
  }
1722
1722
  throw new Error(`ucp complete -> ${res.status}: ${errText}`);
@@ -1724,7 +1724,7 @@ server.setRequestHandler(CallToolRequestSchema, async (req) => {
1724
1724
  const out = (await res.json());
1725
1725
  return finishBooking(sku, {
1726
1726
  booked: out.status === 'completed',
1727
- resort: session.name,
1727
+ product: session.name,
1728
1728
  quantity,
1729
1729
  total: session.amountDisplay,
1730
1730
  protocol: 'ucp',
@@ -1817,7 +1817,7 @@ server.setRequestHandler(CallToolRequestSchema, async (req) => {
1817
1817
  // rail — the card rehydrates to a receipt on reload instead of the picker.
1818
1818
  return finishBooking(sku, {
1819
1819
  booked: paid.status === 'captured',
1820
- resort: session.name,
1820
+ product: session.name,
1821
1821
  quantity,
1822
1822
  protocol: 'x402',
1823
1823
  payer: 'buyer chain wallet (x402)',
@@ -1890,7 +1890,7 @@ server.setRequestHandler(CallToolRequestSchema, async (req) => {
1890
1890
  const paidUsdc = fmtUsdc(BigInt(session.amount));
1891
1891
  return finishBooking(sku, {
1892
1892
  booked: paid.status === 'captured',
1893
- resort: session.name,
1893
+ product: session.name,
1894
1894
  quantity,
1895
1895
  protocol: 'x402',
1896
1896
  payer: 'buyer chain wallet (x402)',
@@ -1997,7 +1997,7 @@ server.setRequestHandler(CallToolRequestSchema, async (req) => {
1997
1997
  }
1998
1998
  return finishBooking(sku, {
1999
1999
  booked: paid.status === 'captured',
2000
- resort: session.name,
2000
+ product: session.name,
2001
2001
  quantity,
2002
2002
  protocol: 'x402',
2003
2003
  payer: 'buyer chain wallet (x402)',
@@ -2027,8 +2027,8 @@ server.setRequestHandler(CallToolRequestSchema, async (req) => {
2027
2027
  }
2028
2028
  const sku = String(args.sku ?? '');
2029
2029
  const quantity = Math.max(Number(args.quantity) || 1, 1);
2030
- const resort = await backend(`/catalog/${encodeURIComponent(sku)}`).catch(() => null);
2031
- const resortName = resort?.name ?? sku;
2030
+ const product = await backend(`/catalog/${encodeURIComponent(sku)}`).catch(() => null);
2031
+ const resortName = product?.name ?? sku;
2032
2032
  // The BUYER mints a Skyfire-signed PAY token (Skyfire's key signs it — the
2033
2033
  // buyer can't self-sign), then presents it to the MERCHANT backend, which
2034
2034
  // charges it via the merchant SDK (checkout.chargeSkyfire → the gateway
@@ -2049,7 +2049,7 @@ server.setRequestHandler(CallToolRequestSchema, async (req) => {
2049
2049
  : `Booked ${resortName} × ${quantity} via Skyfire KYAPay — ${amountStr} verified against Skyfire's live JWKS + recorded (PI ${status}). agentbank verified the agent + payment authorization and recorded it; no seller key is configured, so it did not auto-capture and no money moved.`;
2050
2050
  return finishBooking(sku, {
2051
2051
  booked: out.agent?.verified === true,
2052
- resort: resortName,
2052
+ product: resortName,
2053
2053
  quantity,
2054
2054
  protocol: 'skyfire',
2055
2055
  payer: 'skyfire buyer token',
@@ -2077,8 +2077,8 @@ server.setRequestHandler(CallToolRequestSchema, async (req) => {
2077
2077
  }
2078
2078
  const sku = String(args.sku ?? '');
2079
2079
  const quantity = Math.max(Number(args.quantity) || 1, 1);
2080
- const resort = await backend(`/catalog/${encodeURIComponent(sku)}`).catch(() => null);
2081
- const resortName = resort?.name ?? sku;
2080
+ const product = await backend(`/catalog/${encodeURIComponent(sku)}`).catch(() => null);
2081
+ const resortName = product?.name ?? sku;
2082
2082
  // Three-layer MPP · Tempo — the demo talks only to the MERCHANT backend,
2083
2083
  // which mediates the HTTP-402 dance via the merchant SDK (checkout.openMPP):
2084
2084
  // 1. ask the backend to open the charge → it returns the 402 challenge;
@@ -2103,7 +2103,7 @@ server.setRequestHandler(CallToolRequestSchema, async (req) => {
2103
2103
  const settled = out.status === 'settled';
2104
2104
  return finishBooking(sku, {
2105
2105
  booked: settled,
2106
- resort: resortName,
2106
+ product: resortName,
2107
2107
  quantity,
2108
2108
  protocol: 'mpp',
2109
2109
  payer: 'buyer chain wallet (MPP·Tempo)',
@@ -2160,7 +2160,7 @@ server.setRequestHandler(CallToolRequestSchema, async (req) => {
2160
2160
  // The card hides its button on this — an explicit flag, not a substring
2161
2161
  // of the status, so the two can never drift apart.
2162
2162
  refundPending: Boolean(o.refundRequest),
2163
- resort: o.lineItems?.map((li) => `${li.name} ×${li.quantity}`).join(', ') ?? '',
2163
+ product: o.lineItems?.map((li) => `${li.name} ×${li.quantity}`).join(', ') ?? '',
2164
2164
  total: fmt(o.amount, o.currency),
2165
2165
  protocol: o.protocol,
2166
2166
  date: o.createdAt?.slice(0, 10) ?? '',