imprint-mcp 0.4.9 → 0.4.11

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.
@@ -19,8 +19,10 @@ function buildLeg(leg: any): any[] {
19
19
  out[1] = [[[leg?.dest, 0]]];
20
20
  out[2] = leg?.times ?? null;
21
21
  out[3] = leg?.stops ?? 0;
22
- out[4] = leg?.alliances ?? null;
23
- out[5] = leg?.carriers ?? null;
22
+ // Google uses slot 4 for included alliances and carrier codes. Slot 5 is an
23
+ // exclusion list; putting carrier codes there inverts the filter.
24
+ out[4] = leg?.includeAirlines ?? leg?.alliances ?? null;
25
+ out[5] = leg?.excludeAirlines ?? null;
24
26
  out[6] = leg?.date ?? null;
25
27
  out[7] = leg?.duration ?? null;
26
28
  out[8] = Array.isArray(leg?.selected)
@@ -19,7 +19,13 @@ interface Itinerary {
19
19
  flight_token: string;
20
20
  }
21
21
 
22
+ interface AirlineFilter {
23
+ code: string;
24
+ name: string;
25
+ }
26
+
22
27
  const AIRPORT = /^[A-Z]{3}$/;
28
+ const ALLIANCE_CODES = new Set(['ONEWORLD', 'SKYTEAM', 'STAR_ALLIANCE']);
23
29
 
24
30
  // A leg is [carrierCode, [carrierNames], [segments], originIATA, [departDate],
25
31
  // [departTime], destIATA, [arriveDate], [arriveTime], durationMinutes, ...].
@@ -79,6 +85,38 @@ function walk(node: unknown, found: unknown[][]): void {
79
85
  for (const child of node) walk(child, found);
80
86
  }
81
87
 
88
+ function isPairList(node: unknown): node is string[][] {
89
+ return (
90
+ Array.isArray(node) &&
91
+ node.length > 0 &&
92
+ node.every(
93
+ (item) =>
94
+ Array.isArray(item) && typeof item[0] === 'string' && typeof item[1] === 'string',
95
+ )
96
+ );
97
+ }
98
+
99
+ function toFilters(pairs: string[][]): AirlineFilter[] {
100
+ return pairs.map((pair) => ({ code: pair[0] as string, name: pair[1] as string }));
101
+ }
102
+
103
+ function collectAirlineFilters(
104
+ node: unknown,
105
+ found: { alliances: AirlineFilter[]; carriers: AirlineFilter[] },
106
+ ): void {
107
+ if (!Array.isArray(node)) return;
108
+ if (
109
+ node.length >= 2 &&
110
+ isPairList(node[0]) &&
111
+ isPairList(node[1]) &&
112
+ node[0].some((pair) => ALLIANCE_CODES.has(pair[0] as string))
113
+ ) {
114
+ found.alliances = toFilters(node[0]);
115
+ found.carriers = toFilters(node[1]);
116
+ }
117
+ for (const child of node) collectAirlineFilters(child, found);
118
+ }
119
+
82
120
  function normalize(it: unknown[]): Itinerary {
83
121
  const legs = legsOf(it);
84
122
  const priceTok = it[1] as unknown[];
@@ -158,6 +196,11 @@ export function extract(
158
196
 
159
197
  const found: unknown[][] = [];
160
198
  if (payload != null) walk(payload, found);
199
+ const availableAirlineFilters = {
200
+ alliances: [] as AirlineFilter[],
201
+ carriers: [] as AirlineFilter[],
202
+ };
203
+ if (payload != null) collectAirlineFilters(payload, availableAirlineFilters);
161
204
 
162
205
  const byToken = new Map<string, Itinerary>();
163
206
  for (const it of found) {
@@ -175,5 +218,11 @@ export function extract(
175
218
  return {
176
219
  count: itineraries.length,
177
220
  itineraries,
221
+ resultScope: {
222
+ exhaustive: false,
223
+ note:
224
+ 'Google Flights GetShoppingResults returns a limited sorted subset. A carrier can be available in availableAirlineFilters without appearing in itineraries; call search_flights again with airlines=<code> to fetch that carrier.',
225
+ },
226
+ availableAirlineFilters,
178
227
  };
179
228
  }
@@ -1,15 +1,13 @@
1
1
  // Adapter around the shared FlightsFrontendService body builder.
2
2
  // The tool exposes flat snake_case params (origin, destination, departure_date,
3
3
  // max_stops, …); the shared encoder consumes a structured camelCase shape
4
- // ({ tripType, legs:[{origin,dest,date,times,stops,alliances,carriers,duration}],
4
+ // ({ tripType, legs:[{origin,dest,date,times,stops,includeAirlines,duration}],
5
5
  // maxPrice, bags }). We map between them here and delegate the byte-for-byte
6
6
  // positional encoding to the shared module (required reuse).
7
7
  import { transform as sharedTransform } from '../_shared/flights_request.ts';
8
8
 
9
9
  type Params = Record<string, string | number | boolean | undefined | null>;
10
10
 
11
- const ALLIANCES = new Set(['ONEWORLD', 'SKYTEAM', 'STAR_ALLIANCE']);
12
-
13
11
  function mapTripType(v: unknown): number {
14
12
  if (v == null || v === '') return 1;
15
13
  if (typeof v === 'number') return v;
@@ -42,18 +40,14 @@ function parseTimes(v: unknown): number[] | null {
42
40
  return [Number(m[1]), Number(m[2]), 0, 23];
43
41
  }
44
42
 
45
- function parseAirlines(v: unknown): { alliances: string[] | null; carriers: string[] | null } {
46
- if (v == null || v === '') return { alliances: null, carriers: null };
47
- const parts = String(v)
43
+ function parseAirlines(v: unknown): string[] | null {
44
+ if (v == null || v === '') return null;
45
+ const includeAirlines = String(v)
48
46
  .split(',')
49
47
  .map((x) => x.trim())
50
- .filter(Boolean);
51
- const alliances = parts.filter((p) => ALLIANCES.has(p.toUpperCase())).map((p) => p.toUpperCase());
52
- const carriers = parts.filter((p) => !ALLIANCES.has(p.toUpperCase()));
53
- return {
54
- alliances: alliances.length ? alliances : null,
55
- carriers: carriers.length ? carriers : null,
56
- };
48
+ .filter(Boolean)
49
+ .map((p) => p.toUpperCase());
50
+ return includeAirlines.length ? includeAirlines : null;
57
51
  }
58
52
 
59
53
  function num(v: unknown): number | undefined {
@@ -73,7 +67,7 @@ export function transform(
73
67
  const hasReturnDate = p.return_date != null && String(p.return_date).trim() !== '';
74
68
  const tripType = requestedTripType === 1 && !hasReturnDate ? 2 : requestedTripType;
75
69
  const stops = p.max_stops != null && p.max_stops !== '' ? mapStops(p.max_stops) : 0;
76
- const { alliances, carriers } = parseAirlines(p.airlines);
70
+ const includeAirlines = parseAirlines(p.airlines);
77
71
  const maxDur = num(p.max_duration);
78
72
  const duration = maxDur != null ? [maxDur] : null;
79
73
 
@@ -87,8 +81,7 @@ export function transform(
87
81
  date: p.departure_date ? String(p.departure_date) : null,
88
82
  times: parseTimes(p.outbound_times),
89
83
  stops,
90
- alliances,
91
- carriers,
84
+ includeAirlines,
92
85
  duration,
93
86
  },
94
87
  ];
@@ -101,8 +94,7 @@ export function transform(
101
94
  date: String(p.return_date),
102
95
  times: parseTimes(p.return_times),
103
96
  stops,
104
- alliances,
105
- carriers,
97
+ includeAirlines,
106
98
  duration,
107
99
  });
108
100
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "imprint-mcp",
3
- "version": "0.4.9",
3
+ "version": "0.4.11",
4
4
  "description": "Teach an AI agent how to use any website. Once. Records a real browser session + narration; generates a deterministic MCP tool plus a DOM-replay playbook fallback.",
5
5
  "type": "module",
6
6
  "exports": {
@@ -19,6 +19,7 @@
19
19
  "src/",
20
20
  "prompts/",
21
21
  "examples/",
22
+ "tsconfig.json",
22
23
  "README.md",
23
24
  "LICENSE",
24
25
  "CHANGELOG.md"
package/tsconfig.json ADDED
@@ -0,0 +1,35 @@
1
+ {
2
+ "compilerOptions": {
3
+ "target": "ES2022",
4
+ "module": "ESNext",
5
+ "moduleResolution": "bundler",
6
+ "lib": ["ES2022"],
7
+ "types": ["node", "bun"],
8
+
9
+ "strict": true,
10
+ "noUncheckedIndexedAccess": true,
11
+ "noImplicitOverride": true,
12
+ "noPropertyAccessFromIndexSignature": false,
13
+ "noFallthroughCasesInSwitch": true,
14
+ "noImplicitReturns": true,
15
+ "noUnusedLocals": true,
16
+ "noUnusedParameters": true,
17
+
18
+ "esModuleInterop": true,
19
+ "allowSyntheticDefaultImports": true,
20
+ "resolveJsonModule": true,
21
+ "skipLibCheck": true,
22
+ "forceConsistentCasingInFileNames": true,
23
+
24
+ "noEmit": true,
25
+ "allowImportingTsExtensions": true,
26
+ "verbatimModuleSyntax": false,
27
+
28
+ "paths": {
29
+ "imprint/runtime": ["./src/imprint/runtime.ts"],
30
+ "imprint/types": ["./src/imprint/types.ts"]
31
+ }
32
+ },
33
+ "include": ["src/**/*", "test/**/*", "examples/**/*"],
34
+ "exclude": ["node_modules", "dist"]
35
+ }