letsfg 2026.5.54 → 2026.5.56
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 +9 -98
- package/dist/chunk-3GLZR3AT.mjs +1770 -0
- package/dist/cli.js +274 -280
- package/dist/cli.mjs +15 -66
- package/dist/index.d.mts +302 -84
- package/dist/index.d.ts +302 -84
- package/dist/index.js +1527 -248
- package/dist/index.mjs +21 -7
- package/package.json +4 -2
- package/dist/chunk-LBHJ4GE4.mjs +0 -487
package/dist/cli.mjs
CHANGED
|
@@ -3,7 +3,7 @@ import {
|
|
|
3
3
|
LetsFG,
|
|
4
4
|
LetsFGError,
|
|
5
5
|
offerSummary
|
|
6
|
-
} from "./chunk-
|
|
6
|
+
} from "./chunk-3GLZR3AT.mjs";
|
|
7
7
|
|
|
8
8
|
// src/cli.ts
|
|
9
9
|
function getFlag(args, flag, alias) {
|
|
@@ -53,6 +53,8 @@ async function cmdSearch(args) {
|
|
|
53
53
|
const currency = getFlag(args, "--currency") || "EUR";
|
|
54
54
|
const limit = parseInt(getFlag(args, "--limit", "-l") || "20");
|
|
55
55
|
const sort = getFlag(args, "--sort") || "price";
|
|
56
|
+
const departureFrom = getFlag(args, "--departure-from");
|
|
57
|
+
const departureTo = getFlag(args, "--departure-to");
|
|
56
58
|
const [origin, destination, date] = args;
|
|
57
59
|
if (!origin || !destination || !date) {
|
|
58
60
|
console.error("Usage: letsfg search <origin> <destination> <date> [options]");
|
|
@@ -66,7 +68,9 @@ async function cmdSearch(args) {
|
|
|
66
68
|
maxStopovers: stops,
|
|
67
69
|
currency,
|
|
68
70
|
limit,
|
|
69
|
-
sort
|
|
71
|
+
sort,
|
|
72
|
+
departureTimeFrom: departureFrom,
|
|
73
|
+
departureTimeTo: departureTo
|
|
70
74
|
});
|
|
71
75
|
if (jsonOut) {
|
|
72
76
|
console.log(JSON.stringify({
|
|
@@ -218,50 +222,9 @@ async function cmdRegister(args) {
|
|
|
218
222
|
Save your API key:`);
|
|
219
223
|
console.log(` export LETSFG_API_KEY=${result.api_key}`);
|
|
220
224
|
console.log(`
|
|
221
|
-
Next:
|
|
222
|
-
console.log(` 1. Star https://github.com/LetsFG/LetsFG`);
|
|
223
|
-
console.log(` 2. letsfg star --github <your-github-username>
|
|
225
|
+
Next: letsfg search GDN BCN 2026-07-15
|
|
224
226
|
`);
|
|
225
227
|
}
|
|
226
|
-
async function cmdStar(args) {
|
|
227
|
-
const jsonOut = hasFlag(args, "--json") || hasFlag(args, "-j");
|
|
228
|
-
const apiKey = getFlag(args, "--api-key", "-k");
|
|
229
|
-
const baseUrl = getFlag(args, "--base-url");
|
|
230
|
-
const github = getFlag(args, "--github", "-g");
|
|
231
|
-
if (!github) {
|
|
232
|
-
console.error("Usage: letsfg star --github <your-github-username>");
|
|
233
|
-
process.exit(1);
|
|
234
|
-
}
|
|
235
|
-
const bt = new LetsFG({ apiKey, baseUrl });
|
|
236
|
-
const result = await bt.linkGithub(github);
|
|
237
|
-
if (jsonOut) {
|
|
238
|
-
console.log(JSON.stringify(result, null, 2));
|
|
239
|
-
return;
|
|
240
|
-
}
|
|
241
|
-
const status = result.status;
|
|
242
|
-
if (status === "verified") {
|
|
243
|
-
console.log(`
|
|
244
|
-
\u2713 GitHub star verified! Unlimited access granted.`);
|
|
245
|
-
console.log(` Username: ${result.github_username}`);
|
|
246
|
-
console.log(`
|
|
247
|
-
You're all set \u2014 search, unlock, and book for free.
|
|
248
|
-
`);
|
|
249
|
-
} else if (status === "already_verified") {
|
|
250
|
-
console.log(`
|
|
251
|
-
\u2713 Already verified! You have unlimited access.`);
|
|
252
|
-
console.log(` Username: ${result.github_username}
|
|
253
|
-
`);
|
|
254
|
-
} else if (status === "star_required") {
|
|
255
|
-
console.log(`
|
|
256
|
-
\u2717 Star not found for '${github}'.`);
|
|
257
|
-
console.log(` 1. Star the repo: https://github.com/LetsFG/LetsFG`);
|
|
258
|
-
console.log(` 2. Run this command again.
|
|
259
|
-
`);
|
|
260
|
-
} else {
|
|
261
|
-
console.error(` \u2717 Unexpected status: ${status}`);
|
|
262
|
-
process.exit(1);
|
|
263
|
-
}
|
|
264
|
-
}
|
|
265
228
|
async function cmdSetupPayment(args) {
|
|
266
229
|
const jsonOut = hasFlag(args, "--json") || hasFlag(args, "-j");
|
|
267
230
|
const apiKey = getFlag(args, "--api-key", "-k");
|
|
@@ -298,17 +261,8 @@ async function cmdMe(args) {
|
|
|
298
261
|
Agent: ${p.agent_name} (${p.agent_id})`);
|
|
299
262
|
console.log(` Email: ${p.email}`);
|
|
300
263
|
console.log(` Tier: ${p.tier}`);
|
|
301
|
-
const gh = p.github_username || "";
|
|
302
|
-
const starOk = p.github_star_verified || false;
|
|
303
|
-
if (starOk) {
|
|
304
|
-
console.log(` GitHub: \u2713 ${gh} (star verified)`);
|
|
305
|
-
} else if (gh) {
|
|
306
|
-
console.log(` GitHub: ${gh} (star not yet verified \u2014 run: letsfg star --github ${gh})`);
|
|
307
|
-
} else {
|
|
308
|
-
console.log(` GitHub: Not linked \u2014 run: letsfg star --github <username>`);
|
|
309
|
-
}
|
|
310
264
|
const access = p.access_granted || false;
|
|
311
|
-
console.log(` Access: ${access ? "\u2713 Granted (search, unlock, book)" : "\u2717 Not granted
|
|
265
|
+
console.log(` Access: ${access ? "\u2713 Granted (search, unlock, book)" : "\u2717 Not granted"}`);
|
|
312
266
|
console.log(` Payment: ${p.payment_ready ? "\u2713 Ready" : "\u2014"}`);
|
|
313
267
|
console.log(` Searches: ${u.total_searches || 0}`);
|
|
314
268
|
console.log(` Unlocks: ${u.total_unlocks || 0}`);
|
|
@@ -319,17 +273,16 @@ async function cmdMe(args) {
|
|
|
319
273
|
var HELP = `
|
|
320
274
|
LetsFG \u2014 Agent-native flight search & booking.
|
|
321
275
|
|
|
322
|
-
Search
|
|
323
|
-
|
|
276
|
+
Search hundreds of airlines via the LetsFG cloud engine.
|
|
277
|
+
Free search: authenticate once with Twitter/X, then search instantly.
|
|
324
278
|
|
|
325
279
|
Commands:
|
|
326
|
-
search <origin> <dest> <date> Search for flights (
|
|
280
|
+
search <origin> <dest> <date> Search for flights (free)
|
|
327
281
|
locations <query> Resolve city name to IATA codes
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
setup-payment Legacy payment setup
|
|
282
|
+
unlock <offer_id> Unlock offer \u2014 1% of ticket (min $3)
|
|
283
|
+
book <offer_id> --passenger ... Book flight \u2014 charges ticket price via Stripe
|
|
284
|
+
register --name ... --email ... Register for a Developer API key
|
|
285
|
+
setup-payment Attach Stripe payment method
|
|
333
286
|
me Show agent profile
|
|
334
287
|
|
|
335
288
|
Options:
|
|
@@ -339,7 +292,6 @@ Options:
|
|
|
339
292
|
|
|
340
293
|
Examples:
|
|
341
294
|
letsfg register --name my-agent --email me@example.com
|
|
342
|
-
letsfg star --github octocat
|
|
343
295
|
letsfg search GDN BER 2026-03-03 --sort price
|
|
344
296
|
letsfg unlock off_xxx
|
|
345
297
|
letsfg book off_xxx -p '{"id":"pas_xxx",...}' -e john@ex.com
|
|
@@ -364,9 +316,6 @@ async function main() {
|
|
|
364
316
|
case "register":
|
|
365
317
|
await cmdRegister(args);
|
|
366
318
|
break;
|
|
367
|
-
case "star":
|
|
368
|
-
await cmdStar(args);
|
|
369
|
-
break;
|
|
370
319
|
case "setup-payment":
|
|
371
320
|
await cmdSetupPayment(args);
|
|
372
321
|
break;
|
package/dist/index.d.mts
CHANGED
|
@@ -1,19 +1,286 @@
|
|
|
1
|
+
type TripPurpose = 'honeymoon' | 'special_occasion' | 'business' | 'ski' | 'beach' | 'city_break' | 'family_holiday' | 'graduation' | 'concert_festival' | 'sports_event' | 'spring_break';
|
|
2
|
+
declare const TRIP_PURPOSES: readonly TripPurpose[];
|
|
3
|
+
interface TripPurposeOptions {
|
|
4
|
+
tripPurpose?: TripPurpose | null;
|
|
5
|
+
tripPurposes?: ReadonlyArray<TripPurpose | null | undefined> | null;
|
|
6
|
+
}
|
|
7
|
+
declare function normalizeTripPurposes({ tripPurpose, tripPurposes }: TripPurposeOptions): TripPurpose[];
|
|
8
|
+
declare function getPrimaryTripPurpose(options: TripPurposeOptions): TripPurpose | undefined;
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* LetsFG Personalized Flight Ranking Engine
|
|
12
|
+
*
|
|
13
|
+
* Open-source implementation of the scoring algorithm that powers letsfg.co.
|
|
14
|
+
* Scores each offer across 9 dimensions with personalization weights that shift
|
|
15
|
+
* based on trip context and purpose. Pure TypeScript — no external dependencies,
|
|
16
|
+
* safe to run in both Node and browser.
|
|
17
|
+
*
|
|
18
|
+
* Usage:
|
|
19
|
+
* import { rankOffers, type RankingContext } from 'letsfg/ranking'
|
|
20
|
+
* const ranked = rankOffers(offers, { tripContext: 'family', requireBag: true })
|
|
21
|
+
* // ranked[0].offer is the best pick, ranked[0].heroFacts explains why
|
|
22
|
+
*
|
|
23
|
+
* Companion modules in the same package:
|
|
24
|
+
* - trip-purpose.ts — TripPurpose type + normalization helpers
|
|
25
|
+
* - offer-details.ts — extractOfferDetailSignals, getOfferDetailBadges, etc.
|
|
26
|
+
*/
|
|
27
|
+
|
|
28
|
+
interface RankOfferSegment {
|
|
29
|
+
layover_minutes?: number;
|
|
30
|
+
airline?: string;
|
|
31
|
+
flight_number?: string;
|
|
32
|
+
aircraft?: string;
|
|
33
|
+
origin?: string;
|
|
34
|
+
destination?: string;
|
|
35
|
+
}
|
|
36
|
+
interface RankOffer {
|
|
37
|
+
id: string;
|
|
38
|
+
price: number;
|
|
39
|
+
/** Display total in the user's chosen currency (ticket + fee + ancillaries).
|
|
40
|
+
* When provided, this is used for all price scoring and penalty calculations
|
|
41
|
+
* instead of `price`. Callers should populate this so the ranking reflects
|
|
42
|
+
* exactly what the user will pay. */
|
|
43
|
+
displayPrice?: number;
|
|
44
|
+
google_flights_price?: number;
|
|
45
|
+
currency: string;
|
|
46
|
+
airline: string;
|
|
47
|
+
origin?: string;
|
|
48
|
+
destination?: string;
|
|
49
|
+
departure_time: string;
|
|
50
|
+
arrival_time: string;
|
|
51
|
+
duration_minutes: number;
|
|
52
|
+
stops: number;
|
|
53
|
+
segments?: RankOfferSegment[];
|
|
54
|
+
inbound?: {
|
|
55
|
+
departure_time?: string;
|
|
56
|
+
arrival_time?: string;
|
|
57
|
+
origin?: string;
|
|
58
|
+
destination?: string;
|
|
59
|
+
stops?: number;
|
|
60
|
+
/** Inbound-leg duration in minutes. Required for round-trip scoring to
|
|
61
|
+
* see the return leg in `scoreDuration`. When absent the inbound is
|
|
62
|
+
* treated as zero-duration (best case for the offer). */
|
|
63
|
+
duration_minutes?: number;
|
|
64
|
+
segments?: RankOfferSegment[];
|
|
65
|
+
};
|
|
66
|
+
ancillaries?: {
|
|
67
|
+
cabin_bag?: {
|
|
68
|
+
included?: boolean;
|
|
69
|
+
price?: number;
|
|
70
|
+
currency?: string;
|
|
71
|
+
description?: string;
|
|
72
|
+
};
|
|
73
|
+
checked_bag?: {
|
|
74
|
+
included?: boolean;
|
|
75
|
+
price?: number;
|
|
76
|
+
currency?: string;
|
|
77
|
+
description?: string;
|
|
78
|
+
};
|
|
79
|
+
seat_selection?: {
|
|
80
|
+
included?: boolean;
|
|
81
|
+
price?: number;
|
|
82
|
+
currency?: string;
|
|
83
|
+
description?: string;
|
|
84
|
+
};
|
|
85
|
+
};
|
|
86
|
+
conditions?: {
|
|
87
|
+
refund_before_departure?: 'allowed' | 'not_allowed' | 'allowed_with_fee' | 'unknown';
|
|
88
|
+
change_before_departure?: 'allowed' | 'not_allowed' | 'allowed_with_fee' | 'unknown';
|
|
89
|
+
[key: string]: string | undefined;
|
|
90
|
+
};
|
|
91
|
+
/** Quality tag set by upstream validateOfferBatch when the offer's intrinsic
|
|
92
|
+
* data is untrustworthy (date drift from search criteria, asymmetric leg
|
|
93
|
+
* durations, etc.). The ranker uses this as the highest-priority hero gate:
|
|
94
|
+
* a suspect offer can never be hero unless every offer in the pool is suspect.
|
|
95
|
+
* Rank-not-filter — suspect offers still appear as runner-ups. */
|
|
96
|
+
quality?: 'suspect' | string;
|
|
97
|
+
}
|
|
98
|
+
interface RankingContext {
|
|
99
|
+
tripContext?: 'solo' | 'couple' | 'family' | 'group' | 'business_traveler';
|
|
100
|
+
tripPurpose?: TripPurpose;
|
|
101
|
+
tripPurposes?: ReadonlyArray<TripPurpose>;
|
|
102
|
+
travelerCount?: number;
|
|
103
|
+
depTimePref?: 'early_morning' | 'morning' | 'afternoon' | 'evening' | 'night' | 'red_eye';
|
|
104
|
+
retTimePref?: 'early_morning' | 'morning' | 'afternoon' | 'evening' | 'night' | 'red_eye';
|
|
105
|
+
arrivalTimePref?: 'morning' | 'afternoon' | 'evening';
|
|
106
|
+
requireBag?: boolean;
|
|
107
|
+
requireSeat?: boolean;
|
|
108
|
+
requireMeals?: boolean;
|
|
109
|
+
requireCancellation?: boolean;
|
|
110
|
+
preferredAirline?: string;
|
|
111
|
+
preferQuickFlight?: boolean;
|
|
112
|
+
/** User said "direct" or "nonstop" — strongly boost stops weight but don't filter.
|
|
113
|
+
* If no directs exist, 1-stop will naturally float to the top over 3-stop. */
|
|
114
|
+
preferDirect?: boolean;
|
|
115
|
+
/** User explicitly asked for cheapest / lowest price — price dominates all other factors. */
|
|
116
|
+
preferCheapest?: boolean;
|
|
117
|
+
/** User asked for "1 stop" / "max 2 stops" / "at most 1 connection" — a soft cap
|
|
118
|
+
* on the number of stops. Offers exceeding the cap are gated out of the HERO slot
|
|
119
|
+
* (rank-not-filter: they still appear as runner-ups, just below within-cap offers).
|
|
120
|
+
* When no offer satisfies the cap it is relaxed like the other gates. preferDirect
|
|
121
|
+
* (an implicit cap of 0) is handled by the dedicated 'direct' path; this covers
|
|
122
|
+
* explicit caps >= 1, where the user tolerates a connection but not a 3-leg odyssey. */
|
|
123
|
+
maxStops?: number;
|
|
124
|
+
/** User explicitly wants — or is happy with — a LONG layover (a stopover to break
|
|
125
|
+
* the journey or see a city). Inverts the default short-layover preference in
|
|
126
|
+
* scoreLayover so longer connections score higher, and boosts the layover weight.
|
|
127
|
+
* Default (undefined/false) leaves the normal short-layover scoring intact. */
|
|
128
|
+
preferLongLayover?: boolean;
|
|
129
|
+
/** Hard lower bound on departure time, in minutes from midnight.
|
|
130
|
+
* Flights departing before this time receive a heavy score penalty (effectively filtered out). */
|
|
131
|
+
departAfterMins?: number;
|
|
132
|
+
/** Hard upper bound on departure time, in minutes from midnight. */
|
|
133
|
+
departBeforeMins?: number;
|
|
134
|
+
}
|
|
135
|
+
interface ScoreBreakdown {
|
|
136
|
+
price: number;
|
|
137
|
+
stops: number;
|
|
138
|
+
duration: number;
|
|
139
|
+
depTime: number;
|
|
140
|
+
arrivalTime: number;
|
|
141
|
+
baggage: number;
|
|
142
|
+
savings: number;
|
|
143
|
+
comfortHours: number;
|
|
144
|
+
layover: number;
|
|
145
|
+
}
|
|
146
|
+
interface RankedOffer<T extends RankOffer = RankOffer> {
|
|
147
|
+
offer: T;
|
|
148
|
+
score: number;
|
|
149
|
+
rank: number;
|
|
150
|
+
breakdown: ScoreBreakdown;
|
|
151
|
+
heroFacts: string[];
|
|
152
|
+
tradeoffs: string[];
|
|
153
|
+
/** Hero only: names of user-stated criteria gates that had to be relaxed to
|
|
154
|
+
* find a hero (no offer satisfied them all). Surfaced so the UI can banner
|
|
155
|
+
* the mismatch — e.g. "No direct flights on this route" or "No refundable
|
|
156
|
+
* fare available". Empty/undefined means the hero satisfied every stated
|
|
157
|
+
* criterion. Relax order: refund -> bag -> time -> direct. */
|
|
158
|
+
relaxedGates?: string[];
|
|
159
|
+
}
|
|
160
|
+
/** Remove near-duplicate offers: when multiple connectors return the same physical
|
|
161
|
+
* flight (e.g. Ryanair FR1234 from both the direct connector and Kiwi/Skyscanner),
|
|
162
|
+
* keep only the cheapest. Two offers are considered identical only when they share
|
|
163
|
+
* the same calendar date, route, airline, outbound timing buckets, inbound timing
|
|
164
|
+
* buckets (for round-trips), stop counts, and core fare conditions. Including the
|
|
165
|
+
* inbound leg prevents collapsing distinct return options for the same outbound. */
|
|
166
|
+
declare function deduplicateOffers<T extends RankOffer>(offers: T[]): T[];
|
|
167
|
+
/**
|
|
168
|
+
* Rank an array of flight offers by personalized score.
|
|
169
|
+
* Returns a new array sorted best-first. The original array is not mutated.
|
|
170
|
+
*
|
|
171
|
+
* @param offers Array of flight offers (any type extending RankOffer)
|
|
172
|
+
* @param ctx User intent context from the NL query parser
|
|
173
|
+
*/
|
|
174
|
+
declare function rankOffers<T extends RankOffer>(offers: T[], ctx: RankingContext, options?: {
|
|
175
|
+
skipDedup?: boolean;
|
|
176
|
+
}): RankedOffer<T>[];
|
|
177
|
+
/**
|
|
178
|
+
* From an already-ranked list, picks the top N offers that are genuinely
|
|
179
|
+
* different from each other — so runner-ups are real propositions, not just
|
|
180
|
+
* the same flight at +$3 from a different booking source.
|
|
181
|
+
*
|
|
182
|
+
* Two offers are considered "the same" for this purpose if both their
|
|
183
|
+
* departure time slot (3-hour window) AND their stop count are identical.
|
|
184
|
+
* Diversity requires differing in at least one of those dimensions.
|
|
185
|
+
*
|
|
186
|
+
* Falls back to next-best-ranked when the pool lacks enough diverse options.
|
|
187
|
+
*/
|
|
188
|
+
declare function selectDiverseTop<T extends RankOffer>(ranked: RankedOffer<T>[], n: number): RankedOffer<T>[];
|
|
189
|
+
/**
|
|
190
|
+
* Returns a short human-readable label describing the ranking profile
|
|
191
|
+
* that was applied (e.g. "City break", "Family holiday"). Returns null
|
|
192
|
+
* if the default generic profile is used.
|
|
193
|
+
*/
|
|
194
|
+
declare function getProfileLabel(ctx: RankingContext): string | null;
|
|
195
|
+
|
|
196
|
+
type OfferConditionState = 'allowed' | 'not_allowed' | 'allowed_with_fee' | 'unknown';
|
|
197
|
+
type OfferAmenityState = 'included' | 'available' | 'unavailable' | 'unknown';
|
|
198
|
+
type OfferAmenityConfidence = 'verified' | 'inferred' | 'unknown';
|
|
199
|
+
type OfferAmenitySource = 'ancillary' | 'condition' | 'unknown';
|
|
200
|
+
interface OfferDetailAncillary {
|
|
201
|
+
included?: boolean;
|
|
202
|
+
price?: number;
|
|
203
|
+
currency?: string;
|
|
204
|
+
description?: string;
|
|
205
|
+
}
|
|
206
|
+
interface OfferDetailConditions {
|
|
207
|
+
refund_before_departure?: OfferConditionState;
|
|
208
|
+
change_before_departure?: OfferConditionState;
|
|
209
|
+
[key: string]: string | undefined;
|
|
210
|
+
}
|
|
211
|
+
interface OfferDetailSegmentLike {
|
|
212
|
+
airline?: string;
|
|
213
|
+
flight_number?: string;
|
|
214
|
+
aircraft?: string;
|
|
215
|
+
}
|
|
216
|
+
interface OfferDetailLegLike {
|
|
217
|
+
segments?: OfferDetailSegmentLike[];
|
|
218
|
+
}
|
|
219
|
+
interface OfferDetailLike {
|
|
220
|
+
airline?: string;
|
|
221
|
+
flight_number?: string;
|
|
222
|
+
segments?: OfferDetailSegmentLike[];
|
|
223
|
+
inbound?: OfferDetailLegLike;
|
|
224
|
+
ancillaries?: {
|
|
225
|
+
cabin_bag?: OfferDetailAncillary;
|
|
226
|
+
checked_bag?: OfferDetailAncillary;
|
|
227
|
+
seat_selection?: OfferDetailAncillary;
|
|
228
|
+
};
|
|
229
|
+
conditions?: OfferDetailConditions;
|
|
230
|
+
}
|
|
231
|
+
type OfferServiceSignal = 'included' | 'available' | null;
|
|
232
|
+
interface OfferAmenityAssessment {
|
|
233
|
+
state: OfferAmenityState;
|
|
234
|
+
confidence: OfferAmenityConfidence;
|
|
235
|
+
source: OfferAmenitySource;
|
|
236
|
+
evidence?: string;
|
|
237
|
+
}
|
|
238
|
+
interface OfferDetailSignals {
|
|
239
|
+
refundability: OfferConditionState | null;
|
|
240
|
+
changeability: OfferConditionState | null;
|
|
241
|
+
meals: OfferServiceSignal;
|
|
242
|
+
refreshments: OfferServiceSignal;
|
|
243
|
+
insurance: OfferServiceSignal;
|
|
244
|
+
lounge: OfferServiceSignal;
|
|
245
|
+
wifi: OfferServiceSignal;
|
|
246
|
+
power: OfferServiceSignal;
|
|
247
|
+
entertainment: OfferServiceSignal;
|
|
248
|
+
amenities: {
|
|
249
|
+
meals: OfferAmenityAssessment;
|
|
250
|
+
refreshments: OfferAmenityAssessment;
|
|
251
|
+
insurance: OfferAmenityAssessment;
|
|
252
|
+
lounge: OfferAmenityAssessment;
|
|
253
|
+
wifi: OfferAmenityAssessment;
|
|
254
|
+
power: OfferAmenityAssessment;
|
|
255
|
+
entertainment: OfferAmenityAssessment;
|
|
256
|
+
};
|
|
257
|
+
}
|
|
258
|
+
interface OfferDetailBadge {
|
|
259
|
+
key: string;
|
|
260
|
+
label: string;
|
|
261
|
+
tone: 'positive' | 'neutral' | 'negative';
|
|
262
|
+
}
|
|
263
|
+
declare function extractOfferDetailSignals(offer: OfferDetailLike): OfferDetailSignals;
|
|
264
|
+
declare function getOfferDetailBadges(offer: OfferDetailLike): OfferDetailBadge[];
|
|
265
|
+
declare function getOfferDetailPromptNotes(offer: OfferDetailLike): string[];
|
|
266
|
+
|
|
1
267
|
/**
|
|
2
268
|
* LetsFG — Agent-native flight search & booking SDK for Node.js/TypeScript.
|
|
3
269
|
*
|
|
4
|
-
*
|
|
5
|
-
* Zero external JS dependencies. Uses native fetch (Node 18+).
|
|
270
|
+
* Server-side engine covers hundreds of airlines. Free search via PFS Bearer token
|
|
271
|
+
* or prepaid Developer API. Zero external JS dependencies. Uses native fetch (Node 18+).
|
|
6
272
|
*
|
|
7
273
|
* @example
|
|
8
274
|
* ```ts
|
|
9
|
-
* import { LetsFG
|
|
10
|
-
*
|
|
11
|
-
* // Local search — FREE, no API key
|
|
12
|
-
* const local = await searchLocal('SHA', 'CTU', '2026-03-20');
|
|
275
|
+
* import { LetsFG } from 'letsfg';
|
|
13
276
|
*
|
|
14
|
-
* //
|
|
15
|
-
* const bt = new LetsFG({
|
|
277
|
+
* // PFS (free, Bearer token from `letsfg auth`)
|
|
278
|
+
* const bt = new LetsFG({ bearerToken: process.env.LETSFG_BEARER_TOKEN });
|
|
16
279
|
* const flights = await bt.search('GDN', 'BER', '2026-03-03');
|
|
280
|
+
*
|
|
281
|
+
* // Developer API (prepaid credits)
|
|
282
|
+
* const bt2 = new LetsFG({ apiKey: 'trav_...' });
|
|
283
|
+
* const flights2 = await bt2.search('LHR', 'JFK', '2026-04-15');
|
|
17
284
|
* ```
|
|
18
285
|
*/
|
|
19
286
|
interface FlightSegment {
|
|
@@ -112,28 +379,13 @@ interface SearchOptions {
|
|
|
112
379
|
currency?: string;
|
|
113
380
|
limit?: number;
|
|
114
381
|
sort?: 'price' | 'duration';
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
/** Search mode. Omit for full search (all 200+ connectors). 'fast' = OTAs + key direct airlines (~25 connectors, 20-40s). */
|
|
118
|
-
mode?: 'fast';
|
|
119
|
-
}
|
|
120
|
-
interface CheckoutProgress {
|
|
121
|
-
status: string;
|
|
122
|
-
step: string;
|
|
123
|
-
step_index: number;
|
|
124
|
-
airline: string;
|
|
125
|
-
source: string;
|
|
126
|
-
offer_id: string;
|
|
127
|
-
total_price: number;
|
|
128
|
-
currency: string;
|
|
129
|
-
booking_url: string;
|
|
130
|
-
screenshot_b64: string;
|
|
131
|
-
message: string;
|
|
132
|
-
can_complete_manually: boolean;
|
|
133
|
-
elapsed_seconds: number;
|
|
134
|
-
details: Record<string, unknown>;
|
|
382
|
+
departureTimeFrom?: string;
|
|
383
|
+
departureTimeTo?: string;
|
|
135
384
|
}
|
|
136
385
|
interface LetsFGConfig {
|
|
386
|
+
/** PFS Bearer token from `letsfg auth`. Enables free search via POST /api/search polling. */
|
|
387
|
+
bearerToken?: string;
|
|
388
|
+
/** Developer API key (prepaid credits, no per-booking fee). */
|
|
137
389
|
apiKey?: string;
|
|
138
390
|
baseUrl?: string;
|
|
139
391
|
timeout?: number;
|
|
@@ -189,28 +441,22 @@ declare class ValidationError extends LetsFGError {
|
|
|
189
441
|
declare function offerSummary(offer: FlightOffer): string;
|
|
190
442
|
/** Get cheapest offer from search results */
|
|
191
443
|
declare function cheapestOffer(result: FlightSearchResult): FlightOffer | null;
|
|
192
|
-
/**
|
|
193
|
-
* Search flights using 73 local airline connectors — FREE, no API key needed.
|
|
194
|
-
*
|
|
195
|
-
* Requires: pip install letsfg && playwright install chromium
|
|
196
|
-
*
|
|
197
|
-
* @param origin - IATA code (e.g., "SHA")
|
|
198
|
-
* @param destination - IATA code (e.g., "CTU")
|
|
199
|
-
* @param dateFrom - Departure date "YYYY-MM-DD"
|
|
200
|
-
* @param options - Optional: currency, adults, limit, etc.
|
|
201
|
-
*/
|
|
202
|
-
declare function searchLocal(origin: string, destination: string, dateFrom: string, options?: Partial<SearchOptions>): Promise<FlightSearchResult>;
|
|
203
444
|
declare class LetsFG {
|
|
445
|
+
private bearerToken;
|
|
204
446
|
private apiKey;
|
|
205
447
|
private baseUrl;
|
|
206
448
|
private timeout;
|
|
207
449
|
constructor(config?: LetsFGConfig);
|
|
450
|
+
private requireAuth;
|
|
208
451
|
private requireApiKey;
|
|
452
|
+
/** True when using PFS Bearer token (free search path) */
|
|
453
|
+
private get usingPFS();
|
|
209
454
|
/**
|
|
210
|
-
* Search for flights — FREE
|
|
455
|
+
* Search for flights — FREE.
|
|
211
456
|
*
|
|
212
|
-
* Uses
|
|
213
|
-
*
|
|
457
|
+
* Uses PFS (Bearer token) or Developer API (X-API-Key) depending on config.
|
|
458
|
+
* PFS: async polling (POST /api/search -> poll /api/results/<id> every 10s).
|
|
459
|
+
* Developer API: synchronous 60-90s call.
|
|
214
460
|
*
|
|
215
461
|
* @param origin - IATA code (e.g., "GDN", "LON")
|
|
216
462
|
* @param destination - IATA code (e.g., "BER", "BCN")
|
|
@@ -218,73 +464,45 @@ declare class LetsFG {
|
|
|
218
464
|
* @param options - Optional search parameters
|
|
219
465
|
*/
|
|
220
466
|
search(origin: string, destination: string, dateFrom: string, options?: SearchOptions): Promise<FlightSearchResult>;
|
|
467
|
+
/** PFS path: POST /api/search -> poll /api/results/<id> */
|
|
468
|
+
private searchPFS;
|
|
221
469
|
/**
|
|
222
|
-
* Resolve a city/airport name to IATA codes
|
|
470
|
+
* Resolve a city/airport name to IATA codes.
|
|
223
471
|
*/
|
|
224
472
|
resolveLocation(query: string): Promise<Array<Record<string, unknown>>>;
|
|
225
473
|
/**
|
|
226
|
-
* Unlock a flight offer —
|
|
227
|
-
*
|
|
474
|
+
* Unlock a flight offer — confirms live price, reveals direct airline booking URL.
|
|
475
|
+
* Cost: 1% of ticket price, min $3. Free with Developer API.
|
|
228
476
|
*/
|
|
229
477
|
unlock(offerId: string): Promise<UnlockResult>;
|
|
230
478
|
/**
|
|
231
|
-
* Book a flight — charges ticket price via Stripe.
|
|
232
|
-
* Creates a real airline reservation with PNR.
|
|
233
|
-
*
|
|
479
|
+
* Book a flight via Developer API — charges ticket price via Stripe, creates real PNR.
|
|
234
480
|
* Always provide idempotencyKey to prevent double-bookings on retry.
|
|
235
481
|
*/
|
|
236
482
|
book(offerId: string, passengers: Passenger[], contactEmail: string, contactPhone?: string, idempotencyKey?: string): Promise<BookingResult>;
|
|
237
483
|
/**
|
|
238
|
-
* Set up payment method (required before booking).
|
|
484
|
+
* Set up payment method (required before unlock/booking).
|
|
239
485
|
*/
|
|
240
486
|
setupPayment(token?: string): Promise<Record<string, unknown>>;
|
|
241
|
-
/**
|
|
242
|
-
* Start automated checkout — drives to payment page, NEVER submits payment.
|
|
243
|
-
*
|
|
244
|
-
* Requires unlock first. Returns progress with screenshot and
|
|
245
|
-
* booking URL for manual completion.
|
|
246
|
-
*
|
|
247
|
-
* @param offerId - Offer ID from search results
|
|
248
|
-
* @param passengers - Passenger details (use test data for safety)
|
|
249
|
-
* @param checkoutToken - Token from unlock() response
|
|
250
|
-
*/
|
|
251
|
-
startCheckout(offerId: string, passengers: Passenger[], checkoutToken: string): Promise<CheckoutProgress>;
|
|
252
|
-
/**
|
|
253
|
-
* Start checkout locally via Python (runs on your machine).
|
|
254
|
-
* Requires: pip install letsfg && playwright install chromium
|
|
255
|
-
*
|
|
256
|
-
* @param offer - Full FlightOffer object from search results
|
|
257
|
-
* @param passengers - Passenger details
|
|
258
|
-
* @param checkoutToken - Token from unlock()
|
|
259
|
-
*/
|
|
260
|
-
startCheckoutLocal(offer: FlightOffer, passengers: Passenger[], checkoutToken: string): Promise<CheckoutProgress>;
|
|
261
|
-
/**
|
|
262
|
-
* Link GitHub account for FREE unlimited access.
|
|
263
|
-
*
|
|
264
|
-
* Star https://github.com/LetsFG/LetsFG, then call this with your username.
|
|
265
|
-
* Once verified, access is permanent.
|
|
266
|
-
*/
|
|
267
|
-
linkGithub(githubUsername: string): Promise<Record<string, unknown>>;
|
|
268
487
|
/**
|
|
269
488
|
* Get current agent profile and usage stats.
|
|
270
489
|
*/
|
|
271
490
|
me(): Promise<Record<string, unknown>>;
|
|
272
491
|
/**
|
|
273
|
-
* Register a new agent — no
|
|
492
|
+
* Register a new Developer API agent — no auth needed.
|
|
274
493
|
*/
|
|
275
494
|
static register(agentName: string, email: string, baseUrl?: string, ownerName?: string, description?: string): Promise<Record<string, unknown>>;
|
|
495
|
+
private postWithBearer;
|
|
496
|
+
private getNoAuth;
|
|
497
|
+
private getWithAuth;
|
|
498
|
+
private postWithAuth;
|
|
276
499
|
private post;
|
|
277
500
|
private get;
|
|
278
|
-
private
|
|
501
|
+
private requestWithHeaders;
|
|
279
502
|
}
|
|
280
|
-
/**
|
|
281
|
-
* Get system resource profile and recommended concurrency settings.
|
|
282
|
-
* Calls the Python backend's system-info detection.
|
|
283
|
-
*/
|
|
284
|
-
declare function systemInfo(): Promise<Record<string, unknown>>;
|
|
285
503
|
|
|
286
504
|
declare const BoostedTravel: typeof LetsFG;
|
|
287
505
|
declare const BoostedTravelError: typeof LetsFGError;
|
|
288
506
|
type BoostedTravelConfig = LetsFGConfig;
|
|
289
507
|
|
|
290
|
-
export { AuthenticationError, type BookingResult, BoostedTravel, type BoostedTravelConfig, BoostedTravelError,
|
|
508
|
+
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 };
|