letsfg 2026.5.67 → 2026.5.69
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/README.md +16 -3
- package/dist/{chunk-2SNO3AGS.mjs → chunk-PW775XDB.mjs} +25 -4
- package/dist/cli.js +4 -4
- package/dist/cli.mjs +1 -1
- package/dist/index.d.mts +21 -4
- package/dist/index.d.ts +21 -4
- package/dist/index.js +25 -4
- package/dist/index.mjs +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -86,6 +86,19 @@ and `idempotencyKey`).
|
|
|
86
86
|
- `offerSummary(offer)` — One-line string summary
|
|
87
87
|
- `cheapestOffer(result)` — Get cheapest offer from search
|
|
88
88
|
|
|
89
|
+
## Starlink Wi-Fi
|
|
90
|
+
|
|
91
|
+
Offers may carry `starlink`: `confirmed_all` / `confirmed_some` mean the carrier
|
|
92
|
+
has **fully** fitted that aircraft type; `likely_all` / `likely_some` mean the
|
|
93
|
+
rollout on that type is underway but incomplete. Segments carry `confirmed` or
|
|
94
|
+
`likely`.
|
|
95
|
+
|
|
96
|
+
Only `confirmed_*` is safe to state as fact — `likely_*` is a signal, not a
|
|
97
|
+
promise. Anything ending `_some` has at least one leg without it. An **absent**
|
|
98
|
+
field means no information, **not** an absence of Wi-Fi.
|
|
99
|
+
|
|
100
|
+
Full semantics: [docs/api-search.md](https://github.com/LetsFG/LetsFG/blob/main/docs/api-search.md#starlink-wi-fi).
|
|
101
|
+
|
|
89
102
|
## Zero Dependencies
|
|
90
103
|
|
|
91
104
|
Uses native `fetch` (Node 18+). No `axios`, no `node-fetch`, nothing. Safe for sandboxed environments.
|
|
@@ -138,14 +151,14 @@ print(booking["confirmation"], booking["pay_link"])
|
|
|
138
151
|
|
|
139
152
|
### How you pay
|
|
140
153
|
|
|
141
|
-
**
|
|
154
|
+
**5% now, the rest to the hotel later.** At booking we charge 5% of the price
|
|
142
155
|
to your card as a reservation fee. The remaining balance is paid **directly to
|
|
143
156
|
the supplier** through a `pay_link` we return — we never hold it.
|
|
144
157
|
|
|
145
158
|
`balance_due_by` is the supplier's own auto-cancellation date, not a date we
|
|
146
159
|
invent. Miss it and the room is released.
|
|
147
160
|
|
|
148
|
-
The
|
|
161
|
+
The 5% is **non-refundable**. Cancelling before `balance_due_by` costs nothing
|
|
149
162
|
else; after it, the hotel's own cancellation ladder applies and can reach 100%.
|
|
150
163
|
That ladder ships in the booking's `terms`, so you can always see the cost before
|
|
151
164
|
you cancel.
|
|
@@ -171,7 +184,7 @@ is not metered, only the search call itself.
|
|
|
171
184
|
hotel signup.
|
|
172
185
|
- **Only free-cancellation, pay-later rates are sold.** Those are the rates where
|
|
173
186
|
the balance can safely be settled with the supplier after booking, which is
|
|
174
|
-
what makes
|
|
187
|
+
what makes 5%-now/rest-later work at all. You will see fewer results than a
|
|
175
188
|
metasearch shows you. Every one of them can actually be booked.
|
|
176
189
|
- **Booking is asynchronous.** `book_hotel` returns a `booking_job_id`, not a
|
|
177
190
|
booking — the real thing takes minutes. Poll `hotel_booking(job_id)` until
|
|
@@ -87,6 +87,23 @@ function collectFlightNumbers(offer) {
|
|
|
87
87
|
offer.flight_number
|
|
88
88
|
]);
|
|
89
89
|
}
|
|
90
|
+
function buildStarlinkNote(offer) {
|
|
91
|
+
const segments = collectOfferSegments(offer);
|
|
92
|
+
const label = (s) => s.origin && s.destination ? `${s.origin}\u2192${s.destination}` : (s.flight_number || "").trim();
|
|
93
|
+
const confirmed = segments.filter((s) => s.starlink === "confirmed");
|
|
94
|
+
const likely = segments.filter((s) => s.starlink === "likely");
|
|
95
|
+
if (confirmed.length === 0 && likely.length === 0) return void 0;
|
|
96
|
+
const parts = [];
|
|
97
|
+
if (confirmed.length > 0) {
|
|
98
|
+
const where = confirmed.length === segments.length ? "every leg" : confirmed.map(label).filter(Boolean).join(", ");
|
|
99
|
+
parts.push(`Starlink Wi-Fi on ${where}`);
|
|
100
|
+
}
|
|
101
|
+
if (likely.length > 0) {
|
|
102
|
+
const where = likely.map(label).filter(Boolean).join(", ");
|
|
103
|
+
parts.push(`Starlink being installed on this aircraft type${where ? ` (${where})` : ""}, not guaranteed`);
|
|
104
|
+
}
|
|
105
|
+
return parts.join("; ");
|
|
106
|
+
}
|
|
90
107
|
function collectAircraftTypes(offer) {
|
|
91
108
|
return collectUniqueDetailValues(
|
|
92
109
|
collectOfferSegments(offer).map((segment) => segment.aircraft?.replace(/\s*\([^)]*\)/g, "").trim())
|
|
@@ -428,6 +445,10 @@ function getOfferDetailPromptNotes(offer) {
|
|
|
428
445
|
if (aircraftTypes.length > 0) {
|
|
429
446
|
pushUniqueNote(notes, `Aircraft shown: ${aircraftTypes.join(", ")}`);
|
|
430
447
|
}
|
|
448
|
+
const starlinkNote = buildStarlinkNote(offer);
|
|
449
|
+
if (starlinkNote) {
|
|
450
|
+
pushUniqueNote(notes, starlinkNote);
|
|
451
|
+
}
|
|
431
452
|
const operatingCarriers = collectOperatingCarriers(offer);
|
|
432
453
|
const normalizedOfferAirline = typeof offer.airline === "string" ? offer.airline.trim().toLowerCase() : "";
|
|
433
454
|
if (operatingCarriers.length === 1 && operatingCarriers[0].toLowerCase() !== normalizedOfferAirline) {
|
|
@@ -1688,7 +1709,7 @@ var LetsFG = class {
|
|
|
1688
1709
|
//
|
|
1689
1710
|
// Only free-cancellation, pay-later rates are sold. Those are the rates where
|
|
1690
1711
|
// the guest's balance can safely be settled with the supplier after booking,
|
|
1691
|
-
// which is what makes
|
|
1712
|
+
// which is what makes 5%-now/rest-later work. The result set is smaller than
|
|
1692
1713
|
// a metasearch's, and every row in it can actually be booked.
|
|
1693
1714
|
/**
|
|
1694
1715
|
* Resolve a place name to the city id that searchHotels() needs.
|
|
@@ -1711,7 +1732,7 @@ var LetsFG = class {
|
|
|
1711
1732
|
* — so this gets its own generous timeout rather than the client default.
|
|
1712
1733
|
*
|
|
1713
1734
|
* Each offer carries `price` (what the guest pays), `reservation_fee_now`
|
|
1714
|
-
* (the
|
|
1735
|
+
* (the 5% taken at booking), `balance_to_supplier`, `balance_due_by` and
|
|
1715
1736
|
* `free_cancellation_until`. There is no wholesale figure to quote by mistake.
|
|
1716
1737
|
*
|
|
1717
1738
|
* Keep `session_id` and the chosen offer's `combination_id_v2`: together they
|
|
@@ -1737,7 +1758,7 @@ var LetsFG = class {
|
|
|
1737
1758
|
* Start a booking. Returns a job immediately — it does NOT book inline.
|
|
1738
1759
|
*
|
|
1739
1760
|
* A booking takes minutes: the rate is re-blocked at the supplier, every
|
|
1740
|
-
* price and date rail is checked, the
|
|
1761
|
+
* price and date rail is checked, the 5% reservation fee is charged to your
|
|
1741
1762
|
* card, and only then is the room committed. No proxy holds a connection that
|
|
1742
1763
|
* long, so this returns at once and you poll hotelBooking() for the outcome.
|
|
1743
1764
|
* Use bookHotelAndWait() if you would rather block.
|
|
@@ -1819,7 +1840,7 @@ var LetsFG = class {
|
|
|
1819
1840
|
*
|
|
1820
1841
|
* Free until `balance_due_by`; after that the hotel's own cancellation ladder
|
|
1821
1842
|
* applies and can reach 100%. The ladder ships in the booking's `terms`, so
|
|
1822
|
-
* you can see the cost before calling this. The
|
|
1843
|
+
* you can see the cost before calling this. The 5% reservation fee is NOT
|
|
1823
1844
|
* refunded.
|
|
1824
1845
|
*
|
|
1825
1846
|
* This drives a browser at the supplier and takes over a minute. If it times
|
package/dist/cli.js
CHANGED
|
@@ -605,7 +605,7 @@ var LetsFG = class {
|
|
|
605
605
|
//
|
|
606
606
|
// Only free-cancellation, pay-later rates are sold. Those are the rates where
|
|
607
607
|
// the guest's balance can safely be settled with the supplier after booking,
|
|
608
|
-
// which is what makes
|
|
608
|
+
// which is what makes 5%-now/rest-later work. The result set is smaller than
|
|
609
609
|
// a metasearch's, and every row in it can actually be booked.
|
|
610
610
|
/**
|
|
611
611
|
* Resolve a place name to the city id that searchHotels() needs.
|
|
@@ -628,7 +628,7 @@ var LetsFG = class {
|
|
|
628
628
|
* — so this gets its own generous timeout rather than the client default.
|
|
629
629
|
*
|
|
630
630
|
* Each offer carries `price` (what the guest pays), `reservation_fee_now`
|
|
631
|
-
* (the
|
|
631
|
+
* (the 5% taken at booking), `balance_to_supplier`, `balance_due_by` and
|
|
632
632
|
* `free_cancellation_until`. There is no wholesale figure to quote by mistake.
|
|
633
633
|
*
|
|
634
634
|
* Keep `session_id` and the chosen offer's `combination_id_v2`: together they
|
|
@@ -654,7 +654,7 @@ var LetsFG = class {
|
|
|
654
654
|
* Start a booking. Returns a job immediately — it does NOT book inline.
|
|
655
655
|
*
|
|
656
656
|
* A booking takes minutes: the rate is re-blocked at the supplier, every
|
|
657
|
-
* price and date rail is checked, the
|
|
657
|
+
* price and date rail is checked, the 5% reservation fee is charged to your
|
|
658
658
|
* card, and only then is the room committed. No proxy holds a connection that
|
|
659
659
|
* long, so this returns at once and you poll hotelBooking() for the outcome.
|
|
660
660
|
* Use bookHotelAndWait() if you would rather block.
|
|
@@ -736,7 +736,7 @@ var LetsFG = class {
|
|
|
736
736
|
*
|
|
737
737
|
* Free until `balance_due_by`; after that the hotel's own cancellation ladder
|
|
738
738
|
* applies and can reach 100%. The ladder ships in the booking's `terms`, so
|
|
739
|
-
* you can see the cost before calling this. The
|
|
739
|
+
* you can see the cost before calling this. The 5% reservation fee is NOT
|
|
740
740
|
* refunded.
|
|
741
741
|
*
|
|
742
742
|
* This drives a browser at the supplier and takes over a minute. If it times
|
package/dist/cli.mjs
CHANGED
package/dist/index.d.mts
CHANGED
|
@@ -212,6 +212,10 @@ interface OfferDetailSegmentLike {
|
|
|
212
212
|
airline?: string;
|
|
213
213
|
flight_number?: string;
|
|
214
214
|
aircraft?: string;
|
|
215
|
+
origin?: string;
|
|
216
|
+
destination?: string;
|
|
217
|
+
/** Per-leg Starlink verdict; see StarlinkSegmentVerdict in index.ts. */
|
|
218
|
+
starlink?: 'confirmed' | 'likely';
|
|
215
219
|
}
|
|
216
220
|
interface OfferDetailLegLike {
|
|
217
221
|
segments?: OfferDetailSegmentLike[];
|
|
@@ -296,7 +300,17 @@ interface FlightSegment {
|
|
|
296
300
|
duration_seconds: number;
|
|
297
301
|
cabin_class: string;
|
|
298
302
|
aircraft: string;
|
|
303
|
+
/** Starlink Wi-Fi on this leg. 'confirmed' = the carrier has fitted every
|
|
304
|
+
* aircraft of this type. 'likely' = installation on this type is underway
|
|
305
|
+
* but incomplete, so this airframe may not have it. Undefined means no
|
|
306
|
+
* information — NOT an absence of Wi-Fi. */
|
|
307
|
+
starlink?: StarlinkSegmentVerdict;
|
|
299
308
|
}
|
|
309
|
+
type StarlinkSegmentVerdict = 'confirmed' | 'likely';
|
|
310
|
+
/** Itinerary-level Starlink verdict. '*_some' means at least one leg has none;
|
|
311
|
+
* 'likely_*' means the rollout on that subfleet is underway but incomplete.
|
|
312
|
+
* Only 'confirmed_*' should be shown to an end user as a fact. */
|
|
313
|
+
type StarlinkOfferVerdict = 'confirmed_all' | 'confirmed_some' | 'likely_all' | 'likely_some';
|
|
300
314
|
interface FlightRoute {
|
|
301
315
|
segments: FlightSegment[];
|
|
302
316
|
total_duration_seconds: number;
|
|
@@ -312,6 +326,9 @@ interface FlightOffer {
|
|
|
312
326
|
airlines: string[];
|
|
313
327
|
owner_airline: string;
|
|
314
328
|
bags_price: Record<string, number>;
|
|
329
|
+
/** Starlink Wi-Fi across the whole itinerary; per-leg detail is on each
|
|
330
|
+
* segment's `starlink`. Undefined when no leg has any information. */
|
|
331
|
+
starlink?: StarlinkOfferVerdict;
|
|
315
332
|
availability_seats: number | null;
|
|
316
333
|
conditions: Record<string, string>;
|
|
317
334
|
is_locked: boolean;
|
|
@@ -503,7 +520,7 @@ declare class LetsFG {
|
|
|
503
520
|
* — so this gets its own generous timeout rather than the client default.
|
|
504
521
|
*
|
|
505
522
|
* Each offer carries `price` (what the guest pays), `reservation_fee_now`
|
|
506
|
-
* (the
|
|
523
|
+
* (the 5% taken at booking), `balance_to_supplier`, `balance_due_by` and
|
|
507
524
|
* `free_cancellation_until`. There is no wholesale figure to quote by mistake.
|
|
508
525
|
*
|
|
509
526
|
* Keep `session_id` and the chosen offer's `combination_id_v2`: together they
|
|
@@ -526,7 +543,7 @@ declare class LetsFG {
|
|
|
526
543
|
* Start a booking. Returns a job immediately — it does NOT book inline.
|
|
527
544
|
*
|
|
528
545
|
* A booking takes minutes: the rate is re-blocked at the supplier, every
|
|
529
|
-
* price and date rail is checked, the
|
|
546
|
+
* price and date rail is checked, the 5% reservation fee is charged to your
|
|
530
547
|
* card, and only then is the room committed. No proxy holds a connection that
|
|
531
548
|
* long, so this returns at once and you poll hotelBooking() for the outcome.
|
|
532
549
|
* Use bookHotelAndWait() if you would rather block.
|
|
@@ -589,7 +606,7 @@ declare class LetsFG {
|
|
|
589
606
|
*
|
|
590
607
|
* Free until `balance_due_by`; after that the hotel's own cancellation ladder
|
|
591
608
|
* applies and can reach 100%. The ladder ships in the booking's `terms`, so
|
|
592
|
-
* you can see the cost before calling this. The
|
|
609
|
+
* you can see the cost before calling this. The 5% reservation fee is NOT
|
|
593
610
|
* refunded.
|
|
594
611
|
*
|
|
595
612
|
* This drives a browser at the supplier and takes over a minute. If it times
|
|
@@ -628,4 +645,4 @@ declare const BoostedTravel: typeof LetsFG;
|
|
|
628
645
|
declare const BoostedTravelError: typeof LetsFGError;
|
|
629
646
|
type BoostedTravelConfig = LetsFGConfig;
|
|
630
647
|
|
|
631
|
-
export { AuthenticationError, type BookingResult, BoostedTravel, type BoostedTravelConfig, BoostedTravelError, ErrorCategory, type ErrorCategoryType, ErrorCode, type ErrorCodeType, type FlightOffer, type FlightRoute, type FlightSearchResult, type FlightSegment, LetsFG, type LetsFGConfig, LetsFGError, type OfferDetailSignals, OfferExpiredError, type Passenger, PaymentRequiredError, type RankOffer, type RankedOffer, type RankingContext, type ScoreBreakdown, type SearchOptions, TRIP_PURPOSES, type TripPurpose, type TripPurposeOptions, type UnlockResult, ValidationError, cheapestOffer, deduplicateOffers, LetsFG as default, extractOfferDetailSignals, getOfferDetailBadges, getOfferDetailPromptNotes, getPrimaryTripPurpose, getProfileLabel, normalizeTripPurposes, offerSummary, rankOffers, selectDiverseTop };
|
|
648
|
+
export { AuthenticationError, type BookingResult, BoostedTravel, type BoostedTravelConfig, BoostedTravelError, ErrorCategory, type ErrorCategoryType, ErrorCode, type ErrorCodeType, type FlightOffer, type FlightRoute, type FlightSearchResult, type FlightSegment, LetsFG, type LetsFGConfig, LetsFGError, type OfferDetailSignals, OfferExpiredError, type Passenger, PaymentRequiredError, type RankOffer, type RankedOffer, type RankingContext, type ScoreBreakdown, type SearchOptions, type StarlinkOfferVerdict, type StarlinkSegmentVerdict, TRIP_PURPOSES, type TripPurpose, type TripPurposeOptions, type UnlockResult, ValidationError, cheapestOffer, deduplicateOffers, LetsFG as default, extractOfferDetailSignals, getOfferDetailBadges, getOfferDetailPromptNotes, getPrimaryTripPurpose, getProfileLabel, normalizeTripPurposes, offerSummary, rankOffers, selectDiverseTop };
|
package/dist/index.d.ts
CHANGED
|
@@ -212,6 +212,10 @@ interface OfferDetailSegmentLike {
|
|
|
212
212
|
airline?: string;
|
|
213
213
|
flight_number?: string;
|
|
214
214
|
aircraft?: string;
|
|
215
|
+
origin?: string;
|
|
216
|
+
destination?: string;
|
|
217
|
+
/** Per-leg Starlink verdict; see StarlinkSegmentVerdict in index.ts. */
|
|
218
|
+
starlink?: 'confirmed' | 'likely';
|
|
215
219
|
}
|
|
216
220
|
interface OfferDetailLegLike {
|
|
217
221
|
segments?: OfferDetailSegmentLike[];
|
|
@@ -296,7 +300,17 @@ interface FlightSegment {
|
|
|
296
300
|
duration_seconds: number;
|
|
297
301
|
cabin_class: string;
|
|
298
302
|
aircraft: string;
|
|
303
|
+
/** Starlink Wi-Fi on this leg. 'confirmed' = the carrier has fitted every
|
|
304
|
+
* aircraft of this type. 'likely' = installation on this type is underway
|
|
305
|
+
* but incomplete, so this airframe may not have it. Undefined means no
|
|
306
|
+
* information — NOT an absence of Wi-Fi. */
|
|
307
|
+
starlink?: StarlinkSegmentVerdict;
|
|
299
308
|
}
|
|
309
|
+
type StarlinkSegmentVerdict = 'confirmed' | 'likely';
|
|
310
|
+
/** Itinerary-level Starlink verdict. '*_some' means at least one leg has none;
|
|
311
|
+
* 'likely_*' means the rollout on that subfleet is underway but incomplete.
|
|
312
|
+
* Only 'confirmed_*' should be shown to an end user as a fact. */
|
|
313
|
+
type StarlinkOfferVerdict = 'confirmed_all' | 'confirmed_some' | 'likely_all' | 'likely_some';
|
|
300
314
|
interface FlightRoute {
|
|
301
315
|
segments: FlightSegment[];
|
|
302
316
|
total_duration_seconds: number;
|
|
@@ -312,6 +326,9 @@ interface FlightOffer {
|
|
|
312
326
|
airlines: string[];
|
|
313
327
|
owner_airline: string;
|
|
314
328
|
bags_price: Record<string, number>;
|
|
329
|
+
/** Starlink Wi-Fi across the whole itinerary; per-leg detail is on each
|
|
330
|
+
* segment's `starlink`. Undefined when no leg has any information. */
|
|
331
|
+
starlink?: StarlinkOfferVerdict;
|
|
315
332
|
availability_seats: number | null;
|
|
316
333
|
conditions: Record<string, string>;
|
|
317
334
|
is_locked: boolean;
|
|
@@ -503,7 +520,7 @@ declare class LetsFG {
|
|
|
503
520
|
* — so this gets its own generous timeout rather than the client default.
|
|
504
521
|
*
|
|
505
522
|
* Each offer carries `price` (what the guest pays), `reservation_fee_now`
|
|
506
|
-
* (the
|
|
523
|
+
* (the 5% taken at booking), `balance_to_supplier`, `balance_due_by` and
|
|
507
524
|
* `free_cancellation_until`. There is no wholesale figure to quote by mistake.
|
|
508
525
|
*
|
|
509
526
|
* Keep `session_id` and the chosen offer's `combination_id_v2`: together they
|
|
@@ -526,7 +543,7 @@ declare class LetsFG {
|
|
|
526
543
|
* Start a booking. Returns a job immediately — it does NOT book inline.
|
|
527
544
|
*
|
|
528
545
|
* A booking takes minutes: the rate is re-blocked at the supplier, every
|
|
529
|
-
* price and date rail is checked, the
|
|
546
|
+
* price and date rail is checked, the 5% reservation fee is charged to your
|
|
530
547
|
* card, and only then is the room committed. No proxy holds a connection that
|
|
531
548
|
* long, so this returns at once and you poll hotelBooking() for the outcome.
|
|
532
549
|
* Use bookHotelAndWait() if you would rather block.
|
|
@@ -589,7 +606,7 @@ declare class LetsFG {
|
|
|
589
606
|
*
|
|
590
607
|
* Free until `balance_due_by`; after that the hotel's own cancellation ladder
|
|
591
608
|
* applies and can reach 100%. The ladder ships in the booking's `terms`, so
|
|
592
|
-
* you can see the cost before calling this. The
|
|
609
|
+
* you can see the cost before calling this. The 5% reservation fee is NOT
|
|
593
610
|
* refunded.
|
|
594
611
|
*
|
|
595
612
|
* This drives a browser at the supplier and takes over a minute. If it times
|
|
@@ -628,4 +645,4 @@ declare const BoostedTravel: typeof LetsFG;
|
|
|
628
645
|
declare const BoostedTravelError: typeof LetsFGError;
|
|
629
646
|
type BoostedTravelConfig = LetsFGConfig;
|
|
630
647
|
|
|
631
|
-
export { AuthenticationError, type BookingResult, BoostedTravel, type BoostedTravelConfig, BoostedTravelError, ErrorCategory, type ErrorCategoryType, ErrorCode, type ErrorCodeType, type FlightOffer, type FlightRoute, type FlightSearchResult, type FlightSegment, LetsFG, type LetsFGConfig, LetsFGError, type OfferDetailSignals, OfferExpiredError, type Passenger, PaymentRequiredError, type RankOffer, type RankedOffer, type RankingContext, type ScoreBreakdown, type SearchOptions, TRIP_PURPOSES, type TripPurpose, type TripPurposeOptions, type UnlockResult, ValidationError, cheapestOffer, deduplicateOffers, LetsFG as default, extractOfferDetailSignals, getOfferDetailBadges, getOfferDetailPromptNotes, getPrimaryTripPurpose, getProfileLabel, normalizeTripPurposes, offerSummary, rankOffers, selectDiverseTop };
|
|
648
|
+
export { AuthenticationError, type BookingResult, BoostedTravel, type BoostedTravelConfig, BoostedTravelError, ErrorCategory, type ErrorCategoryType, ErrorCode, type ErrorCodeType, type FlightOffer, type FlightRoute, type FlightSearchResult, type FlightSegment, LetsFG, type LetsFGConfig, LetsFGError, type OfferDetailSignals, OfferExpiredError, type Passenger, PaymentRequiredError, type RankOffer, type RankedOffer, type RankingContext, type ScoreBreakdown, type SearchOptions, type StarlinkOfferVerdict, type StarlinkSegmentVerdict, TRIP_PURPOSES, type TripPurpose, type TripPurposeOptions, type UnlockResult, ValidationError, cheapestOffer, deduplicateOffers, LetsFG as default, extractOfferDetailSignals, getOfferDetailBadges, getOfferDetailPromptNotes, getPrimaryTripPurpose, getProfileLabel, normalizeTripPurposes, offerSummary, rankOffers, selectDiverseTop };
|
package/dist/index.js
CHANGED
|
@@ -135,6 +135,23 @@ function collectFlightNumbers(offer) {
|
|
|
135
135
|
offer.flight_number
|
|
136
136
|
]);
|
|
137
137
|
}
|
|
138
|
+
function buildStarlinkNote(offer) {
|
|
139
|
+
const segments = collectOfferSegments(offer);
|
|
140
|
+
const label = (s) => s.origin && s.destination ? `${s.origin}\u2192${s.destination}` : (s.flight_number || "").trim();
|
|
141
|
+
const confirmed = segments.filter((s) => s.starlink === "confirmed");
|
|
142
|
+
const likely = segments.filter((s) => s.starlink === "likely");
|
|
143
|
+
if (confirmed.length === 0 && likely.length === 0) return void 0;
|
|
144
|
+
const parts = [];
|
|
145
|
+
if (confirmed.length > 0) {
|
|
146
|
+
const where = confirmed.length === segments.length ? "every leg" : confirmed.map(label).filter(Boolean).join(", ");
|
|
147
|
+
parts.push(`Starlink Wi-Fi on ${where}`);
|
|
148
|
+
}
|
|
149
|
+
if (likely.length > 0) {
|
|
150
|
+
const where = likely.map(label).filter(Boolean).join(", ");
|
|
151
|
+
parts.push(`Starlink being installed on this aircraft type${where ? ` (${where})` : ""}, not guaranteed`);
|
|
152
|
+
}
|
|
153
|
+
return parts.join("; ");
|
|
154
|
+
}
|
|
138
155
|
function collectAircraftTypes(offer) {
|
|
139
156
|
return collectUniqueDetailValues(
|
|
140
157
|
collectOfferSegments(offer).map((segment) => segment.aircraft?.replace(/\s*\([^)]*\)/g, "").trim())
|
|
@@ -476,6 +493,10 @@ function getOfferDetailPromptNotes(offer) {
|
|
|
476
493
|
if (aircraftTypes.length > 0) {
|
|
477
494
|
pushUniqueNote(notes, `Aircraft shown: ${aircraftTypes.join(", ")}`);
|
|
478
495
|
}
|
|
496
|
+
const starlinkNote = buildStarlinkNote(offer);
|
|
497
|
+
if (starlinkNote) {
|
|
498
|
+
pushUniqueNote(notes, starlinkNote);
|
|
499
|
+
}
|
|
479
500
|
const operatingCarriers = collectOperatingCarriers(offer);
|
|
480
501
|
const normalizedOfferAirline = typeof offer.airline === "string" ? offer.airline.trim().toLowerCase() : "";
|
|
481
502
|
if (operatingCarriers.length === 1 && operatingCarriers[0].toLowerCase() !== normalizedOfferAirline) {
|
|
@@ -1736,7 +1757,7 @@ var LetsFG = class {
|
|
|
1736
1757
|
//
|
|
1737
1758
|
// Only free-cancellation, pay-later rates are sold. Those are the rates where
|
|
1738
1759
|
// the guest's balance can safely be settled with the supplier after booking,
|
|
1739
|
-
// which is what makes
|
|
1760
|
+
// which is what makes 5%-now/rest-later work. The result set is smaller than
|
|
1740
1761
|
// a metasearch's, and every row in it can actually be booked.
|
|
1741
1762
|
/**
|
|
1742
1763
|
* Resolve a place name to the city id that searchHotels() needs.
|
|
@@ -1759,7 +1780,7 @@ var LetsFG = class {
|
|
|
1759
1780
|
* — so this gets its own generous timeout rather than the client default.
|
|
1760
1781
|
*
|
|
1761
1782
|
* Each offer carries `price` (what the guest pays), `reservation_fee_now`
|
|
1762
|
-
* (the
|
|
1783
|
+
* (the 5% taken at booking), `balance_to_supplier`, `balance_due_by` and
|
|
1763
1784
|
* `free_cancellation_until`. There is no wholesale figure to quote by mistake.
|
|
1764
1785
|
*
|
|
1765
1786
|
* Keep `session_id` and the chosen offer's `combination_id_v2`: together they
|
|
@@ -1785,7 +1806,7 @@ var LetsFG = class {
|
|
|
1785
1806
|
* Start a booking. Returns a job immediately — it does NOT book inline.
|
|
1786
1807
|
*
|
|
1787
1808
|
* A booking takes minutes: the rate is re-blocked at the supplier, every
|
|
1788
|
-
* price and date rail is checked, the
|
|
1809
|
+
* price and date rail is checked, the 5% reservation fee is charged to your
|
|
1789
1810
|
* card, and only then is the room committed. No proxy holds a connection that
|
|
1790
1811
|
* long, so this returns at once and you poll hotelBooking() for the outcome.
|
|
1791
1812
|
* Use bookHotelAndWait() if you would rather block.
|
|
@@ -1867,7 +1888,7 @@ var LetsFG = class {
|
|
|
1867
1888
|
*
|
|
1868
1889
|
* Free until `balance_due_by`; after that the hotel's own cancellation ladder
|
|
1869
1890
|
* applies and can reach 100%. The ladder ships in the booking's `terms`, so
|
|
1870
|
-
* you can see the cost before calling this. The
|
|
1891
|
+
* you can see the cost before calling this. The 5% reservation fee is NOT
|
|
1871
1892
|
* refunded.
|
|
1872
1893
|
*
|
|
1873
1894
|
* This drives a browser at the supplier and takes over a minute. If it times
|
package/dist/index.mjs
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "letsfg",
|
|
3
|
-
"version": "2026.5.
|
|
3
|
+
"version": "2026.5.69",
|
|
4
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
5
|
"main": "dist/index.js",
|
|
6
6
|
"module": "dist/index.mjs",
|