@tks/wayfinder 0.3.1 → 0.5.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
@@ -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
@@ -88,10 +94,16 @@ Search hotels:
88
94
  wayfinder hotels --where "New York, NY" --check-in 2026-04-10 --check-out 2026-04-12
89
95
  ```
90
96
 
91
- Search hotels with filters:
97
+ Search hotels with price and rating filters:
98
+
99
+ ```bash
100
+ wayfinder hotels --where "Tokyo" --check-in 2026-04-10 --check-out 2026-04-13 --adults 2 --min-price 120 --max-price 300 --rating 4
101
+ ```
102
+
103
+ Search family-friendly hotels with cancellation and class filters:
92
104
 
93
105
  ```bash
94
- wayfinder hotels --where "Tokyo" --check-in 2026-04-10 --check-out 2026-04-13 --adults 2 --max-price 300 --rating 4
106
+ wayfinder hotels --where "Tokyo" --check-in 2026-04-10 --check-out 2026-04-13 --adults 2 --children 2 --children-ages 4,7 --free-cancellation --hotel-class 4,5
95
107
  ```
96
108
 
97
109
  Structured hotel output for scripting:
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tks/wayfinder",
3
- "version": "0.3.1",
3
+ "version": "0.5.0",
4
4
  "description": "Travel search for your terminal and your AI agents",
5
5
  "repository": {
6
6
  "type": "git",
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.3.1 travel search
25
+ const HELP_TEXT = `wayfinder travel search
26
26
 
27
27
  Usage:
28
28
  wayfinder setup [--reset]
@@ -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
@@ -63,7 +63,12 @@ Hotels required:
63
63
 
64
64
  Hotels optional filters:
65
65
  --adults <N> Number of adults (default 2)
66
- --max-price <USD> Max nightly rate in USD
66
+ --children <N> Number of children
67
+ --children-ages <A,B> Child ages, comma-separated, example 4,7
68
+ --free-cancellation Only show hotels with free cancellation
69
+ --hotel-class <A,B> Hotel star class, comma-separated: 2,3,4,5
70
+ --min-price <USD> Lower price bound in USD
71
+ --max-price <USD> Upper price bound in USD
67
72
  --rating <3.5|4|4.5|5> Minimum guest rating
68
73
 
69
74
  Places required:
@@ -135,26 +140,50 @@ export async function runWayfinder(
135
140
 
136
141
  const apiKey = resolveApiKey(env, homeDir);
137
142
  if (parsed.mode === "flights") {
138
- const flights = await searchFlights(parsed.query, apiKey, options.fetchImpl ?? fetch);
143
+ if (isMultiDateFlightQuery(parsed.query)) {
144
+ const multiDateFlights = await searchFlightsMultiDate(parsed.query, apiKey, options.fetchImpl ?? fetch);
145
+ const nonEmptyDateResults = multiDateFlights.resultsByDate.filter((entry) => entry.results.length > 0);
139
146
 
140
- if (flights.options.length === 0) {
141
- throw new CliError("No flights found for the selected query", ExitCode.NoResults);
142
- }
147
+ if (nonEmptyDateResults.length === 0) {
148
+ throw new CliError("No flights found for the selected query", ExitCode.NoResults);
149
+ }
143
150
 
144
- if (parsed.outputJson) {
145
- output.stdout(
146
- JSON.stringify(
147
- {
148
- query: parsed.query,
149
- googleFlightsUrl: flights.googleFlightsUrl,
150
- results: flights.options,
151
- },
152
- null,
153
- 2,
154
- ),
155
- );
151
+ if (parsed.outputJson) {
152
+ output.stdout(
153
+ JSON.stringify(
154
+ {
155
+ query: parsed.query,
156
+ resultsByDate: nonEmptyDateResults,
157
+ },
158
+ null,
159
+ 2,
160
+ ),
161
+ );
162
+ } else {
163
+ output.stdout(renderFlightTablesByDate(nonEmptyDateResults));
164
+ }
156
165
  } else {
157
- output.stdout(renderFlightTable(flights.options));
166
+ const flights = await searchFlights(parsed.query, apiKey, options.fetchImpl ?? fetch);
167
+
168
+ if (flights.options.length === 0) {
169
+ throw new CliError("No flights found for the selected query", ExitCode.NoResults);
170
+ }
171
+
172
+ if (parsed.outputJson) {
173
+ output.stdout(
174
+ JSON.stringify(
175
+ {
176
+ query: parsed.query,
177
+ googleFlightsUrl: flights.googleFlightsUrl,
178
+ results: flights.options,
179
+ },
180
+ null,
181
+ 2,
182
+ ),
183
+ );
184
+ } else {
185
+ output.stdout(renderFlightTable(flights.options));
186
+ }
158
187
  }
159
188
  } else if (parsed.mode === "flight-booking") {
160
189
  const bookingResults = await searchFlightBookingOptions(
@@ -340,3 +369,7 @@ async function promptWithFallback(
340
369
  rl.close();
341
370
  }
342
371
  }
372
+
373
+ function isMultiDateFlightQuery(query: FlightsQuery): query is FlightMultiDateQuery {
374
+ return "departureDates" in query;
375
+ }
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,7 +2,8 @@ import { CliError } from "./errors";
2
2
  import {
3
3
  ExitCode,
4
4
  FlightBookingQuery,
5
- FlightQuery,
5
+ FlightsQuery,
6
+ HotelClass,
6
7
  HotelQuery,
7
8
  ParsedArgs,
8
9
  PlaceQuery,
@@ -13,7 +14,7 @@ import {
13
14
  interface FlightRawOptions {
14
15
  from?: string;
15
16
  to?: string;
16
- date?: string;
17
+ dates: string[];
17
18
  airline?: string;
18
19
  maxStops?: string;
19
20
  maxPrice?: string;
@@ -29,6 +30,11 @@ interface HotelRawOptions {
29
30
  checkIn?: string;
30
31
  checkOut?: string;
31
32
  adults?: string;
33
+ children?: string;
34
+ childrenAges?: string;
35
+ freeCancellation: boolean;
36
+ hotelClass?: string;
37
+ minPrice?: string;
32
38
  maxPrice?: string;
33
39
  rating?: string;
34
40
  outputJson: boolean;
@@ -88,6 +94,7 @@ export function parseCliArgs(argv: string[]): ParsedArgs {
88
94
 
89
95
  function parseFlightsArgs(args: string[]): ParsedArgs {
90
96
  const raw: FlightRawOptions = {
97
+ dates: [],
91
98
  excludeBasic: false,
92
99
  outputJson: false,
93
100
  help: false,
@@ -128,7 +135,7 @@ function parseFlightsArgs(args: string[]): ParsedArgs {
128
135
  raw.to = value;
129
136
  break;
130
137
  case "--date":
131
- raw.date = value;
138
+ raw.dates.push(value);
132
139
  break;
133
140
  case "--airline":
134
141
  raw.airline = value;
@@ -169,6 +176,7 @@ function parseFlightsArgs(args: string[]): ParsedArgs {
169
176
 
170
177
  function parseHotelsArgs(args: string[]): ParsedArgs {
171
178
  const raw: HotelRawOptions = {
179
+ freeCancellation: false,
172
180
  outputJson: false,
173
181
  help: false,
174
182
  };
@@ -186,6 +194,11 @@ function parseHotelsArgs(args: string[]): ParsedArgs {
186
194
  continue;
187
195
  }
188
196
 
197
+ if (token === "--free-cancellation") {
198
+ raw.freeCancellation = true;
199
+ continue;
200
+ }
201
+
189
202
  if (!token.startsWith("--")) {
190
203
  throw new CliError(`Unexpected argument: ${token}`, ExitCode.InvalidInput);
191
204
  }
@@ -208,6 +221,18 @@ function parseHotelsArgs(args: string[]): ParsedArgs {
208
221
  case "--adults":
209
222
  raw.adults = value;
210
223
  break;
224
+ case "--children":
225
+ raw.children = value;
226
+ break;
227
+ case "--children-ages":
228
+ raw.childrenAges = value;
229
+ break;
230
+ case "--hotel-class":
231
+ raw.hotelClass = value;
232
+ break;
233
+ case "--min-price":
234
+ raw.minPrice = value;
235
+ break;
211
236
  case "--max-price":
212
237
  raw.maxPrice = value;
213
238
  break;
@@ -382,8 +407,8 @@ function parseSetupArgs(args: string[]): ParsedArgs {
382
407
  };
383
408
  }
384
409
 
385
- function buildFlightQuery(raw: FlightRawOptions): FlightQuery {
386
- if (!raw.from || !raw.to || !raw.date) {
410
+ function buildFlightQuery(raw: FlightRawOptions): FlightsQuery {
411
+ if (!raw.from || !raw.to || raw.dates.length === 0) {
387
412
  throw new CliError("Missing required flags: --from, --to, --date", ExitCode.InvalidInput);
388
413
  }
389
414
 
@@ -394,10 +419,17 @@ function buildFlightQuery(raw: FlightRawOptions): FlightQuery {
394
419
  throw new CliError("Origin and destination must be different airports", ExitCode.InvalidInput);
395
420
  }
396
421
 
397
- const departureDate = normalizeDate(raw.date, "departure");
422
+ const departureDates = [...new Set(raw.dates.map((date) => normalizeDate(date, "departure")))];
423
+ if (departureDates.length > 3) {
424
+ throw new CliError(
425
+ "Too many dates: maximum 3 unique --date values per search",
426
+ ExitCode.InvalidInput,
427
+ );
428
+ }
429
+
398
430
  const airlineCode = raw.airline ? normalizeAirlineCode(raw.airline) : undefined;
399
431
  const maxStops = raw.maxStops ? normalizeMaxStops(raw.maxStops) : undefined;
400
- const maxPrice = raw.maxPrice ? normalizeMaxPrice(raw.maxPrice) : undefined;
432
+ const maxPrice = raw.maxPrice ? normalizePrice(raw.maxPrice, "--max-price") : undefined;
401
433
 
402
434
  const hasDepartAfter = typeof raw.departAfter === "string";
403
435
  const hasDepartBefore = typeof raw.departBefore === "string";
@@ -424,10 +456,9 @@ function buildFlightQuery(raw: FlightRawOptions): FlightQuery {
424
456
  }
425
457
  }
426
458
 
427
- return {
459
+ const shared = {
428
460
  origin,
429
461
  destination,
430
- departureDate,
431
462
  airlineCode,
432
463
  maxStops,
433
464
  maxPrice,
@@ -435,6 +466,18 @@ function buildFlightQuery(raw: FlightRawOptions): FlightQuery {
435
466
  departureBeforeMinutes,
436
467
  excludeBasic: raw.excludeBasic || undefined,
437
468
  };
469
+
470
+ if (departureDates.length === 1) {
471
+ return {
472
+ ...shared,
473
+ departureDate: departureDates[0] as string,
474
+ };
475
+ }
476
+
477
+ return {
478
+ ...shared,
479
+ departureDates,
480
+ };
438
481
  }
439
482
 
440
483
  function buildHotelQuery(raw: HotelRawOptions): HotelQuery {
@@ -449,7 +492,12 @@ function buildHotelQuery(raw: HotelRawOptions): HotelQuery {
449
492
  const checkInDate = normalizeDate(raw.checkIn, "check-in");
450
493
  const checkOutDate = normalizeDate(raw.checkOut, "check-out");
451
494
  const adults = raw.adults ? normalizeAdults(raw.adults) : 2;
452
- const maxPrice = raw.maxPrice ? normalizeMaxPrice(raw.maxPrice) : undefined;
495
+ const children = raw.children ? normalizeChildren(raw.children) : 0;
496
+ const childrenAges = raw.childrenAges ? normalizeChildrenAges(raw.childrenAges) : undefined;
497
+ const freeCancellation = raw.freeCancellation || undefined;
498
+ const hotelClasses = raw.hotelClass ? normalizeHotelClasses(raw.hotelClass) : undefined;
499
+ const minPrice = raw.minPrice ? normalizePrice(raw.minPrice, "--min-price") : undefined;
500
+ const maxPrice = raw.maxPrice ? normalizePrice(raw.maxPrice, "--max-price") : undefined;
453
501
  const minRating = raw.rating ? normalizeMinRating(raw.rating) : undefined;
454
502
 
455
503
  const checkIn = parseDateOnly(checkInDate);
@@ -459,11 +507,31 @@ function buildHotelQuery(raw: HotelRawOptions): HotelQuery {
459
507
  throw new CliError("Check-out date must be after check-in date", ExitCode.InvalidInput);
460
508
  }
461
509
 
510
+ if (childrenAges && children === 0) {
511
+ throw new CliError("--children-ages requires --children", ExitCode.InvalidInput);
512
+ }
513
+
514
+ if (childrenAges && childrenAges.length !== children) {
515
+ throw new CliError(
516
+ "--children-ages count must match --children",
517
+ ExitCode.InvalidInput,
518
+ );
519
+ }
520
+
521
+ if (typeof minPrice === "number" && typeof maxPrice === "number" && minPrice > maxPrice) {
522
+ throw new CliError("--min-price cannot be greater than --max-price", ExitCode.InvalidInput);
523
+ }
524
+
462
525
  return {
463
526
  location,
464
527
  checkInDate,
465
528
  checkOutDate,
466
529
  adults,
530
+ children,
531
+ childrenAges,
532
+ freeCancellation,
533
+ hotelClasses,
534
+ minPrice,
467
535
  maxPrice,
468
536
  minRating,
469
537
  };
@@ -621,11 +689,17 @@ function normalizeMaxStops(value: string): number {
621
689
  return numeric;
622
690
  }
623
691
 
624
- function normalizeMaxPrice(value: string): number {
625
- const numeric = Number.parseInt(value, 10);
692
+ function normalizePrice(value: string, flagName: "--min-price" | "--max-price"): number {
693
+ const trimmed = value.trim();
694
+ if (!/^\d+$/.test(trimmed)) {
695
+ throw new CliError(`${flagName} must be a positive integer`, ExitCode.InvalidInput);
696
+ }
697
+
698
+ const numeric = Number.parseInt(trimmed, 10);
626
699
  if (!Number.isInteger(numeric) || numeric <= 0) {
627
- throw new CliError("--max-price must be a positive integer", ExitCode.InvalidInput);
700
+ throw new CliError(`${flagName} must be a positive integer`, ExitCode.InvalidInput);
628
701
  }
702
+
629
703
  return numeric;
630
704
  }
631
705
 
@@ -647,6 +721,62 @@ function normalizeAdults(value: string): number {
647
721
  return numeric;
648
722
  }
649
723
 
724
+ function normalizeChildren(value: string): number {
725
+ if (!/^\d+$/.test(value.trim())) {
726
+ throw new CliError("--children must be a positive integer", ExitCode.InvalidInput);
727
+ }
728
+
729
+ const numeric = Number.parseInt(value, 10);
730
+ if (!Number.isInteger(numeric) || numeric <= 0) {
731
+ throw new CliError("--children must be a positive integer", ExitCode.InvalidInput);
732
+ }
733
+
734
+ return numeric;
735
+ }
736
+
737
+ function normalizeChildrenAges(value: string): number[] {
738
+ const parts = value.split(",");
739
+ if (parts.length === 0) {
740
+ throw new CliError(
741
+ "--children-ages must be a comma-separated list of ages 1 through 17",
742
+ ExitCode.InvalidInput,
743
+ );
744
+ }
745
+
746
+ return parts.map((part) => {
747
+ const trimmed = part.trim();
748
+ if (!/^\d+$/.test(trimmed)) {
749
+ throw new CliError(
750
+ "--children-ages must be a comma-separated list of ages 1 through 17",
751
+ ExitCode.InvalidInput,
752
+ );
753
+ }
754
+
755
+ const age = Number.parseInt(trimmed, 10);
756
+ if (age < 1 || age > 17) {
757
+ throw new CliError("--children-ages ages must be between 1 and 17", ExitCode.InvalidInput);
758
+ }
759
+
760
+ return age;
761
+ });
762
+ }
763
+
764
+ function normalizeHotelClasses(value: string): HotelClass[] {
765
+ const hotelClasses = value.split(",").map((part) => {
766
+ const trimmed = part.trim();
767
+ if (trimmed !== "2" && trimmed !== "3" && trimmed !== "4" && trimmed !== "5") {
768
+ throw new CliError(
769
+ "--hotel-class must be a comma-separated list of: 2, 3, 4, 5",
770
+ ExitCode.InvalidInput,
771
+ );
772
+ }
773
+
774
+ return Number.parseInt(trimmed, 10) as HotelClass;
775
+ });
776
+
777
+ return [...new Set(hotelClasses)].sort((a, b) => a - b);
778
+ }
779
+
650
780
  function normalizeMinRating(value: string): 3.5 | 4 | 4.5 | 5 {
651
781
  const numeric = Number.parseFloat(value);
652
782
  if (numeric !== 3.5 && numeric !== 4 && numeric !== 4.5 && numeric !== 5) {
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,
@@ -370,9 +405,26 @@ function buildHotelRequestUrl(query: HotelQuery, apiKey: string): string {
370
405
  url.searchParams.set("check_in_date", query.checkInDate);
371
406
  url.searchParams.set("check_out_date", query.checkOutDate);
372
407
  url.searchParams.set("adults", String(query.adults));
408
+ url.searchParams.set("children", String(query.children));
373
409
  url.searchParams.set("currency", "USD");
374
410
  url.searchParams.set("api_key", apiKey);
375
411
 
412
+ if (query.childrenAges && query.childrenAges.length > 0) {
413
+ url.searchParams.set("children_ages", query.childrenAges.join(","));
414
+ }
415
+
416
+ if (query.freeCancellation) {
417
+ url.searchParams.set("free_cancellation", "true");
418
+ }
419
+
420
+ if (query.hotelClasses && query.hotelClasses.length > 0) {
421
+ url.searchParams.set("hotel_class", query.hotelClasses.join(","));
422
+ }
423
+
424
+ if (typeof query.minPrice === "number") {
425
+ url.searchParams.set("min_price", String(query.minPrice));
426
+ }
427
+
376
428
  if (typeof query.maxPrice === "number") {
377
429
  url.searchParams.set("max_price", String(query.maxPrice));
378
430
  }
@@ -685,3 +737,33 @@ function collectBookingLinks(
685
737
  collectBookingLinks(child, links);
686
738
  }
687
739
  }
740
+
741
+ async function mapWithConcurrency<TInput, TOutput>(
742
+ items: TInput[],
743
+ concurrency: number,
744
+ worker: (item: TInput, index: number) => Promise<TOutput>,
745
+ ): Promise<TOutput[]> {
746
+ const output: TOutput[] = new Array(items.length);
747
+ let cursor = 0;
748
+
749
+ async function runWorker(): Promise<void> {
750
+ while (true) {
751
+ const index = cursor;
752
+ cursor += 1;
753
+
754
+ if (index >= items.length) {
755
+ return;
756
+ }
757
+
758
+ output[index] = await worker(items[index] as TInput, index);
759
+ }
760
+ }
761
+
762
+ const workers = Array.from(
763
+ { length: Math.min(Math.max(concurrency, 1), items.length) },
764
+ () => runWorker(),
765
+ );
766
+
767
+ await Promise.all(workers);
768
+ return output;
769
+ }
package/src/types.ts CHANGED
@@ -21,11 +21,32 @@ 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
+
38
+ export type HotelClass = 2 | 3 | 4 | 5;
39
+
24
40
  export interface HotelQuery {
25
41
  location: string;
26
42
  checkInDate: string;
27
43
  checkOutDate: string;
28
44
  adults: number;
45
+ children: number;
46
+ childrenAges?: number[];
47
+ freeCancellation?: boolean;
48
+ hotelClasses?: HotelClass[];
49
+ minPrice?: number;
29
50
  maxPrice?: number;
30
51
  minRating?: 3.5 | 4 | 4.5 | 5;
31
52
  }
@@ -49,7 +70,7 @@ export interface ParsedArgsFlights {
49
70
  help: false;
50
71
  mode: "flights";
51
72
  outputJson: boolean;
52
- query: FlightQuery;
73
+ query: FlightsQuery;
53
74
  }
54
75
 
55
76
  export interface ParsedArgsHotels {
@@ -133,6 +154,16 @@ export interface FlightSearchResult {
133
154
  googleFlightsUrl?: string;
134
155
  }
135
156
 
157
+ export interface FlightDateResult {
158
+ date: string;
159
+ results: FlightOption[];
160
+ googleFlightsUrl?: string;
161
+ }
162
+
163
+ export interface FlightMultiDateSearchResult {
164
+ resultsByDate: FlightDateResult[];
165
+ }
166
+
136
167
  export interface FlightBookingLink {
137
168
  url: string;
138
169
  source?: string;