letsfg 1.6.0 → 1.7.0
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 +2 -2
- package/dist/{chunk-GXEB2JO2.mjs → chunk-RLP7MSF6.mjs} +38 -23
- package/dist/cli.js +87 -23
- package/dist/cli.mjs +1 -1
- package/dist/index.d.mts +5 -2
- package/dist/index.d.ts +5 -2
- package/dist/index.js +38 -23
- package/dist/index.mjs +1 -1
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# LetsFG — Your AI agent just learned to book flights. (Node.js)
|
|
2
2
|
|
|
3
|
-
**
|
|
3
|
+
**200 airlines. Real prices. One function call.** Search 400+ airlines at raw airline prices — **$20–$50 cheaper** than Booking.com, Kayak, and other OTAs. Zero dependencies. Built for AI agents.
|
|
4
4
|
|
|
5
5
|
> **Don't want to install anything?** [**Try it on Messenger**](https://m.me/61579557368989) — search flights instantly, no setup needed.
|
|
6
6
|
|
|
@@ -81,7 +81,7 @@ letsfg book off_xxx -p '{"id":"pas_xxx","given_name":"John",...}' -e john@exampl
|
|
|
81
81
|
|
|
82
82
|
### `searchLocal(origin, destination, dateFrom, options?)`
|
|
83
83
|
|
|
84
|
-
Search
|
|
84
|
+
Search 200 airline connectors locally (no API key needed). Requires Python + `letsfg` installed.
|
|
85
85
|
|
|
86
86
|
```typescript
|
|
87
87
|
import { searchLocal } from 'letsfg';
|
|
@@ -194,7 +194,10 @@ var LetsFG = class {
|
|
|
194
194
|
}
|
|
195
195
|
// ── Core methods ─────────────────────────────────────────────────────
|
|
196
196
|
/**
|
|
197
|
-
* Search for flights — FREE, unlimited.
|
|
197
|
+
* Search for flights — FREE, unlimited, runs locally on your machine.
|
|
198
|
+
*
|
|
199
|
+
* Uses 200 airline connectors via Python subprocess. No backend call.
|
|
200
|
+
* Requires: pip install letsfg && playwright install chromium
|
|
198
201
|
*
|
|
199
202
|
* @param origin - IATA code (e.g., "GDN", "LON")
|
|
200
203
|
* @param destination - IATA code (e.g., "BER", "BCN")
|
|
@@ -202,32 +205,44 @@ var LetsFG = class {
|
|
|
202
205
|
* @param options - Optional search parameters
|
|
203
206
|
*/
|
|
204
207
|
async search(origin, destination, dateFrom, options = {}) {
|
|
205
|
-
|
|
206
|
-
const body = {
|
|
207
|
-
origin: origin.toUpperCase(),
|
|
208
|
-
destination: destination.toUpperCase(),
|
|
209
|
-
date_from: dateFrom,
|
|
210
|
-
adults: options.adults ?? 1,
|
|
211
|
-
children: options.children ?? 0,
|
|
212
|
-
infants: options.infants ?? 0,
|
|
213
|
-
max_stopovers: options.maxStopovers ?? 2,
|
|
214
|
-
currency: options.currency ?? "EUR",
|
|
215
|
-
limit: options.limit ?? 20,
|
|
216
|
-
sort: options.sort ?? "price"
|
|
217
|
-
};
|
|
218
|
-
if (options.returnDate) body.return_from = options.returnDate;
|
|
219
|
-
if (options.cabinClass) body.cabin_class = options.cabinClass;
|
|
220
|
-
return this.post("/api/v1/flights/search", body);
|
|
208
|
+
return searchLocal(origin, destination, dateFrom, options);
|
|
221
209
|
}
|
|
222
210
|
/**
|
|
223
|
-
* Resolve a city/airport name to IATA codes.
|
|
211
|
+
* Resolve a city/airport name to IATA codes — runs locally, no backend call.
|
|
224
212
|
*/
|
|
225
213
|
async resolveLocation(query) {
|
|
226
|
-
|
|
227
|
-
const
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
214
|
+
const { spawn } = await import("child_process");
|
|
215
|
+
const params = JSON.stringify({ __resolve_location: true, query });
|
|
216
|
+
return new Promise((resolve, reject) => {
|
|
217
|
+
const pythonCmd = process.platform === "win32" ? "python" : "python3";
|
|
218
|
+
const child = spawn(pythonCmd, ["-m", "letsfg.local"], {
|
|
219
|
+
stdio: ["pipe", "pipe", "pipe"]
|
|
220
|
+
});
|
|
221
|
+
let stdout = "";
|
|
222
|
+
let stderr = "";
|
|
223
|
+
child.stdout.on("data", (d) => {
|
|
224
|
+
stdout += d.toString();
|
|
225
|
+
});
|
|
226
|
+
child.stderr.on("data", (d) => {
|
|
227
|
+
stderr += d.toString();
|
|
228
|
+
});
|
|
229
|
+
child.on("close", (code) => {
|
|
230
|
+
try {
|
|
231
|
+
const data = JSON.parse(stdout);
|
|
232
|
+
if (data.error) reject(new LetsFGError(data.error));
|
|
233
|
+
else resolve(Array.isArray(data) ? data : data.locations || [data]);
|
|
234
|
+
} catch {
|
|
235
|
+
reject(new LetsFGError(
|
|
236
|
+
`Location resolution failed (code ${code}): ${stdout || stderr}`
|
|
237
|
+
));
|
|
238
|
+
}
|
|
239
|
+
});
|
|
240
|
+
child.on("error", (err) => {
|
|
241
|
+
reject(new LetsFGError(`Cannot start Python: ${err.message}`));
|
|
242
|
+
});
|
|
243
|
+
child.stdin.write(params);
|
|
244
|
+
child.stdin.end();
|
|
245
|
+
});
|
|
231
246
|
}
|
|
232
247
|
/**
|
|
233
248
|
* Unlock a flight offer — FREE with GitHub star.
|
package/dist/cli.js
CHANGED
|
@@ -147,6 +147,55 @@ function offerSummary(offer) {
|
|
|
147
147
|
const airline = offer.owner_airline || offer.airlines[0] || "?";
|
|
148
148
|
return `${offer.currency} ${offer.price.toFixed(2)} | ${airline} | ${route} | ${dur} | ${offer.outbound.stopovers} stop(s)`;
|
|
149
149
|
}
|
|
150
|
+
async function searchLocal(origin, destination, dateFrom, options = {}) {
|
|
151
|
+
const { spawn } = await import("child_process");
|
|
152
|
+
const params = JSON.stringify({
|
|
153
|
+
origin: origin.toUpperCase(),
|
|
154
|
+
destination: destination.toUpperCase(),
|
|
155
|
+
date_from: dateFrom,
|
|
156
|
+
adults: options.adults ?? 1,
|
|
157
|
+
children: options.children ?? 0,
|
|
158
|
+
currency: options.currency ?? "EUR",
|
|
159
|
+
limit: options.limit ?? 50,
|
|
160
|
+
return_date: options.returnDate,
|
|
161
|
+
cabin_class: options.cabinClass,
|
|
162
|
+
...options.maxBrowsers != null && { max_browsers: options.maxBrowsers }
|
|
163
|
+
});
|
|
164
|
+
return new Promise((resolve, reject) => {
|
|
165
|
+
const pythonCmd = process.platform === "win32" ? "python" : "python3";
|
|
166
|
+
const child = spawn(pythonCmd, ["-m", "letsfg.local"], {
|
|
167
|
+
stdio: ["pipe", "pipe", "pipe"]
|
|
168
|
+
});
|
|
169
|
+
let stdout = "";
|
|
170
|
+
let stderr = "";
|
|
171
|
+
child.stdout.on("data", (d) => {
|
|
172
|
+
stdout += d.toString();
|
|
173
|
+
});
|
|
174
|
+
child.stderr.on("data", (d) => {
|
|
175
|
+
stderr += d.toString();
|
|
176
|
+
});
|
|
177
|
+
child.on("close", (code) => {
|
|
178
|
+
try {
|
|
179
|
+
const data = JSON.parse(stdout);
|
|
180
|
+
if (data.error) reject(new LetsFGError(data.error));
|
|
181
|
+
else resolve(data);
|
|
182
|
+
} catch {
|
|
183
|
+
reject(new LetsFGError(
|
|
184
|
+
`Python search failed (code ${code}): ${stdout || stderr}
|
|
185
|
+
Make sure LetsFG is installed: pip install letsfg && playwright install chromium`
|
|
186
|
+
));
|
|
187
|
+
}
|
|
188
|
+
});
|
|
189
|
+
child.on("error", (err) => {
|
|
190
|
+
reject(new LetsFGError(
|
|
191
|
+
`Cannot start Python: ${err.message}
|
|
192
|
+
Install: pip install letsfg && playwright install chromium`
|
|
193
|
+
));
|
|
194
|
+
});
|
|
195
|
+
child.stdin.write(params);
|
|
196
|
+
child.stdin.end();
|
|
197
|
+
});
|
|
198
|
+
}
|
|
150
199
|
var DEFAULT_BASE_URL = "https://api.letsfg.co";
|
|
151
200
|
var LetsFG = class {
|
|
152
201
|
apiKey;
|
|
@@ -166,7 +215,10 @@ var LetsFG = class {
|
|
|
166
215
|
}
|
|
167
216
|
// ── Core methods ─────────────────────────────────────────────────────
|
|
168
217
|
/**
|
|
169
|
-
* Search for flights — FREE, unlimited.
|
|
218
|
+
* Search for flights — FREE, unlimited, runs locally on your machine.
|
|
219
|
+
*
|
|
220
|
+
* Uses 200 airline connectors via Python subprocess. No backend call.
|
|
221
|
+
* Requires: pip install letsfg && playwright install chromium
|
|
170
222
|
*
|
|
171
223
|
* @param origin - IATA code (e.g., "GDN", "LON")
|
|
172
224
|
* @param destination - IATA code (e.g., "BER", "BCN")
|
|
@@ -174,32 +226,44 @@ var LetsFG = class {
|
|
|
174
226
|
* @param options - Optional search parameters
|
|
175
227
|
*/
|
|
176
228
|
async search(origin, destination, dateFrom, options = {}) {
|
|
177
|
-
|
|
178
|
-
const body = {
|
|
179
|
-
origin: origin.toUpperCase(),
|
|
180
|
-
destination: destination.toUpperCase(),
|
|
181
|
-
date_from: dateFrom,
|
|
182
|
-
adults: options.adults ?? 1,
|
|
183
|
-
children: options.children ?? 0,
|
|
184
|
-
infants: options.infants ?? 0,
|
|
185
|
-
max_stopovers: options.maxStopovers ?? 2,
|
|
186
|
-
currency: options.currency ?? "EUR",
|
|
187
|
-
limit: options.limit ?? 20,
|
|
188
|
-
sort: options.sort ?? "price"
|
|
189
|
-
};
|
|
190
|
-
if (options.returnDate) body.return_from = options.returnDate;
|
|
191
|
-
if (options.cabinClass) body.cabin_class = options.cabinClass;
|
|
192
|
-
return this.post("/api/v1/flights/search", body);
|
|
229
|
+
return searchLocal(origin, destination, dateFrom, options);
|
|
193
230
|
}
|
|
194
231
|
/**
|
|
195
|
-
* Resolve a city/airport name to IATA codes.
|
|
232
|
+
* Resolve a city/airport name to IATA codes — runs locally, no backend call.
|
|
196
233
|
*/
|
|
197
234
|
async resolveLocation(query) {
|
|
198
|
-
|
|
199
|
-
const
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
235
|
+
const { spawn } = await import("child_process");
|
|
236
|
+
const params = JSON.stringify({ __resolve_location: true, query });
|
|
237
|
+
return new Promise((resolve, reject) => {
|
|
238
|
+
const pythonCmd = process.platform === "win32" ? "python" : "python3";
|
|
239
|
+
const child = spawn(pythonCmd, ["-m", "letsfg.local"], {
|
|
240
|
+
stdio: ["pipe", "pipe", "pipe"]
|
|
241
|
+
});
|
|
242
|
+
let stdout = "";
|
|
243
|
+
let stderr = "";
|
|
244
|
+
child.stdout.on("data", (d) => {
|
|
245
|
+
stdout += d.toString();
|
|
246
|
+
});
|
|
247
|
+
child.stderr.on("data", (d) => {
|
|
248
|
+
stderr += d.toString();
|
|
249
|
+
});
|
|
250
|
+
child.on("close", (code) => {
|
|
251
|
+
try {
|
|
252
|
+
const data = JSON.parse(stdout);
|
|
253
|
+
if (data.error) reject(new LetsFGError(data.error));
|
|
254
|
+
else resolve(Array.isArray(data) ? data : data.locations || [data]);
|
|
255
|
+
} catch {
|
|
256
|
+
reject(new LetsFGError(
|
|
257
|
+
`Location resolution failed (code ${code}): ${stdout || stderr}`
|
|
258
|
+
));
|
|
259
|
+
}
|
|
260
|
+
});
|
|
261
|
+
child.on("error", (err) => {
|
|
262
|
+
reject(new LetsFGError(`Cannot start Python: ${err.message}`));
|
|
263
|
+
});
|
|
264
|
+
child.stdin.write(params);
|
|
265
|
+
child.stdin.end();
|
|
266
|
+
});
|
|
203
267
|
}
|
|
204
268
|
/**
|
|
205
269
|
* Unlock a flight offer — FREE with GitHub star.
|
package/dist/cli.mjs
CHANGED
package/dist/index.d.mts
CHANGED
|
@@ -205,7 +205,10 @@ declare class LetsFG {
|
|
|
205
205
|
constructor(config?: LetsFGConfig);
|
|
206
206
|
private requireApiKey;
|
|
207
207
|
/**
|
|
208
|
-
* Search for flights — FREE, unlimited.
|
|
208
|
+
* Search for flights — FREE, unlimited, runs locally on your machine.
|
|
209
|
+
*
|
|
210
|
+
* Uses 200 airline connectors via Python subprocess. No backend call.
|
|
211
|
+
* Requires: pip install letsfg && playwright install chromium
|
|
209
212
|
*
|
|
210
213
|
* @param origin - IATA code (e.g., "GDN", "LON")
|
|
211
214
|
* @param destination - IATA code (e.g., "BER", "BCN")
|
|
@@ -214,7 +217,7 @@ declare class LetsFG {
|
|
|
214
217
|
*/
|
|
215
218
|
search(origin: string, destination: string, dateFrom: string, options?: SearchOptions): Promise<FlightSearchResult>;
|
|
216
219
|
/**
|
|
217
|
-
* Resolve a city/airport name to IATA codes.
|
|
220
|
+
* Resolve a city/airport name to IATA codes — runs locally, no backend call.
|
|
218
221
|
*/
|
|
219
222
|
resolveLocation(query: string): Promise<Array<Record<string, unknown>>>;
|
|
220
223
|
/**
|
package/dist/index.d.ts
CHANGED
|
@@ -205,7 +205,10 @@ declare class LetsFG {
|
|
|
205
205
|
constructor(config?: LetsFGConfig);
|
|
206
206
|
private requireApiKey;
|
|
207
207
|
/**
|
|
208
|
-
* Search for flights — FREE, unlimited.
|
|
208
|
+
* Search for flights — FREE, unlimited, runs locally on your machine.
|
|
209
|
+
*
|
|
210
|
+
* Uses 200 airline connectors via Python subprocess. No backend call.
|
|
211
|
+
* Requires: pip install letsfg && playwright install chromium
|
|
209
212
|
*
|
|
210
213
|
* @param origin - IATA code (e.g., "GDN", "LON")
|
|
211
214
|
* @param destination - IATA code (e.g., "BER", "BCN")
|
|
@@ -214,7 +217,7 @@ declare class LetsFG {
|
|
|
214
217
|
*/
|
|
215
218
|
search(origin: string, destination: string, dateFrom: string, options?: SearchOptions): Promise<FlightSearchResult>;
|
|
216
219
|
/**
|
|
217
|
-
* Resolve a city/airport name to IATA codes.
|
|
220
|
+
* Resolve a city/airport name to IATA codes — runs locally, no backend call.
|
|
218
221
|
*/
|
|
219
222
|
resolveLocation(query: string): Promise<Array<Record<string, unknown>>>;
|
|
220
223
|
/**
|
package/dist/index.js
CHANGED
|
@@ -244,7 +244,10 @@ var LetsFG = class {
|
|
|
244
244
|
}
|
|
245
245
|
// ── Core methods ─────────────────────────────────────────────────────
|
|
246
246
|
/**
|
|
247
|
-
* Search for flights — FREE, unlimited.
|
|
247
|
+
* Search for flights — FREE, unlimited, runs locally on your machine.
|
|
248
|
+
*
|
|
249
|
+
* Uses 200 airline connectors via Python subprocess. No backend call.
|
|
250
|
+
* Requires: pip install letsfg && playwright install chromium
|
|
248
251
|
*
|
|
249
252
|
* @param origin - IATA code (e.g., "GDN", "LON")
|
|
250
253
|
* @param destination - IATA code (e.g., "BER", "BCN")
|
|
@@ -252,32 +255,44 @@ var LetsFG = class {
|
|
|
252
255
|
* @param options - Optional search parameters
|
|
253
256
|
*/
|
|
254
257
|
async search(origin, destination, dateFrom, options = {}) {
|
|
255
|
-
|
|
256
|
-
const body = {
|
|
257
|
-
origin: origin.toUpperCase(),
|
|
258
|
-
destination: destination.toUpperCase(),
|
|
259
|
-
date_from: dateFrom,
|
|
260
|
-
adults: options.adults ?? 1,
|
|
261
|
-
children: options.children ?? 0,
|
|
262
|
-
infants: options.infants ?? 0,
|
|
263
|
-
max_stopovers: options.maxStopovers ?? 2,
|
|
264
|
-
currency: options.currency ?? "EUR",
|
|
265
|
-
limit: options.limit ?? 20,
|
|
266
|
-
sort: options.sort ?? "price"
|
|
267
|
-
};
|
|
268
|
-
if (options.returnDate) body.return_from = options.returnDate;
|
|
269
|
-
if (options.cabinClass) body.cabin_class = options.cabinClass;
|
|
270
|
-
return this.post("/api/v1/flights/search", body);
|
|
258
|
+
return searchLocal(origin, destination, dateFrom, options);
|
|
271
259
|
}
|
|
272
260
|
/**
|
|
273
|
-
* Resolve a city/airport name to IATA codes.
|
|
261
|
+
* Resolve a city/airport name to IATA codes — runs locally, no backend call.
|
|
274
262
|
*/
|
|
275
263
|
async resolveLocation(query) {
|
|
276
|
-
|
|
277
|
-
const
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
264
|
+
const { spawn } = await import("child_process");
|
|
265
|
+
const params = JSON.stringify({ __resolve_location: true, query });
|
|
266
|
+
return new Promise((resolve, reject) => {
|
|
267
|
+
const pythonCmd = process.platform === "win32" ? "python" : "python3";
|
|
268
|
+
const child = spawn(pythonCmd, ["-m", "letsfg.local"], {
|
|
269
|
+
stdio: ["pipe", "pipe", "pipe"]
|
|
270
|
+
});
|
|
271
|
+
let stdout = "";
|
|
272
|
+
let stderr = "";
|
|
273
|
+
child.stdout.on("data", (d) => {
|
|
274
|
+
stdout += d.toString();
|
|
275
|
+
});
|
|
276
|
+
child.stderr.on("data", (d) => {
|
|
277
|
+
stderr += d.toString();
|
|
278
|
+
});
|
|
279
|
+
child.on("close", (code) => {
|
|
280
|
+
try {
|
|
281
|
+
const data = JSON.parse(stdout);
|
|
282
|
+
if (data.error) reject(new LetsFGError(data.error));
|
|
283
|
+
else resolve(Array.isArray(data) ? data : data.locations || [data]);
|
|
284
|
+
} catch {
|
|
285
|
+
reject(new LetsFGError(
|
|
286
|
+
`Location resolution failed (code ${code}): ${stdout || stderr}`
|
|
287
|
+
));
|
|
288
|
+
}
|
|
289
|
+
});
|
|
290
|
+
child.on("error", (err) => {
|
|
291
|
+
reject(new LetsFGError(`Cannot start Python: ${err.message}`));
|
|
292
|
+
});
|
|
293
|
+
child.stdin.write(params);
|
|
294
|
+
child.stdin.end();
|
|
295
|
+
});
|
|
281
296
|
}
|
|
282
297
|
/**
|
|
283
298
|
* Unlock a flight offer — FREE with GitHub star.
|
package/dist/index.mjs
CHANGED
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "letsfg",
|
|
3
|
-
"version": "1.
|
|
4
|
-
"description": "Agent-native flight search & booking.
|
|
3
|
+
"version": "1.7.0",
|
|
4
|
+
"description": "Agent-native flight search & booking. 200 airline connectors run locally + enterprise GDS/NDC APIs. Built for autonomous AI agents.",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"module": "dist/index.mjs",
|
|
7
7
|
"types": "dist/index.d.ts",
|