letsfg 2026.5.72 → 2026.5.74

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
@@ -25,6 +25,7 @@ __export(index_exports, {
25
25
  BoostedTravelError: () => BoostedTravelError,
26
26
  ErrorCategory: () => ErrorCategory,
27
27
  ErrorCode: () => ErrorCode,
28
+ HOTEL_BOOKING_FINAL_STATUSES: () => HOTEL_BOOKING_FINAL_STATUSES,
28
29
  LetsFG: () => LetsFG,
29
30
  LetsFGError: () => LetsFGError,
30
31
  OfferExpiredError: () => OfferExpiredError,
@@ -1612,6 +1613,7 @@ var LATE_MERGE_POLL_MS = 3e3;
1612
1613
  var LATE_MERGE_GRACE_MS = 9e4;
1613
1614
  var WAIT_FOR_SPLIT = (process.env.LETSFG_WAIT_FOR_SPLIT || "").trim() !== "0";
1614
1615
  var NON_TERMINAL = ["pending", "searching"];
1616
+ var HOTEL_BOOKING_FINAL_STATUSES = ["succeeded", "failed", "attention"];
1615
1617
  var LetsFG = class {
1616
1618
  bearerToken;
1617
1619
  apiKey;
@@ -1719,13 +1721,23 @@ var LetsFG = class {
1719
1721
  return Array.isArray(data) ? data : data.locations || [];
1720
1722
  }
1721
1723
  /**
1722
- * Unlock a flight offer — confirms live price, reveals direct airline booking URL.
1723
- * Developer API only, legacy — there is no unlock endpoint on a PFS Bearer
1724
- * token, so PFS callers use book() directly.
1724
+ * RETIRED 2026-09-08. Throws instead of calling the server.
1725
+ *
1726
+ * There is no unlock step on any lane. Unlock existed to confirm a live price
1727
+ * before charging; booking now HOLDS the fare on the connected payment method
1728
+ * and captures only once a real airline PNR exists, so a fare that moved
1729
+ * cannot become a charge for a ticket you did not get. If it moves at
1730
+ * checkout you get a `price_change` question to accept or decline instead.
1731
+ *
1732
+ * Kept as a method, and throwing locally rather than making the request, so an
1733
+ * older caller gets one clear sentence at the line that is actually wrong —
1734
+ * not a 410 body to decode, and not a TypeError somewhere else.
1725
1735
  */
1726
- async unlock(offerId) {
1727
- this.requireApiKey();
1728
- return this.post("/developers/api/v1/bookings/unlock", { offer_id: offerId });
1736
+ async unlock(_offerId) {
1737
+ throw new LetsFGError(
1738
+ "unlock() was retired on 2026-09-08 and the endpoint answers 410 Gone. There is no unlock step: call book() directly. The fare is held on the connected payment method and captured only against a real airline PNR. See https://letsfg.co/developers/api/docs",
1739
+ 410
1740
+ );
1729
1741
  }
1730
1742
  /**
1731
1743
  * Book a flight.
@@ -1736,9 +1748,14 @@ var LetsFG = class {
1736
1748
  * complete, { ok, booked: false, booking_url } — hand the link to the user,
1737
1749
  * nothing was charged.
1738
1750
  *
1739
- * Developer API (X-API-Key): charges ticket price + service fee via Stripe,
1740
- * creates a real PNR. Requires unlock() first. Always provide
1741
- * idempotencyKey to prevent double-bookings on retry.
1751
+ * Developer API (X-API-Key): POST /flights/book. NO unlock step. searchId is
1752
+ * REQUIRED — an offer is bookable only inside the search that produced it.
1753
+ * The connected Revolut method is HELD, not charged; a LetsFG booking agent
1754
+ * buys the ticket and the hold is captured only against a real airline PNR.
1755
+ * Returns the 202 { ok, booking_id, state, held, charged: 0, poll_url } —
1756
+ * poll getBooking(bookingId) until `terminal`, or use bookAndWait().
1757
+ * Always provide idempotencyKey: a retry with the same key returns the
1758
+ * existing booking instead of opening a second hold on the card.
1742
1759
  */
1743
1760
  async book(offerId, passengers, contactEmail, contactPhone = "", idempotencyKey = "", searchId = "") {
1744
1761
  this.requireAuth();
@@ -1759,28 +1776,107 @@ var LetsFG = class {
1759
1776
  });
1760
1777
  }
1761
1778
  this.requireApiKey();
1779
+ if (!searchId) {
1780
+ throw new LetsFGError(
1781
+ "searchId is required to book on the Developer API \u2014 pass the search_id from search()'s result. An offer can only be booked inside the search that produced it. (Before 2026-09-08 this argument was ignored on this path.)",
1782
+ 400
1783
+ );
1784
+ }
1785
+ const pax = passengers.map((p) => ({ ...p }));
1786
+ if (contactPhone && pax.length && !pax[0].phone_number) pax[0].phone_number = contactPhone;
1762
1787
  const body = {
1788
+ search_id: searchId,
1763
1789
  offer_id: offerId,
1764
- booking_type: "flight",
1765
- passengers,
1766
- contact_email: contactEmail,
1767
- contact_phone: contactPhone
1790
+ passengers: pax,
1791
+ contact_email: contactEmail
1768
1792
  };
1769
1793
  if (idempotencyKey) body.idempotency_key = idempotencyKey;
1770
- return this.post("/developers/api/v1/bookings/book", body);
1794
+ return this.post("/developers/api/v1/flights/book", body);
1795
+ }
1796
+ /**
1797
+ * Poll a Developer API flight booking.
1798
+ *
1799
+ * Poll every few seconds until `terminal` is true. The poll is ALSO how LetsFG
1800
+ * knows you are still there, which is what keeps a booking paused on a
1801
+ * question alive — so do not back off to minutes.
1802
+ *
1803
+ * States: authorised, card_issued, booking_in_progress, awaiting_settlement,
1804
+ * then completed (with `pnr` and `charged_amount`), failed (hold released,
1805
+ * nothing charged) or needs_attention (a human at LetsFG is on it — do not
1806
+ * book again).
1807
+ */
1808
+ async getBooking(bookingId) {
1809
+ this.requireApiKey();
1810
+ return this.getWithAuth(
1811
+ `/developers/api/v1/flights/bookings/${encodeURIComponent(bookingId)}`
1812
+ );
1813
+ }
1814
+ /**
1815
+ * Answer the open `question` on a booking.
1816
+ *
1817
+ * Echo the question's `round`. A stale round is refused with 409 rather than
1818
+ * guessed at, so an answer to an old question can never be applied to a new
1819
+ * one. Seat: { seats: [...] } or { skip: true }. Price change or paid extra:
1820
+ * { confirm: true } or { skip: true } — declining an extra still completes
1821
+ * the booking, without it.
1822
+ */
1823
+ async answerBooking(bookingId, round, answer = {}) {
1824
+ this.requireApiKey();
1825
+ return this.post(
1826
+ `/developers/api/v1/flights/bookings/${encodeURIComponent(bookingId)}/answer`,
1827
+ { round, ...answer }
1828
+ );
1829
+ }
1830
+ /**
1831
+ * Book and poll to a terminal state. Mirrors bookHotelAndWait().
1832
+ *
1833
+ * Blocks for as long as the booking takes (4–11 minutes typically), so use
1834
+ * book() + getBooking() instead if your caller has a request timeout.
1835
+ *
1836
+ * `onQuestion` returns the answer for answerBooking(). Without it, a fare
1837
+ * increase is ACCEPTED and a paid extra is DECLINED — the conservative
1838
+ * reading of "the traveller asked for this flight".
1839
+ */
1840
+ async bookAndWait(offerId, passengers, contactEmail, searchId, opts = {}) {
1841
+ const { contactPhone = "", idempotencyKey = "", pollMs = 5e3, timeoutMs = 9e5, onQuestion } = opts;
1842
+ const started = await this.book(
1843
+ offerId,
1844
+ passengers,
1845
+ contactEmail,
1846
+ contactPhone,
1847
+ idempotencyKey,
1848
+ searchId
1849
+ );
1850
+ if (!started || started.ok !== true) return started;
1851
+ const bookingId = String(started.booking_id);
1852
+ const deadline = Date.now() + timeoutMs;
1853
+ while (Date.now() < deadline) {
1854
+ await new Promise((r) => setTimeout(r, pollMs));
1855
+ const state = await this.getBooking(bookingId);
1856
+ const question = state.question;
1857
+ if (question) {
1858
+ const answer = onQuestion ? onQuestion(question) : question.kind === "extra" ? { skip: true } : { confirm: true };
1859
+ await this.answerBooking(bookingId, Number(question.round), answer);
1860
+ continue;
1861
+ }
1862
+ if (state.terminal) return state;
1863
+ }
1864
+ return this.getBooking(bookingId);
1771
1865
  }
1772
1866
  // ── Hotels ──────────────────────────────────────────────────────────
1773
1867
  //
1774
- // A card on file is required for EVERY hotel call, search included. That is
1775
- // deliberate: a hotel search opens a real session at the supplier and booking
1776
- // blocks a real rate, so a caller is never allowed to reach the point of
1777
- // commitment only to discover it cannot pay. The same card that authorises
1778
- // flight booking authorises hotels — there is no separate hotel enrolment.
1868
+ // A connected payment method is required for EVERY hotel call, search
1869
+ // included. That is deliberate: a hotel search opens a real session at the
1870
+ // supplier and booking blocks a real rate, so a caller is never allowed to
1871
+ // reach the point of commitment only to discover it cannot pay. The same
1872
+ // method that authorises flight booking authorises hotels.
1779
1873
  //
1780
- // Only free-cancellation, pay-later rates are sold. Those are the rates where
1781
- // the guest's balance can safely be settled with the supplier after booking,
1782
- // which is what makes 5%-now/rest-later work. The result set is smaller than
1783
- // a metasearch's, and every row in it can actually be booked.
1874
+ // How a hotel is paid (since 2026-09-11): the full `price` is HELD on the
1875
+ // connected Revolut method, LetsFG books and pays the supplier itself, and the
1876
+ // hold is captured only once the supplier has confirmed. A booking that fails
1877
+ // releases the hold. There is no reservation fee, no deposit and no pay link —
1878
+ // those belonged to the process retired on 2026-09-11. Every rate type is
1879
+ // sold, refundable and non-refundable.
1784
1880
  /**
1785
1881
  * Resolve a place name to the city id that searchHotels() needs.
1786
1882
  *
@@ -1801,12 +1897,17 @@ var LetsFG = class {
1801
1897
  * Slow by nature — the supplier streams a whole city and every rate is priced
1802
1898
  * — so this gets its own generous timeout rather than the client default.
1803
1899
  *
1804
- * Each offer carries `price` (what the guest pays), `reservation_fee_now`
1805
- * (the 5% taken at booking), `balance_to_supplier`, `balance_due_by` and
1806
- * `free_cancellation_until`. There is no wholesale figure to quote by mistake.
1900
+ * The response carries `session_id`, `currency`, `supplier_currency`,
1901
+ * `markup_rate`, `fx_rate`, `fx_as_of`, `count`, `hotels`, `terms` and
1902
+ * `caveats`. Each offer carries `price` (what the guest pays, in `currency`),
1903
+ * `currency`, `fx_rate`, `expected_cost` (the supplier's cost, in PLN),
1904
+ * `refundable`, `free_cancellation_until` (refundable rates only),
1905
+ * `cancellation_policy` and its own `session_id`.
1807
1906
  *
1808
- * Keep `session_id` and the chosen offer's `combination_id_v2`: together they
1809
- * identify the exact rate, and booking needs both.
1907
+ * `price` is the supplier's cost plus `markup_rate` (6.4% for Revolut Pay or an
1908
+ * EEA-issued card, 8.3% for a card issued outside the EEA); nothing is added at
1909
+ * booking. Keep the chosen offer whole: bookHotel() needs its `session_id`,
1910
+ * `combination_id_v2`, `price`, `expected_cost`, `currency` and `fx_rate`.
1810
1911
  */
1811
1912
  async searchHotels(params) {
1812
1913
  this.requireApiKey();
@@ -1819,7 +1920,8 @@ var LetsFG = class {
1819
1920
  children: params.children ?? 0,
1820
1921
  nationality: params.nationality ?? "PL",
1821
1922
  limit: params.limit ?? 40,
1822
- with_images: params.withImages ?? true
1923
+ with_images: params.withImages ?? true,
1924
+ currency: params.currency ?? "USD"
1823
1925
  };
1824
1926
  if (params.childAges?.length) body.child_ages = params.childAges;
1825
1927
  return this.post("/developers/api/v1/hotels/search", body, 24e4);
@@ -1827,30 +1929,42 @@ var LetsFG = class {
1827
1929
  /**
1828
1930
  * Start a booking. Returns a job immediately — it does NOT book inline.
1829
1931
  *
1830
- * A booking takes minutes: the rate is re-blocked at the supplier, every
1831
- * price and date rail is checked, the 5% reservation fee is charged to your
1832
- * card, and only then is the room committed. No proxy holds a connection that
1833
- * long, so this returns at once and you poll hotelBooking() for the outcome.
1834
- * Use bookHotelAndWait() if you would rather block.
1932
+ * What happens, in order: the offer's full `price` is HELD on the Revolut
1933
+ * payment method connected to this account (authorised, not taken); LetsFG
1934
+ * books the room with the supplier and pays the supplier itself; the hold is
1935
+ * captured only once the supplier has confirmed. If the booking fails for any
1936
+ * reason, the hold is released and nothing is charged. There is no
1937
+ * reservation fee, no deposit and no pay link.
1835
1938
  *
1836
- * Because the fee is taken BEFORE the commit, a declined card costs nothing
1837
- * to unwind: no reservation exists and nothing is charged.
1939
+ * A booking takes minutes and no proxy holds a connection that long, so this
1940
+ * returns at once and you poll hotelBooking() for the outcome. Use
1941
+ * bookHotelAndWait() if you would rather block.
1838
1942
  *
1839
- * Send `expectedPrice` and `expectedBalance` back exactly as search returned
1840
- * them — the booking is refused if the supplier has moved beyond tolerance,
1841
- * so a guest is never charged a price they did not agree to.
1943
+ * Send `expectedPrice` (the offer's `price`), `expectedCost`, `currency` and
1944
+ * `fxRate` exactly as search returned them. The booking is refused if the
1945
+ * supplier's live cost is above `expectedCost`, and a USD offer sent without
1946
+ * its `currency` is refused with 400 price_mismatch (the API assumes PLN).
1947
+ * Guest names, phone and e-mail are checked before anything is held; a problem
1948
+ * returns 400 invalid_details naming the fields.
1842
1949
  *
1843
- * Do NOT call this again for the same rate while a job is running: that books
1844
- * the room twice and charges two reservation fees.
1950
+ * Do NOT call this again for a booking whose job is still running: poll it.
1951
+ * A retry of the same booking returns the job already under way
1952
+ * (`duplicate: true`) rather than holding the money twice.
1845
1953
  */
1846
1954
  async bookHotel(params) {
1847
1955
  this.requireApiKey();
1956
+ if (typeof params.expectedCost !== "number") {
1957
+ throw new LetsFGError(
1958
+ "bookHotel() needs expectedCost: copy the offer's expected_cost, currency and fx_rate. " + ("expectedBalance" in params ? "expectedBalance belonged to the reservation-fee process retired on 2026-09-11 and is not sent. " : "") + "See https://letsfg.co/developers/api/docs",
1959
+ 400
1960
+ );
1961
+ }
1848
1962
  const body = {
1849
1963
  session_id: params.sessionId,
1850
1964
  hotel_code: params.hotelCode,
1851
1965
  combination_id_v2: params.combinationIdV2,
1852
1966
  expected_price: params.expectedPrice,
1853
- expected_balance: params.expectedBalance,
1967
+ expected_cost: params.expectedCost,
1854
1968
  city_id: params.cityId,
1855
1969
  city_name: params.cityName,
1856
1970
  check_in: params.checkIn,
@@ -1862,16 +1976,30 @@ var LetsFG = class {
1862
1976
  phone_country_code: params.phoneCountryCode ?? "48",
1863
1977
  special_requests: params.specialRequests ?? []
1864
1978
  };
1979
+ if (params.currency) body.currency = params.currency;
1980
+ if (params.fxRate != null) body.fx_rate = params.fxRate;
1865
1981
  if (params.combinationId != null) body.combination_id = params.combinationId;
1866
1982
  if (params.hotelName) body.hotel_name = params.hotelName;
1983
+ if (params.idempotencyKey) body.idempotency_key = params.idempotencyKey;
1867
1984
  return this.post("/developers/api/v1/hotels/book", body, 9e4);
1868
1985
  }
1869
1986
  /**
1870
1987
  * Collect the result of a booking started with bookHotel().
1871
1988
  *
1872
- * `status` is 'in_progress', 'succeeded' or 'failed'. On success you get
1873
- * `confirmation`, `reservation_fee_charged`, `pay_link`, `balance_due`,
1874
- * `balance_due_by` and `terms` (including the full cancellation ladder).
1989
+ * `status` is 'in_progress', 'succeeded', 'failed' or 'attention'; the last
1990
+ * three are final (HOTEL_BOOKING_FINAL_STATUSES).
1991
+ *
1992
+ * - succeeded: `confirmation`, `booking_id`, `hotel`, `room`, `total_price` +
1993
+ * `currency` (what the guest is charged), `supplier_paid` +
1994
+ * `supplier_currency` (what the supplier was paid), `payment_status`,
1995
+ * `refundable`, `free_cancellation_until`, `cancellation_ladder`, `terms`.
1996
+ * - failed: `error`, written for the guest. The hold has been released and
1997
+ * nothing was charged.
1998
+ * - attention: `error` (and `confirmation` when known). The outcome could not
1999
+ * be settled automatically; the hold is kept — nothing is charged — while a
2000
+ * person checks with the supplier. Do not book again.
2001
+ *
2002
+ * The guest is e-mailed in every case.
1875
2003
  */
1876
2004
  async hotelBooking(bookingJobId) {
1877
2005
  this.requireApiKey();
@@ -1883,13 +2011,15 @@ var LetsFG = class {
1883
2011
  /**
1884
2012
  * bookHotel(), then poll until the booking settles. Convenience only.
1885
2013
  *
1886
- * Giving up after `maxWaitMs` does NOT cancel anything — the booking may
1887
- * still complete. The returned object carries `booking_job_id` so you can
1888
- * keep polling, and the confirmation is emailed to the guest regardless.
2014
+ * Stops at 'succeeded', 'failed' or 'attention' and never re-books. Giving up
2015
+ * after `maxWaitMs` (default 30 minutes: a booking usually takes 5-10, and
2016
+ * hotel bookings run one at a time) does NOT cancel anything — the booking may
2017
+ * still complete. The returned object carries `booking_job_id` so you can keep
2018
+ * polling, and the guest is e-mailed the outcome regardless.
1889
2019
  */
1890
2020
  async bookHotelAndWait(params) {
1891
2021
  const pollIntervalMs = params.pollIntervalMs ?? 2e4;
1892
- const maxWaitMs = params.maxWaitMs ?? 6e5;
2022
+ const maxWaitMs = params.maxWaitMs ?? 18e5;
1893
2023
  const job = await this.bookHotel(params);
1894
2024
  const jobId = job.booking_job_id;
1895
2025
  if (!jobId) return job;
@@ -1899,19 +2029,19 @@ var LetsFG = class {
1899
2029
  await new Promise((r) => setTimeout(r, pollIntervalMs));
1900
2030
  waited += pollIntervalMs;
1901
2031
  result = await this.hotelBooking(jobId);
1902
- const st = result.status;
1903
- if (st === "succeeded" || st === "failed") return result;
2032
+ if (HOTEL_BOOKING_FINAL_STATUSES.includes(result.status)) return result;
1904
2033
  }
1905
2034
  if (result.booking_job_id == null) result.booking_job_id = jobId;
1906
2035
  return result;
1907
2036
  }
1908
2037
  /**
1909
- * Release a reservation at the supplier.
2038
+ * Release a reservation at the supplier and refund the guest.
1910
2039
  *
1911
- * Free until `balance_due_by`; after that the hotel's own cancellation ladder
1912
- * applies and can reach 100%. The ladder ships in the booking's `terms`, so
1913
- * you can see the cost before calling this. The 5% reservation fee is NOT
1914
- * refunded.
2040
+ * Only this account's own bookings can be cancelled (anything else is 404). A
2041
+ * zero-charge cancellation — a refundable rate before its
2042
+ * `free_cancellation_until` — refunds the charge in full, or releases a hold
2043
+ * not yet captured. A cancellation that would cost money is refused with 409;
2044
+ * the hotel's own ladder is in the booking's `terms`.
1915
2045
  *
1916
2046
  * This drives a browser at the supplier and takes over a minute. If it times
1917
2047
  * out, do not assume it failed — re-check before retrying.
@@ -1925,15 +2055,37 @@ var LetsFG = class {
1925
2055
  );
1926
2056
  }
1927
2057
  /**
1928
- * [Developer API only] Attach a card to a PAID prepaid Developer API account.
2058
+ * [Developer API] Mint a one-time link for connecting a Revolut payment method.
2059
+ *
2060
+ * This replaced setupPayment() on 2026-09-08. Nothing is charged to connect, and
2061
+ * card details never touch LetsFG: the returned `connect_url` opens a hosted page
2062
+ * where the developer saves a card, Revolut Pay or Google Pay. A PERSON must open
2063
+ * it in a browser — there is no endpoint that takes card details, so do not ask a
2064
+ * user for a card number and do not try to automate this step.
1929
2065
  *
1930
- * Most agents should NOT call this. It is unrelated to authenticating for
1931
- * search and booking — for that, run `letsfg auth`, which puts a card on file
1932
- * through a zero-amount setup and creates no billing account.
2066
+ * Most agents should NOT need a Developer API account at all. To authenticate for
2067
+ * search and booking, run `letsfg auth`, which creates no billing account.
1933
2068
  */
1934
- async setupPayment(token = "tok_visa") {
2069
+ async connectPayment() {
1935
2070
  this.requireApiKey();
1936
- return this.post("/developers/api/v1/agents/setup-payment", { token });
2071
+ return this.post("/developers/api/v1/agents/connect-payment", {});
2072
+ }
2073
+ /**
2074
+ * RETIRED 2026-09-08 with Stripe. Throws instead of calling the server.
2075
+ *
2076
+ * `/agents/setup-payment` answers 410 Gone. Payment enrolment moved onto the same
2077
+ * Revolut rail as the rest of the product: call connectPayment() and open the
2078
+ * `connect_url` it returns.
2079
+ *
2080
+ * Kept as a method, and throwing locally rather than making the request, for the
2081
+ * same reason as unlock() — an older caller gets one clear sentence at the line that
2082
+ * is actually wrong, not a 410 body to decode and not a TypeError somewhere else.
2083
+ */
2084
+ async setupPayment(_token) {
2085
+ throw new LetsFGError(
2086
+ "setupPayment() was retired on 2026-09-08 with Stripe and the endpoint answers 410 Gone. Call connectPayment() instead and open the connect_url it returns; nothing is charged to connect. See https://letsfg.co/developers/api/docs",
2087
+ 410
2088
+ );
1937
2089
  }
1938
2090
  /**
1939
2091
  * Get current agent profile and usage stats.
@@ -2028,6 +2180,7 @@ var BoostedTravelError = LetsFGError;
2028
2180
  BoostedTravelError,
2029
2181
  ErrorCategory,
2030
2182
  ErrorCode,
2183
+ HOTEL_BOOKING_FINAL_STATUSES,
2031
2184
  LetsFG,
2032
2185
  LetsFGError,
2033
2186
  OfferExpiredError,
package/dist/index.mjs CHANGED
@@ -4,6 +4,7 @@ import {
4
4
  BoostedTravelError,
5
5
  ErrorCategory,
6
6
  ErrorCode,
7
+ HOTEL_BOOKING_FINAL_STATUSES,
7
8
  LetsFG,
8
9
  LetsFGError,
9
10
  OfferExpiredError,
@@ -22,13 +23,14 @@ import {
22
23
  offerSummary,
23
24
  rankOffers,
24
25
  selectDiverseTop
25
- } from "./chunk-GO3FXQXC.mjs";
26
+ } from "./chunk-GVKYPTY3.mjs";
26
27
  export {
27
28
  AuthenticationError,
28
29
  BoostedTravel,
29
30
  BoostedTravelError,
30
31
  ErrorCategory,
31
32
  ErrorCode,
33
+ HOTEL_BOOKING_FINAL_STATUSES,
32
34
  LetsFG,
33
35
  LetsFGError,
34
36
  OfferExpiredError,
package/package.json CHANGED
@@ -1,56 +1,56 @@
1
- {
2
- "name": "letsfg",
3
- "version": "2026.5.72",
4
- "description": "Flights and hotels for AI agents. Server-side engine covers hundreds of airlines; hotels are real bookable inventory with free cancellation and pay-later terms. Includes open-source ranking engine.",
5
- "main": "dist/index.js",
6
- "module": "dist/index.mjs",
7
- "types": "dist/index.d.ts",
8
- "bin": {
9
- "letsfg": "dist/cli.js"
10
- },
11
- "files": [
12
- "dist/",
13
- "README.md",
14
- "LICENSE"
15
- ],
16
- "scripts": {
17
- "build": "tsup src/index.ts src/cli.ts --format cjs,esm --dts --clean",
18
- "test": "tsx --test src/index.test.ts src/auth.test.ts",
19
- "prepublishOnly": "npm run build"
20
- },
21
- "keywords": [
22
- "flights",
23
- "travel",
24
- "booking",
25
- "agent",
26
- "ai",
27
- "cli",
28
- "autonomous",
29
- "mcp",
30
- "openai",
31
- "airline",
32
- "ndc",
33
- "search",
34
- "ndc",
35
- "hotels",
36
- "hotel-booking",
37
- "hotel-search",
38
- "accommodation"
39
- ],
40
- "author": "LetsFG",
41
- "license": "MIT",
42
- "repository": {
43
- "type": "git",
44
- "url": "https://github.com/LetsFG/LetsFG.git"
45
- },
46
- "homepage": "https://letsfg.co",
47
- "devDependencies": {
48
- "@types/node": "^25.3.3",
49
- "tsup": "^8.0.0",
50
- "tsx": "^4.19.0",
51
- "typescript": "^5.3.0"
52
- },
53
- "engines": {
54
- "node": ">=18.0.0"
55
- }
56
- }
1
+ {
2
+ "name": "letsfg",
3
+ "version": "2026.5.74",
4
+ "description": "Flights and hotels for AI agents. Server-side engine covers hundreds of airlines; hotels are real bookable inventory with free cancellation and pay-later terms. Includes open-source ranking engine.",
5
+ "main": "dist/index.js",
6
+ "module": "dist/index.mjs",
7
+ "types": "dist/index.d.ts",
8
+ "bin": {
9
+ "letsfg": "dist/cli.js"
10
+ },
11
+ "files": [
12
+ "dist/",
13
+ "README.md",
14
+ "LICENSE"
15
+ ],
16
+ "scripts": {
17
+ "build": "tsup src/index.ts src/cli.ts --format cjs,esm --dts --clean",
18
+ "test": "tsx --test src/index.test.ts src/auth.test.ts",
19
+ "prepublishOnly": "npm run build"
20
+ },
21
+ "keywords": [
22
+ "flights",
23
+ "travel",
24
+ "booking",
25
+ "agent",
26
+ "ai",
27
+ "cli",
28
+ "autonomous",
29
+ "mcp",
30
+ "openai",
31
+ "airline",
32
+ "ndc",
33
+ "search",
34
+ "ndc",
35
+ "hotels",
36
+ "hotel-booking",
37
+ "hotel-search",
38
+ "accommodation"
39
+ ],
40
+ "author": "LetsFG",
41
+ "license": "MIT",
42
+ "repository": {
43
+ "type": "git",
44
+ "url": "https://github.com/LetsFG/LetsFG.git"
45
+ },
46
+ "homepage": "https://letsfg.co",
47
+ "devDependencies": {
48
+ "@types/node": "^25.3.3",
49
+ "tsup": "^8.0.0",
50
+ "tsx": "^4.19.0",
51
+ "typescript": "^5.3.0"
52
+ },
53
+ "engines": {
54
+ "node": ">=18.0.0"
55
+ }
56
+ }