imprint-mcp 0.2.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/CHANGELOG.md +168 -0
- package/LICENSE +21 -0
- package/README.md +322 -0
- package/examples/discoverandgo/README.md +57 -0
- package/examples/discoverandgo/book_discoverandgo_museum_pass/cron.json +8 -0
- package/examples/discoverandgo/book_discoverandgo_museum_pass/index.ts +89 -0
- package/examples/discoverandgo/book_discoverandgo_museum_pass/workflow.json +39 -0
- package/examples/echo/README.md +37 -0
- package/examples/echo/echo_test/index.ts +31 -0
- package/examples/google-flights/search_google_flights/index.ts +101 -0
- package/examples/google-flights/search_google_flights/parser.test.ts +140 -0
- package/examples/google-flights/search_google_flights/parser.ts +189 -0
- package/examples/google-flights/search_google_flights/playbook.yaml +130 -0
- package/examples/google-flights/search_google_flights/workflow.json +48 -0
- package/examples/google-hotels/search_google_hotels/index.ts +194 -0
- package/examples/google-hotels/search_google_hotels/parser.test.ts +168 -0
- package/examples/google-hotels/search_google_hotels/parser.ts +330 -0
- package/examples/google-hotels/search_google_hotels/playbook.yaml +125 -0
- package/examples/google-hotels/search_google_hotels/workflow.json +111 -0
- package/examples/namecheap-domains/search_namecheap_domains/index.ts +144 -0
- package/examples/namecheap-domains/search_namecheap_domains/parser.ts +380 -0
- package/examples/namecheap-domains/search_namecheap_domains/playbook.yaml +50 -0
- package/examples/namecheap-domains/search_namecheap_domains/request-transform.ts +136 -0
- package/examples/namecheap-domains/search_namecheap_domains/workflow.json +97 -0
- package/examples/southwest/README.md +81 -0
- package/examples/southwest/search_southwest_flights/backends.json +23 -0
- package/examples/southwest/search_southwest_flights/cron.json +19 -0
- package/examples/southwest/search_southwest_flights/index.ts +110 -0
- package/examples/southwest/search_southwest_flights/playbook.yaml +46 -0
- package/examples/southwest/search_southwest_flights/workflow.json +54 -0
- package/package.json +78 -0
- package/prompts/compile-agent.md +580 -0
- package/prompts/intent-detection.md +198 -0
- package/prompts/playbook-compilation.md +279 -0
- package/prompts/request-triage.md +74 -0
- package/prompts/tool-candidate-detection.md +104 -0
- package/src/cli.ts +1287 -0
- package/src/imprint/agent.ts +468 -0
- package/src/imprint/app-api-hosts.ts +53 -0
- package/src/imprint/backend-ladder.ts +568 -0
- package/src/imprint/check.ts +136 -0
- package/src/imprint/chromium.ts +211 -0
- package/src/imprint/claude-cli-compile.ts +640 -0
- package/src/imprint/cli-credential.ts +394 -0
- package/src/imprint/codex-cli-compile.ts +712 -0
- package/src/imprint/compile-agent-types.ts +40 -0
- package/src/imprint/compile-agent.ts +404 -0
- package/src/imprint/compile-tools.ts +1389 -0
- package/src/imprint/compile.ts +720 -0
- package/src/imprint/cookie-jar.ts +246 -0
- package/src/imprint/credential-bundle.ts +195 -0
- package/src/imprint/credential-extract.ts +290 -0
- package/src/imprint/credential-store.ts +707 -0
- package/src/imprint/cron.ts +312 -0
- package/src/imprint/doctor.ts +223 -0
- package/src/imprint/emit.ts +154 -0
- package/src/imprint/etld.ts +134 -0
- package/src/imprint/freeform-redact.ts +216 -0
- package/src/imprint/inject-listener.ts +137 -0
- package/src/imprint/install.ts +795 -0
- package/src/imprint/integrations.ts +385 -0
- package/src/imprint/is-compiled.ts +2 -0
- package/src/imprint/json-path.ts +100 -0
- package/src/imprint/llm.ts +998 -0
- package/src/imprint/load-json.ts +54 -0
- package/src/imprint/log.ts +33 -0
- package/src/imprint/login.ts +166 -0
- package/src/imprint/mcp-compile-server.ts +282 -0
- package/src/imprint/mcp-maintenance.ts +1790 -0
- package/src/imprint/mcp-server.ts +350 -0
- package/src/imprint/multi-progress.ts +69 -0
- package/src/imprint/notify.ts +155 -0
- package/src/imprint/paths.ts +64 -0
- package/src/imprint/playbook-parser.ts +21 -0
- package/src/imprint/playbook-runner.ts +465 -0
- package/src/imprint/probe-backends.ts +251 -0
- package/src/imprint/progress.ts +28 -0
- package/src/imprint/record.ts +470 -0
- package/src/imprint/redact.ts +550 -0
- package/src/imprint/replay-capture.ts +387 -0
- package/src/imprint/request-context.ts +66 -0
- package/src/imprint/runtime-link.ts +73 -0
- package/src/imprint/runtime.ts +942 -0
- package/src/imprint/sensitive-keys.ts +156 -0
- package/src/imprint/session-diff.ts +409 -0
- package/src/imprint/session-merge.ts +198 -0
- package/src/imprint/session-writer.ts +149 -0
- package/src/imprint/sites.ts +27 -0
- package/src/imprint/stealth-fetch.ts +434 -0
- package/src/imprint/teach-state.ts +235 -0
- package/src/imprint/teach.ts +2120 -0
- package/src/imprint/tool-candidates.ts +423 -0
- package/src/imprint/tool-loader.ts +186 -0
- package/src/imprint/tool-selection.ts +70 -0
- package/src/imprint/tracing.ts +508 -0
- package/src/imprint/types.ts +472 -0
- package/src/imprint/version.ts +21 -0
|
@@ -0,0 +1,330 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Parser for Google Hotels search results (Travel Frontend UI batchexecute, rpcid AtySUc).
|
|
3
|
+
*
|
|
4
|
+
* The session captured a search for hotels in Tahoe City, June 19–27 2026,
|
|
5
|
+
* 1 room with adults + 2 children (ages 3 + 3). The raw response is Google's
|
|
6
|
+
* batchexecute envelope:
|
|
7
|
+
*
|
|
8
|
+
* )]}'
|
|
9
|
+
* <chunkSize>
|
|
10
|
+
* [["wrb.fr","AtySUc","<JSON-string of the actual JSPB payload>",null,null,null,"generic"]]
|
|
11
|
+
* <chunkSize>
|
|
12
|
+
* [["di",NNN], …]
|
|
13
|
+
*
|
|
14
|
+
* The payload is positional JSPB (no field names — values are addressed by
|
|
15
|
+
* array index). Hotels live in "section" sub-arrays under
|
|
16
|
+
* payload[0][0][0][1][i] = [section_type, { "<field_id>": <data> }]
|
|
17
|
+
* where section_type === 34 carries hotel records under key "397419284".
|
|
18
|
+
*
|
|
19
|
+
* Each hotel record is a 48-element array. Empirically observed indices:
|
|
20
|
+
* rec[1] string hotel name
|
|
21
|
+
* rec[2][0] [lat, lng]
|
|
22
|
+
* rec[3] ["3-star hotel", 3] star description + rating
|
|
23
|
+
* rec[6][1][4] [[Y,M,D],[Y,M,D],nights,rooms] search stay
|
|
24
|
+
* rec[6][2][1] [nightlyDisplay, nightlyDisplayHigher, nightlyValueA, null, nightlyValueB]
|
|
25
|
+
* rec[6][2][9] [totalDisplay, totalDisplayHigher]
|
|
26
|
+
* rec[7][0] [guestRating, reviewCount]
|
|
27
|
+
* rec[9] Maps internal place id "0x...:0x..."
|
|
28
|
+
* rec[11][0] description / tagline
|
|
29
|
+
* rec[12][0] primary photo URL
|
|
30
|
+
* rec[20] opaque hotel token "ChY..." (for hotel-detail fetches)
|
|
31
|
+
* rec[25] numeric Google hotel id
|
|
32
|
+
*
|
|
33
|
+
* Top-level metadata (search header + currency) is in section_type === 53
|
|
34
|
+
* under key "416343588" → [resultCount, …, destinationName, …].
|
|
35
|
+
*/
|
|
36
|
+
|
|
37
|
+
export type Hotel = {
|
|
38
|
+
name: string;
|
|
39
|
+
latitude: number | null;
|
|
40
|
+
longitude: number | null;
|
|
41
|
+
starDescription: string | null;
|
|
42
|
+
starRating: number | null;
|
|
43
|
+
guestRating: number | null;
|
|
44
|
+
reviewCount: number | null;
|
|
45
|
+
nightlyPrice: string | null;
|
|
46
|
+
nightlyPriceValue: number | null;
|
|
47
|
+
totalPrice: string | null;
|
|
48
|
+
totalPriceValue: number | null;
|
|
49
|
+
description: string | null;
|
|
50
|
+
photoUrl: string | null;
|
|
51
|
+
hotelId: string | null;
|
|
52
|
+
hotelToken: string | null;
|
|
53
|
+
mapsFeatureId: string | null;
|
|
54
|
+
checkInDate: string | null;
|
|
55
|
+
checkOutDate: string | null;
|
|
56
|
+
nights: number | null;
|
|
57
|
+
};
|
|
58
|
+
|
|
59
|
+
export type ExtractResult = {
|
|
60
|
+
destination: string | null;
|
|
61
|
+
totalResults: number | null;
|
|
62
|
+
currency: string;
|
|
63
|
+
checkInDate: string | null;
|
|
64
|
+
checkOutDate: string | null;
|
|
65
|
+
nights: number | null;
|
|
66
|
+
hotelCount: number;
|
|
67
|
+
hotels: Hotel[];
|
|
68
|
+
};
|
|
69
|
+
|
|
70
|
+
// ─── helpers ───────────────────────────────────────────────────────────────
|
|
71
|
+
|
|
72
|
+
function dateTupleToIso(t: unknown): string | null {
|
|
73
|
+
if (!Array.isArray(t) || t.length < 3) return null;
|
|
74
|
+
const [y, m, d] = t as [unknown, unknown, unknown];
|
|
75
|
+
if (typeof y !== 'number' || typeof m !== 'number' || typeof d !== 'number') return null;
|
|
76
|
+
return `${y}-${String(m).padStart(2, '0')}-${String(d).padStart(2, '0')}`;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
function asArray(x: unknown): unknown[] {
|
|
80
|
+
return Array.isArray(x) ? x : [];
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
function asNumberOrNull(x: unknown): number | null {
|
|
84
|
+
return typeof x === 'number' ? x : null;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
function asStringOrNull(x: unknown): string | null {
|
|
88
|
+
return typeof x === 'string' && x.length > 0 ? x : null;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
// ─── envelope parser ───────────────────────────────────────────────────────
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* Parse Google's batchexecute envelope. Yields the inner JSPB payload of the
|
|
95
|
+
* AtySUc wrb.fr row. Tolerant of off-by-N declared chunk lengths (Google's
|
|
96
|
+
* length counter occasionally counts something other than UTF-16 code units).
|
|
97
|
+
*/
|
|
98
|
+
export function parseEnvelope(raw: string): { rpcid: unknown; payload: unknown } | null {
|
|
99
|
+
let s = raw;
|
|
100
|
+
if (s.startsWith(")]}'")) s = s.slice(4);
|
|
101
|
+
s = s.replace(/^\s+/, '');
|
|
102
|
+
|
|
103
|
+
let i = 0;
|
|
104
|
+
while (i < s.length) {
|
|
105
|
+
while (i < s.length && /\s/.test(s[i]!)) i++;
|
|
106
|
+
if (i >= s.length) break;
|
|
107
|
+
const lenStart = i;
|
|
108
|
+
while (i < s.length && /[0-9]/.test(s[i]!)) i++;
|
|
109
|
+
if (i === lenStart) break;
|
|
110
|
+
const declaredLen = parseInt(s.slice(lenStart, i), 10);
|
|
111
|
+
while (i < s.length && /\s/.test(s[i]!)) i++;
|
|
112
|
+
const chunkStart = i;
|
|
113
|
+
|
|
114
|
+
const chunk = sliceJsonChunk(s, chunkStart, declaredLen);
|
|
115
|
+
if (chunk === null) break;
|
|
116
|
+
i = chunkStart + chunk.length;
|
|
117
|
+
|
|
118
|
+
let parsed: unknown;
|
|
119
|
+
try {
|
|
120
|
+
parsed = JSON.parse(chunk);
|
|
121
|
+
} catch {
|
|
122
|
+
continue;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
if (!Array.isArray(parsed)) continue;
|
|
126
|
+
for (const entry of parsed as unknown[]) {
|
|
127
|
+
if (!Array.isArray(entry)) continue;
|
|
128
|
+
const [tag, rpcid, payloadStr] = entry as unknown[];
|
|
129
|
+
if (tag === 'wrb.fr' && typeof payloadStr === 'string') {
|
|
130
|
+
try {
|
|
131
|
+
return { rpcid, payload: JSON.parse(payloadStr) };
|
|
132
|
+
} catch {
|
|
133
|
+
return { rpcid, payload: null };
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
return null;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
function sliceJsonChunk(s: string, start: number, declaredLen: number): string | null {
|
|
142
|
+
const tryParse = (len: number): string | null => {
|
|
143
|
+
if (len < 1 || start + len > s.length) return null;
|
|
144
|
+
const c = s.slice(start, start + len);
|
|
145
|
+
try {
|
|
146
|
+
JSON.parse(c);
|
|
147
|
+
return c;
|
|
148
|
+
} catch {
|
|
149
|
+
return null;
|
|
150
|
+
}
|
|
151
|
+
};
|
|
152
|
+
const exact = tryParse(declaredLen);
|
|
153
|
+
if (exact !== null) return exact;
|
|
154
|
+
for (let d = 1; d <= 16; d++) {
|
|
155
|
+
const minus = tryParse(declaredLen - d);
|
|
156
|
+
if (minus !== null) return minus;
|
|
157
|
+
const plus = tryParse(declaredLen + d);
|
|
158
|
+
if (plus !== null) return plus;
|
|
159
|
+
}
|
|
160
|
+
return null;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
// ─── hotel record extraction ───────────────────────────────────────────────
|
|
164
|
+
|
|
165
|
+
function extractHotel(rec: unknown[]): Hotel | null {
|
|
166
|
+
if (!Array.isArray(rec) || rec.length < 12) return null;
|
|
167
|
+
const name = asStringOrNull(rec[1]);
|
|
168
|
+
if (!name) return null;
|
|
169
|
+
|
|
170
|
+
// location
|
|
171
|
+
const locInfo = asArray(rec[2]);
|
|
172
|
+
const latLng = asArray(locInfo[0]);
|
|
173
|
+
const latitude = asNumberOrNull(latLng[0]);
|
|
174
|
+
const longitude = asNumberOrNull(latLng[1]);
|
|
175
|
+
|
|
176
|
+
// stars
|
|
177
|
+
const starInfo = asArray(rec[3]);
|
|
178
|
+
const starDescription = asStringOrNull(starInfo[0]);
|
|
179
|
+
const starRating = asNumberOrNull(starInfo[1]);
|
|
180
|
+
|
|
181
|
+
// pricing block — rec[6]
|
|
182
|
+
const priceBlock = asArray(rec[6]);
|
|
183
|
+
const stay = asArray(asArray(priceBlock[1])[4]);
|
|
184
|
+
const checkInDate = dateTupleToIso(stay[0]);
|
|
185
|
+
const checkOutDate = dateTupleToIso(stay[1]);
|
|
186
|
+
const nights = asNumberOrNull(stay[2]);
|
|
187
|
+
|
|
188
|
+
const priceArr = asArray(priceBlock[2]);
|
|
189
|
+
const nightlyTuple = asArray(priceArr[1]);
|
|
190
|
+
const nightlyPrice = asStringOrNull(nightlyTuple[0]);
|
|
191
|
+
const nightlyPriceValue =
|
|
192
|
+
asNumberOrNull(nightlyTuple[2]) ??
|
|
193
|
+
asNumberOrNull(nightlyTuple[4]);
|
|
194
|
+
|
|
195
|
+
const totalTuple = asArray(priceArr[9]);
|
|
196
|
+
const totalPrice = asStringOrNull(totalTuple[0]);
|
|
197
|
+
const totalPriceValue =
|
|
198
|
+
asNumberOrNull(totalTuple[2]) ?? asNumberOrNull(totalTuple[4]);
|
|
199
|
+
|
|
200
|
+
// ratings — rec[7][0] = [guestRating, reviewCount]
|
|
201
|
+
const ratingTuple = asArray(asArray(rec[7])[0]);
|
|
202
|
+
const guestRating = asNumberOrNull(ratingTuple[0]);
|
|
203
|
+
const reviewCount = asNumberOrNull(ratingTuple[1]);
|
|
204
|
+
|
|
205
|
+
// description / photo
|
|
206
|
+
const description = asStringOrNull(asArray(rec[11])[0]);
|
|
207
|
+
const photoUrl = asStringOrNull(asArray(rec[12])[0]);
|
|
208
|
+
|
|
209
|
+
// identifiers
|
|
210
|
+
const mapsFeatureId = asStringOrNull(rec[9]);
|
|
211
|
+
const hotelToken = asStringOrNull(rec[20]);
|
|
212
|
+
const hotelId = asStringOrNull(rec[25]);
|
|
213
|
+
|
|
214
|
+
return {
|
|
215
|
+
name,
|
|
216
|
+
latitude,
|
|
217
|
+
longitude,
|
|
218
|
+
starDescription,
|
|
219
|
+
starRating,
|
|
220
|
+
guestRating,
|
|
221
|
+
reviewCount,
|
|
222
|
+
nightlyPrice,
|
|
223
|
+
nightlyPriceValue,
|
|
224
|
+
totalPrice,
|
|
225
|
+
totalPriceValue,
|
|
226
|
+
description,
|
|
227
|
+
photoUrl,
|
|
228
|
+
hotelId,
|
|
229
|
+
hotelToken,
|
|
230
|
+
mapsFeatureId,
|
|
231
|
+
checkInDate,
|
|
232
|
+
checkOutDate,
|
|
233
|
+
nights,
|
|
234
|
+
};
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
/**
|
|
238
|
+
* Recursively walk the payload pulling out every "397419284" hotel record.
|
|
239
|
+
* The number of nesting levels is not perfectly stable across query types
|
|
240
|
+
* (recommended hotels vs additional hotels vs vacation rentals), so a key
|
|
241
|
+
* sweep is more robust than hard-coded paths.
|
|
242
|
+
*/
|
|
243
|
+
function collectHotels(node: unknown, out: Hotel[], seen: Set<string>): void {
|
|
244
|
+
if (node === null || node === undefined) return;
|
|
245
|
+
if (Array.isArray(node)) {
|
|
246
|
+
for (const child of node) collectHotels(child, out, seen);
|
|
247
|
+
return;
|
|
248
|
+
}
|
|
249
|
+
if (typeof node !== 'object') return;
|
|
250
|
+
const obj = node as Record<string, unknown>;
|
|
251
|
+
for (const k of Object.keys(obj)) {
|
|
252
|
+
if (k === '397419284' && Array.isArray(obj[k])) {
|
|
253
|
+
const wrapper = obj[k] as unknown[];
|
|
254
|
+
for (const rec of wrapper) {
|
|
255
|
+
if (!Array.isArray(rec)) continue;
|
|
256
|
+
const h = extractHotel(rec);
|
|
257
|
+
if (!h) continue;
|
|
258
|
+
const key =
|
|
259
|
+
h.hotelId ??
|
|
260
|
+
h.hotelToken ??
|
|
261
|
+
`${h.name}|${h.latitude ?? ''}|${h.longitude ?? ''}`;
|
|
262
|
+
if (seen.has(key)) continue;
|
|
263
|
+
seen.add(key);
|
|
264
|
+
out.push(h);
|
|
265
|
+
}
|
|
266
|
+
} else {
|
|
267
|
+
collectHotels(obj[k], out, seen);
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
/** Find the search-meta section (type=53, key=416343588) anywhere in the tree. */
|
|
273
|
+
function findSearchMeta(
|
|
274
|
+
node: unknown,
|
|
275
|
+
): { destination: string | null; totalResults: number | null } | null {
|
|
276
|
+
if (node === null || node === undefined) return null;
|
|
277
|
+
if (Array.isArray(node)) {
|
|
278
|
+
for (const child of node) {
|
|
279
|
+
const r = findSearchMeta(child);
|
|
280
|
+
if (r) return r;
|
|
281
|
+
}
|
|
282
|
+
return null;
|
|
283
|
+
}
|
|
284
|
+
if (typeof node !== 'object') return null;
|
|
285
|
+
const obj = node as Record<string, unknown>;
|
|
286
|
+
if (Array.isArray(obj['416343588'])) {
|
|
287
|
+
const arr = obj['416343588'] as unknown[];
|
|
288
|
+
return {
|
|
289
|
+
totalResults: asNumberOrNull(arr[0]),
|
|
290
|
+
destination: asStringOrNull(arr[2]),
|
|
291
|
+
};
|
|
292
|
+
}
|
|
293
|
+
for (const k of Object.keys(obj)) {
|
|
294
|
+
const r = findSearchMeta(obj[k]);
|
|
295
|
+
if (r) return r;
|
|
296
|
+
}
|
|
297
|
+
return null;
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
export function extract(rawResponse: unknown): ExtractResult {
|
|
301
|
+
let payload: unknown = rawResponse;
|
|
302
|
+
if (typeof rawResponse === 'string') {
|
|
303
|
+
const env = parseEnvelope(rawResponse);
|
|
304
|
+
payload = env?.payload;
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
const hotels: Hotel[] = [];
|
|
308
|
+
collectHotels(payload, hotels, new Set());
|
|
309
|
+
|
|
310
|
+
const meta = findSearchMeta(payload) ?? { destination: null, totalResults: null };
|
|
311
|
+
|
|
312
|
+
// Pull stay info from any hotel that has it (all hotels carry the same dates).
|
|
313
|
+
const sample = hotels.find(
|
|
314
|
+
(h) => h.checkInDate !== null && h.checkOutDate !== null,
|
|
315
|
+
);
|
|
316
|
+
const checkInDate = sample?.checkInDate ?? null;
|
|
317
|
+
const checkOutDate = sample?.checkOutDate ?? null;
|
|
318
|
+
const nights = sample?.nights ?? null;
|
|
319
|
+
|
|
320
|
+
return {
|
|
321
|
+
destination: meta.destination,
|
|
322
|
+
totalResults: meta.totalResults,
|
|
323
|
+
currency: 'USD',
|
|
324
|
+
checkInDate,
|
|
325
|
+
checkOutDate,
|
|
326
|
+
nights,
|
|
327
|
+
hotelCount: hotels.length,
|
|
328
|
+
hotels,
|
|
329
|
+
};
|
|
330
|
+
}
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
toolName: search_google_hotels
|
|
2
|
+
summary: Search Google Hotels for a destination, date range, and guest count, returning hotel names with stay-total prices.
|
|
3
|
+
parameters:
|
|
4
|
+
- name: destination
|
|
5
|
+
type: string
|
|
6
|
+
description: City or place name to search hotels in, e.g. "tahoe city"
|
|
7
|
+
- name: check_in_date
|
|
8
|
+
type: string
|
|
9
|
+
description: Check-in date as YYYY-MM-DD
|
|
10
|
+
- name: check_out_date
|
|
11
|
+
type: string
|
|
12
|
+
description: Check-out date as YYYY-MM-DD
|
|
13
|
+
- name: adults
|
|
14
|
+
type: number
|
|
15
|
+
description: Number of adult guests
|
|
16
|
+
default: 2
|
|
17
|
+
- name: children
|
|
18
|
+
type: number
|
|
19
|
+
description: Number of child guests
|
|
20
|
+
default: 0
|
|
21
|
+
steps:
|
|
22
|
+
- action: navigate
|
|
23
|
+
url: https://www.google.com/travel/search
|
|
24
|
+
wait_for: networkidle
|
|
25
|
+
- action: click
|
|
26
|
+
locators:
|
|
27
|
+
- by: aria_label
|
|
28
|
+
value: Search for places, hotels and more
|
|
29
|
+
- by: css
|
|
30
|
+
value: input.II2One.j0Ppje
|
|
31
|
+
wait_for:
|
|
32
|
+
sleep_ms: 300
|
|
33
|
+
- action: type
|
|
34
|
+
locators:
|
|
35
|
+
- by: aria_label
|
|
36
|
+
value: Search for places, hotels and more
|
|
37
|
+
- by: css
|
|
38
|
+
value: input.II2One.j0Ppje
|
|
39
|
+
value: ${destination}
|
|
40
|
+
wait_for:
|
|
41
|
+
sleep_ms: 800
|
|
42
|
+
- action: press
|
|
43
|
+
key: Enter
|
|
44
|
+
wait_for:
|
|
45
|
+
xhr: /TravelFrontendUi/data/batchexecute.*rpcids=AtySUc
|
|
46
|
+
- action: click
|
|
47
|
+
locators:
|
|
48
|
+
- by: aria_label
|
|
49
|
+
value: Check-in
|
|
50
|
+
- by: css
|
|
51
|
+
value: input.TP4Lpb.eoY5cb
|
|
52
|
+
wait_for:
|
|
53
|
+
sleep_ms: 500
|
|
54
|
+
- action: type
|
|
55
|
+
locators:
|
|
56
|
+
- by: aria_label
|
|
57
|
+
value: Check-in
|
|
58
|
+
value: ${check_in_date}
|
|
59
|
+
wait_for:
|
|
60
|
+
sleep_ms: 300
|
|
61
|
+
- action: type
|
|
62
|
+
locators:
|
|
63
|
+
- by: aria_label
|
|
64
|
+
value: Check-out
|
|
65
|
+
value: ${check_out_date}
|
|
66
|
+
wait_for:
|
|
67
|
+
sleep_ms: 300
|
|
68
|
+
- action: click
|
|
69
|
+
locators:
|
|
70
|
+
- by: text
|
|
71
|
+
value: Done
|
|
72
|
+
- by: role
|
|
73
|
+
value: button
|
|
74
|
+
name: Done
|
|
75
|
+
wait_for:
|
|
76
|
+
sleep_ms: 500
|
|
77
|
+
- action: click
|
|
78
|
+
locators:
|
|
79
|
+
- by: aria_label
|
|
80
|
+
value_pattern: guests
|
|
81
|
+
- by: css
|
|
82
|
+
value: div.cQnuXe.k0gFV button
|
|
83
|
+
wait_for:
|
|
84
|
+
sleep_ms: 500
|
|
85
|
+
- action: click
|
|
86
|
+
locators:
|
|
87
|
+
- by: aria_label
|
|
88
|
+
value: Set number of adults
|
|
89
|
+
- by: css
|
|
90
|
+
value: div.P634r.ZP29Te button
|
|
91
|
+
wait_for:
|
|
92
|
+
sleep_ms: 200
|
|
93
|
+
notes: Click adults increment N-2 times where N=${adults}; default capture used 2 extra clicks for 4 adults
|
|
94
|
+
- action: click
|
|
95
|
+
locators:
|
|
96
|
+
- by: text
|
|
97
|
+
value: Done
|
|
98
|
+
- by: role
|
|
99
|
+
value: button
|
|
100
|
+
name: Done
|
|
101
|
+
wait_for:
|
|
102
|
+
xhr: /TravelFrontendUi/data/batchexecute.*rpcids=AtySUc
|
|
103
|
+
- action: click
|
|
104
|
+
locators:
|
|
105
|
+
- by: text
|
|
106
|
+
value: Stay total
|
|
107
|
+
- by: css
|
|
108
|
+
value: label.LLYsl
|
|
109
|
+
wait_for:
|
|
110
|
+
sleep_ms: 300
|
|
111
|
+
- action: click
|
|
112
|
+
locators:
|
|
113
|
+
- by: aria_label
|
|
114
|
+
value: Done
|
|
115
|
+
- by: text
|
|
116
|
+
value: Done
|
|
117
|
+
wait_for:
|
|
118
|
+
xhr: /TravelFrontendUi/data/batchexecute.*rpcids=M0CRd
|
|
119
|
+
result:
|
|
120
|
+
source: xhr
|
|
121
|
+
url_pattern: /TravelFrontendUi/data/batchexecute.*rpcids=M0CRd
|
|
122
|
+
extract: '[].[].[].[].[].[].179305178.1'
|
|
123
|
+
return_as: hotels
|
|
124
|
+
notes: |
|
|
125
|
+
Google Travel uses the proprietary "batchexecute" RPC envelope; the relevant hotel results come back from the M0CRd RPC after the final search submission. The response is a deeply-nested JSON array (not a clean object), so extracting structured prices reliably requires custom parsing of the array indices in the response — the extract path above is approximate and the caller may need to walk the array manually. Date typing into Google's date inputs is finicky; if direct typing fails, fall back to clicking calendar day cells (text matches the day-of-month number). The guest count step requires multiple clicks on the +/- buttons; the playbook here assumes a fixed default (2 adults) and parameterizes via repeated clicks the agent must dispatch based on the adults/children parameter values. The "Stay total" toggle ensures prices reflect the full multi-night stay rather than the per-night rate. Filters (price range, hotel class, amenities, brands) shown in the recording were exploratory and are NOT codified as parameters — extend the playbook if a use case demands them.
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
{
|
|
2
|
+
"toolName": "search_google_hotels",
|
|
3
|
+
"intent": {
|
|
4
|
+
"description": "Search Google Hotels for lodging in a destination over a given date range and party composition, returning the list of nearby hotels with star rating, guest rating, nightly + total prices, and Google identifiers.",
|
|
5
|
+
"userSaid": "i just searched for htoels in tahoe city from june 19 to june 27 for 2 people | i added some adutls and some children | i clicked one of hte hotels | i clicked hte box that shows the stay total price (not nightly prices before taxes and fees) | i played around with all the filters | i played around with the brands"
|
|
6
|
+
},
|
|
7
|
+
"parameters": [
|
|
8
|
+
{
|
|
9
|
+
"name": "query",
|
|
10
|
+
"type": "string",
|
|
11
|
+
"description": "Free-text destination query the user typed into the search box (e.g. 'tahoe city').",
|
|
12
|
+
"default": "tahoe city"
|
|
13
|
+
},
|
|
14
|
+
{
|
|
15
|
+
"name": "place_mid",
|
|
16
|
+
"type": "string",
|
|
17
|
+
"description": "Google Knowledge Graph machine ID for the destination (the '/m/0gyvmkl' style identifier returned by the autocomplete request). Required to anchor the search to a specific place.",
|
|
18
|
+
"default": "/m/0gyvmkl"
|
|
19
|
+
},
|
|
20
|
+
{
|
|
21
|
+
"name": "place_ftid",
|
|
22
|
+
"type": "string",
|
|
23
|
+
"description": "Google Maps feature ID for the destination, of the form '0xHEX:0xHEX'. Comes from the autocomplete response alongside the place_mid.",
|
|
24
|
+
"default": "0x809bd62ecf1fa721:0x2a98b230816c9ed1"
|
|
25
|
+
},
|
|
26
|
+
{
|
|
27
|
+
"name": "place_name",
|
|
28
|
+
"type": "string",
|
|
29
|
+
"description": "Display name for the destination (e.g. 'Tahoe City').",
|
|
30
|
+
"default": "Tahoe City"
|
|
31
|
+
},
|
|
32
|
+
{
|
|
33
|
+
"name": "check_in_year",
|
|
34
|
+
"type": "number",
|
|
35
|
+
"description": "Check-in year (e.g. 2026).",
|
|
36
|
+
"default": 2026
|
|
37
|
+
},
|
|
38
|
+
{
|
|
39
|
+
"name": "check_in_month",
|
|
40
|
+
"type": "number",
|
|
41
|
+
"description": "Check-in month (1–12).",
|
|
42
|
+
"default": 6
|
|
43
|
+
},
|
|
44
|
+
{
|
|
45
|
+
"name": "check_in_day",
|
|
46
|
+
"type": "number",
|
|
47
|
+
"description": "Check-in day of month (1–31).",
|
|
48
|
+
"default": 19
|
|
49
|
+
},
|
|
50
|
+
{
|
|
51
|
+
"name": "check_out_year",
|
|
52
|
+
"type": "number",
|
|
53
|
+
"description": "Check-out year.",
|
|
54
|
+
"default": 2026
|
|
55
|
+
},
|
|
56
|
+
{
|
|
57
|
+
"name": "check_out_month",
|
|
58
|
+
"type": "number",
|
|
59
|
+
"description": "Check-out month (1–12).",
|
|
60
|
+
"default": 6
|
|
61
|
+
},
|
|
62
|
+
{
|
|
63
|
+
"name": "check_out_day",
|
|
64
|
+
"type": "number",
|
|
65
|
+
"description": "Check-out day of month (1–31).",
|
|
66
|
+
"default": 27
|
|
67
|
+
},
|
|
68
|
+
{
|
|
69
|
+
"name": "nights",
|
|
70
|
+
"type": "number",
|
|
71
|
+
"description": "Length of stay in nights. Must equal check_out - check_in.",
|
|
72
|
+
"default": 8
|
|
73
|
+
},
|
|
74
|
+
{
|
|
75
|
+
"name": "child_age_1",
|
|
76
|
+
"type": "number",
|
|
77
|
+
"description": "Age of the first child (omit by leaving default; the workflow currently always sends two children to mirror the recorded request).",
|
|
78
|
+
"default": 3
|
|
79
|
+
},
|
|
80
|
+
{
|
|
81
|
+
"name": "child_age_2",
|
|
82
|
+
"type": "number",
|
|
83
|
+
"description": "Age of the second child.",
|
|
84
|
+
"default": 3
|
|
85
|
+
},
|
|
86
|
+
{
|
|
87
|
+
"name": "currency",
|
|
88
|
+
"type": "string",
|
|
89
|
+
"description": "ISO currency code for displayed prices.",
|
|
90
|
+
"default": "USD"
|
|
91
|
+
}
|
|
92
|
+
],
|
|
93
|
+
"requests": [
|
|
94
|
+
{
|
|
95
|
+
"method": "POST",
|
|
96
|
+
"url": "https://www.google.com/_/TravelFrontendUi/data/batchexecute?rpcids=AtySUc&source-path=%2Ftravel%2Fsearch&hl=en-US&soc-app=162&soc-platform=1&soc-device=1&rt=c",
|
|
97
|
+
"headers": {
|
|
98
|
+
"Content-Type": "application/x-www-form-urlencoded;charset=UTF-8",
|
|
99
|
+
"X-Same-Domain": "1",
|
|
100
|
+
"Origin": "https://www.google.com",
|
|
101
|
+
"Referer": "https://www.google.com/travel/search?q=${param.query}",
|
|
102
|
+
"Accept-Language": "en-US,en;q=0.9",
|
|
103
|
+
"User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/147.0.0.0 Safari/537.36",
|
|
104
|
+
"x-goog-ext-259736195-jspb": "[\"en-US\",\"US\",\"${param.currency}\",2,null,[420],null,null,7,[]]"
|
|
105
|
+
},
|
|
106
|
+
"body": "f.req=%5B%5B%5B%22AtySUc%22%2C%22%5B%5C%22${param.query}%5C%22%2C%5B1%2C%5B%5B%5B${param.child_age_1}%5D%2C%5B${param.child_age_2}%5D%5D%2C0%5D%2C%5B%5Bnull%2C%5B%5B%5C%22${param.place_mid}%5C%22%2Cnull%2Cnull%2Cnull%2Cnull%2C%5C%22${param.place_ftid}%5C%22%2C%5C%22${param.place_name}%5C%22%5D%5D%2C%5B%5D%5D%2C%5Bnull%2C%5B%5B${param.check_in_year}%2C${param.check_in_month}%2C${param.check_in_day}%5D%2C%5B${param.check_out_year}%2C${param.check_out_month}%2C${param.check_out_day}%5D%2C${param.nights}%5D%2Cnull%2Cnull%2Cnull%2C%5B1%5D%5D%5D%2Cnull%2C%5B%5Bnull%2Cnull%2Cnull%2Cnull%2Cnull%2Cnull%2C%5C%22${param.currency}%5C%22%5D%2Cnull%2C%5B%5D%5D%5D%2C%5B1%2Cnull%2Cnull%2C0%2C0%2Cnull%2C13%2Cnull%2C0%5D%5D%22%2Cnull%2C%221%22%5D%5D%5D&"
|
|
107
|
+
}
|
|
108
|
+
],
|
|
109
|
+
"site": "google-hotels",
|
|
110
|
+
"parserModule": "./parser.ts"
|
|
111
|
+
}
|