@agenzo/merchant-cli 0.5.1 → 0.6.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.
package/dist/index.js CHANGED
@@ -41,6 +41,15 @@ var CODE_NUM = {
41
41
  QUOTE_EXPIRED: 4202,
42
42
  BOOKING_FAILED: 4203,
43
43
  CANCELLATION_NOT_ALLOWED: 4204,
44
+ PRICE_MISMATCH: 4205,
45
+ VEHICLE_CLASS_MISMATCH: 4206,
46
+ PAYMENT_METHOD_REQUIRED: 4207,
47
+ QUOTE_CACHE_UNAVAILABLE: 4208,
48
+ ARREARS_OUTSTANDING: 4209,
49
+ CONTACT_REQUIRED: 4210,
50
+ INVALID_PHONE: 4211,
51
+ SEAT_LIMIT_EXCEEDED: 4213,
52
+ RIDE_NOT_FOUND: 4212,
44
53
  NO_AVAILABILITY: 4301,
45
54
  PRICE_CHANGED: 4302,
46
55
  NAME_FORMAT_INVALID: 4303,
@@ -210,6 +219,15 @@ var STABLE_MESSAGE = {
210
219
  VEHICLE_UNAVAILABLE: "No vehicle is available for the requested trip. Please try a different time or class.",
211
220
  QUOTE_EXPIRED: "The quote has expired. Please request a new quote and retry.",
212
221
  BOOKING_FAILED: "The booking could not be completed. Please retry.",
222
+ PRICE_MISMATCH: "The price does not match the quote. Re-quote and use the exact amount from the quote response.",
223
+ VEHICLE_CLASS_MISMATCH: "The vehicle class does not match the quote. Use the vehicle class from the quote response.",
224
+ PAYMENT_METHOD_REQUIRED: "Pay-per-call billing requires an active payment method. Please add a card first.",
225
+ QUOTE_CACHE_UNAVAILABLE: "Quote cache is temporarily unavailable. Please request a new quote and retry.",
226
+ ARREARS_OUTSTANDING: "You have outstanding arrears. Please settle your balance before booking a new ride.",
227
+ CONTACT_REQUIRED: "Passenger contact information is required. Please provide a passenger name and phone number.",
228
+ INVALID_PHONE: "The passenger phone number is not valid. Please provide a valid international number.",
229
+ SEAT_LIMIT_EXCEEDED: "The number of child/infant/toddler seats exceeds the vehicle capacity. Reduce seats or choose a larger vehicle.",
230
+ RIDE_NOT_FOUND: "The ride order was not found or does not belong to you.",
213
231
  CANCELLATION_NOT_ALLOWED: "This order cannot be cancelled in its current state.",
214
232
  NO_AVAILABILITY: "No rooms available for the selected hotel and dates.",
215
233
  PRICE_CHANGED: "The price changed since the quote. Re-quote and confirm.",
@@ -896,7 +914,9 @@ var quoteSchema = {
896
914
  "passenger-count": { type: "int", required: false, description: "Number of passengers" },
897
915
  "luggage-count": { type: "int", required: false, description: "Number of luggage items" },
898
916
  "passenger-email": { type: "string", required: false, description: "Passenger email address" },
899
- "children-count": { type: "int", required: false, description: "Number of children" }
917
+ "children-count": { type: "int", required: false, description: "Number of children" },
918
+ "infant-count": { type: "int", required: false, description: "Number of infants (passenger breakdown; affects seat pricing)" },
919
+ "toddler-count": { type: "int", required: false, description: "Number of toddlers (passenger breakdown; affects seat pricing)" }
900
920
  },
901
921
  response: {
902
922
  vehicle_classes: {
@@ -1241,7 +1261,7 @@ var hotelCreateOrderSchema = {
1241
1261
  cli: CLI_NAME,
1242
1262
  noun: HOTEL_NOUN,
1243
1263
  verb: "create-order",
1244
- description: "Create AND pay for a hotel order in one call, using a product_token and price_items from a recent quote. The backend re-checks availability, authorizes/captures payment via the platform payment gateway (funds path decided server-side by the developer billing_mode \u2014 monthly_settlement or pay_per_call), locks the room with the supplier, and returns the order already PAID. There is no separate settlement step. If any step after funds are taken fails, the funds are automatically released/refunded before the error is returned. check-in/check-out, guest counts, total-amount and price-items must match the quote",
1264
+ description: "Create a hotel order WITHOUT charging any account (re-check availability + createOrder upstream), using a product_token and price_items from a recent quote. The order enters AWAITING_PAYMENT \u2014 no money is moved until pay-order. check-in/check-out, guest counts, total-amount and price-items must match the quote",
1245
1265
  flags: {
1246
1266
  "product-token": { type: "string", required: true, description: "Chosen rate token from quote (quote.response.rates[].product_token). Pass verbatim" },
1247
1267
  "total-amount": { type: "float", required: true, description: "Total price for the stay (quote.response.rates[].total_price.amount)", constraints: "DECIMAL units (NOT cents), 0.01\u2013999999999.99. Must equal the chosen rate total_price.amount" },
@@ -1260,69 +1280,74 @@ var hotelCreateOrderSchema = {
1260
1280
  "contact-email": { type: "string", required: false, description: "Booking contact email" },
1261
1281
  "arrive-time": { type: "string", required: false, description: "Expected arrival time", constraints: "HH:mm, hotel local time" },
1262
1282
  "special-requests": { type: "string", required: false, description: "Free-text special requests (non-binding)" },
1263
- "payment-method-id": { type: "string", required: false, description: "pay_per_call only: charge this SPECIFIC already-bound card instead of the platform auto-selected default/most-recent one. Ignored for monthly_settlement.", constraints: "Must be an ACTIVE card belonging to this developer, else PAYMENT_METHOD_REQUIRED" },
1264
1283
  "idempotency-key": {
1265
1284
  type: "string",
1266
1285
  required: true,
1267
- description: "Unique key forwarded verbatim as the Idempotency-Key header; never auto-generated. Derive from business intent (e.g. hash(user_id + product_token + check_in)) and reuse the SAME key when retrying the identical request. This single call covers both order creation AND payment",
1286
+ description: "Unique key forwarded verbatim as the Idempotency-Key header; never auto-generated. Derive from business intent (e.g. hash(user_id + product_token + check_in)) and reuse the SAME key when retrying the same order creation",
1268
1287
  constraints: "1-128 chars [A-Za-z0-9_-]"
1269
1288
  }
1270
1289
  },
1271
1290
  response: {
1272
- order_id: { type: "string", description: "Our order reference (coOrderCode). Use as --order-id in get / cancel." },
1291
+ order_id: { type: "string", description: "Our order reference (coOrderCode). Use as --order-id in pay-order / get / cancel." },
1273
1292
  fc_order_code: { type: "string", description: "Supplier order reference. Needed as --fc-order-code in cancel and checkout." },
1274
- total_amount: { type: "float", description: "Amount actually charged (DECIMAL units) \u2014 the order is already PAID by the time this response is returned." },
1275
- currency: { type: "string", description: "ISO 4217 currency code of the charge." }
1293
+ total_amount: { type: "float", description: "Amount to be paid in pay-order (DECIMAL units). The order is locked in AWAITING_PAYMENT \u2014 no charge has been made." },
1294
+ currency: { type: "string", description: "ISO 4217 currency code for the payment." }
1276
1295
  },
1277
1296
  example: {
1278
1297
  command: `agenzo-merchant-cli hotel-redaug create-order --product-token <tok> --total-amount 10.00 --currency CNY --price-items '[{"sale_date":"2026-08-20","sale_price":10.00,"breakfast_num":0}]' --check-in 2026-08-20 --check-out 2026-08-21 --adults 2 --guest-name "Zhang San" --contact-name "Zhang San" --contact-phone 13800138000 --idempotency-key create-h1n2`,
1279
- output_summary: "Returns {order_id, fc_order_code, total_amount, currency}. The order is already PAID \u2014 proceed to get to poll for supplier CONFIRMED status."
1298
+ output_summary: "Returns {order_id, fc_order_code, total_amount, currency}. The order is AWAITING_PAYMENT (no charge). Use order_id as --order-id in pay-order to settle."
1280
1299
  },
1281
1300
  error_recovery: {
1282
1301
  NO_AVAILABILITY: "Availability vanished between quote and create-order. Re-quote the same hotel; if still empty, quote another hotel or change dates. Use a NEW idempotency-key for the new product_token.",
1283
1302
  PRICE_CHANGED: "The price/price_items no longer match upstream. Re-quote to get fresh total_price and price_items, confirm the new price with the user, then create-order with the new values and a NEW idempotency-key.",
1284
- NAME_FORMAT_INVALID: "The supplier requires the guest name in a specific format (commonly Latin first/last name for international properties). Re-collect guest-name in Latin letters and retry with a NEW idempotency-key (the request is no longer identical).",
1285
- PAYMENT_METHOD_REQUIRED: "pay_per_call developer has no ACTIVE bound card at all, OR the explicitly-passed --payment-method-id is missing/not ACTIVE/does not belong to this developer. Direct the user to bind a card, drop --payment-method-id to auto-select, or pass a valid one.",
1286
- ACCOUNT_INSUFFICIENT_BALANCE: "monthly_settlement developer settlement credit is insufficient (check via admin-cli accounts get). Direct the user to top up offline; do NOT retry until funded.",
1287
- ACCOUNT_NOT_FOUND: "monthly_settlement developer has no settlement account. Direct the user to complete contract signing.",
1288
- ACCOUNT_SUSPENDED: "The settlement account is suspended. Direct the user to contact support; do NOT retry.",
1303
+ NAME_FORMAT_INVALID: "The supplier requires the guest name in a specific format (commonly Latin first/last name for international properties). Re-collect guest-name in Latin letters and retry with the SAME idempotency-key.",
1289
1304
  PARAM_IDEMPOTENCY_KEY_REQUIRED: "Add a unique --idempotency-key derived from business intent and retry.",
1290
1305
  PARAM_IDEMPOTENCY_KEY_CONFLICT: "The same idempotency-key was used with different parameters. Retry with the original parameters, or generate a NEW key for the new parameters.",
1291
- UPSTREAM_ERROR: "Transient upstream error during checkBooking or createOrder. Any funds already taken are automatically released/refunded before this error is returned. Retry once after ~2s backoff with the SAME idempotency-key."
1306
+ UPSTREAM_ERROR: "Transient upstream error during checkBooking or createOrder. Retry once after ~2s backoff with the SAME idempotency-key."
1292
1307
  }
1293
1308
  };
1294
1309
  var hotelPayOrderSchema = {
1295
1310
  cli: CLI_NAME,
1296
1311
  noun: HOTEL_NOUN,
1297
1312
  verb: "pay-order",
1298
- description: "Trigger supplier confirmation (upstream payOrder) for an order that was already paid via create-order. Takes only --order-id; no payment parameters needed. On success the supplier begins asynchronous confirmation \u2014 poll get until CONFIRMED (3). Idempotent: calling pay-order on an already-confirmed order replays the existing result",
1313
+ description: 'Settle an existing AWAITING_PAYMENT order created by create-order. Takes only --order-id; the billing path is decided server-side by the order billing_mode. monthly_settlement deducts from the developer credit account then confirms with the supplier; pay_per_call verifies the user EVO payment \u2014 the EVO merchantTransID IS the order_id (the user pays via EVO under the order_id), so the platform queries EVO for that order_id and requires an exact amount/currency match before confirming (the response settlement_path is then "pay_per_call"). On success the order becomes PAID. Supports --watch to poll on PAYMENT_NOT_COMPLETED until PAID',
1299
1314
  flags: {
1300
- "order-id": { type: "string", required: true, description: "Order to confirm with the supplier (create-order.response.order_id)." },
1315
+ "order-id": { type: "string", required: true, description: "Order to settle (create-order.response.order_id). For pay_per_call this is also the EVO merchantTransID the user paid under." },
1301
1316
  "idempotency-key": {
1302
1317
  type: "string",
1303
1318
  required: true,
1304
- description: "Unique key forwarded verbatim as the Idempotency-Key header; never auto-generated. Derive from business intent and reuse the SAME key when retrying the same pay-order call",
1319
+ description: "Unique key forwarded verbatim as the Idempotency-Key header; never auto-generated. Derive from business intent (e.g. hash(order_id + attempt)) and reuse the SAME key when retrying the same payment",
1305
1320
  constraints: "1-128 chars [A-Za-z0-9_-]"
1306
1321
  },
1307
- watch: { type: "bool", required: false, default: false, description: "Retained for backward compatibility. Each iteration emits one NDJSON line" },
1322
+ watch: { type: "bool", required: false, default: false, description: "Poll automatically on PAYMENT_NOT_COMPLETED until PAID or timeout. Each iteration emits one NDJSON line" },
1308
1323
  "watch-interval": { type: "int", required: false, default: 5, description: "Seconds between poll attempts in --watch mode" },
1309
1324
  "watch-timeout": { type: "int", required: false, default: 300, description: "Total seconds before giving up in --watch mode" }
1310
1325
  },
1311
1326
  response: {
1312
1327
  order_id: { type: "string", description: "Order reference." },
1313
- settlement_path: { type: "string", description: "The order's billing_mode echoed: 'monthly_settlement' or 'pay_per_call'." },
1314
- status: { type: "string", description: "'PAID' \u2014 supplier confirmation is asynchronous; poll get until CONFIRMED." },
1315
- amount: { type: "float", description: "Settled amount in DECIMAL units (from create-order)." },
1316
- currency: { type: "string", description: "ISO 4217 currency code." }
1328
+ settlement_path: { type: "string", description: "Which billing path was used: 'monthly_settlement' or 'pay_per_call' (same tokens as billing_mode)." },
1329
+ amount: { type: "float", description: "Settled amount in DECIMAL units." },
1330
+ currency: { type: "string", description: "ISO 4217 currency code of the settlement." },
1331
+ pay_status: { type: "int", description: "Upstream pay status (1 = paid). The order internal status is now PAID." },
1332
+ settled_at: { type: "string", description: "ISO 8601 settlement timestamp. On idempotent replay, the original first settled_at is returned unchanged." }
1317
1333
  },
1318
1334
  example: {
1319
1335
  command: "agenzo-merchant-cli hotel-redaug pay-order --order-id hho_01KWC63Z5CD6CKBM33Q7SC2ZDT --idempotency-key pay-h1n2",
1320
- output_summary: 'Triggers supplier payOrder (confirmation/ticket issuance). Returns {order_id, settlement_path, status:"PAID", amount, currency}. Then poll get until CONFIRMED.'
1336
+ output_summary: 'Settlement path is chosen server-side by billing_mode. monthly_settlement: deducts from the credit account, confirms with the supplier. Returns {order_id, settlement_path:"monthly_settlement", amount, currency, pay_status:1, settled_at}. Order becomes PAID. For pay_per_call the user must have already paid via EVO using the order_id as the merchantTransID; the same command settles it (settlement_path:"pay_per_call").'
1321
1337
  },
1322
1338
  error_recovery: {
1323
- INVALID_ORDER_STATE: "The order is not in a payable state (it may already be confirmed, cancelled, or missing auth). Check status via get; do NOT retry pay-order.",
1339
+ PAYMENT_NOT_COMPLETED: "pay_per_call only: EVO has not yet confirmed the payment for this order_id. Retry with the SAME idempotency-key after a delay, or use --watch to poll automatically until PAID.",
1340
+ PAYMENT_NOT_FOUND: "No EVO transaction found for this order_id. Confirm the user paid via EVO using the order_id as the merchantTransID; do NOT retry blindly.",
1341
+ PAYMENT_AMOUNT_MISMATCH: "The EVO-confirmed amount/currency does not match the order. The user must pay the exact order amount in the exact currency under the order_id. Do NOT retry until corrected.",
1342
+ PAYMENT_QUERY_FAILED: "EVO gateway query failed (transport/timeout). Retry once after ~5s with the SAME idempotency-key.",
1343
+ BILLING_MODE_MISMATCH: "The order billing_mode is not a recognized settlement mode (only monthly_settlement and pay_per_call are valid). Check the developer billing_mode; do NOT retry blindly.",
1344
+ INVALID_ORDER_STATE: "The order is not in AWAITING_PAYMENT (it may already be PAID or CANCELLED). Check status via get; do NOT retry pay-order.",
1324
1345
  ORDER_NOT_FOUND: "No order for this --order-id. Verify it is the order_id returned by create-order. Do NOT retry.",
1325
- UPSTREAM_ERROR: "Transient upstream error during supplier payOrder. Retry once after ~5s with the SAME idempotency-key, then poll get to check whether the confirmation actually took effect.",
1346
+ ACCOUNT_INSUFFICIENT_BALANCE: "Settlement credit is insufficient (check via admin-cli accounts get). Direct the user to top up offline.",
1347
+ ACCOUNT_NOT_FOUND: "Developer has no settlement account. Direct the user to complete contract signing.",
1348
+ ACCOUNT_SUSPENDED: "The settlement account is suspended. Direct the user to contact support; do NOT retry.",
1349
+ PAYORDER_FAILED: "Upstream payOrder failed after a successful deduction; the deduction was reversed and the order stays AWAITING_PAYMENT. Retry with the SAME idempotency-key after a delay.",
1350
+ PAYORDER_FAILED_AFTER_PAYMENT: "EVO payment confirmed but upstream payOrder failed. The order is recoverable (the EVO payment under the order_id is preserved). Contact support for reconciliation; do NOT retry automatically.",
1326
1351
  PARAM_IDEMPOTENCY_KEY_REQUIRED: "Add a unique --idempotency-key and retry.",
1327
1352
  PARAM_IDEMPOTENCY_KEY_CONFLICT: "Same idempotency-key used with different parameters. Use the original parameters, or generate a NEW key."
1328
1353
  }
@@ -1736,6 +1761,69 @@ var hotelDetailSchema = {
1736
1761
  PARAM_INVALID: "Ensure --hotel-id is a non-empty string, then retry."
1737
1762
  }
1738
1763
  };
1764
+ var ORDERS_NOUN = "orders";
1765
+ var unifiedOrdersListSchema = {
1766
+ cli: CLI_NAME,
1767
+ noun: ORDERS_NOUN,
1768
+ verb: "list",
1769
+ description: 'List orders across ALL providers (ride + hotel) in one call. Use this for generic "my orders" / "order history" requests. Prefer ride-elife/hotel-redaug list-orders when the user names a specific business.',
1770
+ flags: {
1771
+ "order-type": { type: "string", required: false, description: "Filter by provider: ride | hotel" },
1772
+ status: { type: "string", required: false, description: "Filter by NORMALIZED status (NOT the domain-specific status): PENDING | CONFIRMED | COMPLETED | CANCELLED | FAILED" },
1773
+ page: { type: "int", required: false, default: 1, description: "Page number", constraints: ">= 1" },
1774
+ "page-size": { type: "int", required: false, default: 20, description: "Items per page", constraints: ">= 1" }
1775
+ },
1776
+ response: {
1777
+ orders: {
1778
+ type: "array",
1779
+ description: "Slim cross-provider order-index items. For business-specific fields (vehicle_class, hotel_name, etc.), call `orders get --order-id <id>` or the domain-specific `get`.",
1780
+ items: {
1781
+ order_id: { type: "string", description: "Order id (rio_... for ride, hho_... for hotel). Pass to `orders get`." },
1782
+ order_type: { type: "string", description: "'ride' or 'hotel'" },
1783
+ status: { type: "string", description: "Normalized status: PENDING | CONFIRMED | COMPLETED | CANCELLED | FAILED" },
1784
+ amount: { type: "float|null", description: "Order amount in DECIMAL currency units (NOT cents)" },
1785
+ currency: { type: "string|null", description: "ISO 4217 currency code" },
1786
+ created_at: { type: "string|null", description: "ISO 8601 datetime" },
1787
+ updated_at: { type: "string|null", description: "ISO 8601 datetime" }
1788
+ }
1789
+ },
1790
+ total: { type: "int", description: "Total matching orders across all providers" },
1791
+ page: { type: "int", description: "Current page" },
1792
+ page_size: { type: "int", description: "Items per page" }
1793
+ },
1794
+ example: {
1795
+ command: "agenzo-merchant-cli orders list --page 1 --page-size 10",
1796
+ output_summary: "Returns a paginated, cross-provider list of orders (both ride and hotel). If the user then asks about one order, use its order_id with `orders get`."
1797
+ },
1798
+ error_recovery: {
1799
+ INVALID_REQUEST: "The --status value is not one of PENDING/CONFIRMED/COMPLETED/CANCELLED/FAILED. Fix and retry.",
1800
+ INTERNAL_ERROR: "Transient backend error. Retry once after a short delay.",
1801
+ PARAM_INVALID: "Ensure --page / --page-size are positive integers, then retry."
1802
+ }
1803
+ };
1804
+ var unifiedOrdersGetSchema = {
1805
+ cli: CLI_NAME,
1806
+ noun: ORDERS_NOUN,
1807
+ verb: "get",
1808
+ description: "Get a single order detail by id, regardless of which provider (ride/hotel) it belongs to. The platform resolves order_id -> provider internally and returns that provider's own detail shape.",
1809
+ flags: {
1810
+ "order-id": { type: "string", required: true, description: "Order id from `orders list` (rio_... for ride, hho_... for hotel)" }
1811
+ },
1812
+ response: {
1813
+ "(varies by order_type)": {
1814
+ type: "object",
1815
+ description: "The response shape is delegated to the owning provider and therefore varies: a ride order returns the same shape as `ride-elife get`; a hotel order returns the same shape as `hotel-redaug get`. Treat the result as an opaque object and surface whatever fields it contains \u2014 do NOT assume a fixed schema."
1816
+ }
1817
+ },
1818
+ example: {
1819
+ command: "agenzo-merchant-cli orders get --order-id hho_01K...",
1820
+ output_summary: "Returns the order's detail, delegated to its owning provider (ride or hotel)."
1821
+ },
1822
+ error_recovery: {
1823
+ ORDER_NOT_FOUND: "The order_id does not exist or does not belong to this developer/org. Verify the id from `orders list`. Do NOT retry blindly.",
1824
+ INTERNAL_ERROR: "The order's provider detail lookup is temporarily unavailable. Retry once after a short delay."
1825
+ }
1826
+ };
1739
1827
 
1740
1828
  // src/ride-elife/quote.ts
1741
1829
  function need(value, flag) {
@@ -2285,7 +2373,9 @@ var SERVICE_REGISTRY = [
2285
2373
  },
2286
2374
  workflow: ["quote", "book", "get (poll for status)", "cancel (optional)"],
2287
2375
  since: "2026-06-01",
2288
- discovery: { help_command: "agenzo-merchant-cli ride-elife --help" }
2376
+ discovery: {
2377
+ help_command: "agenzo-merchant-cli ride-elife --help"
2378
+ }
2289
2379
  },
2290
2380
  {
2291
2381
  service_id: "svc_01J0HT5REDAUG0001",
@@ -2337,7 +2427,9 @@ var SERVICE_REGISTRY = [
2337
2427
  "cancel / checkout (optional)"
2338
2428
  ],
2339
2429
  since: "2026-06-25",
2340
- discovery: { help_command: "agenzo-merchant-cli hotel-redaug --help" }
2430
+ discovery: {
2431
+ help_command: "agenzo-merchant-cli hotel-redaug --help"
2432
+ }
2341
2433
  }
2342
2434
  ];
2343
2435
  function findService(serviceId) {
@@ -2433,78 +2525,30 @@ function registerServicesListCommand(parent, deps) {
2433
2525
  }
2434
2526
 
2435
2527
  // src/services/get.ts
2436
- function buildVerbsSummary(data) {
2437
- const verbs = Array.isArray(data.verbs) ? data.verbs : [];
2438
- const verbDescriptions = data.verb_descriptions ?? {};
2439
- const schema = data.schema_content;
2440
- const schemaVerbs = schema?.verbs;
2441
- return verbs.map((verb) => {
2442
- const item = {
2443
- verb,
2444
- description: verbDescriptions[verb] ?? String(schemaVerbs?.[verb]?.description ?? "")
2445
- };
2446
- const annotations = schemaVerbs?.[verb]?.annotations;
2447
- if (annotations) item.annotations = annotations;
2448
- return item;
2449
- });
2450
- }
2451
- function toServiceLayerView(data) {
2452
- const schema = data.schema_content;
2453
- const view = {
2454
- service_id: data.service_id,
2455
- cli_noun: data.cli_noun,
2456
- category: data.category,
2457
- name: data.name,
2458
- summary: data.description ?? schema?.summary,
2459
- status: data.status ?? "available",
2460
- provider: data.provider,
2461
- version: data.version
2462
- };
2463
- if (schema?.selection_hints) view.selection_hints = schema.selection_hints;
2464
- const discovery = data.discovery;
2465
- view.schema_ref = schema?.schema_ref ?? {
2466
- help_command: discovery?.help_command,
2467
- ...discovery?.schema_url ? { schema_url: discovery.schema_url } : {}
2468
- };
2469
- if (schema?.conventions) view.conventions = schema.conventions;
2470
- if (schema?.workflow) {
2471
- view.workflow = schema.workflow;
2472
- } else if (Array.isArray(data.workflow)) {
2473
- view.workflow = { description: data.workflow.join(" \u2192 ") };
2474
- }
2475
- view.verbs_summary = buildVerbsSummary(data);
2476
- if (schema?.cross_service_recovery) view.cross_service_recovery = schema.cross_service_recovery;
2477
- return view;
2478
- }
2479
2528
  function formatServiceGet(s) {
2480
2529
  const lines = [
2481
2530
  ["Service ID", String(s.service_id ?? "")],
2482
- ["CLI Noun", String(s.cli_noun ?? "")],
2483
- ["Category", String(s.category ?? "")],
2484
2531
  ["Name", String(s.name ?? "")],
2485
- ["Summary", String(s.summary ?? "")],
2486
- ["Status", String(s.status ?? "")],
2532
+ ["Description", String(s.description ?? s.summary ?? "")],
2533
+ ["Category", String(s.category ?? "")],
2534
+ ["Version", String(s.version ?? "")],
2487
2535
  ["Provider", String(s.provider ?? "")],
2488
- ["Version", String(s.version ?? "")]
2536
+ ["CLI Noun", String(s.cli_noun ?? "")],
2537
+ ["Verbs", Array.isArray(s.verbs) ? s.verbs.join(", ") : ""]
2489
2538
  ];
2490
- const block = Formatter.keyValue(lines);
2491
- const sections = [];
2492
- for (const key of [
2493
- "selection_hints",
2494
- "schema_ref",
2495
- "conventions",
2496
- "workflow",
2497
- "verbs_summary",
2498
- "cross_service_recovery"
2499
- ]) {
2500
- if (s[key] !== void 0) {
2501
- sections.push(`${key}:
2502
- ${JSON.stringify(s[key], null, 2)}`);
2503
- }
2539
+ if (Array.isArray(s.workflow) && s.workflow.length > 0) {
2540
+ lines.push(["Workflow", s.workflow.join(" \u2192 ")]);
2504
2541
  }
2505
- return sections.length > 0 ? `${block}
2542
+ const block = Formatter.keyValue(lines);
2543
+ const verbDescriptions = s.verb_descriptions;
2544
+ if (verbDescriptions && Object.keys(verbDescriptions).length > 0) {
2545
+ const detail = Object.entries(verbDescriptions).map(([verb, desc]) => ` ${verb}: ${desc}`).join("\n");
2546
+ return `${block}
2506
2547
 
2507
- ${sections.join("\n\n")}` : block;
2548
+ Verb descriptions:
2549
+ ${detail}`;
2550
+ }
2551
+ return block;
2508
2552
  }
2509
2553
  function gateCapability(data, localCaps) {
2510
2554
  const noun = String(data.cli_noun ?? "");
@@ -2525,7 +2569,7 @@ function gateCapability(data, localCaps) {
2525
2569
  return out;
2526
2570
  }
2527
2571
  function registerServiceGetCommand(parent, deps) {
2528
- const cmd = parent.command("get <service-id>").description("Retrieve a single merchant service (service-layer view: workflow + conventions + verbs_summary)").option("--api-key <key>", "API Key for authentication (X-Api-Key)");
2572
+ const cmd = parent.command("get <service-id>").description("Retrieve a single merchant service by id (includes full schema)").option("--api-key <key>", "API Key for authentication (X-Api-Key)");
2529
2573
  cmd.action(async (serviceId) => {
2530
2574
  const opts = cmd.optsWithGlobals();
2531
2575
  const format = resolveFormat(opts.format);
@@ -2554,7 +2598,7 @@ function registerServiceGetCommand(parent, deps) {
2554
2598
  `Service '${serviceId}' is not available in this CLI. Run "services list" to see available services.`
2555
2599
  );
2556
2600
  }
2557
- const resolved = toServiceLayerView(data);
2601
+ const resolved = data;
2558
2602
  const configManager = new ConfigManager();
2559
2603
  const commandResult = {
2560
2604
  data: resolved,
@@ -2564,6 +2608,105 @@ function registerServiceGetCommand(parent, deps) {
2564
2608
  });
2565
2609
  }
2566
2610
 
2611
+ // src/orders/list.ts
2612
+ var DEFAULT_PAGE2 = "1";
2613
+ var DEFAULT_PAGE_SIZE2 = "20";
2614
+ function positiveInt2(value, flag) {
2615
+ const n = Number(value);
2616
+ if (!Number.isFinite(n) || !Number.isInteger(n) || n < 1) {
2617
+ throw new CliError("PARAM_INVALID", `--${flag} must be a positive integer.`);
2618
+ }
2619
+ return String(n);
2620
+ }
2621
+ function formatOrders2(data) {
2622
+ const orders = data.orders ?? [];
2623
+ if (orders.length === 0) {
2624
+ return Formatter.status("info", "No orders found");
2625
+ }
2626
+ const headers = ["Order ID", "Type", "Status", "Amount", "Currency"];
2627
+ const rows = orders.map((o) => [
2628
+ String(o.order_id ?? "-"),
2629
+ String(o.order_type ?? "-"),
2630
+ String(o.status ?? "-"),
2631
+ String(o.amount ?? "-"),
2632
+ String(o.currency ?? "-")
2633
+ ]);
2634
+ const summary = [
2635
+ ["Total", String(data.total ?? "-")],
2636
+ ["Page", String(data.page ?? "-")],
2637
+ ["Page size", String(data.page_size ?? "-")]
2638
+ ];
2639
+ return `${Formatter.table(headers, rows)}
2640
+ ${Formatter.keyValue(summary)}`;
2641
+ }
2642
+ function registerOrdersListCommand(parent, deps) {
2643
+ const cmd = parent.command("list").description("List orders across ALL providers (ride + hotel), with optional type/status filters").option("--api-key <key>", "API Key for authentication (X-Api-Key)").option("--order-type <type>", "Filter by order type: ride | hotel").option("--status <status>", "Filter by normalized status: PENDING | CONFIRMED | COMPLETED | CANCELLED | FAILED").option("--page <page>", "Page number", DEFAULT_PAGE2).option("--page-size <size>", "Page size", DEFAULT_PAGE_SIZE2);
2644
+ attachSchemaHelp(cmd, unifiedOrdersListSchema);
2645
+ cmd.action(async () => {
2646
+ const opts = cmd.optsWithGlobals();
2647
+ const format = resolveFormat(opts.format);
2648
+ const apiKey = await PromptEngine.resolveInput(opts.apiKey, {
2649
+ message: "API Key:",
2650
+ type: "password"
2651
+ });
2652
+ const params = {
2653
+ page: positiveInt2(opts.page ?? DEFAULT_PAGE2, "page"),
2654
+ page_size: positiveInt2(opts.pageSize ?? DEFAULT_PAGE_SIZE2, "page-size")
2655
+ };
2656
+ if (opts.orderType !== void 0) params.order_type = opts.orderType;
2657
+ if (opts.status !== void 0) params.status = opts.status;
2658
+ const spinner = format === "json" ? null : createSpinner("Fetching orders...");
2659
+ const result = await deps.apiClient.get(
2660
+ "/orders",
2661
+ { type: "api-key", key: apiKey },
2662
+ params
2663
+ );
2664
+ spinner?.stop();
2665
+ if (!result.success) {
2666
+ throw CliError.fromApi(result, { auth: "api-key" });
2667
+ }
2668
+ const data = result.data;
2669
+ const configManager = new ConfigManager();
2670
+ const commandResult = {
2671
+ data,
2672
+ text: () => formatOrders2(data)
2673
+ };
2674
+ await renderWithContext(commandResult, { format }, configManager);
2675
+ });
2676
+ }
2677
+
2678
+ // src/orders/get.ts
2679
+ function registerOrdersGetCommand(parent, deps) {
2680
+ const cmd = parent.command("get").description("Get a single order detail by id, regardless of provider (ride/hotel)").option("--api-key <key>", "API Key for authentication (X-Api-Key)").requiredOption("--order-id <id>", "Order id (e.g. rio_... or hho_...)");
2681
+ attachSchemaHelp(cmd, unifiedOrdersGetSchema);
2682
+ cmd.action(async () => {
2683
+ const opts = cmd.optsWithGlobals();
2684
+ const format = resolveFormat(opts.format);
2685
+ const apiKey = await PromptEngine.resolveInput(opts.apiKey, {
2686
+ message: "API Key:",
2687
+ type: "password"
2688
+ });
2689
+ const spinner = format === "json" ? null : createSpinner("Fetching order...");
2690
+ const result = await deps.apiClient.get(
2691
+ `/orders/${encodeURIComponent(opts.orderId)}`,
2692
+ { type: "api-key", key: apiKey }
2693
+ );
2694
+ spinner?.stop();
2695
+ if (!result.success) {
2696
+ throw CliError.fromApi(result, { auth: "api-key" });
2697
+ }
2698
+ const data = result.data;
2699
+ const configManager = new ConfigManager();
2700
+ const commandResult = {
2701
+ data,
2702
+ text: () => Formatter.keyValue(
2703
+ Object.entries(data).map(([k, v]) => [k, typeof v === "object" ? JSON.stringify(v) : String(v)])
2704
+ )
2705
+ };
2706
+ await renderWithContext(commandResult, { format }, configManager);
2707
+ });
2708
+ }
2709
+
2567
2710
  // src/hotel-redaug/search.ts
2568
2711
  function need5(value, flag) {
2569
2712
  if (value === void 0) {
@@ -2585,7 +2728,7 @@ function coord(value, flag, min, max) {
2585
2728
  }
2586
2729
  return n;
2587
2730
  }
2588
- function positiveInt2(value, flag) {
2731
+ function positiveInt3(value, flag) {
2589
2732
  const n = num3(value, flag);
2590
2733
  if (!Number.isInteger(n) || n <= 0) {
2591
2734
  throw new CliError("PARAM_INVALID", `--${flag} must be a positive integer.`);
@@ -2662,8 +2805,8 @@ function registerHotelSearchCommand(parent, deps) {
2662
2805
  if (!(checkOut > checkIn)) {
2663
2806
  throw new CliError("PARAM_INVALID", "--check-out must be strictly after --check-in.");
2664
2807
  }
2665
- const page = positiveInt2(opts.page, "page");
2666
- const pageSize = positiveInt2(opts.pageSize, "page-size");
2808
+ const page = positiveInt3(opts.page, "page");
2809
+ const pageSize = positiveInt3(opts.pageSize, "page-size");
2667
2810
  const body = {};
2668
2811
  if (hasDestination) {
2669
2812
  body.destination_id = need5(opts.destinationId, "destination-id");
@@ -2861,15 +3004,19 @@ function formatCreateOrder(data) {
2861
3004
  const rows = rooms.map((r) => [String(r.room_index ?? "-"), String(r.guest_name ?? "-")]);
2862
3005
  out.push("", Formatter.table(headers, rows));
2863
3006
  }
3007
+ out.push(
3008
+ "",
3009
+ Formatter.status(
3010
+ "info",
3011
+ `Order created with status AWAITING_PAYMENT \u2014 proceed with 'hotel-redaug pay-order --order-id ${data.order_id ?? "<order_id>"}' to settle.`
3012
+ )
3013
+ );
2864
3014
  return out.join("\n");
2865
3015
  }
2866
3016
  function registerHotelCreateOrderCommand(parent, deps) {
2867
- const cmd = parent.command("create-order").description("Create AND pay for a hotel order in one call (billing path decided server-side by billing_mode)").option("--api-key <key>", "API Key for authentication (X-Api-Key)").option("--product-token <token>", "Chosen rate token from quote (rates[].product_token)").option("--total-amount <amount>", "Total stay price in decimal currency units (not cents)").option("--currency <currency>", "ISO 4217 currency code from the chosen rate").option("--price-items <json>", "Per-night price breakdown JSON array (copied verbatim from quote)").option("--check-in <date>", "Check-in date (YYYY-MM-DD, match quote)").option("--check-out <date>", "Check-out date (YYYY-MM-DD, strictly after check-in)").option("--room-num <n>", "Number of rooms (match quote)", "1").option("--adults <n>", "Adults per room (match quote)", "2").option("--children <n>", "Children per room (match quote)", "0").option("--nationality <code>", "Guest nationality (ISO country code)", "CN").option("--guest-name <name>", "Primary guest name").option("--contact-name <name>", "Booking contact name (may equal guest-name)").option("--contact-phone <phone>", "Booking contact phone number (digits)").option("--contact-country-code <code>", "Phone country calling code without '+'", "86").option("--contact-email <email>", "Booking contact email").option("--arrive-time <time>", "Expected arrival time (HH:mm, hotel local time)").option("--special-requests <text>", "Free-text special requests (non-binding)").option(
2868
- "--payment-method-id <id>",
2869
- "pay_per_call only: charge this SPECIFIC already-bound card instead of the platform auto-selected one"
2870
- ).option(
3017
+ const cmd = parent.command("create-order").description("Create a hotel order without charging (lock inventory, await payment)").option("--api-key <key>", "API Key for authentication (X-Api-Key)").option("--product-token <token>", "Chosen rate token from quote (rates[].product_token)").option("--total-amount <amount>", "Total stay price in decimal currency units (not cents)").option("--currency <currency>", "ISO 4217 currency code from the chosen rate").option("--price-items <json>", "Per-night price breakdown JSON array (copied verbatim from quote)").option("--check-in <date>", "Check-in date (YYYY-MM-DD, match quote)").option("--check-out <date>", "Check-out date (YYYY-MM-DD, strictly after check-in)").option("--room-num <n>", "Number of rooms (match quote)", "1").option("--adults <n>", "Adults per room (match quote)", "2").option("--children <n>", "Children per room (match quote)", "0").option("--nationality <code>", "Guest nationality (ISO country code)", "CN").option("--guest-name <name>", "Primary guest name").option("--contact-name <name>", "Booking contact name (may equal guest-name)").option("--contact-phone <phone>", "Booking contact phone number (digits)").option("--contact-country-code <code>", "Phone country calling code without '+'", "86").option("--contact-email <email>", "Booking contact email").option("--arrive-time <time>", "Expected arrival time (HH:mm, hotel local time)").option("--special-requests <text>", "Free-text special requests (non-binding)").option("--hotel-name <name>", "Hotel name (display-only, stored for order summary)").option(
2871
3018
  "--idempotency-key <key>",
2872
- "Idempotency key forwarded verbatim as the Idempotency-Key header (covers both order creation AND payment)"
3019
+ "Idempotency key forwarded verbatim as the Idempotency-Key header"
2873
3020
  );
2874
3021
  attachSchemaHelp(cmd, hotelCreateOrderSchema);
2875
3022
  cmd.action(async () => {
@@ -2911,10 +3058,10 @@ function registerHotelCreateOrderCommand(parent, deps) {
2911
3058
  if (opts.contactEmail !== void 0) body.contact_email = opts.contactEmail;
2912
3059
  if (opts.arriveTime !== void 0) body.arrive_time = opts.arriveTime;
2913
3060
  if (opts.specialRequests !== void 0) body.special_requests = opts.specialRequests;
2914
- if (opts.paymentMethodId !== void 0) body.payment_method_id = opts.paymentMethodId;
3061
+ if (opts.hotelName !== void 0) body.hotel_name = opts.hotelName;
2915
3062
  if (!isYes) {
2916
3063
  const confirmed = await confirm3({
2917
- message: `Create and pay for this hotel order: ${totalAmount} ${currency} (check-in ${checkIn}, check-out ${checkOut})? This locks the rate AND charges the amount immediately.`,
3064
+ message: `Create this hotel order for ${totalAmount} ${currency} (check-in ${checkIn}, check-out ${checkOut})? This locks the rate but does NOT charge anything yet.`,
2918
3065
  default: false
2919
3066
  });
2920
3067
  if (!confirmed) {
@@ -3073,20 +3220,23 @@ var DEFAULT_PAY_WATCH_TIMEOUT_SECONDS = 300;
3073
3220
  function formatPayOrder(data) {
3074
3221
  const lines = [
3075
3222
  ["Order ID", String(data.order_id ?? "-")],
3076
- ["Order status", String(data.status ?? "-")]
3223
+ ["Order status", String(data.order_status ?? "-")]
3077
3224
  ];
3078
3225
  if (data.settlement_path) lines.push(["Settlement path", String(data.settlement_path)]);
3079
- if (data.amount != null && data.currency) {
3080
- lines.push(["Amount", `${data.amount} ${data.currency}`]);
3226
+ if (data.pay_status) lines.push(["Pay status", String(data.pay_status)]);
3227
+ if (data.total_amount != null && data.currency) {
3228
+ lines.push(["Amount", `${data.total_amount} ${data.currency}`]);
3081
3229
  }
3230
+ if (data.billing_entry_id) lines.push(["Billing entry", String(data.billing_entry_id)]);
3231
+ if (data.merchant_trans_id) lines.push(["Merchant trans ID", String(data.merchant_trans_id)]);
3082
3232
  return Formatter.keyValue(lines);
3083
3233
  }
3084
3234
  function isPaymentTerminal(record) {
3085
- const status = record.status;
3235
+ const status = record.order_status;
3086
3236
  return status === "PAID";
3087
3237
  }
3088
3238
  function registerHotelPayOrderCommand(parent, deps) {
3089
- const cmd = parent.command("pay-order").description("Trigger supplier confirmation for a paid hotel order (upstream payOrder)").option("--api-key <key>", "API Key for authentication (X-Api-Key)").option("--order-id <id>", "Order ID to pay (from create-order response)").option(
3239
+ const cmd = parent.command("pay-order").description("Settle an existing hotel order (path decided by billing_mode: monthly_settlement or pay_per_call)").option("--api-key <key>", "API Key for authentication (X-Api-Key)").option("--order-id <id>", "Order ID to pay (from create-order response)").option(
3090
3240
  "--idempotency-key <key>",
3091
3241
  "Idempotency key forwarded verbatim as the Idempotency-Key header"
3092
3242
  ).option("--watch", "Poll until PAID or timeout (NDJSON output)", false).option(
@@ -3121,11 +3271,11 @@ function registerHotelPayOrderCommand(parent, deps) {
3121
3271
  const body = {};
3122
3272
  if (!isYes) {
3123
3273
  const confirmed = await confirm4({
3124
- message: `Confirm hotel order ${orderId} with the supplier? (Payment was already settled in create-order; this triggers supplier confirmation.)`,
3125
- default: true
3274
+ message: `Settle hotel order ${orderId}? This moves money via the order's configured billing mode (monthly_settlement or pay_per_call).`,
3275
+ default: false
3126
3276
  });
3127
3277
  if (!confirmed) {
3128
- throw new CliError("CLIENT_ABORTED", "Confirmation aborted by user.");
3278
+ throw new CliError("CLIENT_ABORTED", "Payment aborted by user.");
3129
3279
  }
3130
3280
  }
3131
3281
  const path = `/hotel/${encodeURIComponent(orderId)}/pay`;
@@ -3168,7 +3318,19 @@ function registerHotelPayOrderCommand(parent, deps) {
3168
3318
  return;
3169
3319
  }
3170
3320
  } else {
3171
- throw CliError.fromApi(result, { auth: "api-key" });
3321
+ const errorCode = result.code ?? result.errorCode;
3322
+ const errorCodeStr = String(errorCode);
3323
+ if (errorCodeStr === "PAYMENT_NOT_COMPLETED" || errorCodeStr === "1933") {
3324
+ const errorLine = {
3325
+ watch_status: "pending",
3326
+ error_code: errorCodeStr === "1933" ? "PAYMENT_NOT_COMPLETED" : errorCodeStr,
3327
+ message: result.errorMessage ?? "Payment not yet completed"
3328
+ };
3329
+ lastRecord = errorLine;
3330
+ ndjsonWriteLine(errorLine);
3331
+ } else {
3332
+ throw CliError.fromApi(result, { auth: "api-key" });
3333
+ }
3172
3334
  }
3173
3335
  if (Date.now() + watchInterval * 1e3 >= deadline) {
3174
3336
  const timeoutLine = {
@@ -3567,16 +3729,16 @@ function registerHotelGetCheckoutCommand(parent, deps) {
3567
3729
  }
3568
3730
 
3569
3731
  // src/hotel-redaug/list-orders.ts
3570
- var DEFAULT_PAGE2 = "1";
3571
- var DEFAULT_PAGE_SIZE2 = "20";
3572
- function positiveInt3(value, flag) {
3732
+ var DEFAULT_PAGE3 = "1";
3733
+ var DEFAULT_PAGE_SIZE3 = "20";
3734
+ function positiveInt4(value, flag) {
3573
3735
  const n = Number(value);
3574
3736
  if (!Number.isFinite(n) || !Number.isInteger(n) || n < 1) {
3575
3737
  throw new CliError("PARAM_INVALID", `--${flag} must be a positive integer.`);
3576
3738
  }
3577
3739
  return String(n);
3578
3740
  }
3579
- function formatOrders2(data) {
3741
+ function formatOrders3(data) {
3580
3742
  const orders = data.orders ?? [];
3581
3743
  if (orders.length === 0) {
3582
3744
  return Formatter.status("info", "No hotel orders found");
@@ -3600,7 +3762,7 @@ function formatOrders2(data) {
3600
3762
  ${Formatter.keyValue(summary)}`;
3601
3763
  }
3602
3764
  function registerHotelListOrdersCommand(parent, deps) {
3603
- const cmd = parent.command("list-orders").description("List the developer's hotel orders with optional status filtering and pagination").option("--api-key <key>", "API Key for authentication (X-Api-Key)").option("--status <status>", "Filter by order status (omitted entirely when absent \u2192 all statuses)").option("--page <page>", "Page number", DEFAULT_PAGE2).option("--page-size <size>", "Page size", DEFAULT_PAGE_SIZE2);
3765
+ const cmd = parent.command("list-orders").description("List the developer's hotel orders with optional status filtering and pagination").option("--api-key <key>", "API Key for authentication (X-Api-Key)").option("--status <status>", "Filter by order status (omitted entirely when absent \u2192 all statuses)").option("--page <page>", "Page number", DEFAULT_PAGE3).option("--page-size <size>", "Page size", DEFAULT_PAGE_SIZE3);
3604
3766
  attachSchemaHelp(cmd, hotelListOrdersSchema);
3605
3767
  cmd.action(async () => {
3606
3768
  const opts = cmd.optsWithGlobals();
@@ -3610,8 +3772,8 @@ function registerHotelListOrdersCommand(parent, deps) {
3610
3772
  type: "password"
3611
3773
  });
3612
3774
  const params = {
3613
- page: positiveInt3(opts.page ?? DEFAULT_PAGE2, "page"),
3614
- page_size: positiveInt3(opts.pageSize ?? DEFAULT_PAGE_SIZE2, "page-size")
3775
+ page: positiveInt4(opts.page ?? DEFAULT_PAGE3, "page"),
3776
+ page_size: positiveInt4(opts.pageSize ?? DEFAULT_PAGE_SIZE3, "page-size")
3615
3777
  };
3616
3778
  if (opts.status !== void 0) params.status = opts.status;
3617
3779
  const spinner = format === "json" ? null : createSpinner("Fetching orders...");
@@ -3628,7 +3790,7 @@ function registerHotelListOrdersCommand(parent, deps) {
3628
3790
  const configManager = new ConfigManager();
3629
3791
  const commandResult = {
3630
3792
  data,
3631
- text: () => formatOrders2(data)
3793
+ text: () => formatOrders3(data)
3632
3794
  };
3633
3795
  await renderWithContext(commandResult, { format }, configManager);
3634
3796
  });
@@ -4182,7 +4344,31 @@ async function main() {
4182
4344
  const flag = thisCommand.opts().format;
4183
4345
  process.env.AGENZO_FORMAT = resolveFormat(flag);
4184
4346
  });
4185
- const rideCmd = program.command("ride-elife").description("Ride ordering (eLife)");
4347
+ const rideCmd = program.command("ride-elife").description(
4348
+ `Ride ordering (eLife) \u2014 quote a fare, book it, poll status, and cancel.
4349
+
4350
+ Workflow (typical order):
4351
+ 1. quote Get fare quotes between two points \u2192 vehicle_classes[] each with a quote_id
4352
+ 2. book Book one vehicle_class using its quote_id \u2192 ride_id (write; needs --idempotency-key)
4353
+ 3. get Poll ride status by --order-id=<ride_id> until terminal (or use --watch for NDJSON)
4354
+ 4. cancel (optional) Cancel a ride by --order-id (write; may incur a fee)
4355
+ list-orders (standalone) List the developer's previous ride orders
4356
+
4357
+ Key notes:
4358
+ \u2022 Coordinates are NOT geocoded by the backend \u2014 supply --pickup-lat/lng and --dropoff-lat/lng.
4359
+ book must repeat the SAME pickup/dropoff coordinates+names and pickup-time used in quote;
4360
+ the quote_id does NOT carry them forward.
4361
+ \u2022 quote_id from quote is opaque and provider-specific \u2014 pass it unchanged to book, never reuse
4362
+ it across services.
4363
+ \u2022 vehicle_class values are case-sensitive literals (Sedan / SUV / MPV-5 / MPV-7 / Van / Luxury /
4364
+ Train) \u2014 pass back to book verbatim.
4365
+ \u2022 --pickup-time is UTC epoch seconds or the literal "now"; convert the user's local time to UTC.
4366
+ \u2022 All amounts are DECIMAL currency units (e.g. 42.50 = $42.50), never minor units (cents).
4367
+ \u2022 Billing mode is decided server-side: pay_per_call needs a PAID --payment-order-id (from
4368
+ payment-cli); monthly_settlement forbids it (settled against the developer's account).
4369
+ \u2022 Write verbs (book / cancel) require --idempotency-key; reuse the SAME key when retrying the
4370
+ same intent.`
4371
+ );
4186
4372
  registerQuoteCommand(rideCmd, deps);
4187
4373
  registerBookCommand(rideCmd, deps);
4188
4374
  registerRideGetCommand(rideCmd, deps);
@@ -4191,6 +4377,11 @@ async function main() {
4191
4377
  const servicesCmd = program.command("services").description("Merchant service discovery");
4192
4378
  registerServicesListCommand(servicesCmd, { discoveryClient, program });
4193
4379
  registerServiceGetCommand(servicesCmd, { discoveryClient, program });
4380
+ const ordersCmd = program.command("orders").description(
4381
+ 'Unified cross-provider order index (spans ride + hotel). Use for generic "my orders" requests; use ride-elife/hotel-redaug commands once the business is known.'
4382
+ );
4383
+ registerOrdersListCommand(ordersCmd, deps);
4384
+ registerOrdersGetCommand(ordersCmd, deps);
4194
4385
  const hotelCmd = program.command("hotel-redaug").description(
4195
4386
  `Hotel booking (Redaug) \u2014 international hotel search, quote, create-order, pay-order, cancel, and check-out.
4196
4387