@tks/wayfinder 0.3.0 → 0.4.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 +12 -0
- package/package.json +1 -1
- package/src/cli.ts +52 -23
- package/src/format.ts +14 -1
- package/src/parse.ts +43 -8
- package/src/serpapi.ts +67 -1
- package/src/types.ts +27 -1
package/README.md
CHANGED
|
@@ -58,6 +58,12 @@ Search one way flights:
|
|
|
58
58
|
wayfinder flights --from SFO --to JFK --date 2026-04-10
|
|
59
59
|
```
|
|
60
60
|
|
|
61
|
+
Search up to 3 departure dates in one command:
|
|
62
|
+
|
|
63
|
+
```bash
|
|
64
|
+
wayfinder flights --from SFO --to JFK --date 2026-04-10 --date 2026-04-11 --date 2026-04-13
|
|
65
|
+
```
|
|
66
|
+
|
|
61
67
|
Search with filters:
|
|
62
68
|
|
|
63
69
|
```bash
|
|
@@ -118,6 +124,12 @@ Search nearby coffee spots:
|
|
|
118
124
|
wayfinder places --near "Shinjuku, Tokyo" --type coffee --limit 5
|
|
119
125
|
```
|
|
120
126
|
|
|
127
|
+
Narrow results to walking-distance intent:
|
|
128
|
+
|
|
129
|
+
```bash
|
|
130
|
+
wayfinder places --near "Domino Park, Brooklyn, NY" --range walk
|
|
131
|
+
```
|
|
132
|
+
|
|
121
133
|
Structured places output for scripting:
|
|
122
134
|
|
|
123
135
|
```bash
|
package/package.json
CHANGED
package/src/cli.ts
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import { getConfigPath, readConfigApiKey, resolveApiKey, writeConfigApiKey } from "./config";
|
|
2
2
|
import { CliError } from "./errors";
|
|
3
|
-
import { renderFlightTable, renderHotelTable, renderPlaceTable } from "./format";
|
|
3
|
+
import { renderFlightTable, renderFlightTablesByDate, renderHotelTable, renderPlaceTable } from "./format";
|
|
4
4
|
import { parseCliArgs } from "./parse";
|
|
5
|
-
import { searchFlightBookingOptions, searchFlights, searchHotels, searchPlaces } from "./serpapi";
|
|
6
|
-
import { ExitCode } from "./types";
|
|
5
|
+
import { searchFlightBookingOptions, searchFlights, searchFlightsMultiDate, searchHotels, searchPlaces } from "./serpapi";
|
|
6
|
+
import { ExitCode, FlightMultiDateQuery, FlightsQuery } from "./types";
|
|
7
7
|
import { createInterface } from "node:readline/promises";
|
|
8
8
|
import { stdin as defaultStdin, stdout as defaultStdout } from "node:process";
|
|
9
9
|
import { existsSync, unlinkSync } from "node:fs";
|
|
@@ -22,7 +22,7 @@ interface RunOptions {
|
|
|
22
22
|
promptImpl?: (prompt: string) => Promise<string>;
|
|
23
23
|
}
|
|
24
24
|
|
|
25
|
-
const HELP_TEXT = `wayfinder v0.
|
|
25
|
+
const HELP_TEXT = `wayfinder v0.4.0 travel search
|
|
26
26
|
|
|
27
27
|
Usage:
|
|
28
28
|
wayfinder setup [--reset]
|
|
@@ -30,7 +30,7 @@ Usage:
|
|
|
30
30
|
wayfinder flights one-way --from SFO --to JFK --date 2026-03-21 [filters]
|
|
31
31
|
wayfinder flights booking --from SFO --to JFK --date 2026-03-21 --token <BOOKING_TOKEN> [--token <BOOKING_TOKEN>] [--json]
|
|
32
32
|
wayfinder hotels --where "New York, NY" --check-in 2026-03-21 --check-out 2026-03-23 [filters]
|
|
33
|
-
wayfinder places --near "Shinjuku, Tokyo" [--type restaurant|coffee] [--limit N] [--json]
|
|
33
|
+
wayfinder places --near "Shinjuku, Tokyo" [--type restaurant|coffee] [--range walk] [--limit N] [--json]
|
|
34
34
|
|
|
35
35
|
Setup:
|
|
36
36
|
Runs interactive key setup and stores your SerpApi key in local config.
|
|
@@ -39,7 +39,7 @@ Setup:
|
|
|
39
39
|
Flights required:
|
|
40
40
|
--from <IATA> Origin airport code
|
|
41
41
|
--to <IATA> Destination airport code
|
|
42
|
-
--date <YYYY-MM-DD> Departure date
|
|
42
|
+
--date <YYYY-MM-DD> Departure date (repeat up to 3 unique dates)
|
|
43
43
|
|
|
44
44
|
Flights optional filters:
|
|
45
45
|
--airline <IATA> Airline code, example UA
|
|
@@ -72,6 +72,7 @@ Places required:
|
|
|
72
72
|
|
|
73
73
|
Places optional filters:
|
|
74
74
|
--type <restaurant|coffee> Place type (default restaurant)
|
|
75
|
+
--range <walk> Heuristic nearby scope; "walk" narrows to walking-distance intent
|
|
75
76
|
--limit <N> Maximum number of results (default 10)
|
|
76
77
|
|
|
77
78
|
Output:
|
|
@@ -134,26 +135,50 @@ export async function runWayfinder(
|
|
|
134
135
|
|
|
135
136
|
const apiKey = resolveApiKey(env, homeDir);
|
|
136
137
|
if (parsed.mode === "flights") {
|
|
137
|
-
|
|
138
|
+
if (isMultiDateFlightQuery(parsed.query)) {
|
|
139
|
+
const multiDateFlights = await searchFlightsMultiDate(parsed.query, apiKey, options.fetchImpl ?? fetch);
|
|
140
|
+
const nonEmptyDateResults = multiDateFlights.resultsByDate.filter((entry) => entry.results.length > 0);
|
|
138
141
|
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
+
if (nonEmptyDateResults.length === 0) {
|
|
143
|
+
throw new CliError("No flights found for the selected query", ExitCode.NoResults);
|
|
144
|
+
}
|
|
142
145
|
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
)
|
|
154
|
-
|
|
146
|
+
if (parsed.outputJson) {
|
|
147
|
+
output.stdout(
|
|
148
|
+
JSON.stringify(
|
|
149
|
+
{
|
|
150
|
+
query: parsed.query,
|
|
151
|
+
resultsByDate: nonEmptyDateResults,
|
|
152
|
+
},
|
|
153
|
+
null,
|
|
154
|
+
2,
|
|
155
|
+
),
|
|
156
|
+
);
|
|
157
|
+
} else {
|
|
158
|
+
output.stdout(renderFlightTablesByDate(nonEmptyDateResults));
|
|
159
|
+
}
|
|
155
160
|
} else {
|
|
156
|
-
|
|
161
|
+
const flights = await searchFlights(parsed.query, apiKey, options.fetchImpl ?? fetch);
|
|
162
|
+
|
|
163
|
+
if (flights.options.length === 0) {
|
|
164
|
+
throw new CliError("No flights found for the selected query", ExitCode.NoResults);
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
if (parsed.outputJson) {
|
|
168
|
+
output.stdout(
|
|
169
|
+
JSON.stringify(
|
|
170
|
+
{
|
|
171
|
+
query: parsed.query,
|
|
172
|
+
googleFlightsUrl: flights.googleFlightsUrl,
|
|
173
|
+
results: flights.options,
|
|
174
|
+
},
|
|
175
|
+
null,
|
|
176
|
+
2,
|
|
177
|
+
),
|
|
178
|
+
);
|
|
179
|
+
} else {
|
|
180
|
+
output.stdout(renderFlightTable(flights.options));
|
|
181
|
+
}
|
|
157
182
|
}
|
|
158
183
|
} else if (parsed.mode === "flight-booking") {
|
|
159
184
|
const bookingResults = await searchFlightBookingOptions(
|
|
@@ -339,3 +364,7 @@ async function promptWithFallback(
|
|
|
339
364
|
rl.close();
|
|
340
365
|
}
|
|
341
366
|
}
|
|
367
|
+
|
|
368
|
+
function isMultiDateFlightQuery(query: FlightsQuery): query is FlightMultiDateQuery {
|
|
369
|
+
return "departureDates" in query;
|
|
370
|
+
}
|
package/src/format.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { FlightOption, HotelOption, PlaceOption } from "./types";
|
|
1
|
+
import { FlightDateResult, FlightOption, HotelOption, PlaceOption } from "./types";
|
|
2
2
|
|
|
3
3
|
const currencyFormatter = new Intl.NumberFormat("en-US", {
|
|
4
4
|
style: "currency",
|
|
@@ -74,6 +74,19 @@ export function renderFlightTable(options: FlightOption[]): string {
|
|
|
74
74
|
return lines.join("\n");
|
|
75
75
|
}
|
|
76
76
|
|
|
77
|
+
export function renderFlightTablesByDate(resultsByDate: FlightDateResult[]): string {
|
|
78
|
+
const sections = resultsByDate.map((result) => {
|
|
79
|
+
const lines = [`DATE: ${result.date}`];
|
|
80
|
+
if (typeof result.googleFlightsUrl === "string") {
|
|
81
|
+
lines.push(`GOOGLE FLIGHTS: ${result.googleFlightsUrl}`);
|
|
82
|
+
}
|
|
83
|
+
lines.push(renderFlightTable(result.results));
|
|
84
|
+
return lines.join("\n");
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
return sections.join("\n\n");
|
|
88
|
+
}
|
|
89
|
+
|
|
77
90
|
export function renderHotelTable(options: HotelOption[]): string {
|
|
78
91
|
const rows = options.map((option) => ({
|
|
79
92
|
nightly: currencyFormatter.format(option.nightlyPrice),
|
package/src/parse.ts
CHANGED
|
@@ -2,17 +2,18 @@ import { CliError } from "./errors";
|
|
|
2
2
|
import {
|
|
3
3
|
ExitCode,
|
|
4
4
|
FlightBookingQuery,
|
|
5
|
-
|
|
5
|
+
FlightsQuery,
|
|
6
6
|
HotelQuery,
|
|
7
7
|
ParsedArgs,
|
|
8
8
|
PlaceQuery,
|
|
9
|
+
PlaceRange,
|
|
9
10
|
PlaceType,
|
|
10
11
|
} from "./types";
|
|
11
12
|
|
|
12
13
|
interface FlightRawOptions {
|
|
13
14
|
from?: string;
|
|
14
15
|
to?: string;
|
|
15
|
-
|
|
16
|
+
dates: string[];
|
|
16
17
|
airline?: string;
|
|
17
18
|
maxStops?: string;
|
|
18
19
|
maxPrice?: string;
|
|
@@ -47,6 +48,7 @@ interface PlaceRawOptions {
|
|
|
47
48
|
near?: string;
|
|
48
49
|
type?: string;
|
|
49
50
|
limit?: string;
|
|
51
|
+
range?: string;
|
|
50
52
|
outputJson: boolean;
|
|
51
53
|
help: boolean;
|
|
52
54
|
}
|
|
@@ -86,6 +88,7 @@ export function parseCliArgs(argv: string[]): ParsedArgs {
|
|
|
86
88
|
|
|
87
89
|
function parseFlightsArgs(args: string[]): ParsedArgs {
|
|
88
90
|
const raw: FlightRawOptions = {
|
|
91
|
+
dates: [],
|
|
89
92
|
excludeBasic: false,
|
|
90
93
|
outputJson: false,
|
|
91
94
|
help: false,
|
|
@@ -126,7 +129,7 @@ function parseFlightsArgs(args: string[]): ParsedArgs {
|
|
|
126
129
|
raw.to = value;
|
|
127
130
|
break;
|
|
128
131
|
case "--date":
|
|
129
|
-
raw.
|
|
132
|
+
raw.dates.push(value);
|
|
130
133
|
break;
|
|
131
134
|
case "--airline":
|
|
132
135
|
raw.airline = value;
|
|
@@ -339,6 +342,9 @@ function parsePlacesArgs(args: string[]): ParsedArgs {
|
|
|
339
342
|
case "--limit":
|
|
340
343
|
raw.limit = value;
|
|
341
344
|
break;
|
|
345
|
+
case "--range":
|
|
346
|
+
raw.range = value;
|
|
347
|
+
break;
|
|
342
348
|
default:
|
|
343
349
|
throw new CliError(`Unknown flag: ${token}`, ExitCode.InvalidInput);
|
|
344
350
|
}
|
|
@@ -377,8 +383,8 @@ function parseSetupArgs(args: string[]): ParsedArgs {
|
|
|
377
383
|
};
|
|
378
384
|
}
|
|
379
385
|
|
|
380
|
-
function buildFlightQuery(raw: FlightRawOptions):
|
|
381
|
-
if (!raw.from || !raw.to ||
|
|
386
|
+
function buildFlightQuery(raw: FlightRawOptions): FlightsQuery {
|
|
387
|
+
if (!raw.from || !raw.to || raw.dates.length === 0) {
|
|
382
388
|
throw new CliError("Missing required flags: --from, --to, --date", ExitCode.InvalidInput);
|
|
383
389
|
}
|
|
384
390
|
|
|
@@ -389,7 +395,14 @@ function buildFlightQuery(raw: FlightRawOptions): FlightQuery {
|
|
|
389
395
|
throw new CliError("Origin and destination must be different airports", ExitCode.InvalidInput);
|
|
390
396
|
}
|
|
391
397
|
|
|
392
|
-
const
|
|
398
|
+
const departureDates = [...new Set(raw.dates.map((date) => normalizeDate(date, "departure")))];
|
|
399
|
+
if (departureDates.length > 3) {
|
|
400
|
+
throw new CliError(
|
|
401
|
+
"Too many dates: maximum 3 unique --date values per search",
|
|
402
|
+
ExitCode.InvalidInput,
|
|
403
|
+
);
|
|
404
|
+
}
|
|
405
|
+
|
|
393
406
|
const airlineCode = raw.airline ? normalizeAirlineCode(raw.airline) : undefined;
|
|
394
407
|
const maxStops = raw.maxStops ? normalizeMaxStops(raw.maxStops) : undefined;
|
|
395
408
|
const maxPrice = raw.maxPrice ? normalizeMaxPrice(raw.maxPrice) : undefined;
|
|
@@ -419,10 +432,9 @@ function buildFlightQuery(raw: FlightRawOptions): FlightQuery {
|
|
|
419
432
|
}
|
|
420
433
|
}
|
|
421
434
|
|
|
422
|
-
|
|
435
|
+
const shared = {
|
|
423
436
|
origin,
|
|
424
437
|
destination,
|
|
425
|
-
departureDate,
|
|
426
438
|
airlineCode,
|
|
427
439
|
maxStops,
|
|
428
440
|
maxPrice,
|
|
@@ -430,6 +442,18 @@ function buildFlightQuery(raw: FlightRawOptions): FlightQuery {
|
|
|
430
442
|
departureBeforeMinutes,
|
|
431
443
|
excludeBasic: raw.excludeBasic || undefined,
|
|
432
444
|
};
|
|
445
|
+
|
|
446
|
+
if (departureDates.length === 1) {
|
|
447
|
+
return {
|
|
448
|
+
...shared,
|
|
449
|
+
departureDate: departureDates[0] as string,
|
|
450
|
+
};
|
|
451
|
+
}
|
|
452
|
+
|
|
453
|
+
return {
|
|
454
|
+
...shared,
|
|
455
|
+
departureDates,
|
|
456
|
+
};
|
|
433
457
|
}
|
|
434
458
|
|
|
435
459
|
function buildHotelQuery(raw: HotelRawOptions): HotelQuery {
|
|
@@ -497,11 +521,13 @@ function buildPlaceQuery(raw: PlaceRawOptions): PlaceQuery {
|
|
|
497
521
|
const near = normalizeLocation(raw.near);
|
|
498
522
|
const type = raw.type ? normalizePlaceType(raw.type) : "restaurant";
|
|
499
523
|
const limit = raw.limit ? normalizeLimit(raw.limit, "--limit") : 10;
|
|
524
|
+
const range = raw.range ? normalizePlaceRange(raw.range) : undefined;
|
|
500
525
|
|
|
501
526
|
return {
|
|
502
527
|
near,
|
|
503
528
|
type,
|
|
504
529
|
limit,
|
|
530
|
+
range,
|
|
505
531
|
};
|
|
506
532
|
}
|
|
507
533
|
|
|
@@ -658,6 +684,15 @@ function normalizePlaceType(value: string): PlaceType {
|
|
|
658
684
|
return normalized;
|
|
659
685
|
}
|
|
660
686
|
|
|
687
|
+
function normalizePlaceRange(value: string): PlaceRange {
|
|
688
|
+
const normalized = value.trim().toLowerCase();
|
|
689
|
+
if (normalized !== "walk") {
|
|
690
|
+
throw new CliError("--range must be: walk", ExitCode.InvalidInput);
|
|
691
|
+
}
|
|
692
|
+
|
|
693
|
+
return normalized;
|
|
694
|
+
}
|
|
695
|
+
|
|
661
696
|
function normalizeTime(value: string, flagName: string): number {
|
|
662
697
|
const match = /^([01]\d|2[0-3]):([0-5]\d)$/.exec(value);
|
|
663
698
|
if (!match) {
|
package/src/serpapi.ts
CHANGED
|
@@ -1,8 +1,11 @@
|
|
|
1
1
|
import { CliError } from "./errors";
|
|
2
2
|
import {
|
|
3
3
|
ExitCode,
|
|
4
|
+
FlightDateResult,
|
|
4
5
|
FlightBookingQuery,
|
|
5
6
|
FlightBookingResult,
|
|
7
|
+
FlightMultiDateQuery,
|
|
8
|
+
FlightMultiDateSearchResult,
|
|
6
9
|
FlightOption,
|
|
7
10
|
FlightQuery,
|
|
8
11
|
FlightSearchResult,
|
|
@@ -134,6 +137,38 @@ export async function searchFlights(
|
|
|
134
137
|
};
|
|
135
138
|
}
|
|
136
139
|
|
|
140
|
+
export async function searchFlightsMultiDate(
|
|
141
|
+
query: FlightMultiDateQuery,
|
|
142
|
+
apiKey: string,
|
|
143
|
+
fetchImpl: typeof fetch = fetch,
|
|
144
|
+
): Promise<FlightMultiDateSearchResult> {
|
|
145
|
+
const resultsByDate = await mapWithConcurrency(query.departureDates, 2, async (date) => {
|
|
146
|
+
const singleDateQuery: FlightQuery = {
|
|
147
|
+
origin: query.origin,
|
|
148
|
+
destination: query.destination,
|
|
149
|
+
departureDate: date,
|
|
150
|
+
airlineCode: query.airlineCode,
|
|
151
|
+
maxStops: query.maxStops,
|
|
152
|
+
maxPrice: query.maxPrice,
|
|
153
|
+
departureAfterMinutes: query.departureAfterMinutes,
|
|
154
|
+
departureBeforeMinutes: query.departureBeforeMinutes,
|
|
155
|
+
excludeBasic: query.excludeBasic,
|
|
156
|
+
};
|
|
157
|
+
|
|
158
|
+
const result = await searchFlights(singleDateQuery, apiKey, fetchImpl);
|
|
159
|
+
const grouped: FlightDateResult = {
|
|
160
|
+
date,
|
|
161
|
+
results: result.options,
|
|
162
|
+
googleFlightsUrl: result.googleFlightsUrl,
|
|
163
|
+
};
|
|
164
|
+
return grouped;
|
|
165
|
+
});
|
|
166
|
+
|
|
167
|
+
return {
|
|
168
|
+
resultsByDate,
|
|
169
|
+
};
|
|
170
|
+
}
|
|
171
|
+
|
|
137
172
|
export async function searchFlightBookingOptions(
|
|
138
173
|
query: FlightBookingQuery,
|
|
139
174
|
apiKey: string,
|
|
@@ -387,10 +422,11 @@ function buildHotelRequestUrl(query: HotelQuery, apiKey: string): string {
|
|
|
387
422
|
function buildPlaceRequestUrl(query: PlaceQuery, apiKey: string): string {
|
|
388
423
|
const url = new URL("https://serpapi.com/search.json");
|
|
389
424
|
const searchTerm = query.type === "coffee" ? "coffee" : "restaurants";
|
|
425
|
+
const rangePrefix = query.range === "walk" ? "within walking distance " : "";
|
|
390
426
|
|
|
391
427
|
url.searchParams.set("engine", "google_maps");
|
|
392
428
|
url.searchParams.set("type", "search");
|
|
393
|
-
url.searchParams.set("q", `${searchTerm} near ${query.near}`);
|
|
429
|
+
url.searchParams.set("q", `${rangePrefix}${searchTerm} near ${query.near}`);
|
|
394
430
|
url.searchParams.set("api_key", apiKey);
|
|
395
431
|
return url.toString();
|
|
396
432
|
}
|
|
@@ -684,3 +720,33 @@ function collectBookingLinks(
|
|
|
684
720
|
collectBookingLinks(child, links);
|
|
685
721
|
}
|
|
686
722
|
}
|
|
723
|
+
|
|
724
|
+
async function mapWithConcurrency<TInput, TOutput>(
|
|
725
|
+
items: TInput[],
|
|
726
|
+
concurrency: number,
|
|
727
|
+
worker: (item: TInput, index: number) => Promise<TOutput>,
|
|
728
|
+
): Promise<TOutput[]> {
|
|
729
|
+
const output: TOutput[] = new Array(items.length);
|
|
730
|
+
let cursor = 0;
|
|
731
|
+
|
|
732
|
+
async function runWorker(): Promise<void> {
|
|
733
|
+
while (true) {
|
|
734
|
+
const index = cursor;
|
|
735
|
+
cursor += 1;
|
|
736
|
+
|
|
737
|
+
if (index >= items.length) {
|
|
738
|
+
return;
|
|
739
|
+
}
|
|
740
|
+
|
|
741
|
+
output[index] = await worker(items[index] as TInput, index);
|
|
742
|
+
}
|
|
743
|
+
}
|
|
744
|
+
|
|
745
|
+
const workers = Array.from(
|
|
746
|
+
{ length: Math.min(Math.max(concurrency, 1), items.length) },
|
|
747
|
+
() => runWorker(),
|
|
748
|
+
);
|
|
749
|
+
|
|
750
|
+
await Promise.all(workers);
|
|
751
|
+
return output;
|
|
752
|
+
}
|
package/src/types.ts
CHANGED
|
@@ -21,6 +21,20 @@ export interface FlightQuery {
|
|
|
21
21
|
excludeBasic?: boolean;
|
|
22
22
|
}
|
|
23
23
|
|
|
24
|
+
export interface FlightMultiDateQuery {
|
|
25
|
+
origin: string;
|
|
26
|
+
destination: string;
|
|
27
|
+
departureDates: string[];
|
|
28
|
+
airlineCode?: string;
|
|
29
|
+
maxStops?: number;
|
|
30
|
+
maxPrice?: number;
|
|
31
|
+
departureAfterMinutes?: number;
|
|
32
|
+
departureBeforeMinutes?: number;
|
|
33
|
+
excludeBasic?: boolean;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export type FlightsQuery = FlightQuery | FlightMultiDateQuery;
|
|
37
|
+
|
|
24
38
|
export interface HotelQuery {
|
|
25
39
|
location: string;
|
|
26
40
|
checkInDate: string;
|
|
@@ -31,11 +45,13 @@ export interface HotelQuery {
|
|
|
31
45
|
}
|
|
32
46
|
|
|
33
47
|
export type PlaceType = "restaurant" | "coffee";
|
|
48
|
+
export type PlaceRange = "walk";
|
|
34
49
|
|
|
35
50
|
export interface PlaceQuery {
|
|
36
51
|
near: string;
|
|
37
52
|
type: PlaceType;
|
|
38
53
|
limit: number;
|
|
54
|
+
range?: PlaceRange;
|
|
39
55
|
}
|
|
40
56
|
|
|
41
57
|
export interface ParsedArgsHelp {
|
|
@@ -47,7 +63,7 @@ export interface ParsedArgsFlights {
|
|
|
47
63
|
help: false;
|
|
48
64
|
mode: "flights";
|
|
49
65
|
outputJson: boolean;
|
|
50
|
-
query:
|
|
66
|
+
query: FlightsQuery;
|
|
51
67
|
}
|
|
52
68
|
|
|
53
69
|
export interface ParsedArgsHotels {
|
|
@@ -131,6 +147,16 @@ export interface FlightSearchResult {
|
|
|
131
147
|
googleFlightsUrl?: string;
|
|
132
148
|
}
|
|
133
149
|
|
|
150
|
+
export interface FlightDateResult {
|
|
151
|
+
date: string;
|
|
152
|
+
results: FlightOption[];
|
|
153
|
+
googleFlightsUrl?: string;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
export interface FlightMultiDateSearchResult {
|
|
157
|
+
resultsByDate: FlightDateResult[];
|
|
158
|
+
}
|
|
159
|
+
|
|
134
160
|
export interface FlightBookingLink {
|
|
135
161
|
url: string;
|
|
136
162
|
source?: string;
|