@agenzo/merchant-cli 0.7.0 → 0.8.1

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
@@ -58,6 +58,7 @@ var CODE_NUM = {
58
58
  CHECKOUT_NOT_ALLOWED: 4306,
59
59
  CHECKOUT_TASK_NOT_FOUND: 4307,
60
60
  PAY_PER_CALL_NOT_AVAILABLE: 4308,
61
+ HOTEL_NOT_FOUND: 4309,
61
62
  RATE_LIMITED: 5001,
62
63
  UPSTREAM_ERROR: 5101,
63
64
  INTERNAL_ERROR: 5201,
@@ -237,6 +238,7 @@ var STABLE_MESSAGE = {
237
238
  CHECKOUT_NOT_ALLOWED: "A partial check-out is not allowed in the current state.",
238
239
  CHECKOUT_TASK_NOT_FOUND: "Check-out application not found.",
239
240
  PAY_PER_CALL_NOT_AVAILABLE: "Pay-per-call billing is not available; use monthly_settlement.",
241
+ HOTEL_NOT_FOUND: "This hotel is no longer available from the supplier.",
240
242
  NOT_IMPLEMENTED: "This operation is not implemented yet."
241
243
  };
242
244
  function toErrorEnvelope(error) {
@@ -629,12 +631,12 @@ var SPINNER_FRAMES = ["\u280B", "\u2819", "\u2839", "\u2838", "\u283C", "\u2834"
629
631
  function createSpinner(message, intervalMs = 60) {
630
632
  let frameIdx = 0;
631
633
  let currentMessage = message;
632
- const render2 = () => {
634
+ const render22 = () => {
633
635
  process.stdout.write(`\r\x1B[K${SPINNER_FRAMES[frameIdx]} ${currentMessage}`);
634
636
  frameIdx = (frameIdx + 1) % SPINNER_FRAMES.length;
635
637
  };
636
- render2();
637
- const timer = setInterval(render2, intervalMs);
638
+ render22();
639
+ const timer = setInterval(render22, intervalMs);
638
640
  return {
639
641
  update(msg) {
640
642
  currentMessage = msg;
@@ -872,6 +874,7 @@ async function watchRideStatus(apiClient, apiKey, orderId, opts) {
872
874
  var CLI_NAME = "agenzo-merchant-cli";
873
875
  var NOUN = "ride-elife";
874
876
  var HOTEL_NOUN = "hotel-redaug";
877
+ var FLIGHT_NOUN = "flight-flink";
875
878
  function wantsJsonSchema(argv = process.argv) {
876
879
  for (let i = 0; i < argv.length; i++) {
877
880
  const a = argv[i];
@@ -1280,6 +1283,12 @@ var hotelCreateOrderSchema = {
1280
1283
  "contact-email": { type: "string", required: false, description: "Booking contact email" },
1281
1284
  "arrive-time": { type: "string", required: false, description: "Expected arrival time", constraints: "HH:mm, hotel local time" },
1282
1285
  "special-requests": { type: "string", required: false, description: "Free-text special requests (non-binding)" },
1286
+ tips: { type: "string", required: false, description: "FCG \u5BA2\u6237\u81EA\u6D4B\u70B95 \u63D0\u793A\u4FE1\u606F: the chosen quote rate's tips (tips[].tipsDetails[]) as a JSON array [{title,text}], captured at booking. Stored on the order for the order-detail page only; platform-local, not sent upstream", constraints: "JSON array of {title (string, optional), text (string, optional)}; else PARAM_INVALID" },
1287
+ "bed-type": {
1288
+ type: "string",
1289
+ required: false,
1290
+ description: "Product bed-type code from the chosen quote rate's beds[].code (e.g. 'L000000' King / '1000000' Queen). Forwarded verbatim to upstream createOrder bedType; upstream recommends passing the product-dimension bed type at booking."
1291
+ },
1283
1292
  "payment-token-id": {
1284
1293
  type: "string",
1285
1294
  required: false,
@@ -1674,10 +1683,15 @@ var hotelDetailSchema = {
1674
1683
  cli: CLI_NAME,
1675
1684
  noun: HOTEL_NOUN,
1676
1685
  verb: "hotel-detail",
1677
- description: "Get detailed information about a specific hotel including facilities, images, location, check-in/check-out times. Use to show the user hotel details before quoting/booking",
1686
+ description: "Get detailed information about a specific hotel including facilities, images, location, check-in/check-out times. Use to show the user hotel details before quoting/booking. hotel_id is stable but hotel_name/room_name may change upstream without notice. special_instructions (when requested via --settings hotelTextPolicies) MUST be shown to the user \u2014 the upstream data provider does not accept liability for booking issues caused by it not being displayed.",
1678
1687
  flags: {
1679
1688
  "hotel-id": { type: "string", required: true, description: "Hotel ID from search results (search.response.hotels[].hotel_id)" },
1680
- "with-images": { type: "bool", required: false, default: true, description: "Include hotel images in the response (set false to reduce payload)" }
1689
+ "with-images": { type: "bool", required: false, default: true, description: "Include hotel images in the response (set false to reduce payload)" },
1690
+ settings: {
1691
+ type: "string",
1692
+ required: false,
1693
+ description: "Optional on-demand upstream fields, comma-separated or JSON array \u2014 request only what you need: hotelFacilityNew, breakfast, importantNotices, parking, chargingParking, hotelCertificates, comment, hotelMeetingInfos, hotelVideoInfos, hotelTextPolicies (includes special_instructions), hotelStructuredPolicies.childPolicy, hotelStructuredPolicies.extraBedPolicy, hotelStructuredPolicies.petPolicy. Omitted codes are NOT returned by upstream (an upstream requirement, not a display choice). Pass hotelTextPolicies to get special_instructions."
1694
+ }
1681
1695
  },
1682
1696
  response: {
1683
1697
  hotel_id: { type: "string|int", description: "Hotel identifier." },
@@ -1755,6 +1769,45 @@ var hotelDetailSchema = {
1755
1769
  }
1756
1770
  }
1757
1771
  }
1772
+ },
1773
+ comment: {
1774
+ type: "array",
1775
+ description: "Guest review scores per channel. Empty unless --settings included 'comment'.",
1776
+ items: {
1777
+ channel: { type: "string|null", description: "Review source channel (e.g. EPS)." },
1778
+ average_score: { type: "float|null", description: "Average review score for that channel." }
1779
+ }
1780
+ },
1781
+ hotel_certificates: {
1782
+ type: "array",
1783
+ description: "Hotel business certificates/qualifications, when the property has any. Empty unless --settings included 'hotelCertificates' (and the property may simply have none).",
1784
+ items: {
1785
+ unify_code: { type: "string|null", description: "Unified business registration code." },
1786
+ certification_name: { type: "string|null", description: "Certificate name." },
1787
+ file_url: { type: "string|null", description: "Certificate image URL." }
1788
+ }
1789
+ },
1790
+ hotel_text_policies: {
1791
+ type: "array",
1792
+ description: "Free-text hotel policies keyed by code (hotelPolicy/instructions/specialInstructions/mandatoryFees/optionalFees/cleanAndSafety/importantNotices/ageLimit/checkInCheckOut). Empty unless --settings included 'hotelTextPolicies'. special_instructions below is also pulled from this same data, surfaced separately because it MUST be shown to the user.",
1793
+ items: {
1794
+ code: { type: "string|null", description: 'Policy code, e.g. "specialInstructions".' },
1795
+ code_name: { type: "string|null", description: 'Display name of the policy code (e.g. "\u7279\u522B\u5165\u4F4F\u8BF4\u660E").' },
1796
+ text: { type: "string|null", description: "Policy text (may contain HTML list markup from upstream)." }
1797
+ }
1798
+ },
1799
+ hotel_structured_policies: {
1800
+ type: "object|null",
1801
+ description: "Structured child/extra-bed/pet policies (raw upstream shape, snake_cased). null unless --settings included the relevant hotelStructuredPolicies.* code(s).",
1802
+ properties: {
1803
+ child_policy: { type: "object|null", description: "Child stay/breakfast policy." },
1804
+ extra_bed_policy: { type: "object|null", description: "Extra-bed charge policy by age range." },
1805
+ pet_policy: { type: "object|null", description: "Pet/service-animal policy." }
1806
+ }
1807
+ },
1808
+ special_instructions: {
1809
+ type: "string|null",
1810
+ description: "IMPORTANT: the property's 'special check-in instructions' (upstream code specialInstructions, distinct from the general 'instructions' code). The Agent/caller MUST surface this to the user in the booking UI when present \u2014 the upstream data provider does not accept liability for booking problems caused by this not being displayed. Only populated when --settings included 'hotelTextPolicies'; null otherwise (does not mean the hotel has none \u2014 it means it wasn't requested)."
1758
1811
  }
1759
1812
  },
1760
1813
  example: {
@@ -1763,7 +1816,8 @@ var hotelDetailSchema = {
1763
1816
  },
1764
1817
  error_recovery: {
1765
1818
  UPSTREAM_ERROR: "Transient upstream error. Retry once after ~2s backoff.",
1766
- PARAM_INVALID: "Ensure --hotel-id is a non-empty string, then retry."
1819
+ PARAM_INVALID: "Ensure --hotel-id is a non-empty string, then retry.",
1820
+ HOTEL_NOT_FOUND: "The hotel is no longer available from the supplier (no matching hotel node in the upstream response). Do not retry with the same hotel_id \u2014 drop it from any local cache/mapping and re-search."
1767
1821
  }
1768
1822
  };
1769
1823
  var ORDERS_NOUN = "orders";
@@ -1829,6 +1883,254 @@ var unifiedOrdersGetSchema = {
1829
1883
  INTERNAL_ERROR: "The order's provider detail lookup is temporarily unavailable. Retry once after a short delay."
1830
1884
  }
1831
1885
  };
1886
+ function flightSchema(verb, description, flags, response, example, extra = {}) {
1887
+ return { cli: CLI_NAME, noun: FLIGHT_NOUN, verb, description, flags, response, example, ...extra };
1888
+ }
1889
+ var flightFindAirportSchema = flightSchema(
1890
+ "find-airport",
1891
+ "Resolve free-text into city/airport candidates (city hits carry children airports).",
1892
+ { keyword: { type: "string", required: true, description: "Free-text place/airport/city name." } },
1893
+ { airports: { type: "array", description: "City/airport candidates with IATA codes." } },
1894
+ { command: "agenzo-merchant-cli flight-flink find-airport --keyword beijing", output_summary: "Returns candidate city/airport IATA codes." }
1895
+ );
1896
+ var flightListAirportsSchema = flightSchema(
1897
+ "list-airports",
1898
+ "Paged airport dictionary (requires page + page-size).",
1899
+ {
1900
+ page: { type: "int", required: false, default: 1, description: "1-based page." },
1901
+ "page-size": { type: "int", required: false, default: 20, description: "Items per page (1-100)." },
1902
+ keyword: { type: "string", required: false, description: "Optional name filter." }
1903
+ },
1904
+ { airports: { type: "array", description: "Airport records." } },
1905
+ { command: "agenzo-merchant-cli flight-flink list-airports --page 1 --page-size 20", output_summary: "Paged airport dictionary." }
1906
+ );
1907
+ var flightListAirlinesSchema = flightSchema(
1908
+ "list-airlines",
1909
+ "Paged airline dictionary (requires page + page-size).",
1910
+ {
1911
+ page: { type: "int", required: false, default: 1, description: "1-based page." },
1912
+ "page-size": { type: "int", required: false, default: 20, description: "Items per page (1-100)." },
1913
+ keyword: { type: "string", required: false, description: "Optional name filter." }
1914
+ },
1915
+ { airlines: { type: "array", description: "Airline records." } },
1916
+ { command: "agenzo-merchant-cli flight-flink list-airlines --page 1 --page-size 20", output_summary: "Paged airline dictionary." }
1917
+ );
1918
+ var flightListNationalitiesSchema = flightSchema(
1919
+ "list-nationalities",
1920
+ "Nationality list (no params).",
1921
+ {},
1922
+ { nationalities: { type: "array", description: "Nationality codes/names." } },
1923
+ { command: "agenzo-merchant-cli flight-flink list-nationalities", output_summary: "Nationality list." }
1924
+ );
1925
+ var flightSearchSchema = flightSchema(
1926
+ "search",
1927
+ "Search flights (one-way/round-trip/multi-city). journeys always carries all legs; journey-id accumulates progress and only the final search returns a real priceKey (product_token).",
1928
+ {
1929
+ "trip-type": { type: "int", required: true, description: "1=one-way, 2=round-trip, 3=multi-city." },
1930
+ journeys: { type: "json", required: true, description: "JSON array of {date, origin, destination, origin_type?, destination_type?}. origin/destination are IATA 3-letter codes; *_type 1=city, 2=airport." },
1931
+ "cabin-class": { type: "string", required: false, default: "economy", description: "economy | premium_economy | business | first." },
1932
+ "adult-num": { type: "int", required: false, default: 1, description: "Adults (1-9)." },
1933
+ "child-num": { type: "int", required: false, default: 0, description: "Children (0-9)." },
1934
+ "infant-num": { type: "int", required: false, default: 0, description: "Infants (0-9)." },
1935
+ airline: { type: "string", required: false, description: "Airline 2-letter code filter." },
1936
+ "transfer-number": { type: "int", required: false, default: 0, description: "0=any,1=direct,2=1 stop,3=2 stops." },
1937
+ "journey-id": { type: "json", required: false, description: "JSON array of already-selected journey ids (relay for round-trip/multi-city)." }
1938
+ },
1939
+ {
1940
+ offers: { type: "array", description: "Offers; each has product_token (when price_key_ready), total_sale_price, currency." },
1941
+ price_key_ready: { type: "bool", description: "True only when all legs are selected (final search)." },
1942
+ journey_ids: { type: "array", description: "journeyId values to relay into the next search." }
1943
+ },
1944
+ { command: `agenzo-merchant-cli flight-flink search --trip-type 1 --journeys '[{"date":"2026-08-01","origin":"PEK","destination":"SHA"}]'`, output_summary: "Returns offers[]; use a product_token where price_key_ready is true, then verify." }
1945
+ );
1946
+ var flightMoreOffersSchema = flightSchema(
1947
+ "more-offers",
1948
+ "More fare offers for a priceKey (carried by product_token).",
1949
+ { "product-token": { type: "string", required: true, description: "Opaque token from search." } },
1950
+ { offers: { type: "array", description: "Additional fare candidates." } },
1951
+ { command: "agenzo-merchant-cli flight-flink more-offers --product-token pt_...", output_summary: "Additional fare offers." }
1952
+ );
1953
+ var flightVerifySchema = flightSchema(
1954
+ "verify",
1955
+ "Pre-booking price verification. Returns the authoritative product_token and a price_changed flag; pass the returned token to create-order.",
1956
+ { "product-token": { type: "string", required: true, description: "Opaque token from search." } },
1957
+ {
1958
+ product_token: { type: "string", description: "Authoritative token to pass to create-order." },
1959
+ total_price: { type: "int", description: "Verified total price (integer)." },
1960
+ currency: { type: "string", description: "Currency code." },
1961
+ price_changed: { type: "bool", description: "True if the price changed since search; re-confirm with the user." }
1962
+ },
1963
+ { command: "agenzo-merchant-cli flight-flink verify --product-token pt_...", output_summary: "Returns the authoritative product_token + price_changed." }
1964
+ );
1965
+ var flightCreateOrderSchema = flightSchema(
1966
+ "create-order",
1967
+ "Create a flight order without charging (locks the fare, status AWAITING_PAYMENT). Forward the verify product_token and its total/currency verbatim.",
1968
+ {
1969
+ "product-token": { type: "string", required: true, description: "Authoritative token from verify." },
1970
+ "total-amount": { type: "string", required: true, description: "Verified total price (decimal units)." },
1971
+ currency: { type: "string", required: true, description: "ISO 4217 currency code." },
1972
+ "trip-type": { type: "int", required: false, default: 1, description: "1/2/3." },
1973
+ "contact-name": { type: "string", required: true, description: "Contact name." },
1974
+ "contact-region": { type: "string", required: true, description: "Contact phone country code (no +)." },
1975
+ "contact-phone": { type: "string", required: true, description: "Contact phone." },
1976
+ "contact-email": { type: "string", required: true, description: "Contact email." },
1977
+ passengers: { type: "json", required: true, description: 'JSON array of passengers. gender/id_type are STRINGS ("1"/"2"). child/infant require adult_passenger_name.' },
1978
+ "idempotency-key": { type: "string", required: true, description: "Forwarded verbatim as the Idempotency-Key header." }
1979
+ },
1980
+ {
1981
+ order_no: { type: "string", description: "Our order reference (ffo_...)." },
1982
+ upstream_order_no: { type: "string", description: "Supplier order number." },
1983
+ status: { type: "string", description: "AWAITING_PAYMENT after a successful create." },
1984
+ total_amount: { type: "float", description: "Total amount." },
1985
+ currency: { type: "string", description: "Currency code." }
1986
+ },
1987
+ { command: "agenzo-merchant-cli flight-flink create-order --product-token pt_... --total-amount 1200 --currency CNY --contact-name 'ZHANG SAN' --contact-region 86 --contact-phone 138... --contact-email a@b.com --passengers '[...]' --idempotency-key k1", output_summary: "Returns order_no; then pay-order to ticket." },
1988
+ {
1989
+ error_recovery: {
1990
+ PRICE_CHANGED: "Price changed since verify. Re-run verify and confirm the new price with the user before retrying.",
1991
+ ORDER_CREATE_FAILED: "Upstream create failed; funds released. Safe to retry with the same idempotency-key.",
1992
+ IDEMPOTENCY_CONFLICT: "Same key reused with different params. Use a fresh idempotency-key."
1993
+ }
1994
+ }
1995
+ );
1996
+ var flightPayOrderSchema = flightSchema(
1997
+ "pay-order",
1998
+ "Settle a created order by --order-no (triggers upstream ticketing). AWAITING_PAYMENT \u2192 PAID.",
1999
+ {
2000
+ "order-no": { type: "string", required: true, description: "Our order reference from create-order." },
2001
+ "idempotency-key": { type: "string", required: true, description: "Forwarded verbatim as the Idempotency-Key header." }
2002
+ },
2003
+ { order_no: { type: "string", description: "Our order reference." }, status: { type: "string", description: "PAID on success." } },
2004
+ { command: "agenzo-merchant-cli flight-flink pay-order --order-no ffo_... --idempotency-key k2", output_summary: "Order becomes PAID; poll get-order until TICKETED." }
2005
+ );
2006
+ var flightGetOrderSchema = flightSchema(
2007
+ "get-order",
2008
+ "Query flight order status by --order-no. Ticketing is asynchronous \u2014 poll until TICKETED.",
2009
+ {
2010
+ "order-no": { type: "string", required: true, description: "Our order reference." },
2011
+ watch: { type: "bool", required: false, default: false, description: "Poll until terminal, one NDJSON line per update." },
2012
+ "watch-interval": { type: "int", required: false, default: 5, description: "Seconds between polls." },
2013
+ "watch-timeout": { type: "int", required: false, default: 600, description: "Max seconds to poll." }
2014
+ },
2015
+ {
2016
+ order_no: { type: "string", description: "Our order reference." },
2017
+ status: { type: "string", description: "AWAITING_PAYMENT | PAID | TICKETING | TICKETED | CANCELLED | FAILED." },
2018
+ ticket_infos: { type: "array", description: "Ticket numbers once TICKETED." }
2019
+ },
2020
+ { command: "agenzo-merchant-cli flight-flink get-order --order-no ffo_...", output_summary: "Returns status; poll until TICKETED." },
2021
+ {
2022
+ polling: {
2023
+ recommended_interval_seconds: 5,
2024
+ terminal_statuses: ["TICKETED", "CANCELLED", "FAILED"],
2025
+ in_progress_statuses: ["AWAITING_PAYMENT", "PAID", "TICKETING"]
2026
+ }
2027
+ }
2028
+ );
2029
+ var flightCancelOrderSchema = flightSchema(
2030
+ "cancel-order",
2031
+ "Cancel an un-ticketed order by --order-no (with refund). A ticketed order is rejected upstream.",
2032
+ {
2033
+ "order-no": { type: "string", required: true, description: "Our order reference." },
2034
+ reason: { type: "string", required: false, description: "Optional cancellation reason." },
2035
+ "idempotency-key": { type: "string", required: true, description: "Forwarded verbatim as the Idempotency-Key header." }
2036
+ },
2037
+ { order_no: { type: "string", description: "Our order reference." }, status: { type: "string", description: "CANCELLED on success." }, refund_amount: { type: "float|null", description: "Refunded amount." } },
2038
+ { command: "agenzo-merchant-cli flight-flink cancel-order --order-no ffo_... --idempotency-key k3", output_summary: "Order becomes CANCELLED; refund issued." }
2039
+ );
2040
+ var flightListOrdersSchema = flightSchema(
2041
+ "list-orders",
2042
+ "List the developer's flight orders (local read, no upstream call).",
2043
+ {
2044
+ status: { type: "string", required: false, description: "Optional status filter." },
2045
+ page: { type: "int", required: false, default: 1, description: "1-based page." },
2046
+ "page-size": { type: "int", required: false, default: 20, description: "Items per page (1-100)." }
2047
+ },
2048
+ { orders: { type: "array", description: "Flight order summaries." }, total: { type: "int", description: "Total count." } },
2049
+ { command: "agenzo-merchant-cli flight-flink list-orders", output_summary: "Paged flight orders." }
2050
+ );
2051
+ var flightChangeSearchSchema = flightSchema(
2052
+ "change-search",
2053
+ "Search rebook-eligible flights for an order.",
2054
+ {
2055
+ "order-no": { type: "string", required: true, description: "Our order reference." },
2056
+ date: { type: "string", required: true, description: "Target change date YYYY-MM-DD." },
2057
+ passenger: { type: "string", required: true, description: "passengerCode." },
2058
+ "segment-id": { type: "string", required: true, description: "Comma-separated segment ids." },
2059
+ "cabin-class": { type: "string", required: false, default: "economy", description: "Cabin class." }
2060
+ },
2061
+ { flights: { type: "array", description: "Rebook-eligible flights." } },
2062
+ { command: "agenzo-merchant-cli flight-flink change-search --order-no ffo_... --date 2026-08-05 --passenger P1 --segment-id S1", output_summary: "Rebook options." }
2063
+ );
2064
+ var flightChangeApplySchema = flightSchema(
2065
+ "change-apply",
2066
+ "Submit a change request. Returns change_order_no, status 0 (pending review).",
2067
+ {
2068
+ "order-no": { type: "string", required: true, description: "Our order reference." },
2069
+ passenger: { type: "string", required: true, description: "passengerCode." },
2070
+ "segment-id": { type: "string", required: true, description: "Comma-separated segment ids." },
2071
+ "product-token": { type: "string", required: true, description: "Change priceKey token." },
2072
+ "contact-name": { type: "string", required: true, description: "Contact name." },
2073
+ "contact-region": { type: "string", required: true, description: "Contact region." },
2074
+ "contact-phone": { type: "string", required: true, description: "Contact phone." },
2075
+ "contact-email": { type: "string", required: true, description: "Contact email." },
2076
+ "idempotency-key": { type: "string", required: true, description: "Idempotency-Key header." }
2077
+ },
2078
+ { change_order_no: { type: "string", description: "Change order number." }, status: { type: "int", description: "0 = pending review." } },
2079
+ { command: "agenzo-merchant-cli flight-flink change-apply --order-no ffo_... --passenger P1 --segment-id S1 --product-token pt_... --contact-name X --contact-region 86 --contact-phone 138 --contact-email a@b.com --idempotency-key k4", output_summary: "Returns change_order_no; pay-order type=1 to confirm." }
2080
+ );
2081
+ var flightChangeDetailSchema = flightSchema(
2082
+ "change-detail",
2083
+ "Change request detail by --change-order-no.",
2084
+ { "change-order-no": { type: "string", required: true, description: "Change order number." } },
2085
+ { status: { type: "int", description: "Change status." } },
2086
+ { command: "agenzo-merchant-cli flight-flink change-detail --change-order-no C1", output_summary: "Change request detail." }
2087
+ );
2088
+ var flightChangeCancelSchema = flightSchema(
2089
+ "change-cancel",
2090
+ "Cancel a change request (immediate terminal).",
2091
+ {
2092
+ "change-order-no": { type: "string", required: true, description: "Change order number." },
2093
+ "idempotency-key": { type: "string", required: true, description: "Idempotency-Key header." }
2094
+ },
2095
+ { status: { type: "int", description: "Terminal 5 (cancelled)." } },
2096
+ { command: "agenzo-merchant-cli flight-flink change-cancel --change-order-no C1 --idempotency-key k5", output_summary: "Change request cancelled." }
2097
+ );
2098
+ var flightRefundApplySchema = flightSchema(
2099
+ "refund-apply",
2100
+ "Submit a refund request. Returns refund_order_no, status 0 (pending review).",
2101
+ {
2102
+ "order-no": { type: "string", required: true, description: "Our order reference." },
2103
+ passenger: { type: "string", required: true, description: "passengerCode." },
2104
+ "segment-id": { type: "string", required: true, description: "Comma-separated segment ids." },
2105
+ "reason-type": { type: "int", required: true, description: "Refund reason type code." },
2106
+ reason: { type: "string", required: false, description: "Free-text reason." },
2107
+ "contact-name": { type: "string", required: true, description: "Contact name." },
2108
+ "contact-region": { type: "string", required: true, description: "Contact region." },
2109
+ "contact-phone": { type: "string", required: true, description: "Contact phone." },
2110
+ "contact-email": { type: "string", required: true, description: "Contact email." },
2111
+ "idempotency-key": { type: "string", required: true, description: "Idempotency-Key header." }
2112
+ },
2113
+ { refund_order_no: { type: "string", description: "Refund order number." }, status: { type: "int", description: "0 = pending review." } },
2114
+ { command: "agenzo-merchant-cli flight-flink refund-apply --order-no ffo_... --passenger P1 --segment-id S1 --reason-type 1 --contact-name X --contact-region 86 --contact-phone 138 --contact-email a@b.com --idempotency-key k6", output_summary: "Returns refund_order_no; refund-confirm 1 to confirm." }
2115
+ );
2116
+ var flightRefundDetailSchema = flightSchema(
2117
+ "refund-detail",
2118
+ "Refund request detail by --refund-order-no.",
2119
+ { "refund-order-no": { type: "string", required: true, description: "Refund order number." } },
2120
+ { status: { type: "int", description: "Refund status." } },
2121
+ { command: "agenzo-merchant-cli flight-flink refund-detail --refund-order-no R1", output_summary: "Refund request detail." }
2122
+ );
2123
+ var flightRefundConfirmSchema = flightSchema(
2124
+ "refund-confirm",
2125
+ 'Confirm ("1") or cancel ("2") a refund. Cancel takes effect immediately to terminal 5.',
2126
+ {
2127
+ "refund-order-no": { type: "string", required: true, description: "Refund order number." },
2128
+ confirm: { type: "string", required: true, description: '"1" confirm / "2" cancel.' },
2129
+ "idempotency-key": { type: "string", required: true, description: "Idempotency-Key header." }
2130
+ },
2131
+ { status: { type: "int", description: "Refund status after the action." } },
2132
+ { command: "agenzo-merchant-cli flight-flink refund-confirm --refund-order-no R1 --confirm 1 --idempotency-key k7", output_summary: "Refund confirmed or cancelled." }
2133
+ );
1832
2134
 
1833
2135
  // src/ride-elife/quote.ts
1834
2136
  function need(value, flag) {
@@ -2435,6 +2737,72 @@ var SERVICE_REGISTRY = [
2435
2737
  discovery: {
2436
2738
  help_command: "agenzo-merchant-cli hotel-redaug --help"
2437
2739
  }
2740
+ },
2741
+ {
2742
+ // 与后端 services/merchant_service/discovery/catalog.py 的 flight-flink 条目 + provider
2743
+ // schema flight-flink.json 的 service_id 保持一致。Agent 拼命令用 cli_noun="flight-flink"。
2744
+ service_id: "svc_01J0HT5FLINK0001",
2745
+ name: "Flight booking (Flink)",
2746
+ description: "International flight booking: airport/city resolution, one-way/round-trip/multi-city search with journeyId relay, price verification, two-step create-then-pay ticketing, order polling, plus change (rebooking) and refund flows.",
2747
+ category: "flight",
2748
+ version: "1.0.0",
2749
+ provider: "flink",
2750
+ cli_noun: "flight-flink",
2751
+ verbs: [
2752
+ "find-airport",
2753
+ "list-airports",
2754
+ "list-airlines",
2755
+ "list-nationalities",
2756
+ "search",
2757
+ "more-offers",
2758
+ "verify",
2759
+ "create-order",
2760
+ "pay-order",
2761
+ "get-order",
2762
+ "cancel-order",
2763
+ "list-orders",
2764
+ "change-search",
2765
+ "change-apply",
2766
+ "change-detail",
2767
+ "change-cancel",
2768
+ "refund-apply",
2769
+ "refund-detail",
2770
+ "refund-confirm"
2771
+ ],
2772
+ verb_descriptions: {
2773
+ "find-airport": "Resolve free-text into city/airport candidates.",
2774
+ "list-airports": "Paged airport dictionary.",
2775
+ "list-airlines": "Paged airline dictionary.",
2776
+ "list-nationalities": "Nationality list.",
2777
+ search: "Search flights (one-way/round-trip/multi-city) with journeyId relay.",
2778
+ "more-offers": "More fare offers for a priceKey.",
2779
+ verify: "Verify the latest price before booking; returns the authoritative product_token.",
2780
+ "create-order": "Create a flight order without charging (locks the fare).",
2781
+ "pay-order": "Settle a created order by --order-no (triggers ticketing).",
2782
+ "get-order": "Query order status (poll ticketing 5\u21928).",
2783
+ "cancel-order": "Cancel an un-ticketed order (with refund).",
2784
+ "list-orders": "List flight orders (local store, no upstream call).",
2785
+ "change-search": "Search rebook-eligible flights.",
2786
+ "change-apply": "Submit a change request (pending review).",
2787
+ "change-detail": "Change request detail.",
2788
+ "change-cancel": "Cancel a change request (immediate terminal).",
2789
+ "refund-apply": "Submit a refund request (pending review).",
2790
+ "refund-detail": "Refund request detail.",
2791
+ "refund-confirm": 'Confirm ("1") or cancel ("2") a refund.'
2792
+ },
2793
+ workflow: [
2794
+ "find-airport",
2795
+ "search (relay journey-id for round-trip/multi-city)",
2796
+ "verify",
2797
+ "create-order (locks the fare, no charge)",
2798
+ "pay-order (settles + ticketing)",
2799
+ "get-order (poll until TICKETED)",
2800
+ "change-* / refund-* / cancel-order (optional)"
2801
+ ],
2802
+ since: "2026-07-09",
2803
+ discovery: {
2804
+ help_command: "agenzo-merchant-cli flight-flink --help"
2805
+ }
2438
2806
  }
2439
2807
  ];
2440
2808
  function findService(serviceId) {
@@ -2969,6 +3337,28 @@ function parsePriceItems(raw) {
2969
3337
  }
2970
3338
  return parsed;
2971
3339
  }
3340
+ function parseTips(raw) {
3341
+ const INVALID = "--tips must be a JSON array of {title (string, optional), text (string, optional)}.";
3342
+ let parsed;
3343
+ try {
3344
+ parsed = JSON.parse(raw);
3345
+ } catch {
3346
+ throw new CliError("PARAM_INVALID", INVALID);
3347
+ }
3348
+ if (!Array.isArray(parsed)) {
3349
+ throw new CliError("PARAM_INVALID", INVALID);
3350
+ }
3351
+ for (const el of parsed) {
3352
+ if (typeof el !== "object" || el === null || Array.isArray(el)) {
3353
+ throw new CliError("PARAM_INVALID", INVALID);
3354
+ }
3355
+ const item = el;
3356
+ if (item.title !== void 0 && typeof item.title !== "string" || item.text !== void 0 && typeof item.text !== "string") {
3357
+ throw new CliError("PARAM_INVALID", INVALID);
3358
+ }
3359
+ }
3360
+ return parsed;
3361
+ }
2972
3362
  function need7(value, flag) {
2973
3363
  if (value === void 0) {
2974
3364
  throw new CliError("PARAM_INVALID", `Missing required --${flag}.`);
@@ -3019,7 +3409,13 @@ function formatCreateOrder(data) {
3019
3409
  return out.join("\n");
3020
3410
  }
3021
3411
  function registerHotelCreateOrderCommand(parent, deps) {
3022
- 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(
3412
+ 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(
3413
+ "--tips <json>",
3414
+ "\u5BA2\u6237\u81EA\u6D4B\u70B95 \u63D0\u793A\u4FE1\u606F: the chosen quote rate's tips as a JSON array [{title,text}]. Stored on the order for the order-detail page ONLY \u2014 platform-local, not sent upstream."
3415
+ ).option("--hotel-name <name>", "Hotel name (display-only, stored for order summary)").option(
3416
+ "--bed-type <code>",
3417
+ "Product bed-type code from the chosen quote rate's beds[].code (e.g. 'L000000' King / '1000000' Queen); forwarded verbatim to upstream createOrder bedType."
3418
+ ).option(
3023
3419
  "--payment-token-id <id>",
3024
3420
  "UPI Agent Pay: payment token id from an already-completed UnionPay network-token capture. When set, the platform skips EVO preauth/capture and only locks the order + records this credential (funds already charged)."
3025
3421
  ).option(
@@ -3066,7 +3462,9 @@ function registerHotelCreateOrderCommand(parent, deps) {
3066
3462
  if (opts.contactEmail !== void 0) body.contact_email = opts.contactEmail;
3067
3463
  if (opts.arriveTime !== void 0) body.arrive_time = opts.arriveTime;
3068
3464
  if (opts.specialRequests !== void 0) body.special_requests = opts.specialRequests;
3465
+ if (opts.tips !== void 0) body.tips = parseTips(opts.tips);
3069
3466
  if (opts.hotelName !== void 0) body.hotel_name = opts.hotelName;
3467
+ if (opts.bedType !== void 0) body.bed_type = opts.bedType;
3070
3468
  if (opts.paymentTokenId !== void 0) body.payment_token_id = opts.paymentTokenId;
3071
3469
  if (!isYes) {
3072
3470
  const chargeNote = opts.paymentTokenId !== void 0 ? "Payment has already been captured via UPI Agent Pay; this only locks the room." : "This locks the rate but does NOT charge anything yet.";
@@ -3456,7 +3854,6 @@ function registerHotelGetCommand(parent, deps) {
3456
3854
  }
3457
3855
 
3458
3856
  // src/hotel-redaug/cancel.ts
3459
- import { confirm as confirm5 } from "@inquirer/prompts";
3460
3857
  function need9(value, flag) {
3461
3858
  if (value === void 0) {
3462
3859
  throw new CliError("PARAM_INVALID", `Missing required --${flag}.`);
@@ -3514,15 +3911,6 @@ function registerHotelCancelCommand(parent, deps) {
3514
3911
  const fcOrderCode = need9(opts.fcOrderCode, "fc-order-code");
3515
3912
  const body = { fc_order_code: fcOrderCode };
3516
3913
  if (opts.reason !== void 0) body.reason = opts.reason;
3517
- if (!isYes) {
3518
- const confirmed = await confirm5({
3519
- message: `Cancel hotel order ${orderId}? A cancellation fee may apply.`,
3520
- default: false
3521
- });
3522
- if (!confirmed) {
3523
- throw new CliError("CLIENT_ABORTED", "Cancellation aborted by user.");
3524
- }
3525
- }
3526
3914
  const idempotencyKey = await resolveIdempotencyKey(opts.idempotencyKey, {
3527
3915
  yes: isYes,
3528
3916
  commandPath: "hotel-redaug cancel"
@@ -3549,7 +3937,7 @@ function registerHotelCancelCommand(parent, deps) {
3549
3937
  }
3550
3938
 
3551
3939
  // src/hotel-redaug/checkout.ts
3552
- import { confirm as confirm6 } from "@inquirer/prompts";
3940
+ import { confirm as confirm5 } from "@inquirer/prompts";
3553
3941
  function need10(value, flag) {
3554
3942
  if (value === void 0) {
3555
3943
  throw new CliError("PARAM_INVALID", `Missing required --${flag}.`);
@@ -3635,7 +4023,7 @@ function registerHotelCheckoutCommand(parent, deps) {
3635
4023
  checkout_rooms: checkoutRooms
3636
4024
  };
3637
4025
  if (!isYes) {
3638
- const confirmed = await confirm6({
4026
+ const confirmed = await confirm5({
3639
4027
  message: `Request check-out / out-of-policy cancellation for order ${orderId} (${checkoutRooms.length} room(s))? The property must approve and charges may apply.`,
3640
4028
  default: false
3641
4029
  });
@@ -4037,6 +4425,23 @@ function need15(value, flag) {
4037
4425
  }
4038
4426
  return value;
4039
4427
  }
4428
+ function parseSettings(value) {
4429
+ if (value === void 0 || value.trim() === "") {
4430
+ return void 0;
4431
+ }
4432
+ const trimmed = value.trim();
4433
+ if (trimmed.startsWith("[")) {
4434
+ try {
4435
+ const parsed = JSON.parse(trimmed);
4436
+ if (Array.isArray(parsed) && parsed.every((v) => typeof v === "string")) {
4437
+ return parsed;
4438
+ }
4439
+ } catch {
4440
+ }
4441
+ throw new CliError("PARAM_INVALID", "Invalid --settings JSON array.");
4442
+ }
4443
+ return trimmed.split(",").map((s) => s.trim()).filter((s) => s.length > 0);
4444
+ }
4040
4445
  function formatHotelDetail(data) {
4041
4446
  const lines = [];
4042
4447
  const kvPairs = [
@@ -4059,6 +4464,10 @@ function formatHotelDetail(data) {
4059
4464
  ["Room Count", String(data.room_num ?? "-")]
4060
4465
  ];
4061
4466
  lines.push(Formatter.keyValue(kvPairs));
4467
+ if (data.special_instructions) {
4468
+ lines.push("");
4469
+ lines.push(Formatter.status("warning", `Special Check-in Instructions: ${data.special_instructions}`));
4470
+ }
4062
4471
  const facilities = data.facilities ?? [];
4063
4472
  if (facilities.length > 0) {
4064
4473
  lines.push("");
@@ -4116,10 +4525,39 @@ function formatHotelDetail(data) {
4116
4525
  lines.push("");
4117
4526
  lines.push(Formatter.status("info", "No room-type details available"));
4118
4527
  }
4528
+ const comments = data.comment ?? [];
4529
+ if (comments.length > 0) {
4530
+ lines.push("");
4531
+ lines.push("Review Scores:");
4532
+ const headers = ["Channel", "Average Score"];
4533
+ const rows = comments.map((c) => [String(c.channel ?? "-"), String(c.average_score ?? "-")]);
4534
+ lines.push(Formatter.table(headers, rows));
4535
+ }
4536
+ const certs = data.hotel_certificates ?? [];
4537
+ if (certs.length > 0) {
4538
+ lines.push("");
4539
+ lines.push("Certificates:");
4540
+ const headers = ["Name", "Unify Code"];
4541
+ const rows = certs.map((c) => [String(c.certification_name ?? "-"), String(c.unify_code ?? "-")]);
4542
+ lines.push(Formatter.table(headers, rows));
4543
+ }
4544
+ const textPolicies = (data.hotel_text_policies ?? []).filter(
4545
+ (p) => p.code !== "specialInstructions"
4546
+ );
4547
+ if (textPolicies.length > 0) {
4548
+ lines.push("");
4549
+ lines.push("Hotel Policies:");
4550
+ const headers = ["Policy", "Text"];
4551
+ const rows = textPolicies.map((p) => [String(p.code_name ?? p.code ?? "-"), String(p.text ?? "-")]);
4552
+ lines.push(Formatter.table(headers, rows));
4553
+ }
4119
4554
  return lines.join("\n");
4120
4555
  }
4121
4556
  function registerHotelDetailCommand(parent, deps) {
4122
- const cmd = parent.command("hotel-detail").description("Get detailed information about a specific hotel (facilities, images, etc.)").option("--api-key <key>", "API Key for authentication (X-Api-Key)").option("--hotel-id <id>", "Hotel ID (required)").option("--with-images [value]", "Include hotel images (default: true)", true);
4557
+ const cmd = parent.command("hotel-detail").description("Get detailed information about a specific hotel (facilities, images, etc.)").option("--api-key <key>", "API Key for authentication (X-Api-Key)").option("--hotel-id <id>", "Hotel ID (required)").option("--with-images [value]", "Include hotel images (default: true)", true).option(
4558
+ "--settings <list>",
4559
+ "Optional on-demand upstream fields, comma-separated or JSON array (hotelFacilityNew, breakfast, importantNotices, parking, chargingParking, hotelCertificates, comment, hotelMeetingInfos, hotelVideoInfos, hotelTextPolicies, hotelStructuredPolicies.childPolicy, hotelStructuredPolicies.extraBedPolicy, hotelStructuredPolicies.petPolicy). Pass hotelTextPolicies to get special_instructions."
4560
+ );
4123
4561
  attachSchemaHelp(cmd, hotelDetailSchema);
4124
4562
  cmd.action(async () => {
4125
4563
  const opts = cmd.optsWithGlobals();
@@ -4131,10 +4569,14 @@ function registerHotelDetailCommand(parent, deps) {
4131
4569
  const hotelId = need15(opts.hotelId, "hotel-id");
4132
4570
  const rawWithImages = opts.withImages;
4133
4571
  const withImages = rawWithImages !== "false" && rawWithImages !== false;
4572
+ const settings = parseSettings(opts.settings);
4134
4573
  const body = {
4135
4574
  hotel_id: hotelId,
4136
4575
  with_images: withImages
4137
4576
  };
4577
+ if (settings !== void 0) {
4578
+ body.settings = settings;
4579
+ }
4138
4580
  const result = await deps.apiClient.post(
4139
4581
  "/hotel/detail",
4140
4582
  { type: "api-key", key: apiKey },
@@ -4331,6 +4773,763 @@ function registerHotelSkillCommand(parent) {
4331
4773
  });
4332
4774
  }
4333
4775
 
4776
+ // src/flight-flink/_helpers.ts
4777
+ function need16(value, flag) {
4778
+ if (value === void 0) {
4779
+ throw new CliError("PARAM_INVALID", `Missing required --${flag}.`);
4780
+ }
4781
+ return value;
4782
+ }
4783
+ function num8(value, flag) {
4784
+ const n = Number(need16(value, flag));
4785
+ if (!Number.isFinite(n)) {
4786
+ throw new CliError("PARAM_INVALID", `--${flag} must be a number.`);
4787
+ }
4788
+ return n;
4789
+ }
4790
+ function jsonArray(raw, flag) {
4791
+ let parsed;
4792
+ try {
4793
+ parsed = JSON.parse(raw);
4794
+ } catch {
4795
+ throw new CliError("PARAM_INVALID", `--${flag} must be a JSON array.`);
4796
+ }
4797
+ if (!Array.isArray(parsed)) {
4798
+ throw new CliError("PARAM_INVALID", `--${flag} must be a JSON array.`);
4799
+ }
4800
+ return parsed;
4801
+ }
4802
+ function resolveApiKey(flag) {
4803
+ return PromptEngine.resolveInput(flag, { message: "API Key:", type: "password" });
4804
+ }
4805
+ async function render2(data, format, text) {
4806
+ const resolved = resolveFormat(format);
4807
+ const result = { data, text: () => text(data) };
4808
+ await renderWithContext(result, { format: resolved }, new ConfigManager());
4809
+ }
4810
+
4811
+ // src/flight-flink/find-airport.ts
4812
+ function registerFindAirportCommand(parent, deps) {
4813
+ const cmd = parent.command("find-airport").description("Resolve free-text into city/airport candidates").option("--api-key <key>", "API Key for authentication (X-Api-Key)").option("--keyword <text>", "Free-text place/airport/city name");
4814
+ attachSchemaHelp(cmd, flightFindAirportSchema);
4815
+ cmd.action(async () => {
4816
+ const opts = cmd.optsWithGlobals();
4817
+ const format = resolveFormat(opts.format);
4818
+ const apiKey = await resolveApiKey(opts.apiKey);
4819
+ const keyword = need16(opts.keyword, "keyword");
4820
+ const spinner = format === "json" ? null : createSpinner("Resolving airports...");
4821
+ const result = await deps.apiClient.post(
4822
+ "/flight/find-airport",
4823
+ { type: "api-key", key: apiKey },
4824
+ { keyword }
4825
+ );
4826
+ spinner?.stop();
4827
+ if (!result.success) throw CliError.fromApi(result, { auth: "api-key" });
4828
+ await render2(
4829
+ result.data,
4830
+ opts.format,
4831
+ (d) => JSON.stringify(d.airports ?? d, null, 2)
4832
+ );
4833
+ });
4834
+ }
4835
+
4836
+ // src/flight-flink/list-airports.ts
4837
+ function registerListAirportsCommand(parent, deps) {
4838
+ const cmd = parent.command("list-airports").description("Paged airport dictionary").option("--api-key <key>", "API Key for authentication (X-Api-Key)").option("--page <n>", "Page (1-based)", "1").option("--page-size <n>", "Items per page (1-100)", "20").option("--keyword <text>", "Optional name filter");
4839
+ attachSchemaHelp(cmd, flightListAirportsSchema);
4840
+ cmd.action(async () => {
4841
+ const opts = cmd.optsWithGlobals();
4842
+ const format = resolveFormat(opts.format);
4843
+ const apiKey = await resolveApiKey(opts.apiKey);
4844
+ const body = {
4845
+ page: num8(opts.page, "page"),
4846
+ page_size: num8(opts.pageSize, "page-size")
4847
+ };
4848
+ if (opts.keyword !== void 0) body.keyword = opts.keyword;
4849
+ const spinner = format === "json" ? null : createSpinner("Fetching airports...");
4850
+ const result = await deps.apiClient.post(
4851
+ "/flight/airports",
4852
+ { type: "api-key", key: apiKey },
4853
+ body
4854
+ );
4855
+ spinner?.stop();
4856
+ if (!result.success) throw CliError.fromApi(result, { auth: "api-key" });
4857
+ await render2(result.data, opts.format, (d) => JSON.stringify(d, null, 2));
4858
+ });
4859
+ }
4860
+
4861
+ // src/flight-flink/list-airlines.ts
4862
+ function registerListAirlinesCommand(parent, deps) {
4863
+ const cmd = parent.command("list-airlines").description("Paged airline dictionary").option("--api-key <key>", "API Key for authentication (X-Api-Key)").option("--page <n>", "Page (1-based)", "1").option("--page-size <n>", "Items per page (1-100)", "20").option("--keyword <text>", "Optional name filter");
4864
+ attachSchemaHelp(cmd, flightListAirlinesSchema);
4865
+ cmd.action(async () => {
4866
+ const opts = cmd.optsWithGlobals();
4867
+ const format = resolveFormat(opts.format);
4868
+ const apiKey = await resolveApiKey(opts.apiKey);
4869
+ const body = {
4870
+ page: num8(opts.page, "page"),
4871
+ page_size: num8(opts.pageSize, "page-size")
4872
+ };
4873
+ if (opts.keyword !== void 0) body.keyword = opts.keyword;
4874
+ const spinner = format === "json" ? null : createSpinner("Fetching airlines...");
4875
+ const result = await deps.apiClient.post(
4876
+ "/flight/airlines",
4877
+ { type: "api-key", key: apiKey },
4878
+ body
4879
+ );
4880
+ spinner?.stop();
4881
+ if (!result.success) throw CliError.fromApi(result, { auth: "api-key" });
4882
+ await render2(result.data, opts.format, (d) => JSON.stringify(d, null, 2));
4883
+ });
4884
+ }
4885
+
4886
+ // src/flight-flink/list-nationalities.ts
4887
+ function registerListNationalitiesCommand(parent, deps) {
4888
+ const cmd = parent.command("list-nationalities").description("Nationality list").option("--api-key <key>", "API Key for authentication (X-Api-Key)");
4889
+ attachSchemaHelp(cmd, flightListNationalitiesSchema);
4890
+ cmd.action(async () => {
4891
+ const opts = cmd.optsWithGlobals();
4892
+ const format = resolveFormat(opts.format);
4893
+ const apiKey = await resolveApiKey(opts.apiKey);
4894
+ const spinner = format === "json" ? null : createSpinner("Fetching nationalities...");
4895
+ const result = await deps.apiClient.post(
4896
+ "/flight/nationalities",
4897
+ { type: "api-key", key: apiKey },
4898
+ {}
4899
+ );
4900
+ spinner?.stop();
4901
+ if (!result.success) throw CliError.fromApi(result, { auth: "api-key" });
4902
+ await render2(result.data, opts.format, (d) => JSON.stringify(d, null, 2));
4903
+ });
4904
+ }
4905
+
4906
+ // src/flight-flink/search.ts
4907
+ function registerSearchCommand(parent, deps) {
4908
+ const cmd = parent.command("search").description("Search flights (one-way/round-trip/multi-city, journeyId relay)").option("--api-key <key>", "API Key for authentication (X-Api-Key)").option("--trip-type <n>", "1=one-way, 2=round-trip, 3=multi-city").option("--journeys <json>", "JSON array of {date, origin, destination, origin_type?, destination_type?}").option("--cabin-class <c>", "economy | premium_economy | business | first", "economy").option("--adult-num <n>", "Adults (1-9)", "1").option("--child-num <n>", "Children (0-9)", "0").option("--infant-num <n>", "Infants (0-9)", "0").option("--airline <code>", "Airline 2-letter code filter").option("--transfer-number <n>", "0=any,1=direct,2=1 stop,3=2 stops", "0").option("--journey-id <json>", "JSON array of already-selected journey ids (relay)");
4909
+ attachSchemaHelp(cmd, flightSearchSchema);
4910
+ cmd.action(async () => {
4911
+ const opts = cmd.optsWithGlobals();
4912
+ const format = resolveFormat(opts.format);
4913
+ const apiKey = await resolveApiKey(opts.apiKey);
4914
+ const body = {
4915
+ trip_type: num8(opts.tripType, "trip-type"),
4916
+ journeys: jsonArray(need16(opts.journeys, "journeys"), "journeys"),
4917
+ cabin_class: opts.cabinClass,
4918
+ adult_num: num8(opts.adultNum, "adult-num"),
4919
+ child_num: num8(opts.childNum, "child-num"),
4920
+ infant_num: num8(opts.infantNum, "infant-num"),
4921
+ transfer_number: num8(opts.transferNumber, "transfer-number")
4922
+ };
4923
+ if (opts.airline !== void 0) body.airline = opts.airline;
4924
+ if (opts.journeyId !== void 0) {
4925
+ body.journey_id = jsonArray(opts.journeyId, "journey-id");
4926
+ }
4927
+ const spinner = format === "json" ? null : createSpinner("Searching flights...");
4928
+ const result = await deps.apiClient.post(
4929
+ "/flight/search",
4930
+ { type: "api-key", key: apiKey },
4931
+ body
4932
+ );
4933
+ spinner?.stop();
4934
+ if (!result.success) throw CliError.fromApi(result, { auth: "api-key" });
4935
+ await render2(result.data, opts.format, (d) => JSON.stringify(d, null, 2));
4936
+ });
4937
+ }
4938
+
4939
+ // src/flight-flink/more-offers.ts
4940
+ function registerMoreOffersCommand(parent, deps) {
4941
+ const cmd = parent.command("more-offers").description("More fare offers for a priceKey (carried by product_token)").option("--api-key <key>", "API Key for authentication (X-Api-Key)").option("--product-token <token>", "Opaque token from search");
4942
+ attachSchemaHelp(cmd, flightMoreOffersSchema);
4943
+ cmd.action(async () => {
4944
+ const opts = cmd.optsWithGlobals();
4945
+ const format = resolveFormat(opts.format);
4946
+ const apiKey = await resolveApiKey(opts.apiKey);
4947
+ const productToken = need16(opts.productToken, "product-token");
4948
+ const spinner = format === "json" ? null : createSpinner("Fetching offers...");
4949
+ const result = await deps.apiClient.post(
4950
+ "/flight/more-offers",
4951
+ { type: "api-key", key: apiKey },
4952
+ { product_token: productToken }
4953
+ );
4954
+ spinner?.stop();
4955
+ if (!result.success) throw CliError.fromApi(result, { auth: "api-key" });
4956
+ await render2(result.data, opts.format, (d) => JSON.stringify(d, null, 2));
4957
+ });
4958
+ }
4959
+
4960
+ // src/flight-flink/verify.ts
4961
+ function registerVerifyCommand(parent, deps) {
4962
+ const cmd = parent.command("verify").description("Verify the latest price before booking; returns the authoritative product_token").option("--api-key <key>", "API Key for authentication (X-Api-Key)").option("--product-token <token>", "Opaque token from search");
4963
+ attachSchemaHelp(cmd, flightVerifySchema);
4964
+ cmd.action(async () => {
4965
+ const opts = cmd.optsWithGlobals();
4966
+ const format = resolveFormat(opts.format);
4967
+ const apiKey = await resolveApiKey(opts.apiKey);
4968
+ const productToken = need16(opts.productToken, "product-token");
4969
+ const spinner = format === "json" ? null : createSpinner("Verifying price...");
4970
+ const result = await deps.apiClient.post(
4971
+ "/flight/verify",
4972
+ { type: "api-key", key: apiKey },
4973
+ { product_token: productToken }
4974
+ );
4975
+ spinner?.stop();
4976
+ if (!result.success) throw CliError.fromApi(result, { auth: "api-key" });
4977
+ await render2(
4978
+ result.data,
4979
+ opts.format,
4980
+ (d) => Formatter.keyValue([
4981
+ ["Product token", String(d.product_token ?? "-")],
4982
+ ["Total price", `${d.total_price ?? "-"} ${d.currency ?? ""}`.trim()],
4983
+ ["Price changed", String(d.price_changed)]
4984
+ ])
4985
+ );
4986
+ });
4987
+ }
4988
+
4989
+ // src/flight-flink/create-order.ts
4990
+ import { confirm as confirm6 } from "@inquirer/prompts";
4991
+ function registerCreateOrderCommand(parent, deps) {
4992
+ const cmd = parent.command("create-order").description("Create a flight order without charging (locks the fare, await payment)").option("--api-key <key>", "API Key for authentication (X-Api-Key)").option("--product-token <token>", "Authoritative rate token from verify").option("--total-amount <amount>", "Verified total price in decimal units").option("--currency <currency>", "ISO 4217 currency code").option("--trip-type <n>", "1/2/3", "1").option("--contact-name <name>", "Booking contact name").option("--contact-region <code>", "Contact phone country code (no '+')").option("--contact-phone <phone>", "Booking contact phone").option("--contact-email <email>", "Booking contact email").option("--passengers <json>", "JSON array of passengers (gender/id_type are strings)").option("--payment-method-id <id>", "Optional bound-card id (pay_per_call only)").option("--idempotency-key <key>", "Forwarded verbatim as the Idempotency-Key header");
4993
+ attachSchemaHelp(cmd, flightCreateOrderSchema);
4994
+ cmd.action(async () => {
4995
+ const opts = cmd.optsWithGlobals();
4996
+ const format = resolveFormat(opts.format);
4997
+ const isYes = Boolean(opts.yes);
4998
+ const apiKey = await resolveApiKey(opts.apiKey);
4999
+ const productToken = need16(opts.productToken, "product-token");
5000
+ const totalAmount = num8(opts.totalAmount, "total-amount");
5001
+ const currency = need16(opts.currency, "currency");
5002
+ const body = {
5003
+ product_token: productToken,
5004
+ total_amount: totalAmount,
5005
+ currency,
5006
+ trip_type: num8(opts.tripType, "trip-type"),
5007
+ contact_name: need16(opts.contactName, "contact-name"),
5008
+ contact_region: need16(opts.contactRegion, "contact-region"),
5009
+ contact_phone: need16(opts.contactPhone, "contact-phone"),
5010
+ contact_email: need16(opts.contactEmail, "contact-email"),
5011
+ passengers: jsonArray(need16(opts.passengers, "passengers"), "passengers")
5012
+ };
5013
+ if (opts.paymentMethodId !== void 0) body.payment_method_id = opts.paymentMethodId;
5014
+ if (!isYes) {
5015
+ const ok = await confirm6({
5016
+ message: `Create this flight order for ${totalAmount} ${currency}? This locks the fare but does NOT charge yet.`,
5017
+ default: false
5018
+ });
5019
+ if (!ok) throw new CliError("CLIENT_ABORTED", "Order creation aborted by user.");
5020
+ }
5021
+ const idempotencyKey = await resolveIdempotencyKey(opts.idempotencyKey, {
5022
+ yes: isYes,
5023
+ commandPath: "flight-flink create-order"
5024
+ });
5025
+ const spinner = format === "json" ? null : createSpinner("Creating flight order...");
5026
+ const result = await deps.apiClient.post(
5027
+ "/flight/create-order",
5028
+ { type: "api-key", key: apiKey },
5029
+ body,
5030
+ { "Idempotency-Key": idempotencyKey }
5031
+ );
5032
+ spinner?.stop();
5033
+ if (!result.success) throw CliError.fromApi(result, { auth: "api-key" });
5034
+ await render2(
5035
+ result.data,
5036
+ opts.format,
5037
+ (d) => [
5038
+ Formatter.keyValue([
5039
+ ["Order no", String(d.order_no ?? "-")],
5040
+ ["Upstream order no", String(d.upstream_order_no ?? "-")],
5041
+ ["Status", String(d.status ?? "-")],
5042
+ ["Total amount", `${d.total_amount ?? "-"} ${d.currency ?? ""}`.trim()]
5043
+ ]),
5044
+ Formatter.status(
5045
+ "info",
5046
+ `Order created (AWAITING_PAYMENT) \u2014 settle with 'flight-flink pay-order --order-no ${d.order_no ?? "<order_no>"}'.`
5047
+ )
5048
+ ].join("\n\n")
5049
+ );
5050
+ });
5051
+ }
5052
+
5053
+ // src/flight-flink/pay-order.ts
5054
+ import { confirm as confirm7 } from "@inquirer/prompts";
5055
+ function registerPayOrderCommand(parent, deps) {
5056
+ const cmd = parent.command("pay-order").description("Settle a created order by --order-no (triggers ticketing)").option("--api-key <key>", "API Key for authentication (X-Api-Key)").option("--order-no <id>", "Our order reference from create-order").option("--idempotency-key <key>", "Forwarded verbatim as the Idempotency-Key header");
5057
+ attachSchemaHelp(cmd, flightPayOrderSchema);
5058
+ cmd.action(async () => {
5059
+ const opts = cmd.optsWithGlobals();
5060
+ const format = resolveFormat(opts.format);
5061
+ const isYes = Boolean(opts.yes);
5062
+ const apiKey = await resolveApiKey(opts.apiKey);
5063
+ const orderNo = need16(opts.orderNo, "order-no");
5064
+ if (!isYes) {
5065
+ const ok = await confirm7({
5066
+ message: `Pay and ticket order ${orderNo}? This settles the order and issues tickets.`,
5067
+ default: false
5068
+ });
5069
+ if (!ok) throw new CliError("CLIENT_ABORTED", "Payment aborted by user.");
5070
+ }
5071
+ const idempotencyKey = await resolveIdempotencyKey(opts.idempotencyKey, {
5072
+ yes: isYes,
5073
+ commandPath: "flight-flink pay-order"
5074
+ });
5075
+ const spinner = format === "json" ? null : createSpinner("Paying flight order...");
5076
+ const result = await deps.apiClient.post(
5077
+ `/flight/${encodeURIComponent(orderNo)}/pay`,
5078
+ { type: "api-key", key: apiKey },
5079
+ {},
5080
+ { "Idempotency-Key": idempotencyKey }
5081
+ );
5082
+ spinner?.stop();
5083
+ if (!result.success) throw CliError.fromApi(result, { auth: "api-key" });
5084
+ await render2(
5085
+ result.data,
5086
+ opts.format,
5087
+ (d) => Formatter.keyValue([
5088
+ ["Order no", String(d.order_no ?? "-")],
5089
+ ["Status", String(d.status ?? "-")],
5090
+ ["Amount", `${d.amount ?? "-"} ${d.currency ?? ""}`.trim()]
5091
+ ])
5092
+ );
5093
+ });
5094
+ }
5095
+
5096
+ // src/flight-flink/get-order.ts
5097
+ var TERMINAL = /* @__PURE__ */ new Set(["TICKETED", "CANCELLED", "FAILED"]);
5098
+ function sleep(ms) {
5099
+ return new Promise((r) => setTimeout(r, ms));
5100
+ }
5101
+ function registerGetOrderCommand(parent, deps) {
5102
+ const cmd = parent.command("get-order").description("Query a flight order status by id (poll ticketing 5\u21928)").option("--api-key <key>", "API Key for authentication (X-Api-Key)").option("--order-no <id>", "Our order reference").option("--watch", "Poll until a terminal status, one NDJSON line per update").option("--watch-interval <seconds>", "Seconds between polls", "5").option("--watch-timeout <seconds>", "Max seconds to poll", "600");
5103
+ attachSchemaHelp(cmd, flightGetOrderSchema);
5104
+ cmd.action(async () => {
5105
+ const opts = cmd.optsWithGlobals();
5106
+ const format = resolveFormat(opts.format);
5107
+ const apiKey = await resolveApiKey(opts.apiKey);
5108
+ const orderNo = need16(opts.orderNo, "order-no");
5109
+ const path = `/flight/${encodeURIComponent(orderNo)}/status`;
5110
+ const auth = { type: "api-key", key: apiKey };
5111
+ if (opts.watch) {
5112
+ const interval = num8(opts.watchInterval, "watch-interval") * 1e3;
5113
+ const timeoutMs = num8(opts.watchTimeout, "watch-timeout") * 1e3;
5114
+ const deadline = Date.now() + timeoutMs;
5115
+ for (; ; ) {
5116
+ const r = await deps.apiClient.get(path, auth);
5117
+ if (!r.success) throw CliError.fromApi(r, { auth: "api-key" });
5118
+ process.stdout.write(`${JSON.stringify(r.data)}
5119
+ `);
5120
+ if (TERMINAL.has(String(r.data.status))) return;
5121
+ if (Date.now() + interval >= deadline) {
5122
+ process.stdout.write(`${JSON.stringify({ watch_status: "timeout", order_no: orderNo })}
5123
+ `);
5124
+ return;
5125
+ }
5126
+ await sleep(interval);
5127
+ }
5128
+ }
5129
+ const spinner = format === "json" ? null : createSpinner("Fetching flight order status...");
5130
+ const result = await deps.apiClient.get(path, auth);
5131
+ spinner?.stop();
5132
+ if (!result.success) throw CliError.fromApi(result, { auth: "api-key" });
5133
+ await render2(
5134
+ result.data,
5135
+ opts.format,
5136
+ (d) => Formatter.keyValue([
5137
+ ["Order no", String(d.order_no ?? "-")],
5138
+ ["Upstream order no", String(d.upstream_order_no ?? "-")],
5139
+ ["PNR", String(d.pnr ?? "-")],
5140
+ ["Status", String(d.status ?? "-")],
5141
+ ["Total amount", `${d.total_amount ?? "-"} ${d.currency ?? ""}`.trim()]
5142
+ ])
5143
+ );
5144
+ });
5145
+ }
5146
+
5147
+ // src/flight-flink/cancel-order.ts
5148
+ import { confirm as confirm8 } from "@inquirer/prompts";
5149
+ function registerCancelOrderCommand(parent, deps) {
5150
+ const cmd = parent.command("cancel-order").description("Cancel an un-ticketed flight order (with refund)").option("--api-key <key>", "API Key for authentication (X-Api-Key)").option("--order-no <id>", "Our order reference").option("--reason <text>", "Optional cancellation reason").option("--idempotency-key <key>", "Forwarded verbatim as the Idempotency-Key header");
5151
+ attachSchemaHelp(cmd, flightCancelOrderSchema);
5152
+ cmd.action(async () => {
5153
+ const opts = cmd.optsWithGlobals();
5154
+ const format = resolveFormat(opts.format);
5155
+ const isYes = Boolean(opts.yes);
5156
+ const apiKey = await resolveApiKey(opts.apiKey);
5157
+ const orderNo = need16(opts.orderNo, "order-no");
5158
+ if (!isYes) {
5159
+ const ok = await confirm8({
5160
+ message: `Cancel order ${orderNo}? This requests cancellation and a refund.`,
5161
+ default: false
5162
+ });
5163
+ if (!ok) throw new CliError("CLIENT_ABORTED", "Cancellation aborted by user.");
5164
+ }
5165
+ const idempotencyKey = await resolveIdempotencyKey(opts.idempotencyKey, {
5166
+ yes: isYes,
5167
+ commandPath: "flight-flink cancel-order"
5168
+ });
5169
+ const body = {};
5170
+ if (opts.reason !== void 0) body.reason = opts.reason;
5171
+ const spinner = format === "json" ? null : createSpinner("Cancelling flight order...");
5172
+ const result = await deps.apiClient.post(
5173
+ `/flight/${encodeURIComponent(orderNo)}/cancel`,
5174
+ { type: "api-key", key: apiKey },
5175
+ body,
5176
+ { "Idempotency-Key": idempotencyKey }
5177
+ );
5178
+ spinner?.stop();
5179
+ if (!result.success) throw CliError.fromApi(result, { auth: "api-key" });
5180
+ await render2(
5181
+ result.data,
5182
+ opts.format,
5183
+ (d) => Formatter.keyValue([
5184
+ ["Order no", String(d.order_no ?? "-")],
5185
+ ["Status", String(d.status ?? "-")],
5186
+ ["Refund amount", `${d.refund_amount ?? "-"} ${d.currency ?? ""}`.trim()]
5187
+ ])
5188
+ );
5189
+ });
5190
+ }
5191
+
5192
+ // src/flight-flink/list-orders.ts
5193
+ function registerListOrdersCommand2(parent, deps) {
5194
+ const cmd = parent.command("list-orders").description("List the developer's flight orders (local read, no upstream call)").option("--api-key <key>", "API Key for authentication (X-Api-Key)").option("--status <status>", "Optional status filter").option("--page <n>", "Page (1-based)", "1").option("--page-size <n>", "Items per page (1-100)", "20");
5195
+ attachSchemaHelp(cmd, flightListOrdersSchema);
5196
+ cmd.action(async () => {
5197
+ const opts = cmd.optsWithGlobals();
5198
+ const format = resolveFormat(opts.format);
5199
+ const apiKey = await resolveApiKey(opts.apiKey);
5200
+ const params = new URLSearchParams();
5201
+ if (opts.status !== void 0) params.set("status", opts.status);
5202
+ params.set("page", String(num8(opts.page, "page")));
5203
+ params.set("page_size", String(num8(opts.pageSize, "page-size")));
5204
+ const spinner = format === "json" ? null : createSpinner("Fetching flight orders...");
5205
+ const result = await deps.apiClient.get(
5206
+ `/flight/orders?${params.toString()}`,
5207
+ { type: "api-key", key: apiKey }
5208
+ );
5209
+ spinner?.stop();
5210
+ if (!result.success) throw CliError.fromApi(result, { auth: "api-key" });
5211
+ await render2(result.data, opts.format, (d) => {
5212
+ const rows = (d.orders ?? []).map((o) => [
5213
+ String(o.order_no ?? "-"),
5214
+ String(o.status ?? "-"),
5215
+ `${o.total_amount ?? "-"} ${o.currency ?? ""}`.trim()
5216
+ ]);
5217
+ return Formatter.table(["Order no", "Status", "Amount"], rows);
5218
+ });
5219
+ });
5220
+ }
5221
+
5222
+ // src/flight-flink/change-search.ts
5223
+ function registerChangeSearchCommand(parent, deps) {
5224
+ const cmd = parent.command("change-search").description("Search rebook-eligible flights for an order").option("--api-key <key>", "API Key for authentication (X-Api-Key)").option("--order-no <id>", "Our order reference").option("--date <date>", "Target change date YYYY-MM-DD").option("--passenger <code>", "passengerCode").option("--segment-id <ids>", "Comma-separated segment ids").option("--cabin-class <c>", "Cabin class", "economy");
5225
+ attachSchemaHelp(cmd, flightChangeSearchSchema);
5226
+ cmd.action(async () => {
5227
+ const opts = cmd.optsWithGlobals();
5228
+ const format = resolveFormat(opts.format);
5229
+ const apiKey = await resolveApiKey(opts.apiKey);
5230
+ const body = {
5231
+ order_no: need16(opts.orderNo, "order-no"),
5232
+ date: need16(opts.date, "date"),
5233
+ passenger: need16(opts.passenger, "passenger"),
5234
+ segment_id: need16(opts.segmentId, "segment-id"),
5235
+ cabin_class: opts.cabinClass
5236
+ };
5237
+ const spinner = format === "json" ? null : createSpinner("Searching change options...");
5238
+ const result = await deps.apiClient.post(
5239
+ "/flight/change/search",
5240
+ { type: "api-key", key: apiKey },
5241
+ body
5242
+ );
5243
+ spinner?.stop();
5244
+ if (!result.success) throw CliError.fromApi(result, { auth: "api-key" });
5245
+ await render2(result.data, opts.format, (d) => JSON.stringify(d, null, 2));
5246
+ });
5247
+ }
5248
+
5249
+ // src/flight-flink/change-apply.ts
5250
+ import { confirm as confirm9 } from "@inquirer/prompts";
5251
+ function registerChangeApplyCommand(parent, deps) {
5252
+ const cmd = parent.command("change-apply").description("Submit a change request (returns change_order_no, pending review)").option("--api-key <key>", "API Key for authentication (X-Api-Key)").option("--order-no <id>", "Our order reference").option("--passenger <code>", "passengerCode").option("--segment-id <ids>", "Comma-separated segment ids").option("--product-token <token>", "Change priceKey token").option("--contact-name <name>", "Contact name").option("--contact-region <code>", "Contact region").option("--contact-phone <phone>", "Contact phone").option("--contact-email <email>", "Contact email").option("--idempotency-key <key>", "Forwarded verbatim as the Idempotency-Key header");
5253
+ attachSchemaHelp(cmd, flightChangeApplySchema);
5254
+ cmd.action(async () => {
5255
+ const opts = cmd.optsWithGlobals();
5256
+ const format = resolveFormat(opts.format);
5257
+ const isYes = Boolean(opts.yes);
5258
+ const apiKey = await resolveApiKey(opts.apiKey);
5259
+ const body = {
5260
+ order_no: need16(opts.orderNo, "order-no"),
5261
+ passenger: need16(opts.passenger, "passenger"),
5262
+ segment_id: need16(opts.segmentId, "segment-id"),
5263
+ product_token: need16(opts.productToken, "product-token"),
5264
+ contact_name: need16(opts.contactName, "contact-name"),
5265
+ contact_region: need16(opts.contactRegion, "contact-region"),
5266
+ contact_phone: need16(opts.contactPhone, "contact-phone"),
5267
+ contact_email: need16(opts.contactEmail, "contact-email")
5268
+ };
5269
+ if (!isYes) {
5270
+ const ok = await confirm9({
5271
+ message: `Submit a change request for order ${body.order_no}?`,
5272
+ default: false
5273
+ });
5274
+ if (!ok) throw new CliError("CLIENT_ABORTED", "Change request aborted by user.");
5275
+ }
5276
+ const idempotencyKey = await resolveIdempotencyKey(opts.idempotencyKey, {
5277
+ yes: isYes,
5278
+ commandPath: "flight-flink change-apply"
5279
+ });
5280
+ const spinner = format === "json" ? null : createSpinner("Submitting change request...");
5281
+ const result = await deps.apiClient.post(
5282
+ "/flight/change/apply",
5283
+ { type: "api-key", key: apiKey },
5284
+ body,
5285
+ { "Idempotency-Key": idempotencyKey }
5286
+ );
5287
+ spinner?.stop();
5288
+ if (!result.success) throw CliError.fromApi(result, { auth: "api-key" });
5289
+ await render2(result.data, opts.format, (d) => JSON.stringify(d, null, 2));
5290
+ });
5291
+ }
5292
+
5293
+ // src/flight-flink/change-detail.ts
5294
+ function registerChangeDetailCommand(parent, deps) {
5295
+ const cmd = parent.command("change-detail").description("Change request detail by change order number").option("--api-key <key>", "API Key for authentication (X-Api-Key)").option("--change-order-no <id>", "Change order number");
5296
+ attachSchemaHelp(cmd, flightChangeDetailSchema);
5297
+ cmd.action(async () => {
5298
+ const opts = cmd.optsWithGlobals();
5299
+ const format = resolveFormat(opts.format);
5300
+ const apiKey = await resolveApiKey(opts.apiKey);
5301
+ const changeOrderNo = need16(opts.changeOrderNo, "change-order-no");
5302
+ const spinner = format === "json" ? null : createSpinner("Fetching change detail...");
5303
+ const result = await deps.apiClient.get(
5304
+ `/flight/change/${encodeURIComponent(changeOrderNo)}`,
5305
+ { type: "api-key", key: apiKey }
5306
+ );
5307
+ spinner?.stop();
5308
+ if (!result.success) throw CliError.fromApi(result, { auth: "api-key" });
5309
+ await render2(result.data, opts.format, (d) => JSON.stringify(d, null, 2));
5310
+ });
5311
+ }
5312
+
5313
+ // src/flight-flink/change-cancel.ts
5314
+ import { confirm as confirm10 } from "@inquirer/prompts";
5315
+ function registerChangeCancelCommand(parent, deps) {
5316
+ const cmd = parent.command("change-cancel").description("Cancel a change request (immediate terminal)").option("--api-key <key>", "API Key for authentication (X-Api-Key)").option("--change-order-no <id>", "Change order number").option("--idempotency-key <key>", "Forwarded verbatim as the Idempotency-Key header");
5317
+ attachSchemaHelp(cmd, flightChangeCancelSchema);
5318
+ cmd.action(async () => {
5319
+ const opts = cmd.optsWithGlobals();
5320
+ const format = resolveFormat(opts.format);
5321
+ const isYes = Boolean(opts.yes);
5322
+ const apiKey = await resolveApiKey(opts.apiKey);
5323
+ const changeOrderNo = need16(opts.changeOrderNo, "change-order-no");
5324
+ if (!isYes) {
5325
+ const ok = await confirm10({ message: `Cancel change request ${changeOrderNo}?`, default: false });
5326
+ if (!ok) throw new CliError("CLIENT_ABORTED", "Change cancellation aborted by user.");
5327
+ }
5328
+ const idempotencyKey = await resolveIdempotencyKey(opts.idempotencyKey, {
5329
+ yes: isYes,
5330
+ commandPath: "flight-flink change-cancel"
5331
+ });
5332
+ const spinner = format === "json" ? null : createSpinner("Cancelling change request...");
5333
+ const result = await deps.apiClient.post(
5334
+ `/flight/change/${encodeURIComponent(changeOrderNo)}/cancel`,
5335
+ { type: "api-key", key: apiKey },
5336
+ {},
5337
+ { "Idempotency-Key": idempotencyKey }
5338
+ );
5339
+ spinner?.stop();
5340
+ if (!result.success) throw CliError.fromApi(result, { auth: "api-key" });
5341
+ await render2(result.data, opts.format, (d) => JSON.stringify(d, null, 2));
5342
+ });
5343
+ }
5344
+
5345
+ // src/flight-flink/refund-apply.ts
5346
+ import { confirm as confirm11 } from "@inquirer/prompts";
5347
+ function registerRefundApplyCommand(parent, deps) {
5348
+ const cmd = parent.command("refund-apply").description("Submit a refund request (returns refund_order_no, pending review)").option("--api-key <key>", "API Key for authentication (X-Api-Key)").option("--order-no <id>", "Our order reference").option("--passenger <code>", "passengerCode").option("--segment-id <ids>", "Comma-separated segment ids").option("--reason-type <n>", "Refund reason type code").option("--reason <text>", "Free-text reason").option("--contact-name <name>", "Contact name").option("--contact-region <code>", "Contact region").option("--contact-phone <phone>", "Contact phone").option("--contact-email <email>", "Contact email").option("--idempotency-key <key>", "Forwarded verbatim as the Idempotency-Key header");
5349
+ attachSchemaHelp(cmd, flightRefundApplySchema);
5350
+ cmd.action(async () => {
5351
+ const opts = cmd.optsWithGlobals();
5352
+ const format = resolveFormat(opts.format);
5353
+ const isYes = Boolean(opts.yes);
5354
+ const apiKey = await resolveApiKey(opts.apiKey);
5355
+ const body = {
5356
+ order_no: need16(opts.orderNo, "order-no"),
5357
+ passenger: need16(opts.passenger, "passenger"),
5358
+ segment_id: need16(opts.segmentId, "segment-id"),
5359
+ reason_type: num8(opts.reasonType, "reason-type"),
5360
+ contact_name: need16(opts.contactName, "contact-name"),
5361
+ contact_region: need16(opts.contactRegion, "contact-region"),
5362
+ contact_phone: need16(opts.contactPhone, "contact-phone"),
5363
+ contact_email: need16(opts.contactEmail, "contact-email")
5364
+ };
5365
+ if (opts.reason !== void 0) body.reason = opts.reason;
5366
+ if (!isYes) {
5367
+ const ok = await confirm11({
5368
+ message: `Submit a refund request for order ${body.order_no}?`,
5369
+ default: false
5370
+ });
5371
+ if (!ok) throw new CliError("CLIENT_ABORTED", "Refund request aborted by user.");
5372
+ }
5373
+ const idempotencyKey = await resolveIdempotencyKey(opts.idempotencyKey, {
5374
+ yes: isYes,
5375
+ commandPath: "flight-flink refund-apply"
5376
+ });
5377
+ const spinner = format === "json" ? null : createSpinner("Submitting refund request...");
5378
+ const result = await deps.apiClient.post(
5379
+ "/flight/refund/apply",
5380
+ { type: "api-key", key: apiKey },
5381
+ body,
5382
+ { "Idempotency-Key": idempotencyKey }
5383
+ );
5384
+ spinner?.stop();
5385
+ if (!result.success) throw CliError.fromApi(result, { auth: "api-key" });
5386
+ await render2(result.data, opts.format, (d) => JSON.stringify(d, null, 2));
5387
+ });
5388
+ }
5389
+
5390
+ // src/flight-flink/refund-detail.ts
5391
+ function registerRefundDetailCommand(parent, deps) {
5392
+ const cmd = parent.command("refund-detail").description("Refund request detail by refund order number").option("--api-key <key>", "API Key for authentication (X-Api-Key)").option("--refund-order-no <id>", "Refund order number");
5393
+ attachSchemaHelp(cmd, flightRefundDetailSchema);
5394
+ cmd.action(async () => {
5395
+ const opts = cmd.optsWithGlobals();
5396
+ const format = resolveFormat(opts.format);
5397
+ const apiKey = await resolveApiKey(opts.apiKey);
5398
+ const refundOrderNo = need16(opts.refundOrderNo, "refund-order-no");
5399
+ const spinner = format === "json" ? null : createSpinner("Fetching refund detail...");
5400
+ const result = await deps.apiClient.get(
5401
+ `/flight/refund/${encodeURIComponent(refundOrderNo)}`,
5402
+ { type: "api-key", key: apiKey }
5403
+ );
5404
+ spinner?.stop();
5405
+ if (!result.success) throw CliError.fromApi(result, { auth: "api-key" });
5406
+ await render2(result.data, opts.format, (d) => JSON.stringify(d, null, 2));
5407
+ });
5408
+ }
5409
+
5410
+ // src/flight-flink/refund-confirm.ts
5411
+ import { confirm as confirm12 } from "@inquirer/prompts";
5412
+ function registerRefundConfirmCommand(parent, deps) {
5413
+ const cmd = parent.command("refund-confirm").description('Confirm ("1") or cancel ("2") a refund').option("--api-key <key>", "API Key for authentication (X-Api-Key)").option("--refund-order-no <id>", "Refund order number").option("--confirm <flag>", '"1" confirm / "2" cancel').option("--idempotency-key <key>", "Forwarded verbatim as the Idempotency-Key header");
5414
+ attachSchemaHelp(cmd, flightRefundConfirmSchema);
5415
+ cmd.action(async () => {
5416
+ const opts = cmd.optsWithGlobals();
5417
+ const format = resolveFormat(opts.format);
5418
+ const isYes = Boolean(opts.yes);
5419
+ const apiKey = await resolveApiKey(opts.apiKey);
5420
+ const refundOrderNo = need16(opts.refundOrderNo, "refund-order-no");
5421
+ const confirmFlag = need16(opts.confirm, "confirm");
5422
+ if (confirmFlag !== "1" && confirmFlag !== "2") {
5423
+ throw new CliError("PARAM_INVALID", '--confirm must be "1" (confirm) or "2" (cancel).');
5424
+ }
5425
+ if (!isYes) {
5426
+ const action = confirmFlag === "1" ? "confirm" : "cancel";
5427
+ const ok = await confirm12({ message: `${action} refund ${refundOrderNo}?`, default: false });
5428
+ if (!ok) throw new CliError("CLIENT_ABORTED", "Refund confirmation aborted by user.");
5429
+ }
5430
+ const idempotencyKey = await resolveIdempotencyKey(opts.idempotencyKey, {
5431
+ yes: isYes,
5432
+ commandPath: "flight-flink refund-confirm"
5433
+ });
5434
+ const spinner = format === "json" ? null : createSpinner("Processing refund confirmation...");
5435
+ const result = await deps.apiClient.post(
5436
+ `/flight/refund/${encodeURIComponent(refundOrderNo)}/confirm`,
5437
+ { type: "api-key", key: apiKey },
5438
+ { confirm: confirmFlag },
5439
+ { "Idempotency-Key": idempotencyKey }
5440
+ );
5441
+ spinner?.stop();
5442
+ if (!result.success) throw CliError.fromApi(result, { auth: "api-key" });
5443
+ await render2(result.data, opts.format, (d) => JSON.stringify(d, null, 2));
5444
+ });
5445
+ }
5446
+
5447
+ // src/flight-flink/skill.ts
5448
+ var SKILL_TEXT2 = `
5449
+ ---
5450
+ name: flight-flink
5451
+ description: Search, book, and manage international flight reservations. Two-step create-then-pay ticketing with journeyId relay for round-trip/multi-city, plus change (rebooking) and refund flows. Use when the user wants to find flights, verify a fare, book a ticket, or view/cancel/change/refund an existing flight order. Not for hotels or rides.
5452
+ ---
5453
+
5454
+ # flight-flink \u2014 Agent Skill
5455
+
5456
+ You orchestrate flight bookings through CLI verbs. This guide covers HOW to chain
5457
+ verbs and pass data between them. For exact flags and field types, read the schema
5458
+ (\`flight-flink <verb> --help --format json\`).
5459
+
5460
+ ## Prerequisites
5461
+ - API key with merchant scope. Pass --api-key <key> --format json on every call; add --yes on writes.
5462
+ - Billing is decided server-side by the developer's billing_mode (monthly_settlement or pay_per_call).
5463
+
5464
+ ## Workflow (book)
5465
+ 1. find-airport --keyword "<place>" \u2192 resolve to IATA city/airport codes
5466
+ 2. search --trip-type <1|2|3> --journeys '<json>'
5467
+ - journeys ALWAYS carries ALL legs. For round-trip/multi-city, relay journey-id
5468
+ from the previous search into the next until price_key_ready is true.
5469
+ - Only offers where price_key_ready is true carry a real product_token; earlier-leg
5470
+ offers carry only an identifier (product_token null).
5471
+ - Code<->type consistency: tag each code with its matching type \u2014 an AIRPORT code (e.g. NRT)
5472
+ uses type 2, a CITY code (e.g. TYO) uses type 1. Mislabeling (e.g. NRT with type 1, since NRT
5473
+ is an airport whose city is TYO) is rejected with code 400 "destination not exist".
5474
+ find-airport returns the correct type per candidate (city hit \u2192 type 1 with children airports;
5475
+ airport hit \u2192 type 2) \u2014 pass code + type together verbatim.
5476
+ 3. verify --product-token <token from search>
5477
+ - Returns the authoritative product_token + price_changed. If price_changed, re-confirm
5478
+ the new price with the user before booking. Pass the RETURNED token to create-order.
5479
+ 4. create-order --product-token <verify token> --total-amount <verify total> --currency <cur>
5480
+ --contact-* --passengers '<json>' --idempotency-key <k>
5481
+ - Locks the fare, NO charge. Order status \u2192 AWAITING_PAYMENT. Save order_no.
5482
+ - passengers: gender/id_type are STRINGS ("1"/"2"); child/infant require adult_passenger_name.
5483
+ 5. pay-order --order-no <order_no> --idempotency-key <k>
5484
+ - Settles + triggers ticketing. Status \u2192 PAID.
5485
+ 6. get-order --order-no <order_no> [--watch]
5486
+ - Ticketing is asynchronous. Poll until TICKETED (ticket_infos populated). Do NOT
5487
+ report success before TICKETED. Single-passenger orders ticket in ~100-110s;
5488
+ multi-passenger orders (adult+child+infant) take longer (150s+) \u2014 widen --watch-timeout
5489
+ for multi-passenger bookings; still TICKETING past 150s is normal, not a failure.
5490
+
5491
+ ## Change (rebooking)
5492
+ - change-search \u2192 change-apply (returns change_order_no) \u2192 change-detail
5493
+ - Confirm branch: pay-order the change order (settlement). Cancel branch: change-cancel.
5494
+
5495
+ ## Refund
5496
+ - refund-apply (returns refund_order_no) \u2192 refund-detail \u2192 refund-confirm --confirm 1 (confirm)
5497
+ or --confirm 2 (cancel). Cancel takes effect immediately.
5498
+
5499
+ ## Cancel (un-ticketed order)
5500
+ - cancel-order --order-no <order_no> --idempotency-key <k> \u2192 CANCELLED + refund.
5501
+ A ticketed order is rejected upstream \u2014 use refund-apply instead.
5502
+
5503
+ ## Data passing
5504
+ - product_token : search \u2192 verify \u2192 create-order (opaque; never fabricate or edit; verify's token wins)
5505
+ - order_no : create-order \u2192 pay-order / get-order / cancel-order
5506
+ - journey_id : search \u2192 next search (relay for round-trip/multi-city)
5507
+ - change_order_no / refund_order_no : *-apply \u2192 *-detail / *-confirm / pay-order (change)
5508
+
5509
+ ## Invariants
5510
+ - Never create-order without a fresh verify (tokens/prices change).
5511
+ - Never claim "booked" until get-order returns TICKETED.
5512
+ - create-order and pay-order are separate steps; create-order does NOT charge.
5513
+ - Write verbs (create-order/pay-order/cancel-order/change-apply/change-cancel/refund-apply/
5514
+ refund-confirm) always need --idempotency-key; reuse the SAME key when retrying the same intent.
5515
+ - --yes only skips this CLI's interactive prompt (for non-interactive Agents); it does NOT
5516
+ remove the requirement to confirm the flight/fare/price with the user in the chat UI.
5517
+
5518
+ ## Errors
5519
+ - INVALID_ARGUMENT \u2192 bad argument; run <verb> --help and correct it.
5520
+ - PRICE_CHANGED \u2192 price changed since verify; re-verify and confirm the new price.
5521
+ - ORDER_CREATE_FAILED \u2192 upstream create failed; funds released. Safe to retry with same key.
5522
+ - PAYMENT_FAILED \u2192 settlement/payment failed; check balance/method and retry.
5523
+ - NOT_FOUND \u2192 no such order for this key/owner. Do NOT retry blindly.
5524
+ - IDEMPOTENCY_CONFLICT \u2192 same key reused with different params; use a fresh key.
5525
+ - UPSTREAM_ERROR \u2192 transient; retry once after a short wait.
5526
+ `.trim();
5527
+ function registerFlightSkillCommand(parent) {
5528
+ parent.command("skill").description("Print the flight-flink usage guide (orchestration flow, data passing, rules)").action(() => {
5529
+ console.log(SKILL_TEXT2);
5530
+ });
5531
+ }
5532
+
4334
5533
  // src/index.ts
4335
5534
  var programRef;
4336
5535
  async function main() {
@@ -4344,7 +5543,7 @@ async function main() {
4344
5543
  const program = new Command();
4345
5544
  programRef = program;
4346
5545
  program.name("agenzo-merchant-cli").version(getCurrentVersion()).description(
4347
- "Agenzo merchant fulfillment plane: service discovery (services), ride ordering (ride-elife), and hotel booking (hotel-redaug)"
5546
+ "Agenzo merchant fulfillment plane: service discovery (services), ride ordering (ride-elife), hotel booking (hotel-redaug), and flight booking (flight-flink)"
4348
5547
  ).option("--verbose", "Show verbose logs").option("--yes", "Skip confirmation prompts (for automation/AI Agents)").option(
4349
5548
  "--format <format>",
4350
5549
  "Output format: json | table (default: json; or set AGENZO_FORMAT)",
@@ -4439,6 +5638,49 @@ Key notes:
4439
5638
  registerHotelListCitiesCommand(hotelCmd, deps);
4440
5639
  registerHotelDetailCommand(hotelCmd, deps);
4441
5640
  registerHotelSkillCommand(hotelCmd);
5641
+ const flightCmd = program.command("flight-flink").description(
5642
+ `Flight booking (Flink) \u2014 international flight search, price verification, two-step create-then-pay ticketing, plus change (rebooking) and refund.
5643
+
5644
+ Workflow (typical order):
5645
+ 1. find-airport Resolve a place name \u2192 IATA city/airport codes
5646
+ 2. search Search flights (one-way/round-trip/multi-city). journeys carries ALL legs;
5647
+ relay --journey-id for round-trip/multi-city until price_key_ready is true
5648
+ 3. verify Verify the fare \u2192 authoritative product_token + price_changed flag
5649
+ 4. create-order Create the order using verify's product_token (locks the fare, no charge)
5650
+ 5. pay-order Settle by --order-no (triggers ticketing); order \u2192 PAID
5651
+ 6. get-order Poll by --order-no until TICKETED (or use --watch for NDJSON)
5652
+ cancel-order (optional) Cancel an un-ticketed order (+ refund)
5653
+ change-* (optional) change-search \u2192 change-apply \u2192 change-detail; confirm via pay-order, cancel via change-cancel
5654
+ refund-* (optional) refund-apply \u2192 refund-detail \u2192 refund-confirm (1=confirm, 2=cancel)
5655
+ list-orders List the developer's flight orders
5656
+
5657
+ Key notes:
5658
+ \u2022 product_token from search/verify is opaque \u2014 pass it unchanged; verify's token supersedes search's
5659
+ \u2022 passengers is a JSON array; gender/id_type are STRINGS ("1"/"2"); child/infant need adult_passenger_name
5660
+ \u2022 Ticketing is asynchronous \u2014 never claim "booked" until get-order returns TICKETED
5661
+ \u2022 create-order and pay-order are separate steps; create-order does NOT charge
5662
+ \u2022 Write verbs require --idempotency-key; reuse the SAME key when retrying the same intent`
5663
+ );
5664
+ registerFindAirportCommand(flightCmd, deps);
5665
+ registerListAirportsCommand(flightCmd, deps);
5666
+ registerListAirlinesCommand(flightCmd, deps);
5667
+ registerListNationalitiesCommand(flightCmd, deps);
5668
+ registerSearchCommand(flightCmd, deps);
5669
+ registerMoreOffersCommand(flightCmd, deps);
5670
+ registerVerifyCommand(flightCmd, deps);
5671
+ registerCreateOrderCommand(flightCmd, deps);
5672
+ registerPayOrderCommand(flightCmd, deps);
5673
+ registerGetOrderCommand(flightCmd, deps);
5674
+ registerCancelOrderCommand(flightCmd, deps);
5675
+ registerListOrdersCommand2(flightCmd, deps);
5676
+ registerChangeSearchCommand(flightCmd, deps);
5677
+ registerChangeApplyCommand(flightCmd, deps);
5678
+ registerChangeDetailCommand(flightCmd, deps);
5679
+ registerChangeCancelCommand(flightCmd, deps);
5680
+ registerRefundApplyCommand(flightCmd, deps);
5681
+ registerRefundDetailCommand(flightCmd, deps);
5682
+ registerRefundConfirmCommand(flightCmd, deps);
5683
+ registerFlightSkillCommand(flightCmd);
4442
5684
  await program.parseAsync(process.argv);
4443
5685
  }
4444
5686
  function resolveActiveFormat() {