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 CHANGED
@@ -1,6 +1,6 @@
1
1
  # LetsFG — Your AI agent just learned to book flights. (Node.js)
2
2
 
3
- **195 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.
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 195 airline connectors locally (no API key needed). Requires Python + `letsfg` installed.
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
- this.requireApiKey();
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
- this.requireApiKey();
227
- const data = await this.get(`/api/v1/flights/locations/${encodeURIComponent(query)}`);
228
- if (Array.isArray(data)) return data;
229
- if (data && Array.isArray(data.locations)) return data.locations;
230
- return data ? [data] : [];
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
- this.requireApiKey();
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
- this.requireApiKey();
199
- const data = await this.get(`/api/v1/flights/locations/${encodeURIComponent(query)}`);
200
- if (Array.isArray(data)) return data;
201
- if (data && Array.isArray(data.locations)) return data.locations;
202
- return data ? [data] : [];
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
@@ -3,7 +3,7 @@ import {
3
3
  LetsFG,
4
4
  LetsFGError,
5
5
  offerSummary
6
- } from "./chunk-GXEB2JO2.mjs";
6
+ } from "./chunk-RLP7MSF6.mjs";
7
7
 
8
8
  // src/cli.ts
9
9
  function getFlag(args, flag, alias) {
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
- this.requireApiKey();
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
- this.requireApiKey();
277
- const data = await this.get(`/api/v1/flights/locations/${encodeURIComponent(query)}`);
278
- if (Array.isArray(data)) return data;
279
- if (data && Array.isArray(data.locations)) return data.locations;
280
- return data ? [data] : [];
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
@@ -14,7 +14,7 @@ import {
14
14
  offerSummary,
15
15
  searchLocal,
16
16
  systemInfo
17
- } from "./chunk-GXEB2JO2.mjs";
17
+ } from "./chunk-RLP7MSF6.mjs";
18
18
  export {
19
19
  AuthenticationError,
20
20
  BoostedTravel,
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "letsfg",
3
- "version": "1.6.0",
4
- "description": "Agent-native flight search & booking. 195 airline connectors run locally + enterprise GDS/NDC APIs. Built for autonomous AI agents.",
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",