boostedtravel 0.2.5 → 1.0.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 +18 -109
- package/index.d.ts +8 -0
- package/index.js +9 -0
- package/index.mjs +8 -0
- package/package.json +12 -42
- package/dist/chunk-UY6AN74O.mjs +0 -390
- package/dist/cli.d.mts +0 -1
- package/dist/cli.d.ts +0 -1
- package/dist/cli.js +0 -620
- package/dist/cli.mjs +0 -339
- package/dist/index.d.mts +0 -254
- package/dist/index.d.ts +0 -254
- package/dist/index.js +0 -439
- package/dist/index.mjs +0 -32
package/README.md
CHANGED
|
@@ -1,121 +1,30 @@
|
|
|
1
|
-
#
|
|
1
|
+
# ⚠️ boostedtravel has been renamed to **letsfg**
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
This package is a redirect. All development has moved to [`letsfg`](https://www.npmjs.com/package/letsfg).
|
|
4
4
|
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
## Install
|
|
8
|
-
|
|
9
|
-
```bash
|
|
10
|
-
npm install boostedtravel
|
|
11
|
-
```
|
|
12
|
-
|
|
13
|
-
## Quick Start (SDK)
|
|
14
|
-
|
|
15
|
-
```typescript
|
|
16
|
-
import { BoostedTravel, cheapestOffer, offerSummary } from 'boostedtravel';
|
|
17
|
-
|
|
18
|
-
// Register (one-time)
|
|
19
|
-
const creds = await BoostedTravel.register('my-agent', 'agent@example.com');
|
|
20
|
-
console.log(creds.api_key); // Save this
|
|
21
|
-
|
|
22
|
-
// Use
|
|
23
|
-
const bt = new BoostedTravel({ apiKey: 'trav_...' });
|
|
24
|
-
|
|
25
|
-
// Search — FREE
|
|
26
|
-
const flights = await bt.search('GDN', 'BER', '2026-03-03');
|
|
27
|
-
const best = cheapestOffer(flights);
|
|
28
|
-
console.log(offerSummary(best));
|
|
29
|
-
|
|
30
|
-
// Unlock — $1
|
|
31
|
-
const unlock = await bt.unlock(best.id);
|
|
32
|
-
|
|
33
|
-
// Book — FREE after unlock
|
|
34
|
-
const booking = await bt.book(
|
|
35
|
-
best.id,
|
|
36
|
-
[{
|
|
37
|
-
id: flights.passenger_ids[0],
|
|
38
|
-
given_name: 'John',
|
|
39
|
-
family_name: 'Doe',
|
|
40
|
-
born_on: '1990-01-15',
|
|
41
|
-
gender: 'm',
|
|
42
|
-
title: 'mr',
|
|
43
|
-
email: 'john@example.com',
|
|
44
|
-
}],
|
|
45
|
-
'john@example.com'
|
|
46
|
-
);
|
|
47
|
-
console.log(`PNR: ${booking.booking_reference}`);
|
|
48
|
-
```
|
|
49
|
-
|
|
50
|
-
## Quick Start (CLI)
|
|
5
|
+
## Migration
|
|
51
6
|
|
|
52
7
|
```bash
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
boostedtravel search GDN BER 2026-03-03 --sort price
|
|
56
|
-
boostedtravel search LON BCN 2026-04-01 --json # Machine-readable
|
|
57
|
-
boostedtravel unlock off_xxx
|
|
58
|
-
boostedtravel book off_xxx -p '{"id":"pas_xxx","given_name":"John",...}' -e john@example.com
|
|
8
|
+
npm install letsfg
|
|
59
9
|
```
|
|
60
10
|
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
### `new BoostedTravel({ apiKey, baseUrl?, timeout? })`
|
|
64
|
-
|
|
65
|
-
### `bt.search(origin, destination, dateFrom, options?)`
|
|
66
|
-
### `bt.resolveLocation(query)`
|
|
67
|
-
### `bt.unlock(offerId)`
|
|
68
|
-
### `bt.book(offerId, passengers, contactEmail, contactPhone?)`
|
|
69
|
-
### `bt.setupPayment(token?)`
|
|
70
|
-
### `bt.me()`
|
|
71
|
-
### `BoostedTravel.register(agentName, email, baseUrl?, ownerName?, description?)`
|
|
72
|
-
|
|
73
|
-
### Helpers
|
|
74
|
-
- `offerSummary(offer)` — One-line string summary
|
|
75
|
-
- `cheapestOffer(result)` — Get cheapest offer from search
|
|
76
|
-
|
|
77
|
-
### `searchLocal(origin, destination, dateFrom, options?)`
|
|
78
|
-
|
|
79
|
-
Search 75 airline connectors locally (no API key needed). Requires Python + `boostedtravel` installed.
|
|
80
|
-
|
|
81
|
-
```typescript
|
|
82
|
-
import { searchLocal } from 'boostedtravel';
|
|
83
|
-
|
|
84
|
-
const result = await searchLocal('GDN', 'BCN', '2026-06-15');
|
|
85
|
-
console.log(result.total_results);
|
|
86
|
-
|
|
87
|
-
// Limit browser concurrency for constrained environments
|
|
88
|
-
const result2 = await searchLocal('GDN', 'BCN', '2026-06-15', { maxBrowsers: 4 });
|
|
89
|
-
```
|
|
90
|
-
|
|
91
|
-
### `systemInfo()`
|
|
92
|
-
|
|
93
|
-
Get system resource profile and recommended concurrency settings.
|
|
94
|
-
|
|
95
|
-
```typescript
|
|
96
|
-
import { systemInfo } from 'boostedtravel';
|
|
97
|
-
|
|
98
|
-
const info = await systemInfo();
|
|
99
|
-
console.log(info);
|
|
100
|
-
// { platform: 'win32', cpu_cores: 16, ram_total_gb: 31.2, ram_available_gb: 14.7,
|
|
101
|
-
// tier: 'standard', recommended_max_browsers: 8, current_max_browsers: 8 }
|
|
102
|
-
```
|
|
103
|
-
|
|
104
|
-
## Zero Dependencies
|
|
105
|
-
|
|
106
|
-
Uses native `fetch` (Node 18+). No `axios`, no `node-fetch`, nothing. Safe for sandboxed environments.
|
|
107
|
-
|
|
108
|
-
## Performance Tuning
|
|
11
|
+
The new package is a drop-in replacement:
|
|
109
12
|
|
|
110
|
-
|
|
13
|
+
```ts
|
|
14
|
+
// Old (still works via this redirect)
|
|
15
|
+
import { BoostedTravel } from 'boostedtravel';
|
|
111
16
|
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
await searchLocal('LHR', 'BCN', '2026-04-15', { maxBrowsers: 4 });
|
|
17
|
+
// New (recommended)
|
|
18
|
+
import { LetsFG } from 'letsfg';
|
|
115
19
|
```
|
|
116
20
|
|
|
117
|
-
|
|
21
|
+
Both `BoostedTravel` and `LetsFG` class names work in the new package.
|
|
22
|
+
Both `BOOSTEDTRAVEL_API_KEY` and `LETSFG_API_KEY` environment variables are supported.
|
|
118
23
|
|
|
119
|
-
##
|
|
24
|
+
## Links
|
|
120
25
|
|
|
121
|
-
|
|
26
|
+
- **New package**: [letsfg on npm](https://www.npmjs.com/package/letsfg)
|
|
27
|
+
- **MCP Server**: [letsfg-mcp on npm](https://www.npmjs.com/package/letsfg-mcp)
|
|
28
|
+
- **Python SDK**: [letsfg on PyPI](https://pypi.org/project/letsfg/)
|
|
29
|
+
- **GitHub**: [LetsFG/LetsFG](https://github.com/LetsFG/LetsFG)
|
|
30
|
+
- **Website**: [letsfg.co](https://letsfg.co)
|
package/index.d.ts
ADDED
package/index.js
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ⚠️ boostedtravel has been renamed to letsfg.
|
|
3
|
+
*
|
|
4
|
+
* Install the new package: npm install letsfg
|
|
5
|
+
* https://www.npmjs.com/package/letsfg
|
|
6
|
+
*
|
|
7
|
+
* This module re-exports everything from letsfg for backward compatibility.
|
|
8
|
+
*/
|
|
9
|
+
module.exports = require('letsfg');
|
package/index.mjs
ADDED
package/package.json
CHANGED
|
@@ -1,50 +1,20 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "boostedtravel",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"description": "
|
|
5
|
-
"main": "
|
|
6
|
-
"module": "
|
|
7
|
-
"types": "
|
|
8
|
-
"
|
|
9
|
-
|
|
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
|
-
"prepublishOnly": "npm run build"
|
|
19
|
-
},
|
|
20
|
-
"keywords": [
|
|
21
|
-
"flights",
|
|
22
|
-
"travel",
|
|
23
|
-
"booking",
|
|
24
|
-
"agent",
|
|
25
|
-
"ai",
|
|
26
|
-
"cli",
|
|
27
|
-
"autonomous",
|
|
28
|
-
"mcp",
|
|
29
|
-
"openai",
|
|
30
|
-
"airline",
|
|
31
|
-
"ndc",
|
|
32
|
-
"search",
|
|
33
|
-
"ndc"
|
|
34
|
-
],
|
|
35
|
-
"author": "BoostedTravel",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "⚠️ This package has been renamed to 'letsfg'. Install letsfg instead: npm install letsfg — https://www.npmjs.com/package/letsfg",
|
|
5
|
+
"main": "index.js",
|
|
6
|
+
"module": "index.mjs",
|
|
7
|
+
"types": "index.d.ts",
|
|
8
|
+
"keywords": ["travel", "flights", "booking", "ai", "agent", "mcp", "letsfg"],
|
|
9
|
+
"author": "LetsFG <hi@letsfg.co>",
|
|
36
10
|
"license": "MIT",
|
|
37
11
|
"repository": {
|
|
38
12
|
"type": "git",
|
|
39
|
-
"url": "https://github.com/
|
|
13
|
+
"url": "https://github.com/LetsFG/LetsFG"
|
|
40
14
|
},
|
|
41
|
-
"homepage": "https://
|
|
42
|
-
"
|
|
43
|
-
"
|
|
44
|
-
"tsup": "^8.0.0",
|
|
45
|
-
"typescript": "^5.3.0"
|
|
15
|
+
"homepage": "https://www.npmjs.com/package/letsfg",
|
|
16
|
+
"dependencies": {
|
|
17
|
+
"letsfg": "^1.0.0"
|
|
46
18
|
},
|
|
47
|
-
"
|
|
48
|
-
"node": ">=18.0.0"
|
|
49
|
-
}
|
|
19
|
+
"deprecated": "This package has been renamed to 'letsfg'. Install: npm install letsfg — https://www.npmjs.com/package/letsfg"
|
|
50
20
|
}
|
package/dist/chunk-UY6AN74O.mjs
DELETED
|
@@ -1,390 +0,0 @@
|
|
|
1
|
-
// src/index.ts
|
|
2
|
-
var ErrorCode = {
|
|
3
|
-
// Transient (safe to retry after short delay)
|
|
4
|
-
SUPPLIER_TIMEOUT: "SUPPLIER_TIMEOUT",
|
|
5
|
-
RATE_LIMITED: "RATE_LIMITED",
|
|
6
|
-
SERVICE_UNAVAILABLE: "SERVICE_UNAVAILABLE",
|
|
7
|
-
NETWORK_ERROR: "NETWORK_ERROR",
|
|
8
|
-
// Validation (fix input, then retry)
|
|
9
|
-
INVALID_IATA: "INVALID_IATA",
|
|
10
|
-
INVALID_DATE: "INVALID_DATE",
|
|
11
|
-
INVALID_PASSENGERS: "INVALID_PASSENGERS",
|
|
12
|
-
UNSUPPORTED_ROUTE: "UNSUPPORTED_ROUTE",
|
|
13
|
-
MISSING_PARAMETER: "MISSING_PARAMETER",
|
|
14
|
-
INVALID_PARAMETER: "INVALID_PARAMETER",
|
|
15
|
-
// Business (requires human decision)
|
|
16
|
-
AUTH_INVALID: "AUTH_INVALID",
|
|
17
|
-
PAYMENT_REQUIRED: "PAYMENT_REQUIRED",
|
|
18
|
-
PAYMENT_DECLINED: "PAYMENT_DECLINED",
|
|
19
|
-
OFFER_EXPIRED: "OFFER_EXPIRED",
|
|
20
|
-
OFFER_NOT_UNLOCKED: "OFFER_NOT_UNLOCKED",
|
|
21
|
-
FARE_CHANGED: "FARE_CHANGED",
|
|
22
|
-
ALREADY_BOOKED: "ALREADY_BOOKED",
|
|
23
|
-
BOOKING_FAILED: "BOOKING_FAILED"
|
|
24
|
-
};
|
|
25
|
-
var ErrorCategory = {
|
|
26
|
-
TRANSIENT: "transient",
|
|
27
|
-
VALIDATION: "validation",
|
|
28
|
-
BUSINESS: "business"
|
|
29
|
-
};
|
|
30
|
-
var CODE_TO_CATEGORY = {
|
|
31
|
-
[ErrorCode.SUPPLIER_TIMEOUT]: ErrorCategory.TRANSIENT,
|
|
32
|
-
[ErrorCode.RATE_LIMITED]: ErrorCategory.TRANSIENT,
|
|
33
|
-
[ErrorCode.SERVICE_UNAVAILABLE]: ErrorCategory.TRANSIENT,
|
|
34
|
-
[ErrorCode.NETWORK_ERROR]: ErrorCategory.TRANSIENT,
|
|
35
|
-
[ErrorCode.INVALID_IATA]: ErrorCategory.VALIDATION,
|
|
36
|
-
[ErrorCode.INVALID_DATE]: ErrorCategory.VALIDATION,
|
|
37
|
-
[ErrorCode.INVALID_PASSENGERS]: ErrorCategory.VALIDATION,
|
|
38
|
-
[ErrorCode.UNSUPPORTED_ROUTE]: ErrorCategory.VALIDATION,
|
|
39
|
-
[ErrorCode.MISSING_PARAMETER]: ErrorCategory.VALIDATION,
|
|
40
|
-
[ErrorCode.INVALID_PARAMETER]: ErrorCategory.VALIDATION,
|
|
41
|
-
[ErrorCode.AUTH_INVALID]: ErrorCategory.BUSINESS,
|
|
42
|
-
[ErrorCode.PAYMENT_REQUIRED]: ErrorCategory.BUSINESS,
|
|
43
|
-
[ErrorCode.PAYMENT_DECLINED]: ErrorCategory.BUSINESS,
|
|
44
|
-
[ErrorCode.OFFER_EXPIRED]: ErrorCategory.BUSINESS,
|
|
45
|
-
[ErrorCode.OFFER_NOT_UNLOCKED]: ErrorCategory.BUSINESS,
|
|
46
|
-
[ErrorCode.FARE_CHANGED]: ErrorCategory.BUSINESS,
|
|
47
|
-
[ErrorCode.ALREADY_BOOKED]: ErrorCategory.BUSINESS,
|
|
48
|
-
[ErrorCode.BOOKING_FAILED]: ErrorCategory.BUSINESS
|
|
49
|
-
};
|
|
50
|
-
function inferErrorCode(statusCode, detail) {
|
|
51
|
-
const d = detail.toLowerCase();
|
|
52
|
-
if (statusCode === 401) return ErrorCode.AUTH_INVALID;
|
|
53
|
-
if (statusCode === 402) return d.includes("declined") ? ErrorCode.PAYMENT_DECLINED : ErrorCode.PAYMENT_REQUIRED;
|
|
54
|
-
if (statusCode === 410) return ErrorCode.OFFER_EXPIRED;
|
|
55
|
-
if (statusCode === 422) {
|
|
56
|
-
if (d.includes("iata") || d.includes("airport")) return ErrorCode.INVALID_IATA;
|
|
57
|
-
if (d.includes("date")) return ErrorCode.INVALID_DATE;
|
|
58
|
-
if (d.includes("passenger")) return ErrorCode.INVALID_PASSENGERS;
|
|
59
|
-
if (d.includes("route")) return ErrorCode.UNSUPPORTED_ROUTE;
|
|
60
|
-
return ErrorCode.INVALID_PARAMETER;
|
|
61
|
-
}
|
|
62
|
-
if (statusCode === 429) return ErrorCode.RATE_LIMITED;
|
|
63
|
-
if (statusCode === 503) return ErrorCode.SERVICE_UNAVAILABLE;
|
|
64
|
-
if (statusCode === 504) return ErrorCode.SUPPLIER_TIMEOUT;
|
|
65
|
-
if (statusCode === 409) return ErrorCode.ALREADY_BOOKED;
|
|
66
|
-
return statusCode >= 500 ? ErrorCode.BOOKING_FAILED : ErrorCode.INVALID_PARAMETER;
|
|
67
|
-
}
|
|
68
|
-
var BoostedTravelError = class extends Error {
|
|
69
|
-
statusCode;
|
|
70
|
-
response;
|
|
71
|
-
errorCode;
|
|
72
|
-
errorCategory;
|
|
73
|
-
isRetryable;
|
|
74
|
-
constructor(message, statusCode = 0, response = {}, errorCode = "") {
|
|
75
|
-
super(message);
|
|
76
|
-
this.name = "BoostedTravelError";
|
|
77
|
-
this.statusCode = statusCode;
|
|
78
|
-
this.response = response;
|
|
79
|
-
this.errorCode = errorCode || response.error_code || "";
|
|
80
|
-
this.errorCategory = CODE_TO_CATEGORY[this.errorCode] || ErrorCategory.BUSINESS;
|
|
81
|
-
this.isRetryable = this.errorCategory === ErrorCategory.TRANSIENT;
|
|
82
|
-
}
|
|
83
|
-
};
|
|
84
|
-
var AuthenticationError = class extends BoostedTravelError {
|
|
85
|
-
constructor(message, response = {}) {
|
|
86
|
-
super(message, 401, response, ErrorCode.AUTH_INVALID);
|
|
87
|
-
this.name = "AuthenticationError";
|
|
88
|
-
}
|
|
89
|
-
};
|
|
90
|
-
var PaymentRequiredError = class extends BoostedTravelError {
|
|
91
|
-
constructor(message, response = {}) {
|
|
92
|
-
const code = message.toLowerCase().includes("declined") ? ErrorCode.PAYMENT_DECLINED : ErrorCode.PAYMENT_REQUIRED;
|
|
93
|
-
super(message, 402, response, code);
|
|
94
|
-
this.name = "PaymentRequiredError";
|
|
95
|
-
}
|
|
96
|
-
};
|
|
97
|
-
var OfferExpiredError = class extends BoostedTravelError {
|
|
98
|
-
constructor(message, response = {}) {
|
|
99
|
-
super(message, 410, response, ErrorCode.OFFER_EXPIRED);
|
|
100
|
-
this.name = "OfferExpiredError";
|
|
101
|
-
}
|
|
102
|
-
};
|
|
103
|
-
var ValidationError = class extends BoostedTravelError {
|
|
104
|
-
constructor(message, statusCode = 422, response = {}, errorCode = "") {
|
|
105
|
-
super(message, statusCode, response, errorCode || ErrorCode.INVALID_PARAMETER);
|
|
106
|
-
this.name = "ValidationError";
|
|
107
|
-
}
|
|
108
|
-
};
|
|
109
|
-
function routeStr(route) {
|
|
110
|
-
if (!route.segments.length) return "";
|
|
111
|
-
const codes = [route.segments[0].origin, ...route.segments.map((s) => s.destination)];
|
|
112
|
-
return codes.join(" \u2192 ");
|
|
113
|
-
}
|
|
114
|
-
function durationHuman(seconds) {
|
|
115
|
-
const h = Math.floor(seconds / 3600);
|
|
116
|
-
const m = Math.floor(seconds % 3600 / 60);
|
|
117
|
-
return `${h}h${m.toString().padStart(2, "0")}m`;
|
|
118
|
-
}
|
|
119
|
-
function offerSummary(offer) {
|
|
120
|
-
const route = routeStr(offer.outbound);
|
|
121
|
-
const dur = durationHuman(offer.outbound.total_duration_seconds);
|
|
122
|
-
const airline = offer.owner_airline || offer.airlines[0] || "?";
|
|
123
|
-
return `${offer.currency} ${offer.price.toFixed(2)} | ${airline} | ${route} | ${dur} | ${offer.outbound.stopovers} stop(s)`;
|
|
124
|
-
}
|
|
125
|
-
function cheapestOffer(result) {
|
|
126
|
-
if (!result.offers.length) return null;
|
|
127
|
-
return result.offers.reduce((min, o) => o.price < min.price ? o : min, result.offers[0]);
|
|
128
|
-
}
|
|
129
|
-
async function searchLocal(origin, destination, dateFrom, options = {}) {
|
|
130
|
-
const { spawn } = await import("child_process");
|
|
131
|
-
const params = JSON.stringify({
|
|
132
|
-
origin: origin.toUpperCase(),
|
|
133
|
-
destination: destination.toUpperCase(),
|
|
134
|
-
date_from: dateFrom,
|
|
135
|
-
adults: options.adults ?? 1,
|
|
136
|
-
children: options.children ?? 0,
|
|
137
|
-
currency: options.currency ?? "EUR",
|
|
138
|
-
limit: options.limit ?? 50,
|
|
139
|
-
return_date: options.returnDate,
|
|
140
|
-
cabin_class: options.cabinClass,
|
|
141
|
-
...options.maxBrowsers != null && { max_browsers: options.maxBrowsers }
|
|
142
|
-
});
|
|
143
|
-
return new Promise((resolve, reject) => {
|
|
144
|
-
const pythonCmd = process.platform === "win32" ? "python" : "python3";
|
|
145
|
-
const child = spawn(pythonCmd, ["-m", "boostedtravel.local"], {
|
|
146
|
-
stdio: ["pipe", "pipe", "pipe"]
|
|
147
|
-
});
|
|
148
|
-
let stdout = "";
|
|
149
|
-
let stderr = "";
|
|
150
|
-
child.stdout.on("data", (d) => {
|
|
151
|
-
stdout += d.toString();
|
|
152
|
-
});
|
|
153
|
-
child.stderr.on("data", (d) => {
|
|
154
|
-
stderr += d.toString();
|
|
155
|
-
});
|
|
156
|
-
child.on("close", (code) => {
|
|
157
|
-
try {
|
|
158
|
-
const data = JSON.parse(stdout);
|
|
159
|
-
if (data.error) reject(new BoostedTravelError(data.error));
|
|
160
|
-
else resolve(data);
|
|
161
|
-
} catch {
|
|
162
|
-
reject(new BoostedTravelError(
|
|
163
|
-
`Python search failed (code ${code}): ${stdout || stderr}
|
|
164
|
-
Make sure boostedtravel is installed: pip install boostedtravel && playwright install chromium`
|
|
165
|
-
));
|
|
166
|
-
}
|
|
167
|
-
});
|
|
168
|
-
child.on("error", (err) => {
|
|
169
|
-
reject(new BoostedTravelError(
|
|
170
|
-
`Cannot start Python: ${err.message}
|
|
171
|
-
Install: pip install boostedtravel && playwright install chromium`
|
|
172
|
-
));
|
|
173
|
-
});
|
|
174
|
-
child.stdin.write(params);
|
|
175
|
-
child.stdin.end();
|
|
176
|
-
});
|
|
177
|
-
}
|
|
178
|
-
var DEFAULT_BASE_URL = "https://api.boostedchat.com";
|
|
179
|
-
var BoostedTravel = class {
|
|
180
|
-
apiKey;
|
|
181
|
-
baseUrl;
|
|
182
|
-
timeout;
|
|
183
|
-
constructor(config = {}) {
|
|
184
|
-
this.apiKey = config.apiKey || process.env.BOOSTEDTRAVEL_API_KEY || "";
|
|
185
|
-
this.baseUrl = (config.baseUrl || process.env.BOOSTEDTRAVEL_BASE_URL || DEFAULT_BASE_URL).replace(/\/$/, "");
|
|
186
|
-
this.timeout = config.timeout || 3e4;
|
|
187
|
-
}
|
|
188
|
-
requireApiKey() {
|
|
189
|
-
if (!this.apiKey) {
|
|
190
|
-
throw new AuthenticationError(
|
|
191
|
-
"API key required for this operation. Set apiKey in config or BOOSTEDTRAVEL_API_KEY env var.\nNote: searchLocal() works without an API key."
|
|
192
|
-
);
|
|
193
|
-
}
|
|
194
|
-
}
|
|
195
|
-
// ── Core methods ─────────────────────────────────────────────────────
|
|
196
|
-
/**
|
|
197
|
-
* Search for flights — FREE, unlimited.
|
|
198
|
-
*
|
|
199
|
-
* @param origin - IATA code (e.g., "GDN", "LON")
|
|
200
|
-
* @param destination - IATA code (e.g., "BER", "BCN")
|
|
201
|
-
* @param dateFrom - Departure date "YYYY-MM-DD"
|
|
202
|
-
* @param options - Optional search parameters
|
|
203
|
-
*/
|
|
204
|
-
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);
|
|
221
|
-
}
|
|
222
|
-
/**
|
|
223
|
-
* Resolve a city/airport name to IATA codes.
|
|
224
|
-
*/
|
|
225
|
-
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] : [];
|
|
231
|
-
}
|
|
232
|
-
/**
|
|
233
|
-
* Unlock a flight offer — $1 fee.
|
|
234
|
-
* Confirms price, reserves for 30 minutes.
|
|
235
|
-
*/
|
|
236
|
-
async unlock(offerId) {
|
|
237
|
-
this.requireApiKey();
|
|
238
|
-
return this.post("/api/v1/bookings/unlock", { offer_id: offerId });
|
|
239
|
-
}
|
|
240
|
-
/**
|
|
241
|
-
* Book a flight — FREE after unlock.
|
|
242
|
-
* Creates a real airline reservation with PNR.
|
|
243
|
-
*
|
|
244
|
-
* Always provide idempotencyKey to prevent double-bookings on retry.
|
|
245
|
-
*/
|
|
246
|
-
async book(offerId, passengers, contactEmail, contactPhone = "", idempotencyKey = "") {
|
|
247
|
-
this.requireApiKey();
|
|
248
|
-
const body = {
|
|
249
|
-
offer_id: offerId,
|
|
250
|
-
booking_type: "flight",
|
|
251
|
-
passengers,
|
|
252
|
-
contact_email: contactEmail,
|
|
253
|
-
contact_phone: contactPhone
|
|
254
|
-
};
|
|
255
|
-
if (idempotencyKey) body.idempotency_key = idempotencyKey;
|
|
256
|
-
return this.post("/api/v1/bookings/book", body);
|
|
257
|
-
}
|
|
258
|
-
/**
|
|
259
|
-
* Set up payment method (payment token).
|
|
260
|
-
*/
|
|
261
|
-
async setupPayment(token = "tok_visa") {
|
|
262
|
-
this.requireApiKey();
|
|
263
|
-
return this.post("/api/v1/agents/setup-payment", { token });
|
|
264
|
-
}
|
|
265
|
-
/**
|
|
266
|
-
* Get current agent profile and usage stats.
|
|
267
|
-
*/
|
|
268
|
-
async me() {
|
|
269
|
-
this.requireApiKey();
|
|
270
|
-
return this.get("/api/v1/agents/me");
|
|
271
|
-
}
|
|
272
|
-
// ── Static methods ───────────────────────────────────────────────────
|
|
273
|
-
/**
|
|
274
|
-
* Register a new agent — no API key needed.
|
|
275
|
-
*/
|
|
276
|
-
static async register(agentName, email, baseUrl, ownerName = "", description = "") {
|
|
277
|
-
const url = (baseUrl || DEFAULT_BASE_URL).replace(/\/$/, "");
|
|
278
|
-
const resp = await fetch(`${url}/api/v1/agents/register`, {
|
|
279
|
-
method: "POST",
|
|
280
|
-
headers: { "Content-Type": "application/json" },
|
|
281
|
-
body: JSON.stringify({
|
|
282
|
-
agent_name: agentName,
|
|
283
|
-
email,
|
|
284
|
-
owner_name: ownerName,
|
|
285
|
-
description
|
|
286
|
-
})
|
|
287
|
-
});
|
|
288
|
-
const data = await resp.json();
|
|
289
|
-
if (!resp.ok) {
|
|
290
|
-
throw new BoostedTravelError(
|
|
291
|
-
data.detail || `Registration failed (${resp.status})`,
|
|
292
|
-
resp.status,
|
|
293
|
-
data
|
|
294
|
-
);
|
|
295
|
-
}
|
|
296
|
-
return data;
|
|
297
|
-
}
|
|
298
|
-
// ── Internal ────────────────────────────────────────────────────────
|
|
299
|
-
async post(path, body) {
|
|
300
|
-
return this.request(path, {
|
|
301
|
-
method: "POST",
|
|
302
|
-
body: JSON.stringify(body)
|
|
303
|
-
});
|
|
304
|
-
}
|
|
305
|
-
async get(path) {
|
|
306
|
-
return this.request(path, { method: "GET" });
|
|
307
|
-
}
|
|
308
|
-
async request(path, init) {
|
|
309
|
-
const controller = new AbortController();
|
|
310
|
-
const timer = setTimeout(() => controller.abort(), this.timeout);
|
|
311
|
-
try {
|
|
312
|
-
const resp = await fetch(`${this.baseUrl}${path}`, {
|
|
313
|
-
...init,
|
|
314
|
-
headers: {
|
|
315
|
-
"Content-Type": "application/json",
|
|
316
|
-
"X-API-Key": this.apiKey,
|
|
317
|
-
"User-Agent": "boostedtravel-js/0.1.0",
|
|
318
|
-
...init.headers || {}
|
|
319
|
-
},
|
|
320
|
-
signal: controller.signal
|
|
321
|
-
});
|
|
322
|
-
const data = await resp.json();
|
|
323
|
-
if (!resp.ok) {
|
|
324
|
-
const detail = data.detail || `API error (${resp.status})`;
|
|
325
|
-
const code = data.error_code || inferErrorCode(resp.status, detail);
|
|
326
|
-
if (resp.status === 401) throw new AuthenticationError(detail, data);
|
|
327
|
-
if (resp.status === 402) throw new PaymentRequiredError(detail, data);
|
|
328
|
-
if (resp.status === 410) throw new OfferExpiredError(detail, data);
|
|
329
|
-
if (resp.status === 422) throw new ValidationError(detail, resp.status, data, code);
|
|
330
|
-
throw new BoostedTravelError(detail, resp.status, data, code);
|
|
331
|
-
}
|
|
332
|
-
return data;
|
|
333
|
-
} finally {
|
|
334
|
-
clearTimeout(timer);
|
|
335
|
-
}
|
|
336
|
-
}
|
|
337
|
-
};
|
|
338
|
-
async function systemInfo() {
|
|
339
|
-
const { spawn } = await import("child_process");
|
|
340
|
-
return new Promise((resolve, reject) => {
|
|
341
|
-
const pythonCmd = process.platform === "win32" ? "python" : "python3";
|
|
342
|
-
const child = spawn(pythonCmd, ["-m", "boostedtravel.local"], {
|
|
343
|
-
stdio: ["pipe", "pipe", "pipe"]
|
|
344
|
-
});
|
|
345
|
-
let stdout = "";
|
|
346
|
-
let stderr = "";
|
|
347
|
-
child.stdout.on("data", (d) => {
|
|
348
|
-
stdout += d.toString();
|
|
349
|
-
});
|
|
350
|
-
child.stderr.on("data", (d) => {
|
|
351
|
-
stderr += d.toString();
|
|
352
|
-
});
|
|
353
|
-
child.on("close", (code) => {
|
|
354
|
-
try {
|
|
355
|
-
const data = JSON.parse(stdout);
|
|
356
|
-
if (data.error) reject(new BoostedTravelError(data.error));
|
|
357
|
-
else resolve(data);
|
|
358
|
-
} catch {
|
|
359
|
-
reject(new BoostedTravelError(
|
|
360
|
-
`Python system-info failed (code ${code}): ${stdout || stderr}`
|
|
361
|
-
));
|
|
362
|
-
}
|
|
363
|
-
});
|
|
364
|
-
child.on("error", (err) => {
|
|
365
|
-
reject(new BoostedTravelError(
|
|
366
|
-
`Cannot start Python: ${err.message}
|
|
367
|
-
Install: pip install boostedtravel`
|
|
368
|
-
));
|
|
369
|
-
});
|
|
370
|
-
child.stdin.write(JSON.stringify({ __system_info: true }));
|
|
371
|
-
child.stdin.end();
|
|
372
|
-
});
|
|
373
|
-
}
|
|
374
|
-
var index_default = BoostedTravel;
|
|
375
|
-
|
|
376
|
-
export {
|
|
377
|
-
ErrorCode,
|
|
378
|
-
ErrorCategory,
|
|
379
|
-
BoostedTravelError,
|
|
380
|
-
AuthenticationError,
|
|
381
|
-
PaymentRequiredError,
|
|
382
|
-
OfferExpiredError,
|
|
383
|
-
ValidationError,
|
|
384
|
-
offerSummary,
|
|
385
|
-
cheapestOffer,
|
|
386
|
-
searchLocal,
|
|
387
|
-
BoostedTravel,
|
|
388
|
-
systemInfo,
|
|
389
|
-
index_default
|
|
390
|
-
};
|
package/dist/cli.d.mts
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
package/dist/cli.d.ts
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|