letsfg 2026.5.70 → 2026.5.72

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/cli.mjs CHANGED
@@ -3,12 +3,213 @@ import {
3
3
  LetsFG,
4
4
  LetsFGError,
5
5
  offerSummary
6
- } from "./chunk-SYNHNDEL.mjs";
7
- import {
8
- BearerTokenError,
9
- getBearerToken,
10
- paymentAuth
11
- } from "./chunk-XNO2W77S.mjs";
6
+ } from "./chunk-GO3FXQXC.mjs";
7
+
8
+ // src/auth.ts
9
+ import { readFileSync, writeFileSync, mkdirSync, chmodSync, existsSync } from "fs";
10
+ import { homedir, platform } from "os";
11
+ import { join, dirname } from "path";
12
+ import { createHash, randomBytes } from "crypto";
13
+ import { createServer } from "http";
14
+ var BASE_URL = process.env.LETSFG_BASE_URL || "https://letsfg.co";
15
+ var DEV_ROOT = `${BASE_URL}/developers/api`;
16
+ var FALLBACK_TTL_MS = 55 * 60 * 1e3;
17
+ var REFRESH_SKEW_MS = 5 * 60 * 1e3;
18
+ var BearerTokenError = class extends Error {
19
+ };
20
+ function configPath() {
21
+ const base = platform() === "win32" ? process.env.APPDATA || homedir() : homedir();
22
+ return join(base, ".letsfg", "config.json");
23
+ }
24
+ function loadConfig() {
25
+ const p = configPath();
26
+ if (!existsSync(p)) return {};
27
+ try {
28
+ return JSON.parse(readFileSync(p, "utf-8"));
29
+ } catch {
30
+ return {};
31
+ }
32
+ }
33
+ function saveConfig(cfg) {
34
+ const p = configPath();
35
+ mkdirSync(dirname(p), { recursive: true });
36
+ writeFileSync(p, JSON.stringify(cfg, null, 2));
37
+ try {
38
+ chmodSync(p, 384);
39
+ } catch {
40
+ }
41
+ }
42
+ function saveToken(token, expiresAt, extra) {
43
+ const cfg = loadConfig();
44
+ cfg.pfs_auth = {
45
+ ...cfg.pfs_auth ?? {},
46
+ ...extra ?? {},
47
+ token,
48
+ expires_at: expiresAt ?? Date.now() + FALLBACK_TTL_MS
49
+ };
50
+ saveConfig(cfg);
51
+ }
52
+ function getBearerToken() {
53
+ const env = process.env.LETSFG_BEARER_TOKEN;
54
+ if (env) return env;
55
+ const auth = loadConfig().pfs_auth;
56
+ if (auth?.token && Date.now() < auth.expires_at - REFRESH_SKEW_MS) {
57
+ return auth.token;
58
+ }
59
+ if (auth?.refresh_token) {
60
+ throw new BearerTokenError(
61
+ "LetsFG token expired. Call ensureBearerToken() to refresh it, or run: letsfg auth"
62
+ );
63
+ }
64
+ throw new BearerTokenError(
65
+ "No valid LetsFG Bearer token.\n Run: letsfg auth (connects a card at letsfg.co/connect - nothing is charged)\n Or: export LETSFG_BEARER_TOKEN=<token>"
66
+ );
67
+ }
68
+ async function discover() {
69
+ const fallback = {
70
+ authorization_endpoint: `${BASE_URL}/connect`,
71
+ token_endpoint: `${DEV_ROOT}/oauth/token`,
72
+ registration_endpoint: `${DEV_ROOT}/oauth/register`
73
+ };
74
+ try {
75
+ const resp = await fetch(`${DEV_ROOT}/.well-known/oauth-authorization-server`);
76
+ if (!resp.ok) return fallback;
77
+ const d = await resp.json();
78
+ return {
79
+ authorization_endpoint: d.authorization_endpoint || fallback.authorization_endpoint,
80
+ token_endpoint: d.token_endpoint || fallback.token_endpoint,
81
+ registration_endpoint: d.registration_endpoint || fallback.registration_endpoint
82
+ };
83
+ } catch {
84
+ return fallback;
85
+ }
86
+ }
87
+ function pkce() {
88
+ const verifier = randomBytes(32).toString("base64url");
89
+ const challenge = createHash("sha256").update(verifier).digest("base64url");
90
+ return { verifier, challenge };
91
+ }
92
+ async function postForm(url, form) {
93
+ const resp = await fetch(url, {
94
+ method: "POST",
95
+ // RFC 6749 4.1.3 - the token endpoint takes form encoding. The server also
96
+ // accepts JSON, but sending the spec encoding keeps this honest.
97
+ headers: { "Content-Type": "application/x-www-form-urlencoded", "X-Client-Type": "js-sdk" },
98
+ body: new URLSearchParams(form).toString()
99
+ });
100
+ const data = await resp.json().catch(() => ({}));
101
+ return { status: resp.status, data };
102
+ }
103
+ function openBrowser(url) {
104
+ import("child_process").then(({ exec }) => {
105
+ const cmd = platform() === "win32" ? `start "" "${url}"` : platform() === "darwin" ? `open "${url}"` : `xdg-open "${url}"`;
106
+ exec(cmd, () => {
107
+ });
108
+ }).catch(() => {
109
+ });
110
+ }
111
+ function startCallbackServer(expectedState) {
112
+ return new Promise((resolve, reject) => {
113
+ let settle = null;
114
+ let fail = null;
115
+ const codePromise = new Promise((res, rej) => {
116
+ settle = res;
117
+ fail = rej;
118
+ });
119
+ const server = createServer((req, res) => {
120
+ const url = new URL(req.url || "/", "http://127.0.0.1");
121
+ if (!url.pathname.startsWith("/callback")) {
122
+ res.writeHead(404).end();
123
+ return;
124
+ }
125
+ const err = url.searchParams.get("error");
126
+ const code = url.searchParams.get("code");
127
+ const state = url.searchParams.get("state");
128
+ res.writeHead(200, { "Content-Type": "text/html; charset=utf-8" });
129
+ if (err || !code) {
130
+ res.end("<h1>LetsFG</h1><p>That did not complete. Nothing was charged - run <code>letsfg auth</code> again.</p>");
131
+ fail?.(new BearerTokenError(`Authorisation failed: ${err || "no code returned"}`));
132
+ return;
133
+ }
134
+ if (state !== expectedState) {
135
+ res.end("<h1>LetsFG</h1><p>State mismatch - nothing was charged. Run <code>letsfg auth</code> again.</p>");
136
+ fail?.(new BearerTokenError("State mismatch on the OAuth callback - aborted."));
137
+ return;
138
+ }
139
+ res.end("<h1>LetsFG</h1><p>Connected. You can close this tab and go back to your terminal.</p>");
140
+ settle?.(code);
141
+ });
142
+ server.on("error", reject);
143
+ server.listen(0, "127.0.0.1", () => {
144
+ const addr = server.address();
145
+ const port = typeof addr === "object" && addr ? addr.port : 0;
146
+ if (!port) {
147
+ reject(new BearerTokenError("Could not open a local callback port."));
148
+ return;
149
+ }
150
+ resolve({ port, waitForCode: () => codePromise, close: () => server.close() });
151
+ });
152
+ });
153
+ }
154
+ async function connectAuth(openBrowserFlag = true) {
155
+ const meta = await discover();
156
+ const { verifier, challenge } = pkce();
157
+ const state = randomBytes(16).toString("base64url");
158
+ const { port, waitForCode, close } = await startCallbackServer(state);
159
+ const redirectUri = `http://127.0.0.1:${port}/callback`;
160
+ try {
161
+ const reg = await fetch(meta.registration_endpoint, {
162
+ method: "POST",
163
+ headers: { "Content-Type": "application/json" },
164
+ body: JSON.stringify({
165
+ client_name: "letsfg-cli",
166
+ redirect_uris: [redirectUri],
167
+ grant_types: ["authorization_code", "refresh_token"],
168
+ response_types: ["code"],
169
+ token_endpoint_auth_method: "none"
170
+ })
171
+ });
172
+ const regData = await reg.json().catch(() => ({}));
173
+ const clientId = typeof regData.client_id === "string" ? regData.client_id : "";
174
+ if (!reg.ok || !clientId) {
175
+ throw new BearerTokenError(
176
+ `Could not register with LetsFG (HTTP ${reg.status}). See ${BASE_URL}/for-agents`
177
+ );
178
+ }
179
+ const authUrl = `${meta.authorization_endpoint}?response_type=code&client_id=${encodeURIComponent(clientId)}&redirect_uri=${encodeURIComponent(redirectUri)}&code_challenge=${challenge}&code_challenge_method=S256&state=${encodeURIComponent(state)}&scope=flights`;
180
+ console.log("\n LetsFG needs a card connected before it can search or book.");
181
+ console.log(" Nothing is charged now - you pay the fare only when you book,");
182
+ console.log(" and it is held, not taken, until the airline confirms.\n");
183
+ console.log(" Open this and add a card (or pay 0.00 with Revolut Pay):\n");
184
+ console.log(` ${authUrl}
185
+ `);
186
+ if (openBrowserFlag) openBrowser(authUrl);
187
+ process.stdout.write(" Waiting for you to finish... ");
188
+ const code = await waitForCode();
189
+ const { status, data } = await postForm(meta.token_endpoint, {
190
+ grant_type: "authorization_code",
191
+ code,
192
+ client_id: clientId,
193
+ redirect_uri: redirectUri,
194
+ code_verifier: verifier
195
+ });
196
+ if (status !== 200 || !data.access_token) {
197
+ throw new BearerTokenError(
198
+ `Could not complete authentication (HTTP ${status}). ${String(data.error_description || data.error || "")}`.trim()
199
+ );
200
+ }
201
+ const expiresIn = typeof data.expires_in === "number" ? data.expires_in * 1e3 : FALLBACK_TTL_MS;
202
+ saveToken(String(data.access_token), Date.now() + expiresIn, {
203
+ refresh_token: typeof data.refresh_token === "string" ? data.refresh_token : void 0,
204
+ client_id: clientId
205
+ });
206
+ console.log("done. Card connected - the token refreshes itself from now on.");
207
+ return String(data.access_token);
208
+ } finally {
209
+ close();
210
+ }
211
+ }
212
+ var paymentAuth = connectAuth;
12
213
 
13
214
  // src/cli.ts
14
215
  function resolveCredentials(apiKeyFlag) {
@@ -246,17 +447,19 @@ async function cmdLocations(args) {
246
447
  }
247
448
  }
248
449
  async function cmdAuth(args) {
249
- const cardToken = getFlag(args, "--card-token");
250
- const paymentMethodId = getFlag(args, "--payment-method");
251
- const noBrowser = hasFlag(args, "--no-browser");
252
- if (cardToken || paymentMethodId) {
253
- const { verifyPaymentMethod } = await import("./auth-SYZ5AYZ6.mjs");
254
- await verifyPaymentMethod({ cardToken: cardToken || void 0, paymentMethodId: paymentMethodId || void 0 });
255
- console.log("\n \u2713 Authenticated. Nothing was charged.");
256
- } else {
257
- await paymentAuth(!noBrowser);
450
+ for (const dead of ["--card-token", "--payment-method"]) {
451
+ if (getFlag(args, dead)) {
452
+ console.error(
453
+ `
454
+ ${dead} was part of the Stripe enrolment, retired 2026-09-02.
455
+ Run plain \`letsfg auth\` instead - it opens letsfg.co/connect to add a card.
456
+ `
457
+ );
458
+ process.exit(1);
459
+ }
258
460
  }
259
- console.log("\n You're all set. Run: letsfg search WAW BCN 2026-07-15\n");
461
+ await paymentAuth(!hasFlag(args, "--no-browser"));
462
+ console.log("\n You are all set. Run: letsfg search WAW BCN 2026-07-15\n");
260
463
  }
261
464
  async function cmdRegister(args) {
262
465
  const jsonOut = hasFlag(args, "--json") || hasFlag(args, "-j");
@@ -334,11 +537,12 @@ var HELP = `
334
537
  LetsFG \u2014 Agent-native flight search & booking.
335
538
 
336
539
  Search hundreds of airlines via the LetsFG cloud engine.
337
- Authenticate once with letsfg auth \u2014 a zero-amount card setup, nothing is
338
- charged \u2014 then search and book.
540
+ Authenticate once with letsfg auth \u2014 it opens letsfg.co/connect so you can add
541
+ a card. Nothing is charged; you pay the fare only when you book, and it is held,
542
+ not taken, until the airline confirms.
339
543
 
340
544
  Commands:
341
- auth Put a card on file -> 90-day token. Nothing charged
545
+ auth Connect a card at letsfg.co/connect. Nothing charged
342
546
  search <origin> <dest> <date> Search for flights (free), prints search_id
343
547
  locations <query> Resolve city name to IATA codes
344
548
  book <offer_id> --search-id ... Book a flight. No LetsFG fee, no unlock step
@@ -348,15 +552,13 @@ Developer API only (a SEPARATE paid product \u2014 most agents should not use th
348
552
  they create a billing account. Use auth above instead):
349
553
  register --name ... --email ... Create a paid Developer API account
350
554
  setup-payment Attach a card to that paid account
351
- unlock <offer_id> [Developer API only] Unlock offer \u2014 1% of ticket (min $3)
555
+ unlock <offer_id> [Developer API only] Unlock offer (legacy)
352
556
 
353
557
  Options:
354
558
  --json, -j Output raw JSON
355
559
  --api-key, -k Developer API key (or set LETSFG_API_KEY) \u2014 switches book/search to the paid path
356
560
  --base-url API URL (default: https://letsfg.co)
357
- --card-token (auth only) Stripe tok_... you already hold, for a headless auth
358
- --payment-method (auth only) Stripe pm_... you already hold, for a headless auth
359
- --no-browser (auth only) Don't try to auto-open the card setup page
561
+ --no-browser (auth only) Print the connect URL instead of opening a browser
360
562
 
361
563
  Examples:
362
564
  letsfg auth
package/dist/index.d.mts CHANGED
@@ -283,7 +283,7 @@ declare function getOfferDetailPromptNotes(offer: OfferDetailLike): string[];
283
283
  * const flights = await bt.search('GDN', 'BER', '2026-03-03');
284
284
  *
285
285
  * // Developer API (prepaid credits)
286
- * const bt2 = new LetsFG({ apiKey: 'trav_...' });
286
+ * const bt2 = new LetsFG({ apiKey: 'letsfg_...' });
287
287
  * const flights2 = await bt2.search('LHR', 'JFK', '2026-04-15');
288
288
  * ```
289
289
  */
@@ -473,7 +473,7 @@ declare class LetsFG {
473
473
  *
474
474
  * Uses PFS (Bearer token) or Developer API (X-API-Key) depending on config.
475
475
  * PFS: async polling (POST /api/search -> poll /api/results/<id> every 10s).
476
- * Developer API: synchronous 60-90s call.
476
+ * Developer API: synchronous call.
477
477
  *
478
478
  * @param origin - IATA code (e.g., "GDN", "LON")
479
479
  * @param destination - IATA code (e.g., "BER", "BCN")
@@ -496,8 +496,8 @@ declare class LetsFG {
496
496
  resolveLocation(query: string): Promise<Array<Record<string, unknown>>>;
497
497
  /**
498
498
  * Unlock a flight offer — confirms live price, reveals direct airline booking URL.
499
- * Cost: 1% of ticket price, min $3. Developer API only — there is no unlock
500
- * endpoint on a PFS Bearer token, so PFS callers use book() directly.
499
+ * Developer API only, legacy — there is no unlock endpoint on a PFS Bearer
500
+ * token, so PFS callers use book() directly.
501
501
  */
502
502
  unlock(offerId: string): Promise<UnlockResult>;
503
503
  /**
package/dist/index.d.ts CHANGED
@@ -283,7 +283,7 @@ declare function getOfferDetailPromptNotes(offer: OfferDetailLike): string[];
283
283
  * const flights = await bt.search('GDN', 'BER', '2026-03-03');
284
284
  *
285
285
  * // Developer API (prepaid credits)
286
- * const bt2 = new LetsFG({ apiKey: 'trav_...' });
286
+ * const bt2 = new LetsFG({ apiKey: 'letsfg_...' });
287
287
  * const flights2 = await bt2.search('LHR', 'JFK', '2026-04-15');
288
288
  * ```
289
289
  */
@@ -473,7 +473,7 @@ declare class LetsFG {
473
473
  *
474
474
  * Uses PFS (Bearer token) or Developer API (X-API-Key) depending on config.
475
475
  * PFS: async polling (POST /api/search -> poll /api/results/<id> every 10s).
476
- * Developer API: synchronous 60-90s call.
476
+ * Developer API: synchronous call.
477
477
  *
478
478
  * @param origin - IATA code (e.g., "GDN", "LON")
479
479
  * @param destination - IATA code (e.g., "BER", "BCN")
@@ -496,8 +496,8 @@ declare class LetsFG {
496
496
  resolveLocation(query: string): Promise<Array<Record<string, unknown>>>;
497
497
  /**
498
498
  * Unlock a flight offer — confirms live price, reveals direct airline booking URL.
499
- * Cost: 1% of ticket price, min $3. Developer API only — there is no unlock
500
- * endpoint on a PFS Bearer token, so PFS callers use book() directly.
499
+ * Developer API only, legacy — there is no unlock endpoint on a PFS Bearer
500
+ * token, so PFS callers use book() directly.
501
501
  */
502
502
  unlock(offerId: string): Promise<UnlockResult>;
503
503
  /**
package/dist/index.js CHANGED
@@ -1606,8 +1606,12 @@ function cheapestOffer(result) {
1606
1606
  return result.offers.reduce((min, o) => o.price < min.price ? o : min, result.offers[0]);
1607
1607
  }
1608
1608
  var DEFAULT_BASE_URL = "https://letsfg.co";
1609
- var PFS_POLL_INTERVAL_MS = 1e4;
1609
+ var PFS_POLL_INTERVAL_MS = 2e3;
1610
1610
  var PFS_POLL_TIMEOUT_MS = 12e4;
1611
+ var LATE_MERGE_POLL_MS = 3e3;
1612
+ var LATE_MERGE_GRACE_MS = 9e4;
1613
+ var WAIT_FOR_SPLIT = (process.env.LETSFG_WAIT_FOR_SPLIT || "").trim() !== "0";
1614
+ var NON_TERMINAL = ["pending", "searching"];
1611
1615
  var LetsFG = class {
1612
1616
  bearerToken;
1613
1617
  apiKey;
@@ -1643,7 +1647,7 @@ var LetsFG = class {
1643
1647
  *
1644
1648
  * Uses PFS (Bearer token) or Developer API (X-API-Key) depending on config.
1645
1649
  * PFS: async polling (POST /api/search -> poll /api/results/<id> every 10s).
1646
- * Developer API: synchronous 60-90s call.
1650
+ * Developer API: synchronous call.
1647
1651
  *
1648
1652
  * @param origin - IATA code (e.g., "GDN", "LON")
1649
1653
  * @param destination - IATA code (e.g., "BER", "BCN")
@@ -1675,17 +1679,28 @@ var LetsFG = class {
1675
1679
  /** PFS path: POST /api/search -> poll /api/results/<id> */
1676
1680
  async searchPFS(body) {
1677
1681
  const { search_id } = await this.postWithBearer("/api/search", body);
1682
+ const poll = () => this.getNoAuth(`/api/results/${search_id}`);
1683
+ const inbound = (r) => Boolean(r.split_ticket_pending || r.gf_enrich_pending);
1678
1684
  const deadline = Date.now() + PFS_POLL_TIMEOUT_MS;
1685
+ let terminal = null;
1679
1686
  while (Date.now() < deadline) {
1680
- await new Promise((r) => setTimeout(r, PFS_POLL_INTERVAL_MS));
1681
- const result = await this.getNoAuth(
1682
- `/api/results/${search_id}`
1683
- );
1684
- if (!["pending", "searching"].includes(result.status)) {
1685
- return result;
1687
+ const result = await poll();
1688
+ if (!NON_TERMINAL.includes(result.status)) {
1689
+ terminal = result;
1690
+ break;
1686
1691
  }
1692
+ await new Promise((r) => setTimeout(r, PFS_POLL_INTERVAL_MS));
1693
+ }
1694
+ if (!terminal) {
1695
+ throw new LetsFGError("Search timed out after 120s. Try polling /api/results/<id> directly.", 504);
1696
+ }
1697
+ const lateDeadline = Date.now() + LATE_MERGE_GRACE_MS;
1698
+ while (WAIT_FOR_SPLIT && inbound(terminal) && Date.now() < lateDeadline) {
1699
+ await new Promise((r) => setTimeout(r, LATE_MERGE_POLL_MS));
1700
+ const merged = await poll();
1701
+ if (!NON_TERMINAL.includes(merged.status)) terminal = merged;
1687
1702
  }
1688
- throw new LetsFGError("Search timed out after 120s. Try polling /api/results/<id> directly.", 504);
1703
+ return terminal;
1689
1704
  }
1690
1705
  /**
1691
1706
  * Resolve a city/airport name to IATA codes.
@@ -1705,8 +1720,8 @@ var LetsFG = class {
1705
1720
  }
1706
1721
  /**
1707
1722
  * Unlock a flight offer — confirms live price, reveals direct airline booking URL.
1708
- * Cost: 1% of ticket price, min $3. Developer API only — there is no unlock
1709
- * endpoint on a PFS Bearer token, so PFS callers use book() directly.
1723
+ * Developer API only, legacy — there is no unlock endpoint on a PFS Bearer
1724
+ * token, so PFS callers use book() directly.
1710
1725
  */
1711
1726
  async unlock(offerId) {
1712
1727
  this.requireApiKey();
package/dist/index.mjs CHANGED
@@ -22,7 +22,7 @@ import {
22
22
  offerSummary,
23
23
  rankOffers,
24
24
  selectDiverseTop
25
- } from "./chunk-SYNHNDEL.mjs";
25
+ } from "./chunk-GO3FXQXC.mjs";
26
26
  export {
27
27
  AuthenticationError,
28
28
  BoostedTravel,
package/package.json CHANGED
@@ -1,56 +1,56 @@
1
- {
2
- "name": "letsfg",
3
- "version": "2026.5.70",
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.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,16 +0,0 @@
1
- import {
2
- BearerTokenError,
3
- getBearerToken,
4
- paymentAuth,
5
- requestEnrolment,
6
- saveToken,
7
- verifyPaymentMethod
8
- } from "./chunk-XNO2W77S.mjs";
9
- export {
10
- BearerTokenError,
11
- getBearerToken,
12
- paymentAuth,
13
- requestEnrolment,
14
- saveToken,
15
- verifyPaymentMethod
16
- };