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,158 @@
|
|
|
1
|
+
// _shared/freq.ts
|
|
2
|
+
// Shared request-builder for the google-hotels batchexecute RPC. Every tool's
|
|
3
|
+
// per-tool transform constructs its own inner payload, then calls these helpers
|
|
4
|
+
// to wrap it in the f.req envelope and assemble the request URL.
|
|
5
|
+
//
|
|
6
|
+
// NOTE: the published export-signature comment claims encodeDate maps month-1
|
|
7
|
+
// (e.g. encodeDate('2026-07-03') => [2026,6,3]). The recording disproves this:
|
|
8
|
+
// every wire date is 1-based (seq 300 sends [2026,7,3] for a Jul 3 stay; seq
|
|
9
|
+
// 2547 sends [2026,6,8] for Jun 8). We follow the recording and emit the ISO
|
|
10
|
+
// month unchanged.
|
|
11
|
+
|
|
12
|
+
const BASE_URL = 'https://www.google.com/_/TravelFrontendUi/data/batchexecute';
|
|
13
|
+
|
|
14
|
+
// Default rpcid -> mode (seq 222 mejVKc='generic', seq 229 AtySUc='1',
|
|
15
|
+
// seq 286 M0CRd='generic', seq 497 ocp93e='1').
|
|
16
|
+
const MODE_BY_RPCID: Record<string, '1' | 'generic'> = {
|
|
17
|
+
mejVKc: 'generic',
|
|
18
|
+
M0CRd: 'generic',
|
|
19
|
+
AtySUc: '1',
|
|
20
|
+
ocp93e: '1',
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Wrap a tool-specific inner payload in the batchexecute f.req envelope:
|
|
25
|
+
* [[[ rpcid, JSON.stringify(innerPayload), null, mode ]]], URL-encoded with a
|
|
26
|
+
* trailing '&'. Spaces become %20 (encodeURIComponent, not '+') and the inner
|
|
27
|
+
* JSON's quotes become %5C%22, matching the recorded wire bytes exactly.
|
|
28
|
+
*/
|
|
29
|
+
export function buildFreqBody(
|
|
30
|
+
rpcid: string,
|
|
31
|
+
innerPayload: unknown,
|
|
32
|
+
mode: '1' | 'generic',
|
|
33
|
+
): string {
|
|
34
|
+
const inner = JSON.stringify(innerPayload);
|
|
35
|
+
const envelope = [[[rpcid, inner, null, mode]]];
|
|
36
|
+
return 'f.req=' + encodeURIComponent(JSON.stringify(envelope)) + '&';
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Assemble the batchexecute URL. f.sid and bl are session-bound (differ per
|
|
41
|
+
* capture); _reqid is browser-minted and need not be reproduced exactly, so we
|
|
42
|
+
* emit a fresh positive integer. Query order matches the recording.
|
|
43
|
+
*/
|
|
44
|
+
export function buildBatchExecuteUrl(
|
|
45
|
+
rpcid: string,
|
|
46
|
+
session: { f_sid: string; bl: string },
|
|
47
|
+
): string {
|
|
48
|
+
const reqid = Math.floor(Math.random() * 9_000_000) + 1_000_000;
|
|
49
|
+
const params = new URLSearchParams();
|
|
50
|
+
params.set('rpcids', rpcid);
|
|
51
|
+
params.set('source-path', '/travel/search');
|
|
52
|
+
params.set('f.sid', session.f_sid);
|
|
53
|
+
params.set('bl', session.bl);
|
|
54
|
+
params.set('hl', 'en-US');
|
|
55
|
+
params.set('soc-app', '162');
|
|
56
|
+
params.set('soc-platform', '1');
|
|
57
|
+
params.set('soc-device', '1');
|
|
58
|
+
params.set('_reqid', String(reqid));
|
|
59
|
+
params.set('rt', 'c');
|
|
60
|
+
return BASE_URL + '?' + params.toString();
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Parse a 'YYYY-MM-DD' ISO date into the wire tuple [year, month, day].
|
|
65
|
+
* Month is 1-based (emitted verbatim), as proven by the recorded payloads.
|
|
66
|
+
*/
|
|
67
|
+
export function encodeDate(isoDate: string): [number, number, number] {
|
|
68
|
+
const m = /^(\d{4})-(\d{2})-(\d{2})$/.exec(isoDate);
|
|
69
|
+
if (!m) throw new Error(`encodeDate: bad ISO date: ${isoDate}`);
|
|
70
|
+
// Under noUncheckedIndexedAccess captures are string | undefined; the regex
|
|
71
|
+
// match guarantees all three groups, so assert them.
|
|
72
|
+
return [Number(m[1]!), Number(m[2]!), Number(m[3]!)];
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
type DecodedFreq = { innerPayload: unknown; mode: '1' | 'generic' };
|
|
76
|
+
|
|
77
|
+
// Inverse of buildFreqBody: recover the inner payload + mode from a recorded
|
|
78
|
+
// f.req body so transform() can re-sign a recorded request faithfully.
|
|
79
|
+
function decodeFreqBody(raw: string): DecodedFreq | null {
|
|
80
|
+
const at = raw.indexOf('f.req=');
|
|
81
|
+
if (at === -1) return null;
|
|
82
|
+
let val = raw.slice(at + 'f.req='.length);
|
|
83
|
+
const amp = val.indexOf('&');
|
|
84
|
+
if (amp !== -1) val = val.slice(0, amp);
|
|
85
|
+
let envelope: unknown;
|
|
86
|
+
try {
|
|
87
|
+
envelope = JSON.parse(decodeURIComponent(val));
|
|
88
|
+
} catch {
|
|
89
|
+
return null;
|
|
90
|
+
}
|
|
91
|
+
if (!Array.isArray(envelope)) return null;
|
|
92
|
+
const lvl1 = envelope[0];
|
|
93
|
+
if (!Array.isArray(lvl1)) return null;
|
|
94
|
+
const row = lvl1[0];
|
|
95
|
+
if (!Array.isArray(row)) return null;
|
|
96
|
+
const innerStr = row[1];
|
|
97
|
+
const rawMode = row[3];
|
|
98
|
+
if (typeof innerStr !== 'string') return null;
|
|
99
|
+
let innerPayload: unknown;
|
|
100
|
+
try {
|
|
101
|
+
innerPayload = JSON.parse(innerStr);
|
|
102
|
+
} catch {
|
|
103
|
+
return null;
|
|
104
|
+
}
|
|
105
|
+
const mode: '1' | 'generic' = rawMode === 'generic' ? 'generic' : '1';
|
|
106
|
+
return { innerPayload, mode };
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/**
|
|
110
|
+
* Thin entry the runtime calls. Parses rpcid/f.sid/bl from the recorded URL,
|
|
111
|
+
* resolves the inner payload + mode (from params, or by decoding a supplied
|
|
112
|
+
* recorded body), and returns the re-signed { url, body }. Never throws for a
|
|
113
|
+
* recorded batchexecute URL.
|
|
114
|
+
*/
|
|
115
|
+
export function transform(
|
|
116
|
+
method: string,
|
|
117
|
+
url: string,
|
|
118
|
+
responses: Record<string, any>,
|
|
119
|
+
params?: Record<string, any>,
|
|
120
|
+
): { url: string; body: string } {
|
|
121
|
+
void method;
|
|
122
|
+
void responses;
|
|
123
|
+
const u = new URL(url);
|
|
124
|
+
const rpcid = u.searchParams.get('rpcids') ?? '';
|
|
125
|
+
const fSid = u.searchParams.get('f.sid') ?? '';
|
|
126
|
+
const bl = u.searchParams.get('bl') ?? '';
|
|
127
|
+
|
|
128
|
+
const builtUrl = buildBatchExecuteUrl(rpcid, { f_sid: fSid, bl });
|
|
129
|
+
|
|
130
|
+
let innerPayload: unknown = params ? params.innerPayload : undefined;
|
|
131
|
+
let mode: '1' | 'generic' | undefined =
|
|
132
|
+
params && (params.mode === '1' || params.mode === 'generic')
|
|
133
|
+
? params.mode
|
|
134
|
+
: undefined;
|
|
135
|
+
|
|
136
|
+
// Recover inner payload / mode from a recorded body when not supplied so a
|
|
137
|
+
// re-sign of the recorded request reproduces its f.req byte-for-byte.
|
|
138
|
+
if (innerPayload === undefined && params) {
|
|
139
|
+
const rawBody =
|
|
140
|
+
typeof params.requestBody === 'string'
|
|
141
|
+
? params.requestBody
|
|
142
|
+
: typeof params.body === 'string'
|
|
143
|
+
? params.body
|
|
144
|
+
: undefined;
|
|
145
|
+
if (typeof rawBody === 'string') {
|
|
146
|
+
const decoded = decodeFreqBody(rawBody);
|
|
147
|
+
if (decoded) {
|
|
148
|
+
innerPayload = decoded.innerPayload;
|
|
149
|
+
if (mode === undefined) mode = decoded.mode;
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
if (mode === undefined) mode = MODE_BY_RPCID[rpcid] ?? '1';
|
|
155
|
+
if (innerPayload === undefined) innerPayload = [];
|
|
156
|
+
|
|
157
|
+
return { url: builtUrl, body: buildFreqBody(rpcid, innerPayload, mode) };
|
|
158
|
+
}
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* GENERATED by `imprint emit` — DO NOT EDIT BY HAND.
|
|
3
|
+
*
|
|
4
|
+
* Tool: autocomplete_hotel_location
|
|
5
|
+
* Site: google-hotels
|
|
6
|
+
* Intent: Resolve a free-text place query into Google Hotels location autocomplete suggestions (display names and place identifiers).
|
|
7
|
+
*
|
|
8
|
+
* To regenerate: imprint emit ~/.imprint/google-hotels/autocomplete_hotel_location/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": "autocomplete_hotel_location",
|
|
21
|
+
"intent": {
|
|
22
|
+
"description": "Resolve a free-text place query into Google Hotels location autocomplete suggestions (display names and place identifiers).",
|
|
23
|
+
"userSaid": "searched for hotels at chicago loop; changed location to tahoe city; searched for hotels in tahoe; searched for hotels in denver downtown"
|
|
24
|
+
},
|
|
25
|
+
"parameters": [
|
|
26
|
+
{
|
|
27
|
+
"name": "query",
|
|
28
|
+
"type": "string",
|
|
29
|
+
"description": "Partial place/hotel text to autocomplete, e.g. 'chicago loop' or 'tahoe city hotels'.",
|
|
30
|
+
"verified": true
|
|
31
|
+
}
|
|
32
|
+
],
|
|
33
|
+
"requests": [
|
|
34
|
+
{
|
|
35
|
+
"method": "POST",
|
|
36
|
+
"url": "https://www.google.com/_/TravelFrontendUi/data/batchexecute?rpcids=mejVKc&source-path=%2Ftravel%2Fsearch&f.sid=-7129101702754032847&bl=boq_travel-frontend-ui_20260603.00_p0&hl=en-US&soc-app=162&soc-platform=1&soc-device=1&_reqid=1943921&rt=c",
|
|
37
|
+
"headers": {
|
|
38
|
+
"Content-Type": "application/x-www-form-urlencoded;charset=UTF-8",
|
|
39
|
+
"X-Same-Domain": "1",
|
|
40
|
+
"x-goog-ext-259736195-jspb": "[\"en-US\",\"US\",\"USD\",2,null,[420],null,null,7,[]]",
|
|
41
|
+
"Origin": "https://www.google.com",
|
|
42
|
+
"Referer": "https://www.google.com/travel/search",
|
|
43
|
+
"Accept-Language": "en-US,en;q=0.9",
|
|
44
|
+
"User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/148.0.0.0 Safari/537.36"
|
|
45
|
+
},
|
|
46
|
+
"body": "f.req=%5B%5B%5B%22mejVKc%22%2C%22%5B%5C%22${param.query}%5C%22%2C%5C%22${param.query}%5C%22%2C1%2C1%2Cnull%2C30%5D%22%2Cnull%2C%22generic%22%5D%5D%5D&",
|
|
47
|
+
"effect": "safe"
|
|
48
|
+
}
|
|
49
|
+
],
|
|
50
|
+
"site": "google-hotels",
|
|
51
|
+
"parserModule": "./parser.ts",
|
|
52
|
+
"requestTransformModule": "./request-transform.ts",
|
|
53
|
+
"liveVerified": true
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
export interface AutocompleteHotelLocationInput {
|
|
57
|
+
/** Partial place/hotel text to autocomplete, e.g. 'chicago loop' or 'tahoe city hotels'. */
|
|
58
|
+
query: string;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
export async function autocompleteHotelLocation(
|
|
62
|
+
input: AutocompleteHotelLocationInput,
|
|
63
|
+
opts: { credentials?: CredentialStore; fetchImpl?: typeof fetch; initialState?: Record<string, unknown> } = {},
|
|
64
|
+
): Promise<ToolResult> {
|
|
65
|
+
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
66
|
+
const params: Record<string, string | number | boolean> = {
|
|
67
|
+
query: input.query,
|
|
68
|
+
|
|
69
|
+
};
|
|
70
|
+
return executeWorkflow({
|
|
71
|
+
workflow: WORKFLOW,
|
|
72
|
+
params,
|
|
73
|
+
credentials: opts.credentials,
|
|
74
|
+
fetchImpl: opts.fetchImpl,
|
|
75
|
+
initialState: opts.initialState,
|
|
76
|
+
workflowPath: join(__dirname, 'workflow.json'),
|
|
77
|
+
});
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
export { WORKFLOW };
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
// Parser for autocomplete_hotel_location (Google Hotels mejVKc autocomplete).
|
|
2
|
+
// Decodes the batchexecute anti-XSSI envelope via the shared helper, then maps
|
|
3
|
+
// the inner suggestion list into clean named-field objects.
|
|
4
|
+
//
|
|
5
|
+
// Inner JSON shape (seq 222 / seq 2550):
|
|
6
|
+
// [ [ <entry>, <entry>, ... ], <trailing-meta> ]
|
|
7
|
+
// Each <entry> is positional:
|
|
8
|
+
// [0] number 1 = place/hotel suggestion, 0 = map/other suggestion
|
|
9
|
+
// [1] string suggested query text (e.g. "chicago loop hotels")
|
|
10
|
+
// [5] array bolded match segments: [ [text, isBold], ... ]
|
|
11
|
+
// [7] array id container; [7][0] is the place id triple [a,b,c]
|
|
12
|
+
// [11] string repeats the suggested query text
|
|
13
|
+
|
|
14
|
+
import { parseBatchExecute } from '../_shared/batchexecute.ts';
|
|
15
|
+
|
|
16
|
+
interface Segment {
|
|
17
|
+
text: string;
|
|
18
|
+
bold: boolean;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
interface Suggestion {
|
|
22
|
+
query: string;
|
|
23
|
+
segments: Segment[];
|
|
24
|
+
id: number[] | null;
|
|
25
|
+
isPlaceSuggestion: boolean;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export function extract(
|
|
29
|
+
rawResponse: unknown,
|
|
30
|
+
context?: {
|
|
31
|
+
params: Record<string, string | number | boolean>;
|
|
32
|
+
responses: unknown[];
|
|
33
|
+
},
|
|
34
|
+
): unknown {
|
|
35
|
+
const raw =
|
|
36
|
+
typeof rawResponse === 'string' ? rawResponse : JSON.stringify(rawResponse);
|
|
37
|
+
|
|
38
|
+
const inner = parseBatchExecute(raw, 'mejVKc');
|
|
39
|
+
const list: any[] =
|
|
40
|
+
Array.isArray(inner) && Array.isArray(inner[0]) ? inner[0] : [];
|
|
41
|
+
|
|
42
|
+
const suggestions: Suggestion[] = list
|
|
43
|
+
.filter(
|
|
44
|
+
(e: any) =>
|
|
45
|
+
Array.isArray(e) && typeof e[1] === 'string' && e[1].length > 0,
|
|
46
|
+
)
|
|
47
|
+
.map((e: any) => {
|
|
48
|
+
const segments: Segment[] = Array.isArray(e[5])
|
|
49
|
+
? e[5]
|
|
50
|
+
.filter((s: any) => Array.isArray(s))
|
|
51
|
+
.map((s: any) => ({ text: String(s[0] ?? ''), bold: Boolean(s[1]) }))
|
|
52
|
+
: [];
|
|
53
|
+
const id =
|
|
54
|
+
Array.isArray(e[7]) && Array.isArray(e[7][0])
|
|
55
|
+
? (e[7][0] as number[])
|
|
56
|
+
: null;
|
|
57
|
+
return {
|
|
58
|
+
query: e[1] as string,
|
|
59
|
+
segments,
|
|
60
|
+
id,
|
|
61
|
+
isPlaceSuggestion: e[0] === 1,
|
|
62
|
+
};
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
const queryParam =
|
|
66
|
+
context?.params && typeof context.params.query !== 'undefined'
|
|
67
|
+
? String(context.params.query)
|
|
68
|
+
: null;
|
|
69
|
+
|
|
70
|
+
return { query: queryParam, suggestions };
|
|
71
|
+
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
toolName: autocomplete_hotel_location
|
|
2
|
+
summary: Resolve a free-text place query into Google Hotels location autocomplete suggestions via the mejVKc endpoint.
|
|
3
|
+
parameters:
|
|
4
|
+
- name: query
|
|
5
|
+
type: string
|
|
6
|
+
description: Partial place/hotel text to autocomplete, e.g. "chicago loop" or "tahoe city"
|
|
7
|
+
steps:
|
|
8
|
+
- action: navigate
|
|
9
|
+
url: https://www.google.com/travel/search
|
|
10
|
+
wait_for: networkidle
|
|
11
|
+
- action: click
|
|
12
|
+
locators:
|
|
13
|
+
- by: aria_label
|
|
14
|
+
value: Search for places, hotels and more
|
|
15
|
+
- by: role
|
|
16
|
+
value: textbox
|
|
17
|
+
name: 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: ${query}
|
|
29
|
+
wait_for:
|
|
30
|
+
xhr: rpcids=mejVKc
|
|
31
|
+
result:
|
|
32
|
+
source: xhr
|
|
33
|
+
url_pattern: rpcids=mejVKc
|
|
34
|
+
extract: "[0][].1"
|
|
35
|
+
return_as: suggestions
|
|
36
|
+
notes: "The mejVKc response uses Google's batchexecute anti-XSSI envelope: strip the )]}' prefix and chunk-length lines, locate the [\"wrb.fr\",\"mejVKc\",\"<inner>\"] tuple, then JSON.parse the doubly-encoded inner string. The parsed inner JSON is [ [suggestion, ...], [meta] ]; each suggestion is a positional array whose index 1 is the suggested query string (e.g. \"granlibakken tahoe\"), index 5 holds the bolded match segments, and index 7 holds the internal id triple used to disambiguate the place. The extract path [0][].1 yields the list of suggestion strings; downstream callers needing the id triple should read index 7 of each entry. Autocomplete fires per keystroke during recording (debounced); only the final mejVKc response for the fully-typed query is relevant. No auth required — unauthenticated POST to /_/TravelFrontendUi/data/batchexecute. The DOM dropdown options (li.Q1RWxd span.S5TdWc) are an alternative DOM-source fallback if the XHR shape changes."
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
// Per-tool request-transform for autocomplete_hotel_location (rpcid=mejVKc).
|
|
2
|
+
// Imports the shared google-hotels batchexecute helpers (../_shared/freq.ts)
|
|
3
|
+
// and constructs the mejVKc-specific inner payload from the tool's `query`
|
|
4
|
+
// param, then wraps it with the shared envelope/URL builders.
|
|
5
|
+
//
|
|
6
|
+
// Inner payload shape per the recording (seq 2550, newer build 20260603):
|
|
7
|
+
// ["tahoe city hotels","tahoe city hotels",1,1,null,30]
|
|
8
|
+
// i.e. [query, query(context), 1, 1, null, 30] with mode 'generic'.
|
|
9
|
+
// (Older seq 222 carried a user-geo context string + trailing fields; that geo
|
|
10
|
+
// context is session-specific and not a caller-controlled parameter, so we
|
|
11
|
+
// follow the newer same-session pair and mirror the query into element [1].)
|
|
12
|
+
|
|
13
|
+
import { buildBatchExecuteUrl, buildFreqBody } from '../_shared/freq.ts';
|
|
14
|
+
|
|
15
|
+
export function transform(
|
|
16
|
+
method: string,
|
|
17
|
+
url: string,
|
|
18
|
+
responses: Record<string, any>,
|
|
19
|
+
params?: Record<string, any>,
|
|
20
|
+
): { url: string; body: string } {
|
|
21
|
+
void method;
|
|
22
|
+
void responses;
|
|
23
|
+
|
|
24
|
+
const query = String(params?.query ?? '');
|
|
25
|
+
|
|
26
|
+
// Session params (f.sid / bl) ride along in the workflow URL; buildBatchExecuteUrl
|
|
27
|
+
// reassembles the URL with a fresh _reqid and the rpcids/source-path/etc params.
|
|
28
|
+
const u = new URL(url);
|
|
29
|
+
const fSid = u.searchParams.get('f.sid') ?? '';
|
|
30
|
+
const bl = u.searchParams.get('bl') ?? '';
|
|
31
|
+
const builtUrl = buildBatchExecuteUrl('mejVKc', { f_sid: fSid, bl });
|
|
32
|
+
|
|
33
|
+
const innerPayload = [query, query, 1, 1, null, 30];
|
|
34
|
+
const body = buildFreqBody('mejVKc', innerPayload, 'generic');
|
|
35
|
+
|
|
36
|
+
return { url: builtUrl, body };
|
|
37
|
+
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
{
|
|
2
|
+
"toolName": "autocomplete_hotel_location",
|
|
3
|
+
"intent": {
|
|
4
|
+
"description": "Resolve a free-text place query into Google Hotels location autocomplete suggestions (display names and place identifiers).",
|
|
5
|
+
"userSaid": "searched for hotels at chicago loop; changed location to tahoe city; searched for hotels in tahoe; searched for hotels in denver downtown"
|
|
6
|
+
},
|
|
7
|
+
"parameters": [
|
|
8
|
+
{
|
|
9
|
+
"name": "query",
|
|
10
|
+
"type": "string",
|
|
11
|
+
"description": "Partial place/hotel text to autocomplete, e.g. 'chicago loop' or 'tahoe city hotels'.",
|
|
12
|
+
"verified": true
|
|
13
|
+
}
|
|
14
|
+
],
|
|
15
|
+
"site": "google-hotels",
|
|
16
|
+
"requestTransformModule": "./request-transform.ts",
|
|
17
|
+
"parserModule": "./parser.ts",
|
|
18
|
+
"requests": [
|
|
19
|
+
{
|
|
20
|
+
"method": "POST",
|
|
21
|
+
"url": "https://www.google.com/_/TravelFrontendUi/data/batchexecute?rpcids=mejVKc&source-path=%2Ftravel%2Fsearch&f.sid=-7129101702754032847&bl=boq_travel-frontend-ui_20260603.00_p0&hl=en-US&soc-app=162&soc-platform=1&soc-device=1&_reqid=1943921&rt=c",
|
|
22
|
+
"headers": {
|
|
23
|
+
"Content-Type": "application/x-www-form-urlencoded;charset=UTF-8",
|
|
24
|
+
"X-Same-Domain": "1",
|
|
25
|
+
"x-goog-ext-259736195-jspb": "[\"en-US\",\"US\",\"USD\",2,null,[420],null,null,7,[]]",
|
|
26
|
+
"Origin": "https://www.google.com",
|
|
27
|
+
"Referer": "https://www.google.com/travel/search",
|
|
28
|
+
"Accept-Language": "en-US,en;q=0.9",
|
|
29
|
+
"User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/148.0.0.0 Safari/537.36"
|
|
30
|
+
},
|
|
31
|
+
"body": "f.req=%5B%5B%5B%22mejVKc%22%2C%22%5B%5C%22${param.query}%5C%22%2C%5C%22${param.query}%5C%22%2C1%2C1%2Cnull%2C30%5D%22%2Cnull%2C%22generic%22%5D%5D%5D&",
|
|
32
|
+
"effect": "safe"
|
|
33
|
+
}
|
|
34
|
+
],
|
|
35
|
+
"liveVerified": true
|
|
36
|
+
}
|
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* GENERATED by `imprint emit` — DO NOT EDIT BY HAND.
|
|
3
|
+
*
|
|
4
|
+
* Tool: get_hotel_booking_options
|
|
5
|
+
* Site: google-hotels
|
|
6
|
+
* Intent: Get booking options (per-provider nightly and stay-total prices with click-through URLs) for the top hotel in a location/date window.
|
|
7
|
+
*
|
|
8
|
+
* To regenerate: imprint emit ~/.imprint/google-hotels/get_hotel_booking_options/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_booking_options",
|
|
21
|
+
"intent": {
|
|
22
|
+
"description": "Get booking options (per-provider nightly and stay-total prices with click-through URLs) for the top hotel in a location/date window.",
|
|
23
|
+
"userSaid": "clicked one of the offerings, saw booking options; changed to stay total pricing; looked at more booking options; changed number of adults"
|
|
24
|
+
},
|
|
25
|
+
"parameters": [
|
|
26
|
+
{
|
|
27
|
+
"name": "location_context",
|
|
28
|
+
"type": "string",
|
|
29
|
+
"description": "Opaque area token from search_hotels (shape '<mid>|<displayName>', e.g. '/m/0gz469|Chicago Loop').",
|
|
30
|
+
"default": "/m/0gz469|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": true
|
|
39
|
+
},
|
|
40
|
+
{
|
|
41
|
+
"name": "check_out_date",
|
|
42
|
+
"type": "string",
|
|
43
|
+
"description": "Check-out date (YYYY-MM-DD)",
|
|
44
|
+
"default": "2026-07-06",
|
|
45
|
+
"verified": true
|
|
46
|
+
},
|
|
47
|
+
{
|
|
48
|
+
"name": "adults",
|
|
49
|
+
"type": "number",
|
|
50
|
+
"description": "Number of adults for pricing",
|
|
51
|
+
"default": 2,
|
|
52
|
+
"verified": true
|
|
53
|
+
},
|
|
54
|
+
{
|
|
55
|
+
"name": "children",
|
|
56
|
+
"type": "number",
|
|
57
|
+
"description": "Number of children for pricing (0 = none)",
|
|
58
|
+
"default": 0,
|
|
59
|
+
"verified": false,
|
|
60
|
+
"verifyNote": "annotated"
|
|
61
|
+
},
|
|
62
|
+
{
|
|
63
|
+
"name": "price_mode",
|
|
64
|
+
"type": "string",
|
|
65
|
+
"description": "nightly | stay_total. Each offer carries both; the flag selects the UI default.",
|
|
66
|
+
"default": "nightly",
|
|
67
|
+
"verified": true
|
|
68
|
+
}
|
|
69
|
+
],
|
|
70
|
+
"requests": [
|
|
71
|
+
{
|
|
72
|
+
"method": "POST",
|
|
73
|
+
"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",
|
|
74
|
+
"headers": {
|
|
75
|
+
"Content-Type": "application/x-www-form-urlencoded;charset=UTF-8",
|
|
76
|
+
"X-Same-Domain": "1",
|
|
77
|
+
"x-goog-ext-259736195-jspb": "[\"en-US\",\"US\",\"USD\",2,null,[420],null,null,7,[]]",
|
|
78
|
+
"x-goog-ext-190139975-jspb": "[\"US\",\"ZZ\",\"x6c25Q==\"]",
|
|
79
|
+
"Referer": "https://www.google.com/travel/search"
|
|
80
|
+
},
|
|
81
|
+
"body": "f.req={\"location_context\":\"${param.location_context}\",\"check_in_date\":\"${param.check_in_date}\",\"check_out_date\":\"${param.check_out_date}\",\"adults\":\"${param.adults}\",\"children\":\"${param.children}\"}",
|
|
82
|
+
"effect": "safe"
|
|
83
|
+
},
|
|
84
|
+
{
|
|
85
|
+
"method": "POST",
|
|
86
|
+
"url": "https://www.google.com/_/TravelFrontendUi/data/batchexecute?rpcids=M0CRd&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=2552256&rt=c",
|
|
87
|
+
"headers": {
|
|
88
|
+
"Content-Type": "application/x-www-form-urlencoded;charset=UTF-8",
|
|
89
|
+
"X-Same-Domain": "1",
|
|
90
|
+
"x-goog-ext-259736195-jspb": "[\"en-US\",\"US\",\"USD\",2,null,[420],null,null,7,[]]",
|
|
91
|
+
"x-goog-ext-190139975-jspb": "[\"US\",\"ZZ\",\"x6c25Q==\"]",
|
|
92
|
+
"Referer": "https://www.google.com/travel/search"
|
|
93
|
+
},
|
|
94
|
+
"body": "f.req={\"location_context\":\"${param.location_context}\",\"check_in_date\":\"${param.check_in_date}\",\"check_out_date\":\"${param.check_out_date}\",\"adults\":\"${param.adults}\",\"children\":\"${param.children}\",\"price_mode\":\"${param.price_mode}\"}",
|
|
95
|
+
"effect": "safe"
|
|
96
|
+
}
|
|
97
|
+
],
|
|
98
|
+
"site": "google-hotels",
|
|
99
|
+
"parserModule": "./parser.ts",
|
|
100
|
+
"requestTransformModule": "./request-transform.ts",
|
|
101
|
+
"liveVerified": true
|
|
102
|
+
};
|
|
103
|
+
|
|
104
|
+
export interface GetHotelBookingOptionsInput {
|
|
105
|
+
/** Opaque area token from search_hotels (shape '<mid>|<displayName>', e.g. '/m/0gz469|Chicago Loop'). */
|
|
106
|
+
location_context?: string;
|
|
107
|
+
/** Check-in date (YYYY-MM-DD) */
|
|
108
|
+
check_in_date?: string;
|
|
109
|
+
/** Check-out date (YYYY-MM-DD) */
|
|
110
|
+
check_out_date?: string;
|
|
111
|
+
/** Number of adults for pricing */
|
|
112
|
+
adults?: number;
|
|
113
|
+
/** Number of children for pricing (0 = none) */
|
|
114
|
+
children?: number;
|
|
115
|
+
/** nightly | stay_total. Each offer carries both; the flag selects the UI default. */
|
|
116
|
+
price_mode?: string;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
export async function getHotelBookingOptions(
|
|
120
|
+
input: GetHotelBookingOptionsInput,
|
|
121
|
+
opts: { credentials?: CredentialStore; fetchImpl?: typeof fetch; initialState?: Record<string, unknown> } = {},
|
|
122
|
+
): Promise<ToolResult> {
|
|
123
|
+
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
124
|
+
const params: Record<string, string | number | boolean> = {
|
|
125
|
+
location_context: input.location_context ?? "/m/0gz469|Chicago Loop",
|
|
126
|
+
check_in_date: input.check_in_date ?? "2026-07-03",
|
|
127
|
+
check_out_date: input.check_out_date ?? "2026-07-06",
|
|
128
|
+
adults: input.adults ?? 2,
|
|
129
|
+
children: input.children ?? 0,
|
|
130
|
+
price_mode: input.price_mode ?? "nightly",
|
|
131
|
+
|
|
132
|
+
};
|
|
133
|
+
return executeWorkflow({
|
|
134
|
+
workflow: WORKFLOW,
|
|
135
|
+
params,
|
|
136
|
+
credentials: opts.credentials,
|
|
137
|
+
fetchImpl: opts.fetchImpl,
|
|
138
|
+
initialState: opts.initialState,
|
|
139
|
+
workflowPath: join(__dirname, 'workflow.json'),
|
|
140
|
+
});
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
export { WORKFLOW };
|