@agenzo/merchant-cli 0.5.1 → 0.5.2

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
@@ -896,7 +896,9 @@ var quoteSchema = {
896
896
  "passenger-count": { type: "int", required: false, description: "Number of passengers" },
897
897
  "luggage-count": { type: "int", required: false, description: "Number of luggage items" },
898
898
  "passenger-email": { type: "string", required: false, description: "Passenger email address" },
899
- "children-count": { type: "int", required: false, description: "Number of children" }
899
+ "children-count": { type: "int", required: false, description: "Number of children" },
900
+ "infant-count": { type: "int", required: false, description: "Number of infants (passenger breakdown; affects seat pricing)" },
901
+ "toddler-count": { type: "int", required: false, description: "Number of toddlers (passenger breakdown; affects seat pricing)" }
900
902
  },
901
903
  response: {
902
904
  vehicle_classes: {
@@ -1241,7 +1243,7 @@ var hotelCreateOrderSchema = {
1241
1243
  cli: CLI_NAME,
1242
1244
  noun: HOTEL_NOUN,
1243
1245
  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",
1246
+ 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
1247
  flags: {
1246
1248
  "product-token": { type: "string", required: true, description: "Chosen rate token from quote (quote.response.rates[].product_token). Pass verbatim" },
1247
1249
  "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 +1262,74 @@ var hotelCreateOrderSchema = {
1260
1262
  "contact-email": { type: "string", required: false, description: "Booking contact email" },
1261
1263
  "arrive-time": { type: "string", required: false, description: "Expected arrival time", constraints: "HH:mm, hotel local time" },
1262
1264
  "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
1265
  "idempotency-key": {
1265
1266
  type: "string",
1266
1267
  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",
1268
+ 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
1269
  constraints: "1-128 chars [A-Za-z0-9_-]"
1269
1270
  }
1270
1271
  },
1271
1272
  response: {
1272
- order_id: { type: "string", description: "Our order reference (coOrderCode). Use as --order-id in get / cancel." },
1273
+ order_id: { type: "string", description: "Our order reference (coOrderCode). Use as --order-id in pay-order / get / cancel." },
1273
1274
  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." }
1275
+ 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." },
1276
+ currency: { type: "string", description: "ISO 4217 currency code for the payment." }
1276
1277
  },
1277
1278
  example: {
1278
1279
  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."
1280
+ 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
1281
  },
1281
1282
  error_recovery: {
1282
1283
  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
1284
  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.",
1285
+ 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
1286
  PARAM_IDEMPOTENCY_KEY_REQUIRED: "Add a unique --idempotency-key derived from business intent and retry.",
1290
1287
  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."
1288
+ UPSTREAM_ERROR: "Transient upstream error during checkBooking or createOrder. Retry once after ~2s backoff with the SAME idempotency-key."
1292
1289
  }
1293
1290
  };
1294
1291
  var hotelPayOrderSchema = {
1295
1292
  cli: CLI_NAME,
1296
1293
  noun: HOTEL_NOUN,
1297
1294
  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",
1295
+ 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
1296
  flags: {
1300
- "order-id": { type: "string", required: true, description: "Order to confirm with the supplier (create-order.response.order_id)." },
1297
+ "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
1298
  "idempotency-key": {
1302
1299
  type: "string",
1303
1300
  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",
1301
+ 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
1302
  constraints: "1-128 chars [A-Za-z0-9_-]"
1306
1303
  },
1307
- watch: { type: "bool", required: false, default: false, description: "Retained for backward compatibility. Each iteration emits one NDJSON line" },
1304
+ 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
1305
  "watch-interval": { type: "int", required: false, default: 5, description: "Seconds between poll attempts in --watch mode" },
1309
1306
  "watch-timeout": { type: "int", required: false, default: 300, description: "Total seconds before giving up in --watch mode" }
1310
1307
  },
1311
1308
  response: {
1312
1309
  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." }
1310
+ settlement_path: { type: "string", description: "Which billing path was used: 'monthly_settlement' or 'pay_per_call' (same tokens as billing_mode)." },
1311
+ amount: { type: "float", description: "Settled amount in DECIMAL units." },
1312
+ currency: { type: "string", description: "ISO 4217 currency code of the settlement." },
1313
+ pay_status: { type: "int", description: "Upstream pay status (1 = paid). The order internal status is now PAID." },
1314
+ settled_at: { type: "string", description: "ISO 8601 settlement timestamp. On idempotent replay, the original first settled_at is returned unchanged." }
1317
1315
  },
1318
1316
  example: {
1319
1317
  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.'
1318
+ 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
1319
  },
1322
1320
  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.",
1321
+ 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.",
1322
+ 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.",
1323
+ 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.",
1324
+ PAYMENT_QUERY_FAILED: "EVO gateway query failed (transport/timeout). Retry once after ~5s with the SAME idempotency-key.",
1325
+ 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.",
1326
+ 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
1327
  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.",
1328
+ ACCOUNT_INSUFFICIENT_BALANCE: "Settlement credit is insufficient (check via admin-cli accounts get). Direct the user to top up offline.",
1329
+ ACCOUNT_NOT_FOUND: "Developer has no settlement account. Direct the user to complete contract signing.",
1330
+ ACCOUNT_SUSPENDED: "The settlement account is suspended. Direct the user to contact support; do NOT retry.",
1331
+ 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.",
1332
+ 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
1333
  PARAM_IDEMPOTENCY_KEY_REQUIRED: "Add a unique --idempotency-key and retry.",
1327
1334
  PARAM_IDEMPOTENCY_KEY_CONFLICT: "Same idempotency-key used with different parameters. Use the original parameters, or generate a NEW key."
1328
1335
  }
@@ -1736,6 +1743,69 @@ var hotelDetailSchema = {
1736
1743
  PARAM_INVALID: "Ensure --hotel-id is a non-empty string, then retry."
1737
1744
  }
1738
1745
  };
1746
+ var ORDERS_NOUN = "orders";
1747
+ var unifiedOrdersListSchema = {
1748
+ cli: CLI_NAME,
1749
+ noun: ORDERS_NOUN,
1750
+ verb: "list",
1751
+ 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.',
1752
+ flags: {
1753
+ "order-type": { type: "string", required: false, description: "Filter by provider: ride | hotel" },
1754
+ status: { type: "string", required: false, description: "Filter by NORMALIZED status (NOT the domain-specific status): PENDING | CONFIRMED | COMPLETED | CANCELLED | FAILED" },
1755
+ page: { type: "int", required: false, default: 1, description: "Page number", constraints: ">= 1" },
1756
+ "page-size": { type: "int", required: false, default: 20, description: "Items per page", constraints: ">= 1" }
1757
+ },
1758
+ response: {
1759
+ orders: {
1760
+ type: "array",
1761
+ 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`.",
1762
+ items: {
1763
+ order_id: { type: "string", description: "Order id (rio_... for ride, hho_... for hotel). Pass to `orders get`." },
1764
+ order_type: { type: "string", description: "'ride' or 'hotel'" },
1765
+ status: { type: "string", description: "Normalized status: PENDING | CONFIRMED | COMPLETED | CANCELLED | FAILED" },
1766
+ amount: { type: "float|null", description: "Order amount in DECIMAL currency units (NOT cents)" },
1767
+ currency: { type: "string|null", description: "ISO 4217 currency code" },
1768
+ created_at: { type: "string|null", description: "ISO 8601 datetime" },
1769
+ updated_at: { type: "string|null", description: "ISO 8601 datetime" }
1770
+ }
1771
+ },
1772
+ total: { type: "int", description: "Total matching orders across all providers" },
1773
+ page: { type: "int", description: "Current page" },
1774
+ page_size: { type: "int", description: "Items per page" }
1775
+ },
1776
+ example: {
1777
+ command: "agenzo-merchant-cli orders list --page 1 --page-size 10",
1778
+ 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`."
1779
+ },
1780
+ error_recovery: {
1781
+ INVALID_REQUEST: "The --status value is not one of PENDING/CONFIRMED/COMPLETED/CANCELLED/FAILED. Fix and retry.",
1782
+ INTERNAL_ERROR: "Transient backend error. Retry once after a short delay.",
1783
+ PARAM_INVALID: "Ensure --page / --page-size are positive integers, then retry."
1784
+ }
1785
+ };
1786
+ var unifiedOrdersGetSchema = {
1787
+ cli: CLI_NAME,
1788
+ noun: ORDERS_NOUN,
1789
+ verb: "get",
1790
+ 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.",
1791
+ flags: {
1792
+ "order-id": { type: "string", required: true, description: "Order id from `orders list` (rio_... for ride, hho_... for hotel)" }
1793
+ },
1794
+ response: {
1795
+ "(varies by order_type)": {
1796
+ type: "object",
1797
+ 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."
1798
+ }
1799
+ },
1800
+ example: {
1801
+ command: "agenzo-merchant-cli orders get --order-id hho_01K...",
1802
+ output_summary: "Returns the order's detail, delegated to its owning provider (ride or hotel)."
1803
+ },
1804
+ error_recovery: {
1805
+ 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.",
1806
+ INTERNAL_ERROR: "The order's provider detail lookup is temporarily unavailable. Retry once after a short delay."
1807
+ }
1808
+ };
1739
1809
 
1740
1810
  // src/ride-elife/quote.ts
1741
1811
  function need(value, flag) {
@@ -2285,7 +2355,9 @@ var SERVICE_REGISTRY = [
2285
2355
  },
2286
2356
  workflow: ["quote", "book", "get (poll for status)", "cancel (optional)"],
2287
2357
  since: "2026-06-01",
2288
- discovery: { help_command: "agenzo-merchant-cli ride-elife --help" }
2358
+ discovery: {
2359
+ help_command: "agenzo-merchant-cli ride-elife --help"
2360
+ }
2289
2361
  },
2290
2362
  {
2291
2363
  service_id: "svc_01J0HT5REDAUG0001",
@@ -2337,7 +2409,9 @@ var SERVICE_REGISTRY = [
2337
2409
  "cancel / checkout (optional)"
2338
2410
  ],
2339
2411
  since: "2026-06-25",
2340
- discovery: { help_command: "agenzo-merchant-cli hotel-redaug --help" }
2412
+ discovery: {
2413
+ help_command: "agenzo-merchant-cli hotel-redaug --help"
2414
+ }
2341
2415
  }
2342
2416
  ];
2343
2417
  function findService(serviceId) {
@@ -2433,78 +2507,30 @@ function registerServicesListCommand(parent, deps) {
2433
2507
  }
2434
2508
 
2435
2509
  // 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
2510
  function formatServiceGet(s) {
2480
2511
  const lines = [
2481
2512
  ["Service ID", String(s.service_id ?? "")],
2482
- ["CLI Noun", String(s.cli_noun ?? "")],
2483
- ["Category", String(s.category ?? "")],
2484
2513
  ["Name", String(s.name ?? "")],
2485
- ["Summary", String(s.summary ?? "")],
2486
- ["Status", String(s.status ?? "")],
2514
+ ["Description", String(s.description ?? s.summary ?? "")],
2515
+ ["Category", String(s.category ?? "")],
2516
+ ["Version", String(s.version ?? "")],
2487
2517
  ["Provider", String(s.provider ?? "")],
2488
- ["Version", String(s.version ?? "")]
2518
+ ["CLI Noun", String(s.cli_noun ?? "")],
2519
+ ["Verbs", Array.isArray(s.verbs) ? s.verbs.join(", ") : ""]
2489
2520
  ];
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
- }
2521
+ if (Array.isArray(s.workflow) && s.workflow.length > 0) {
2522
+ lines.push(["Workflow", s.workflow.join(" \u2192 ")]);
2504
2523
  }
2505
- return sections.length > 0 ? `${block}
2524
+ const block = Formatter.keyValue(lines);
2525
+ const verbDescriptions = s.verb_descriptions;
2526
+ if (verbDescriptions && Object.keys(verbDescriptions).length > 0) {
2527
+ const detail = Object.entries(verbDescriptions).map(([verb, desc]) => ` ${verb}: ${desc}`).join("\n");
2528
+ return `${block}
2506
2529
 
2507
- ${sections.join("\n\n")}` : block;
2530
+ Verb descriptions:
2531
+ ${detail}`;
2532
+ }
2533
+ return block;
2508
2534
  }
2509
2535
  function gateCapability(data, localCaps) {
2510
2536
  const noun = String(data.cli_noun ?? "");
@@ -2525,7 +2551,7 @@ function gateCapability(data, localCaps) {
2525
2551
  return out;
2526
2552
  }
2527
2553
  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)");
2554
+ 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
2555
  cmd.action(async (serviceId) => {
2530
2556
  const opts = cmd.optsWithGlobals();
2531
2557
  const format = resolveFormat(opts.format);
@@ -2554,7 +2580,7 @@ function registerServiceGetCommand(parent, deps) {
2554
2580
  `Service '${serviceId}' is not available in this CLI. Run "services list" to see available services.`
2555
2581
  );
2556
2582
  }
2557
- const resolved = toServiceLayerView(data);
2583
+ const resolved = data;
2558
2584
  const configManager = new ConfigManager();
2559
2585
  const commandResult = {
2560
2586
  data: resolved,
@@ -2564,6 +2590,105 @@ function registerServiceGetCommand(parent, deps) {
2564
2590
  });
2565
2591
  }
2566
2592
 
2593
+ // src/orders/list.ts
2594
+ var DEFAULT_PAGE2 = "1";
2595
+ var DEFAULT_PAGE_SIZE2 = "20";
2596
+ function positiveInt2(value, flag) {
2597
+ const n = Number(value);
2598
+ if (!Number.isFinite(n) || !Number.isInteger(n) || n < 1) {
2599
+ throw new CliError("PARAM_INVALID", `--${flag} must be a positive integer.`);
2600
+ }
2601
+ return String(n);
2602
+ }
2603
+ function formatOrders2(data) {
2604
+ const orders = data.orders ?? [];
2605
+ if (orders.length === 0) {
2606
+ return Formatter.status("info", "No orders found");
2607
+ }
2608
+ const headers = ["Order ID", "Type", "Status", "Amount", "Currency"];
2609
+ const rows = orders.map((o) => [
2610
+ String(o.order_id ?? "-"),
2611
+ String(o.order_type ?? "-"),
2612
+ String(o.status ?? "-"),
2613
+ String(o.amount ?? "-"),
2614
+ String(o.currency ?? "-")
2615
+ ]);
2616
+ const summary = [
2617
+ ["Total", String(data.total ?? "-")],
2618
+ ["Page", String(data.page ?? "-")],
2619
+ ["Page size", String(data.page_size ?? "-")]
2620
+ ];
2621
+ return `${Formatter.table(headers, rows)}
2622
+ ${Formatter.keyValue(summary)}`;
2623
+ }
2624
+ function registerOrdersListCommand(parent, deps) {
2625
+ 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);
2626
+ attachSchemaHelp(cmd, unifiedOrdersListSchema);
2627
+ cmd.action(async () => {
2628
+ const opts = cmd.optsWithGlobals();
2629
+ const format = resolveFormat(opts.format);
2630
+ const apiKey = await PromptEngine.resolveInput(opts.apiKey, {
2631
+ message: "API Key:",
2632
+ type: "password"
2633
+ });
2634
+ const params = {
2635
+ page: positiveInt2(opts.page ?? DEFAULT_PAGE2, "page"),
2636
+ page_size: positiveInt2(opts.pageSize ?? DEFAULT_PAGE_SIZE2, "page-size")
2637
+ };
2638
+ if (opts.orderType !== void 0) params.order_type = opts.orderType;
2639
+ if (opts.status !== void 0) params.status = opts.status;
2640
+ const spinner = format === "json" ? null : createSpinner("Fetching orders...");
2641
+ const result = await deps.apiClient.get(
2642
+ "/orders",
2643
+ { type: "api-key", key: apiKey },
2644
+ params
2645
+ );
2646
+ spinner?.stop();
2647
+ if (!result.success) {
2648
+ throw CliError.fromApi(result, { auth: "api-key" });
2649
+ }
2650
+ const data = result.data;
2651
+ const configManager = new ConfigManager();
2652
+ const commandResult = {
2653
+ data,
2654
+ text: () => formatOrders2(data)
2655
+ };
2656
+ await renderWithContext(commandResult, { format }, configManager);
2657
+ });
2658
+ }
2659
+
2660
+ // src/orders/get.ts
2661
+ function registerOrdersGetCommand(parent, deps) {
2662
+ 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_...)");
2663
+ attachSchemaHelp(cmd, unifiedOrdersGetSchema);
2664
+ cmd.action(async () => {
2665
+ const opts = cmd.optsWithGlobals();
2666
+ const format = resolveFormat(opts.format);
2667
+ const apiKey = await PromptEngine.resolveInput(opts.apiKey, {
2668
+ message: "API Key:",
2669
+ type: "password"
2670
+ });
2671
+ const spinner = format === "json" ? null : createSpinner("Fetching order...");
2672
+ const result = await deps.apiClient.get(
2673
+ `/orders/${encodeURIComponent(opts.orderId)}`,
2674
+ { type: "api-key", key: apiKey }
2675
+ );
2676
+ spinner?.stop();
2677
+ if (!result.success) {
2678
+ throw CliError.fromApi(result, { auth: "api-key" });
2679
+ }
2680
+ const data = result.data;
2681
+ const configManager = new ConfigManager();
2682
+ const commandResult = {
2683
+ data,
2684
+ text: () => Formatter.keyValue(
2685
+ Object.entries(data).map(([k, v]) => [k, typeof v === "object" ? JSON.stringify(v) : String(v)])
2686
+ )
2687
+ };
2688
+ await renderWithContext(commandResult, { format }, configManager);
2689
+ });
2690
+ }
2691
+
2567
2692
  // src/hotel-redaug/search.ts
2568
2693
  function need5(value, flag) {
2569
2694
  if (value === void 0) {
@@ -2585,7 +2710,7 @@ function coord(value, flag, min, max) {
2585
2710
  }
2586
2711
  return n;
2587
2712
  }
2588
- function positiveInt2(value, flag) {
2713
+ function positiveInt3(value, flag) {
2589
2714
  const n = num3(value, flag);
2590
2715
  if (!Number.isInteger(n) || n <= 0) {
2591
2716
  throw new CliError("PARAM_INVALID", `--${flag} must be a positive integer.`);
@@ -2662,8 +2787,8 @@ function registerHotelSearchCommand(parent, deps) {
2662
2787
  if (!(checkOut > checkIn)) {
2663
2788
  throw new CliError("PARAM_INVALID", "--check-out must be strictly after --check-in.");
2664
2789
  }
2665
- const page = positiveInt2(opts.page, "page");
2666
- const pageSize = positiveInt2(opts.pageSize, "page-size");
2790
+ const page = positiveInt3(opts.page, "page");
2791
+ const pageSize = positiveInt3(opts.pageSize, "page-size");
2667
2792
  const body = {};
2668
2793
  if (hasDestination) {
2669
2794
  body.destination_id = need5(opts.destinationId, "destination-id");
@@ -2861,15 +2986,19 @@ function formatCreateOrder(data) {
2861
2986
  const rows = rooms.map((r) => [String(r.room_index ?? "-"), String(r.guest_name ?? "-")]);
2862
2987
  out.push("", Formatter.table(headers, rows));
2863
2988
  }
2989
+ out.push(
2990
+ "",
2991
+ Formatter.status(
2992
+ "info",
2993
+ `Order created with status AWAITING_PAYMENT \u2014 proceed with 'hotel-redaug pay-order --order-id ${data.order_id ?? "<order_id>"}' to settle.`
2994
+ )
2995
+ );
2864
2996
  return out.join("\n");
2865
2997
  }
2866
2998
  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(
2999
+ 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
3000
  "--idempotency-key <key>",
2872
- "Idempotency key forwarded verbatim as the Idempotency-Key header (covers both order creation AND payment)"
3001
+ "Idempotency key forwarded verbatim as the Idempotency-Key header"
2873
3002
  );
2874
3003
  attachSchemaHelp(cmd, hotelCreateOrderSchema);
2875
3004
  cmd.action(async () => {
@@ -2911,10 +3040,10 @@ function registerHotelCreateOrderCommand(parent, deps) {
2911
3040
  if (opts.contactEmail !== void 0) body.contact_email = opts.contactEmail;
2912
3041
  if (opts.arriveTime !== void 0) body.arrive_time = opts.arriveTime;
2913
3042
  if (opts.specialRequests !== void 0) body.special_requests = opts.specialRequests;
2914
- if (opts.paymentMethodId !== void 0) body.payment_method_id = opts.paymentMethodId;
3043
+ if (opts.hotelName !== void 0) body.hotel_name = opts.hotelName;
2915
3044
  if (!isYes) {
2916
3045
  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.`,
3046
+ 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
3047
  default: false
2919
3048
  });
2920
3049
  if (!confirmed) {
@@ -3073,20 +3202,23 @@ var DEFAULT_PAY_WATCH_TIMEOUT_SECONDS = 300;
3073
3202
  function formatPayOrder(data) {
3074
3203
  const lines = [
3075
3204
  ["Order ID", String(data.order_id ?? "-")],
3076
- ["Order status", String(data.status ?? "-")]
3205
+ ["Order status", String(data.order_status ?? "-")]
3077
3206
  ];
3078
3207
  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}`]);
3208
+ if (data.pay_status) lines.push(["Pay status", String(data.pay_status)]);
3209
+ if (data.total_amount != null && data.currency) {
3210
+ lines.push(["Amount", `${data.total_amount} ${data.currency}`]);
3081
3211
  }
3212
+ if (data.billing_entry_id) lines.push(["Billing entry", String(data.billing_entry_id)]);
3213
+ if (data.merchant_trans_id) lines.push(["Merchant trans ID", String(data.merchant_trans_id)]);
3082
3214
  return Formatter.keyValue(lines);
3083
3215
  }
3084
3216
  function isPaymentTerminal(record) {
3085
- const status = record.status;
3217
+ const status = record.order_status;
3086
3218
  return status === "PAID";
3087
3219
  }
3088
3220
  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(
3221
+ 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
3222
  "--idempotency-key <key>",
3091
3223
  "Idempotency key forwarded verbatim as the Idempotency-Key header"
3092
3224
  ).option("--watch", "Poll until PAID or timeout (NDJSON output)", false).option(
@@ -3121,11 +3253,11 @@ function registerHotelPayOrderCommand(parent, deps) {
3121
3253
  const body = {};
3122
3254
  if (!isYes) {
3123
3255
  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
3256
+ message: `Settle hotel order ${orderId}? This moves money via the order's configured billing mode (monthly_settlement or pay_per_call).`,
3257
+ default: false
3126
3258
  });
3127
3259
  if (!confirmed) {
3128
- throw new CliError("CLIENT_ABORTED", "Confirmation aborted by user.");
3260
+ throw new CliError("CLIENT_ABORTED", "Payment aborted by user.");
3129
3261
  }
3130
3262
  }
3131
3263
  const path = `/hotel/${encodeURIComponent(orderId)}/pay`;
@@ -3168,7 +3300,19 @@ function registerHotelPayOrderCommand(parent, deps) {
3168
3300
  return;
3169
3301
  }
3170
3302
  } else {
3171
- throw CliError.fromApi(result, { auth: "api-key" });
3303
+ const errorCode = result.code ?? result.errorCode;
3304
+ const errorCodeStr = String(errorCode);
3305
+ if (errorCodeStr === "PAYMENT_NOT_COMPLETED" || errorCodeStr === "1933") {
3306
+ const errorLine = {
3307
+ watch_status: "pending",
3308
+ error_code: errorCodeStr === "1933" ? "PAYMENT_NOT_COMPLETED" : errorCodeStr,
3309
+ message: result.errorMessage ?? "Payment not yet completed"
3310
+ };
3311
+ lastRecord = errorLine;
3312
+ ndjsonWriteLine(errorLine);
3313
+ } else {
3314
+ throw CliError.fromApi(result, { auth: "api-key" });
3315
+ }
3172
3316
  }
3173
3317
  if (Date.now() + watchInterval * 1e3 >= deadline) {
3174
3318
  const timeoutLine = {
@@ -3567,16 +3711,16 @@ function registerHotelGetCheckoutCommand(parent, deps) {
3567
3711
  }
3568
3712
 
3569
3713
  // src/hotel-redaug/list-orders.ts
3570
- var DEFAULT_PAGE2 = "1";
3571
- var DEFAULT_PAGE_SIZE2 = "20";
3572
- function positiveInt3(value, flag) {
3714
+ var DEFAULT_PAGE3 = "1";
3715
+ var DEFAULT_PAGE_SIZE3 = "20";
3716
+ function positiveInt4(value, flag) {
3573
3717
  const n = Number(value);
3574
3718
  if (!Number.isFinite(n) || !Number.isInteger(n) || n < 1) {
3575
3719
  throw new CliError("PARAM_INVALID", `--${flag} must be a positive integer.`);
3576
3720
  }
3577
3721
  return String(n);
3578
3722
  }
3579
- function formatOrders2(data) {
3723
+ function formatOrders3(data) {
3580
3724
  const orders = data.orders ?? [];
3581
3725
  if (orders.length === 0) {
3582
3726
  return Formatter.status("info", "No hotel orders found");
@@ -3600,7 +3744,7 @@ function formatOrders2(data) {
3600
3744
  ${Formatter.keyValue(summary)}`;
3601
3745
  }
3602
3746
  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);
3747
+ 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
3748
  attachSchemaHelp(cmd, hotelListOrdersSchema);
3605
3749
  cmd.action(async () => {
3606
3750
  const opts = cmd.optsWithGlobals();
@@ -3610,8 +3754,8 @@ function registerHotelListOrdersCommand(parent, deps) {
3610
3754
  type: "password"
3611
3755
  });
3612
3756
  const params = {
3613
- page: positiveInt3(opts.page ?? DEFAULT_PAGE2, "page"),
3614
- page_size: positiveInt3(opts.pageSize ?? DEFAULT_PAGE_SIZE2, "page-size")
3757
+ page: positiveInt4(opts.page ?? DEFAULT_PAGE3, "page"),
3758
+ page_size: positiveInt4(opts.pageSize ?? DEFAULT_PAGE_SIZE3, "page-size")
3615
3759
  };
3616
3760
  if (opts.status !== void 0) params.status = opts.status;
3617
3761
  const spinner = format === "json" ? null : createSpinner("Fetching orders...");
@@ -3628,7 +3772,7 @@ function registerHotelListOrdersCommand(parent, deps) {
3628
3772
  const configManager = new ConfigManager();
3629
3773
  const commandResult = {
3630
3774
  data,
3631
- text: () => formatOrders2(data)
3775
+ text: () => formatOrders3(data)
3632
3776
  };
3633
3777
  await renderWithContext(commandResult, { format }, configManager);
3634
3778
  });
@@ -4182,7 +4326,31 @@ async function main() {
4182
4326
  const flag = thisCommand.opts().format;
4183
4327
  process.env.AGENZO_FORMAT = resolveFormat(flag);
4184
4328
  });
4185
- const rideCmd = program.command("ride-elife").description("Ride ordering (eLife)");
4329
+ const rideCmd = program.command("ride-elife").description(
4330
+ `Ride ordering (eLife) \u2014 quote a fare, book it, poll status, and cancel.
4331
+
4332
+ Workflow (typical order):
4333
+ 1. quote Get fare quotes between two points \u2192 vehicle_classes[] each with a quote_id
4334
+ 2. book Book one vehicle_class using its quote_id \u2192 ride_id (write; needs --idempotency-key)
4335
+ 3. get Poll ride status by --order-id=<ride_id> until terminal (or use --watch for NDJSON)
4336
+ 4. cancel (optional) Cancel a ride by --order-id (write; may incur a fee)
4337
+ list-orders (standalone) List the developer's previous ride orders
4338
+
4339
+ Key notes:
4340
+ \u2022 Coordinates are NOT geocoded by the backend \u2014 supply --pickup-lat/lng and --dropoff-lat/lng.
4341
+ book must repeat the SAME pickup/dropoff coordinates+names and pickup-time used in quote;
4342
+ the quote_id does NOT carry them forward.
4343
+ \u2022 quote_id from quote is opaque and provider-specific \u2014 pass it unchanged to book, never reuse
4344
+ it across services.
4345
+ \u2022 vehicle_class values are case-sensitive literals (Sedan / SUV / MPV-5 / MPV-7 / Van / Luxury /
4346
+ Train) \u2014 pass back to book verbatim.
4347
+ \u2022 --pickup-time is UTC epoch seconds or the literal "now"; convert the user's local time to UTC.
4348
+ \u2022 All amounts are DECIMAL currency units (e.g. 42.50 = $42.50), never minor units (cents).
4349
+ \u2022 Billing mode is decided server-side: pay_per_call needs a PAID --payment-order-id (from
4350
+ payment-cli); monthly_settlement forbids it (settled against the developer's account).
4351
+ \u2022 Write verbs (book / cancel) require --idempotency-key; reuse the SAME key when retrying the
4352
+ same intent.`
4353
+ );
4186
4354
  registerQuoteCommand(rideCmd, deps);
4187
4355
  registerBookCommand(rideCmd, deps);
4188
4356
  registerRideGetCommand(rideCmd, deps);
@@ -4191,6 +4359,11 @@ async function main() {
4191
4359
  const servicesCmd = program.command("services").description("Merchant service discovery");
4192
4360
  registerServicesListCommand(servicesCmd, { discoveryClient, program });
4193
4361
  registerServiceGetCommand(servicesCmd, { discoveryClient, program });
4362
+ const ordersCmd = program.command("orders").description(
4363
+ '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.'
4364
+ );
4365
+ registerOrdersListCommand(ordersCmd, deps);
4366
+ registerOrdersGetCommand(ordersCmd, deps);
4194
4367
  const hotelCmd = program.command("hotel-redaug").description(
4195
4368
  `Hotel booking (Redaug) \u2014 international hotel search, quote, create-order, pay-order, cancel, and check-out.
4196
4369