imprint-mcp 0.2.1 → 0.3.1
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 +193 -189
- package/examples/discoverandgo/README.md +1 -1
- package/examples/echo/README.md +1 -1
- package/examples/google-flights/README.md +28 -0
- package/examples/google-flights/_shared/batchexecute.ts +63 -0
- package/examples/google-flights/_shared/flights_request.ts +95 -0
- package/examples/google-flights/_shared/package.json +9 -0
- package/examples/google-flights/get_flight_booking_details/index.ts +159 -0
- package/examples/google-flights/get_flight_booking_details/package.json +9 -0
- package/examples/google-flights/get_flight_booking_details/parser.ts +182 -0
- package/examples/google-flights/get_flight_booking_details/playbook.yaml +138 -0
- package/examples/google-flights/get_flight_booking_details/request-transform.ts +86 -0
- package/examples/google-flights/get_flight_booking_details/workflow.json +98 -0
- package/examples/google-flights/get_flight_calendar_prices/index.ts +131 -0
- package/examples/google-flights/get_flight_calendar_prices/package.json +9 -0
- package/examples/google-flights/get_flight_calendar_prices/parser.ts +86 -0
- package/examples/google-flights/get_flight_calendar_prices/playbook.yaml +97 -0
- package/examples/google-flights/get_flight_calendar_prices/request-transform.ts +31 -0
- package/examples/google-flights/get_flight_calendar_prices/workflow.json +78 -0
- package/examples/google-flights/lookup_airport/index.ts +101 -0
- package/examples/google-flights/lookup_airport/package.json +9 -0
- package/examples/google-flights/lookup_airport/parser.ts +66 -0
- package/examples/google-flights/lookup_airport/playbook.yaml +47 -0
- package/examples/google-flights/lookup_airport/request-transform.ts +20 -0
- package/examples/google-flights/lookup_airport/workflow.json +57 -0
- package/examples/google-flights/search_flights/index.ts +219 -0
- package/examples/google-flights/search_flights/package.json +9 -0
- package/examples/google-flights/search_flights/parser.ts +169 -0
- package/examples/google-flights/search_flights/playbook.yaml +184 -0
- package/examples/google-flights/search_flights/request-transform.ts +119 -0
- package/examples/google-flights/search_flights/workflow.json +143 -0
- package/examples/google-hotels/README.md +29 -0
- package/examples/google-hotels/_shared/batchexecute.ts +73 -0
- package/examples/google-hotels/_shared/freq.ts +158 -0
- package/examples/google-hotels/_shared/package.json +9 -0
- package/examples/google-hotels/autocomplete_hotel_location/index.ts +80 -0
- package/examples/google-hotels/autocomplete_hotel_location/package.json +9 -0
- package/examples/google-hotels/autocomplete_hotel_location/parser.ts +71 -0
- package/examples/google-hotels/autocomplete_hotel_location/playbook.yaml +36 -0
- package/examples/google-hotels/autocomplete_hotel_location/request-transform.ts +37 -0
- package/examples/google-hotels/autocomplete_hotel_location/workflow.json +36 -0
- package/examples/google-hotels/get_hotel_booking_options/index.ts +143 -0
- package/examples/google-hotels/get_hotel_booking_options/package.json +9 -0
- package/examples/google-hotels/get_hotel_booking_options/parser.ts +271 -0
- package/examples/google-hotels/get_hotel_booking_options/playbook.yaml +154 -0
- package/examples/google-hotels/get_hotel_booking_options/request-transform.ts +154 -0
- package/examples/google-hotels/get_hotel_booking_options/workflow.json +84 -0
- package/examples/google-hotels/get_hotel_reviews/index.ts +81 -0
- package/examples/google-hotels/get_hotel_reviews/package.json +9 -0
- package/examples/google-hotels/get_hotel_reviews/parser.ts +128 -0
- package/examples/google-hotels/get_hotel_reviews/playbook.yaml +64 -0
- package/examples/google-hotels/get_hotel_reviews/request-transform.ts +42 -0
- package/examples/google-hotels/get_hotel_reviews/workflow.json +37 -0
- package/examples/google-hotels/search_hotels/index.ts +207 -0
- package/examples/google-hotels/search_hotels/package.json +9 -0
- package/examples/google-hotels/search_hotels/parser.ts +260 -0
- package/examples/google-hotels/search_hotels/playbook.yaml +87 -0
- package/examples/google-hotels/search_hotels/request-transform.ts +197 -0
- package/examples/google-hotels/search_hotels/workflow.json +127 -0
- package/examples/southwest/README.md +3 -2
- package/examples/southwest/search_southwest_flights/index.ts +18 -1
- package/examples/southwest/search_southwest_flights/workflow.json +18 -1
- package/package.json +3 -2
- package/prompts/audit-agent.md +71 -0
- package/prompts/build-planning.md +74 -0
- package/prompts/compile-agent.md +131 -27
- package/prompts/prereq-builder.md +64 -0
- package/prompts/prereq-planner.md +34 -0
- package/prompts/tool-planning.md +39 -0
- package/src/cli.ts +116 -3
- package/src/imprint/agent.ts +5 -0
- package/src/imprint/audit.ts +996 -0
- package/src/imprint/backend-ladder.ts +1214 -184
- package/src/imprint/build-plan.ts +1051 -0
- package/src/imprint/cdp-browser-fetch.ts +592 -0
- package/src/imprint/cdp-jar-cache.ts +320 -0
- package/src/imprint/chromium.ts +414 -8
- package/src/imprint/claude-cli-compile.ts +125 -25
- package/src/imprint/codex-cli-compile.ts +26 -23
- package/src/imprint/compile-agent-types.ts +38 -0
- package/src/imprint/compile-agent.ts +63 -25
- package/src/imprint/compile-tools.ts +1666 -66
- package/src/imprint/compile.ts +13 -1
- package/src/imprint/concurrency.ts +87 -0
- package/src/imprint/cron.ts +4 -0
- package/src/imprint/doctor.ts +48 -3
- package/src/imprint/freeform-redact.ts +5 -4
- package/src/imprint/install.ts +79 -4
- package/src/imprint/integrations.ts +3 -3
- package/src/imprint/llm.ts +56 -8
- package/src/imprint/mcp-compile-server.ts +43 -10
- package/src/imprint/mcp-maintenance.ts +18 -102
- package/src/imprint/mcp-server.ts +73 -7
- package/src/imprint/multi-progress.ts +7 -2
- package/src/imprint/param-grounding.ts +367 -0
- package/src/imprint/paths.ts +29 -0
- package/src/imprint/playbook-runner.ts +101 -40
- package/src/imprint/prereq-builder.ts +651 -0
- package/src/imprint/probe-backends.ts +6 -3
- package/src/imprint/record.ts +10 -1
- package/src/imprint/redact.ts +30 -2
- package/src/imprint/replay-capture.ts +19 -18
- package/src/imprint/runtime.ts +19 -10
- package/src/imprint/session-diff.ts +79 -2
- package/src/imprint/session-merge.ts +9 -5
- package/src/imprint/stealth-chromium.ts +79 -0
- package/src/imprint/stealth-fetch.ts +309 -29
- package/src/imprint/stealth-token-cache.ts +88 -0
- package/src/imprint/teach-plan.ts +251 -0
- package/src/imprint/teach-state.ts +10 -0
- package/src/imprint/teach.ts +456 -142
- package/src/imprint/tool-candidates.ts +72 -14
- package/src/imprint/tool-plan.ts +313 -0
- package/src/imprint/tracing.ts +135 -6
- package/src/imprint/types.ts +61 -3
- package/examples/google-flights/search_google_flights/index.ts +0 -101
- package/examples/google-flights/search_google_flights/parser.test.ts +0 -140
- package/examples/google-flights/search_google_flights/parser.ts +0 -189
- package/examples/google-flights/search_google_flights/playbook.yaml +0 -130
- package/examples/google-flights/search_google_flights/workflow.json +0 -48
- package/examples/google-hotels/search_google_hotels/index.ts +0 -194
- package/examples/google-hotels/search_google_hotels/parser.test.ts +0 -168
- package/examples/google-hotels/search_google_hotels/parser.ts +0 -330
- package/examples/google-hotels/search_google_hotels/playbook.yaml +0 -125
- package/examples/google-hotels/search_google_hotels/workflow.json +0 -111
- package/examples/namecheap-domains/search_namecheap_domains/index.ts +0 -144
- package/examples/namecheap-domains/search_namecheap_domains/parser.ts +0 -380
- package/examples/namecheap-domains/search_namecheap_domains/playbook.yaml +0 -50
- package/examples/namecheap-domains/search_namecheap_domains/request-transform.ts +0 -136
- package/examples/namecheap-domains/search_namecheap_domains/workflow.json +0 -97
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* GENERATED by `imprint emit` — DO NOT EDIT BY HAND.
|
|
3
|
+
*
|
|
4
|
+
* Tool: get_hotel_reviews
|
|
5
|
+
* Site: google-hotels
|
|
6
|
+
* Intent: Fetch aggregated reviews and reviewer snippets for a selected Google Hotels hotel.
|
|
7
|
+
*
|
|
8
|
+
* To regenerate: imprint emit ~/.imprint/google-hotels/get_hotel_reviews/workflow.json --force
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import { fileURLToPath } from 'node:url';
|
|
12
|
+
import { dirname, join } from 'node:path';
|
|
13
|
+
import {
|
|
14
|
+
executeWorkflow,
|
|
15
|
+
type CredentialStore,
|
|
16
|
+
} from 'imprint/runtime';
|
|
17
|
+
import type { ToolResult, Workflow } from 'imprint/types';
|
|
18
|
+
|
|
19
|
+
const WORKFLOW: Workflow = {
|
|
20
|
+
"toolName": "get_hotel_reviews",
|
|
21
|
+
"intent": {
|
|
22
|
+
"description": "Fetch aggregated reviews and reviewer snippets for a selected Google Hotels hotel.",
|
|
23
|
+
"userSaid": "clicked one of the offerings, saw booking options"
|
|
24
|
+
},
|
|
25
|
+
"parameters": [
|
|
26
|
+
{
|
|
27
|
+
"name": "hotel_id",
|
|
28
|
+
"type": "string",
|
|
29
|
+
"description": "Opaque hotel token (e.g. \"ChcI78-luoXdhoaIARoKL20vMDJ2cGdnMRAB\") obtained from the search_hotels tool's hotel_id output.",
|
|
30
|
+
"verified": true,
|
|
31
|
+
"sourcedFrom": {
|
|
32
|
+
"tool": "search_hotels",
|
|
33
|
+
"field": "hotel_id"
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
],
|
|
37
|
+
"requests": [
|
|
38
|
+
{
|
|
39
|
+
"method": "POST",
|
|
40
|
+
"url": "https://www.google.com/_/TravelFrontendUi/data/batchexecute?rpcids=ocp93e&source-path=%2Ftravel%2Fsearch&f.sid=7513562915459271421&bl=boq_travel-frontend-ui_20260527.01_p0&hl=en-US&soc-app=162&soc-platform=1&soc-device=1&_reqid=3252256&rt=c",
|
|
41
|
+
"headers": {
|
|
42
|
+
"Content-Type": "application/x-www-form-urlencoded;charset=UTF-8",
|
|
43
|
+
"X-Same-Domain": "1",
|
|
44
|
+
"Origin": "https://www.google.com",
|
|
45
|
+
"Referer": "https://www.google.com/travel/search"
|
|
46
|
+
},
|
|
47
|
+
"body": "f.req=%5B%5B%5B%22ocp93e%22%2C%22%5Bnull%2Cnull%2Cnull%2Cnull%2Cnull%2Cnull%2Cnull%2Cnull%2C%5C%22${param.hotel_id}%5C%22%2Cnull%2Cnull%2C%5B%5B%5D%5D%5D%22%2Cnull%2C%221%22%5D%5D%5D&",
|
|
48
|
+
"effect": "safe"
|
|
49
|
+
}
|
|
50
|
+
],
|
|
51
|
+
"site": "google-hotels",
|
|
52
|
+
"parserModule": "./parser.ts",
|
|
53
|
+
"requestTransformModule": "./request-transform.ts",
|
|
54
|
+
"liveVerified": true
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
export interface GetHotelReviewsInput {
|
|
58
|
+
/** Opaque hotel token (e.g. "ChcI78-luoXdhoaIARoKL20vMDJ2cGdnMRAB") obtained from the search_hotels tool's hotel_id output. */
|
|
59
|
+
hotel_id: string;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
export async function getHotelReviews(
|
|
63
|
+
input: GetHotelReviewsInput,
|
|
64
|
+
opts: { credentials?: CredentialStore; fetchImpl?: typeof fetch; initialState?: Record<string, unknown> } = {},
|
|
65
|
+
): Promise<ToolResult> {
|
|
66
|
+
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
67
|
+
const params: Record<string, string | number | boolean> = {
|
|
68
|
+
hotel_id: input.hotel_id,
|
|
69
|
+
|
|
70
|
+
};
|
|
71
|
+
return executeWorkflow({
|
|
72
|
+
workflow: WORKFLOW,
|
|
73
|
+
params,
|
|
74
|
+
credentials: opts.credentials,
|
|
75
|
+
fetchImpl: opts.fetchImpl,
|
|
76
|
+
initialState: opts.initialState,
|
|
77
|
+
workflowPath: join(__dirname, 'workflow.json'),
|
|
78
|
+
});
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
export { WORKFLOW };
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
import { parseBatchExecute } from '../_shared/batchexecute.ts';
|
|
2
|
+
|
|
3
|
+
// Shape of one review-source "entry" in the ocp93e inner JSON (recording seq 497):
|
|
4
|
+
// entry[0] = provider: [ providerName, null, [iconUrl, w, h], code, count ]
|
|
5
|
+
// entry[1] = review: [ [reviewerName, profileLink, [avatarUrl, w, h]],
|
|
6
|
+
// dateText, [score, outOf], [[ [code, text, ...], ... ]],
|
|
7
|
+
// sourceLink, ... ]
|
|
8
|
+
// The entries list lives at root[0][0] (root = [[[ entry, entry, ... ]]]).
|
|
9
|
+
|
|
10
|
+
interface ParsedReview {
|
|
11
|
+
reviewerName: string | null;
|
|
12
|
+
profileLink: string | null;
|
|
13
|
+
avatarUrl: string | null;
|
|
14
|
+
date: string | null;
|
|
15
|
+
ratingScore: number | null;
|
|
16
|
+
ratingOutOf: number | null;
|
|
17
|
+
texts: string[];
|
|
18
|
+
sourceLink: string | null;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
interface ParsedEntry {
|
|
22
|
+
provider: {
|
|
23
|
+
name: string | null;
|
|
24
|
+
iconUrl: string | null;
|
|
25
|
+
count: number | null;
|
|
26
|
+
};
|
|
27
|
+
review: ParsedReview;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
function asArray(v: unknown): any[] {
|
|
31
|
+
return Array.isArray(v) ? (v as any[]) : [];
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
function looksLikeEntry(e: unknown): boolean {
|
|
35
|
+
// entry[0] is the provider array whose first element is the provider name string.
|
|
36
|
+
return (
|
|
37
|
+
Array.isArray(e) &&
|
|
38
|
+
Array.isArray((e as any[])[0]) &&
|
|
39
|
+
typeof (e as any[])[0][0] === 'string'
|
|
40
|
+
);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
// The entries-list nesting depth is the one value the recording does not fully
|
|
44
|
+
// pin down, so locate the array whose elements have the entry shape rather than
|
|
45
|
+
// hardcoding root[0][0]. Falls back to a recursive search if the expected path
|
|
46
|
+
// does not match.
|
|
47
|
+
function findEntriesList(root: unknown): any[] {
|
|
48
|
+
const direct = asArray(asArray(asArray(root)[0])[0]);
|
|
49
|
+
if (direct.length > 0 && direct.some(looksLikeEntry)) return direct;
|
|
50
|
+
|
|
51
|
+
let best: any[] = [];
|
|
52
|
+
const visit = (node: unknown, depth: number) => {
|
|
53
|
+
if (depth > 8 || !Array.isArray(node)) return;
|
|
54
|
+
const arr = node as any[];
|
|
55
|
+
if (arr.length > 0 && arr.every(looksLikeEntry)) {
|
|
56
|
+
if (arr.length > best.length) best = arr;
|
|
57
|
+
return;
|
|
58
|
+
}
|
|
59
|
+
for (const child of arr) visit(child, depth + 1);
|
|
60
|
+
};
|
|
61
|
+
visit(root, 0);
|
|
62
|
+
return best;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
function parseEntry(entry: any[]): ParsedEntry {
|
|
66
|
+
const provider = asArray(entry[0]);
|
|
67
|
+
const review = asArray(entry[1]);
|
|
68
|
+
const reviewer = asArray(review[0]);
|
|
69
|
+
const rating = asArray(review[2]);
|
|
70
|
+
|
|
71
|
+
// review[3] = [[ [code, text, null, text, ...], ... ]] -> collect text strings.
|
|
72
|
+
const textGroups = asArray(asArray(review[3])[0]);
|
|
73
|
+
const texts: string[] = [];
|
|
74
|
+
for (const t of textGroups) {
|
|
75
|
+
if (Array.isArray(t) && typeof t[1] === 'string' && t[1].trim() !== '') {
|
|
76
|
+
texts.push(t[1]);
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
return {
|
|
81
|
+
provider: {
|
|
82
|
+
name: typeof provider[0] === 'string' ? provider[0] : null,
|
|
83
|
+
iconUrl: Array.isArray(provider[2]) && typeof provider[2][0] === 'string'
|
|
84
|
+
? provider[2][0]
|
|
85
|
+
: null,
|
|
86
|
+
count: typeof provider[4] === 'number' ? provider[4] : null,
|
|
87
|
+
},
|
|
88
|
+
review: {
|
|
89
|
+
reviewerName: typeof reviewer[0] === 'string' ? reviewer[0] : null,
|
|
90
|
+
profileLink: typeof reviewer[1] === 'string' ? reviewer[1] : null,
|
|
91
|
+
avatarUrl: Array.isArray(reviewer[2]) && typeof reviewer[2][0] === 'string'
|
|
92
|
+
? reviewer[2][0]
|
|
93
|
+
: null,
|
|
94
|
+
date: typeof review[1] === 'string' && review[1] !== '' ? review[1] : null,
|
|
95
|
+
ratingScore: typeof rating[0] === 'number' ? rating[0] : null,
|
|
96
|
+
ratingOutOf: typeof rating[1] === 'number' ? rating[1] : null,
|
|
97
|
+
texts,
|
|
98
|
+
sourceLink: typeof review[4] === 'string' && review[4] !== '' ? review[4] : null,
|
|
99
|
+
},
|
|
100
|
+
};
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
export function extract(
|
|
104
|
+
rawResponse: unknown,
|
|
105
|
+
_context?: {
|
|
106
|
+
params: Record<string, string | number | boolean>;
|
|
107
|
+
responses: unknown[];
|
|
108
|
+
},
|
|
109
|
+
): unknown {
|
|
110
|
+
const raw =
|
|
111
|
+
typeof rawResponse === 'string' ? rawResponse : JSON.stringify(rawResponse);
|
|
112
|
+
const root = parseBatchExecute(raw, 'ocp93e');
|
|
113
|
+
if (root == null) return { reviews: [], count: 0 };
|
|
114
|
+
|
|
115
|
+
const entries = findEntriesList(root);
|
|
116
|
+
const reviews = entries
|
|
117
|
+
.filter(looksLikeEntry)
|
|
118
|
+
.map((e) => parseEntry(e as any[]))
|
|
119
|
+
// Drop content-less placeholder rows (API no-match sentinel).
|
|
120
|
+
.filter(
|
|
121
|
+
(e) =>
|
|
122
|
+
e.provider.name != null ||
|
|
123
|
+
e.review.reviewerName != null ||
|
|
124
|
+
e.review.texts.length > 0,
|
|
125
|
+
);
|
|
126
|
+
|
|
127
|
+
return { reviews, count: reviews.length };
|
|
128
|
+
}
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
toolName: get_hotel_reviews
|
|
2
|
+
summary: Open a hotel's detail panel on Google Hotels and capture its aggregated reviews (the ocp93e batchexecute response).
|
|
3
|
+
parameters:
|
|
4
|
+
- name: location
|
|
5
|
+
type: string
|
|
6
|
+
description: Destination/area to search for hotels, e.g. "Chicago Loop". Used to populate the search box and pick the matching autocomplete suggestion.
|
|
7
|
+
- name: hotel_name
|
|
8
|
+
type: string
|
|
9
|
+
description: Display name of the hotel whose reviews are wanted, e.g. "Hyatt Regency Chicago". Used to click the matching result card.
|
|
10
|
+
steps:
|
|
11
|
+
- action: navigate
|
|
12
|
+
url: https://www.google.com/travel/search
|
|
13
|
+
wait_for: networkidle
|
|
14
|
+
- action: click
|
|
15
|
+
locators:
|
|
16
|
+
- by: aria_label
|
|
17
|
+
value: Search for places, hotels and more
|
|
18
|
+
- by: css
|
|
19
|
+
value: input.II2One.j0Ppje
|
|
20
|
+
wait_for:
|
|
21
|
+
sleep_ms: 300
|
|
22
|
+
- action: type
|
|
23
|
+
locators:
|
|
24
|
+
- by: aria_label
|
|
25
|
+
value: Search for places, hotels and more
|
|
26
|
+
- by: css
|
|
27
|
+
value: input.II2One.j0Ppje
|
|
28
|
+
value: ${location}
|
|
29
|
+
wait_for:
|
|
30
|
+
sleep_ms: 600
|
|
31
|
+
- action: click
|
|
32
|
+
locators:
|
|
33
|
+
- by: text
|
|
34
|
+
value_pattern: ${location}
|
|
35
|
+
- by: css
|
|
36
|
+
value: ul.F3AVKd > li.Q1RWxd span.S5TdWc
|
|
37
|
+
wait_for:
|
|
38
|
+
xhr: rpcids=AtySUc
|
|
39
|
+
- action: click
|
|
40
|
+
locators:
|
|
41
|
+
- by: aria_label
|
|
42
|
+
value_pattern: ${hotel_name}
|
|
43
|
+
- by: text
|
|
44
|
+
value_pattern: ${hotel_name}
|
|
45
|
+
- by: css
|
|
46
|
+
value: a.PVOOXe
|
|
47
|
+
wait_for:
|
|
48
|
+
xhr: rpcids=ocp93e
|
|
49
|
+
result:
|
|
50
|
+
source: xhr
|
|
51
|
+
url_pattern: rpcids=ocp93e
|
|
52
|
+
extract: "[0][0][0][]"
|
|
53
|
+
return_as: reviews
|
|
54
|
+
notes: >-
|
|
55
|
+
The ocp93e response uses Google's anti-XSSI batchexecute envelope: strip the ")]}'" prefix and chunk-length lines,
|
|
56
|
+
JSON.parse the outer array, then JSON.parse the escaped inner string at ["wrb.fr","ocp93e", <innerJsonString>].
|
|
57
|
+
The inner JSON is positional (no object keys); the extract index path "[0][0][0][]" iterates the review-source entries.
|
|
58
|
+
Within each entry: index [0] = provider block [providerName, null, [iconUrl,w,h], type, id]; index [1] = reviewer block
|
|
59
|
+
[[reviewerName, profileUrl, [avatarUrl,w,h]], relativeDate, [ratingNum, ratingMax], [[ [sentiment, reviewText, ...] ]], reviewUrl, ...].
|
|
60
|
+
Reviews load automatically when the hotel detail panel opens (the same click also triggers M0CRd pricing) — opening the
|
|
61
|
+
Reviews tab is not required. Check-in/out dates were set during recording but do not affect the review payload, so they are
|
|
62
|
+
omitted as parameters. f.sid / bl / X-Goog-BatchExecute-Bgr are session-bound and are supplied by the live browser context,
|
|
63
|
+
not by this playbook. If the hotel result is not visible in the initial list, the agent may need to scroll the results column
|
|
64
|
+
before the ${hotel_name} click resolves.
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
// Per-tool request-transform for get_hotel_reviews. Builds the ocp93e inner
|
|
2
|
+
// payload from the hotel_id param and delegates envelope/URL construction to the
|
|
3
|
+
// shared google-hotels batchexecute helpers.
|
|
4
|
+
import { buildFreqBody, buildBatchExecuteUrl } from '../_shared/freq.ts';
|
|
5
|
+
|
|
6
|
+
export function transform(
|
|
7
|
+
method: string,
|
|
8
|
+
url: string,
|
|
9
|
+
responses: Record<string, any>,
|
|
10
|
+
params?: Record<string, any>,
|
|
11
|
+
): { url: string; body: string } {
|
|
12
|
+
void method;
|
|
13
|
+
void responses;
|
|
14
|
+
|
|
15
|
+
const hotelId = String(params?.hotel_id ?? '');
|
|
16
|
+
|
|
17
|
+
// Recorded inner payload (seq 497): positions 0-7 null, [8] = hotel token,
|
|
18
|
+
// 9 & 10 null, [11] = constant [[]].
|
|
19
|
+
const innerPayload = [
|
|
20
|
+
null,
|
|
21
|
+
null,
|
|
22
|
+
null,
|
|
23
|
+
null,
|
|
24
|
+
null,
|
|
25
|
+
null,
|
|
26
|
+
null,
|
|
27
|
+
null,
|
|
28
|
+
hotelId,
|
|
29
|
+
null,
|
|
30
|
+
null,
|
|
31
|
+
[[]],
|
|
32
|
+
];
|
|
33
|
+
|
|
34
|
+
const u = new URL(url);
|
|
35
|
+
const fSid = u.searchParams.get('f.sid') ?? '';
|
|
36
|
+
const bl = u.searchParams.get('bl') ?? '';
|
|
37
|
+
|
|
38
|
+
return {
|
|
39
|
+
url: buildBatchExecuteUrl('ocp93e', { f_sid: fSid, bl }),
|
|
40
|
+
body: buildFreqBody('ocp93e', innerPayload, '1'),
|
|
41
|
+
};
|
|
42
|
+
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
{
|
|
2
|
+
"toolName": "get_hotel_reviews",
|
|
3
|
+
"intent": {
|
|
4
|
+
"description": "Fetch aggregated reviews and reviewer snippets for a selected Google Hotels hotel.",
|
|
5
|
+
"userSaid": "clicked one of the offerings, saw booking options"
|
|
6
|
+
},
|
|
7
|
+
"site": "google-hotels",
|
|
8
|
+
"parameters": [
|
|
9
|
+
{
|
|
10
|
+
"name": "hotel_id",
|
|
11
|
+
"type": "string",
|
|
12
|
+
"description": "Opaque hotel token (e.g. \"ChcI78-luoXdhoaIARoKL20vMDJ2cGdnMRAB\") obtained from the search_hotels tool's hotel_id output.",
|
|
13
|
+
"verified": true,
|
|
14
|
+
"sourcedFrom": {
|
|
15
|
+
"tool": "search_hotels",
|
|
16
|
+
"field": "hotel_id"
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
],
|
|
20
|
+
"requestTransformModule": "./request-transform.ts",
|
|
21
|
+
"parserModule": "./parser.ts",
|
|
22
|
+
"requests": [
|
|
23
|
+
{
|
|
24
|
+
"method": "POST",
|
|
25
|
+
"url": "https://www.google.com/_/TravelFrontendUi/data/batchexecute?rpcids=ocp93e&source-path=%2Ftravel%2Fsearch&f.sid=7513562915459271421&bl=boq_travel-frontend-ui_20260527.01_p0&hl=en-US&soc-app=162&soc-platform=1&soc-device=1&_reqid=3252256&rt=c",
|
|
26
|
+
"headers": {
|
|
27
|
+
"Content-Type": "application/x-www-form-urlencoded;charset=UTF-8",
|
|
28
|
+
"X-Same-Domain": "1",
|
|
29
|
+
"Origin": "https://www.google.com",
|
|
30
|
+
"Referer": "https://www.google.com/travel/search"
|
|
31
|
+
},
|
|
32
|
+
"body": "f.req=%5B%5B%5B%22ocp93e%22%2C%22%5Bnull%2Cnull%2Cnull%2Cnull%2Cnull%2Cnull%2Cnull%2Cnull%2C%5C%22${param.hotel_id}%5C%22%2Cnull%2Cnull%2C%5B%5B%5D%5D%5D%22%2Cnull%2C%221%22%5D%5D%5D&",
|
|
33
|
+
"effect": "safe"
|
|
34
|
+
}
|
|
35
|
+
],
|
|
36
|
+
"liveVerified": true
|
|
37
|
+
}
|
|
@@ -0,0 +1,207 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* GENERATED by `imprint emit` — DO NOT EDIT BY HAND.
|
|
3
|
+
*
|
|
4
|
+
* Tool: search_hotels
|
|
5
|
+
* Site: google-hotels
|
|
6
|
+
* Intent: Search Google Hotels for a location and date range with optional filters (price, rating, amenities, star class, brands, sort, property type).
|
|
7
|
+
*
|
|
8
|
+
* To regenerate: imprint emit ~/.imprint/google-hotels/search_hotels/workflow.json --force
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import { fileURLToPath } from 'node:url';
|
|
12
|
+
import { dirname, join } from 'node:path';
|
|
13
|
+
import {
|
|
14
|
+
executeWorkflow,
|
|
15
|
+
type CredentialStore,
|
|
16
|
+
} from 'imprint/runtime';
|
|
17
|
+
import type { ToolResult, Workflow } from 'imprint/types';
|
|
18
|
+
|
|
19
|
+
const WORKFLOW: Workflow = {
|
|
20
|
+
"toolName": "search_hotels",
|
|
21
|
+
"intent": {
|
|
22
|
+
"description": "Search Google Hotels for a location and date range with optional filters (price, rating, amenities, star class, brands, sort, property type).",
|
|
23
|
+
"userSaid": "searched for hotels at chicago loop from july 3-6; filtered for > 4.0 rating; filtered min and max price; added amenities; added 2/4/5 star; added brand filters; sorted by lowest price, highest rating, most reviewed; changed property type to vacation rentals; changed location to tahoe city and denver downtown"
|
|
24
|
+
},
|
|
25
|
+
"parameters": [
|
|
26
|
+
{
|
|
27
|
+
"name": "location",
|
|
28
|
+
"type": "string",
|
|
29
|
+
"description": "Destination query, e.g. 'chicago loop', 'tahoe city', 'denver downtown'",
|
|
30
|
+
"default": "chicago loop",
|
|
31
|
+
"verified": true
|
|
32
|
+
},
|
|
33
|
+
{
|
|
34
|
+
"name": "check_in_date",
|
|
35
|
+
"type": "string",
|
|
36
|
+
"description": "Check-in date (YYYY-MM-DD)",
|
|
37
|
+
"default": "2026-07-03",
|
|
38
|
+
"verified": false,
|
|
39
|
+
"verifyNote": "annotated"
|
|
40
|
+
},
|
|
41
|
+
{
|
|
42
|
+
"name": "check_out_date",
|
|
43
|
+
"type": "string",
|
|
44
|
+
"description": "Check-out date (YYYY-MM-DD)",
|
|
45
|
+
"default": "2026-07-06",
|
|
46
|
+
"verified": false,
|
|
47
|
+
"verifyNote": "annotated"
|
|
48
|
+
},
|
|
49
|
+
{
|
|
50
|
+
"name": "adults",
|
|
51
|
+
"type": "number",
|
|
52
|
+
"description": "Number of adult travelers",
|
|
53
|
+
"default": 2,
|
|
54
|
+
"verified": false,
|
|
55
|
+
"verifyNote": "annotated"
|
|
56
|
+
},
|
|
57
|
+
{
|
|
58
|
+
"name": "children",
|
|
59
|
+
"type": "number",
|
|
60
|
+
"description": "Number of child travelers",
|
|
61
|
+
"default": 0,
|
|
62
|
+
"verified": false,
|
|
63
|
+
"verifyNote": "annotated"
|
|
64
|
+
},
|
|
65
|
+
{
|
|
66
|
+
"name": "min_rating",
|
|
67
|
+
"type": "number",
|
|
68
|
+
"description": "Minimum guest rating filter, e.g. 4.0 (0 = no filter)",
|
|
69
|
+
"default": 0,
|
|
70
|
+
"verified": false,
|
|
71
|
+
"verifyNote": "annotated"
|
|
72
|
+
},
|
|
73
|
+
{
|
|
74
|
+
"name": "min_price",
|
|
75
|
+
"type": "number",
|
|
76
|
+
"description": "Minimum nightly price filter (0 = no filter)",
|
|
77
|
+
"default": 0,
|
|
78
|
+
"verified": false,
|
|
79
|
+
"verifyNote": "annotated"
|
|
80
|
+
},
|
|
81
|
+
{
|
|
82
|
+
"name": "max_price",
|
|
83
|
+
"type": "number",
|
|
84
|
+
"description": "Maximum nightly price filter, e.g. 338 (0 = no filter)",
|
|
85
|
+
"default": 0,
|
|
86
|
+
"verified": true
|
|
87
|
+
},
|
|
88
|
+
{
|
|
89
|
+
"name": "amenities",
|
|
90
|
+
"type": "string",
|
|
91
|
+
"description": "Comma-separated amenity names or codes (e.g. 'free wi-fi,pool' or '6,9'); empty = no filter",
|
|
92
|
+
"default": "",
|
|
93
|
+
"verified": false,
|
|
94
|
+
"verifyNote": "annotated"
|
|
95
|
+
},
|
|
96
|
+
{
|
|
97
|
+
"name": "hotel_class",
|
|
98
|
+
"type": "string",
|
|
99
|
+
"description": "Comma-separated star classes, e.g. '2,3' or '4,5'; empty = no filter",
|
|
100
|
+
"default": "",
|
|
101
|
+
"verified": true
|
|
102
|
+
},
|
|
103
|
+
{
|
|
104
|
+
"name": "brands",
|
|
105
|
+
"type": "string",
|
|
106
|
+
"description": "Comma-separated brand codes ('parent:child' or numeric); empty = no filter",
|
|
107
|
+
"default": "",
|
|
108
|
+
"verified": false,
|
|
109
|
+
"verifyNote": "annotated"
|
|
110
|
+
},
|
|
111
|
+
{
|
|
112
|
+
"name": "sort_by",
|
|
113
|
+
"type": "string",
|
|
114
|
+
"description": "Sort order: relevance | lowest_price | highest_rating | most_reviewed",
|
|
115
|
+
"default": "relevance",
|
|
116
|
+
"verified": true
|
|
117
|
+
},
|
|
118
|
+
{
|
|
119
|
+
"name": "property_type",
|
|
120
|
+
"type": "string",
|
|
121
|
+
"description": "hotels | vacation_rentals",
|
|
122
|
+
"default": "hotels",
|
|
123
|
+
"verified": true
|
|
124
|
+
}
|
|
125
|
+
],
|
|
126
|
+
"requests": [
|
|
127
|
+
{
|
|
128
|
+
"method": "POST",
|
|
129
|
+
"url": "https://www.google.com/_/TravelFrontendUi/data/batchexecute?rpcids=AtySUc&source-path=%2Ftravel%2Fsearch&f.sid=7513562915459271421&bl=boq_travel-frontend-ui_20260527.01_p0&hl=en-US&soc-app=162&soc-platform=1&soc-device=1&_reqid=2252256&rt=c",
|
|
130
|
+
"headers": {
|
|
131
|
+
"Content-Type": "application/x-www-form-urlencoded;charset=UTF-8",
|
|
132
|
+
"X-Same-Domain": "1",
|
|
133
|
+
"x-goog-ext-259736195-jspb": "[\"en-US\",\"US\",\"USD\",2,null,[420],null,null,7,[]]",
|
|
134
|
+
"x-goog-ext-190139975-jspb": "[\"US\",\"ZZ\",\"x6c25Q==\"]",
|
|
135
|
+
"Referer": "https://www.google.com/travel/search"
|
|
136
|
+
},
|
|
137
|
+
"body": "f.req={\"location\":\"${param.location}\",\"check_in_date\":\"${param.check_in_date}\",\"check_out_date\":\"${param.check_out_date}\",\"adults\":\"${param.adults}\",\"children\":\"${param.children}\",\"min_rating\":\"${param.min_rating}\",\"min_price\":\"${param.min_price}\",\"max_price\":\"${param.max_price}\",\"amenities\":\"${param.amenities}\",\"hotel_class\":\"${param.hotel_class}\",\"brands\":\"${param.brands}\",\"sort_by\":\"${param.sort_by}\",\"property_type\":\"${param.property_type}\"}",
|
|
138
|
+
"effect": "safe"
|
|
139
|
+
}
|
|
140
|
+
],
|
|
141
|
+
"site": "google-hotels",
|
|
142
|
+
"parserModule": "./parser.ts",
|
|
143
|
+
"requestTransformModule": "./request-transform.ts",
|
|
144
|
+
"liveVerified": true
|
|
145
|
+
};
|
|
146
|
+
|
|
147
|
+
export interface SearchHotelsInput {
|
|
148
|
+
/** Destination query, e.g. 'chicago loop', 'tahoe city', 'denver downtown' */
|
|
149
|
+
location?: string;
|
|
150
|
+
/** Check-in date (YYYY-MM-DD) */
|
|
151
|
+
check_in_date?: string;
|
|
152
|
+
/** Check-out date (YYYY-MM-DD) */
|
|
153
|
+
check_out_date?: string;
|
|
154
|
+
/** Number of adult travelers */
|
|
155
|
+
adults?: number;
|
|
156
|
+
/** Number of child travelers */
|
|
157
|
+
children?: number;
|
|
158
|
+
/** Minimum guest rating filter, e.g. 4.0 (0 = no filter) */
|
|
159
|
+
min_rating?: number;
|
|
160
|
+
/** Minimum nightly price filter (0 = no filter) */
|
|
161
|
+
min_price?: number;
|
|
162
|
+
/** Maximum nightly price filter, e.g. 338 (0 = no filter) */
|
|
163
|
+
max_price?: number;
|
|
164
|
+
/** Comma-separated amenity names or codes (e.g. 'free wi-fi,pool' or '6,9'); empty = no filter */
|
|
165
|
+
amenities?: string;
|
|
166
|
+
/** Comma-separated star classes, e.g. '2,3' or '4,5'; empty = no filter */
|
|
167
|
+
hotel_class?: string;
|
|
168
|
+
/** Comma-separated brand codes ('parent:child' or numeric); empty = no filter */
|
|
169
|
+
brands?: string;
|
|
170
|
+
/** Sort order: relevance | lowest_price | highest_rating | most_reviewed */
|
|
171
|
+
sort_by?: string;
|
|
172
|
+
/** hotels | vacation_rentals */
|
|
173
|
+
property_type?: string;
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
export async function searchHotels(
|
|
177
|
+
input: SearchHotelsInput,
|
|
178
|
+
opts: { credentials?: CredentialStore; fetchImpl?: typeof fetch; initialState?: Record<string, unknown> } = {},
|
|
179
|
+
): Promise<ToolResult> {
|
|
180
|
+
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
181
|
+
const params: Record<string, string | number | boolean> = {
|
|
182
|
+
location: input.location ?? "chicago loop",
|
|
183
|
+
check_in_date: input.check_in_date ?? "2026-07-03",
|
|
184
|
+
check_out_date: input.check_out_date ?? "2026-07-06",
|
|
185
|
+
adults: input.adults ?? 2,
|
|
186
|
+
children: input.children ?? 0,
|
|
187
|
+
min_rating: input.min_rating ?? 0,
|
|
188
|
+
min_price: input.min_price ?? 0,
|
|
189
|
+
max_price: input.max_price ?? 0,
|
|
190
|
+
amenities: input.amenities ?? "",
|
|
191
|
+
hotel_class: input.hotel_class ?? "",
|
|
192
|
+
brands: input.brands ?? "",
|
|
193
|
+
sort_by: input.sort_by ?? "relevance",
|
|
194
|
+
property_type: input.property_type ?? "hotels",
|
|
195
|
+
|
|
196
|
+
};
|
|
197
|
+
return executeWorkflow({
|
|
198
|
+
workflow: WORKFLOW,
|
|
199
|
+
params,
|
|
200
|
+
credentials: opts.credentials,
|
|
201
|
+
fetchImpl: opts.fetchImpl,
|
|
202
|
+
initialState: opts.initialState,
|
|
203
|
+
workflowPath: join(__dirname, 'workflow.json'),
|
|
204
|
+
});
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
export { WORKFLOW };
|