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.
Files changed (130) hide show
  1. package/README.md +193 -189
  2. package/examples/discoverandgo/README.md +1 -1
  3. package/examples/echo/README.md +1 -1
  4. package/examples/google-flights/README.md +28 -0
  5. package/examples/google-flights/_shared/batchexecute.ts +63 -0
  6. package/examples/google-flights/_shared/flights_request.ts +95 -0
  7. package/examples/google-flights/_shared/package.json +9 -0
  8. package/examples/google-flights/get_flight_booking_details/index.ts +159 -0
  9. package/examples/google-flights/get_flight_booking_details/package.json +9 -0
  10. package/examples/google-flights/get_flight_booking_details/parser.ts +182 -0
  11. package/examples/google-flights/get_flight_booking_details/playbook.yaml +138 -0
  12. package/examples/google-flights/get_flight_booking_details/request-transform.ts +86 -0
  13. package/examples/google-flights/get_flight_booking_details/workflow.json +98 -0
  14. package/examples/google-flights/get_flight_calendar_prices/index.ts +131 -0
  15. package/examples/google-flights/get_flight_calendar_prices/package.json +9 -0
  16. package/examples/google-flights/get_flight_calendar_prices/parser.ts +86 -0
  17. package/examples/google-flights/get_flight_calendar_prices/playbook.yaml +97 -0
  18. package/examples/google-flights/get_flight_calendar_prices/request-transform.ts +31 -0
  19. package/examples/google-flights/get_flight_calendar_prices/workflow.json +78 -0
  20. package/examples/google-flights/lookup_airport/index.ts +101 -0
  21. package/examples/google-flights/lookup_airport/package.json +9 -0
  22. package/examples/google-flights/lookup_airport/parser.ts +66 -0
  23. package/examples/google-flights/lookup_airport/playbook.yaml +47 -0
  24. package/examples/google-flights/lookup_airport/request-transform.ts +20 -0
  25. package/examples/google-flights/lookup_airport/workflow.json +57 -0
  26. package/examples/google-flights/search_flights/index.ts +219 -0
  27. package/examples/google-flights/search_flights/package.json +9 -0
  28. package/examples/google-flights/search_flights/parser.ts +169 -0
  29. package/examples/google-flights/search_flights/playbook.yaml +184 -0
  30. package/examples/google-flights/search_flights/request-transform.ts +119 -0
  31. package/examples/google-flights/search_flights/workflow.json +143 -0
  32. package/examples/google-hotels/README.md +29 -0
  33. package/examples/google-hotels/_shared/batchexecute.ts +73 -0
  34. package/examples/google-hotels/_shared/freq.ts +158 -0
  35. package/examples/google-hotels/_shared/package.json +9 -0
  36. package/examples/google-hotels/autocomplete_hotel_location/index.ts +80 -0
  37. package/examples/google-hotels/autocomplete_hotel_location/package.json +9 -0
  38. package/examples/google-hotels/autocomplete_hotel_location/parser.ts +71 -0
  39. package/examples/google-hotels/autocomplete_hotel_location/playbook.yaml +36 -0
  40. package/examples/google-hotels/autocomplete_hotel_location/request-transform.ts +37 -0
  41. package/examples/google-hotels/autocomplete_hotel_location/workflow.json +36 -0
  42. package/examples/google-hotels/get_hotel_booking_options/index.ts +143 -0
  43. package/examples/google-hotels/get_hotel_booking_options/package.json +9 -0
  44. package/examples/google-hotels/get_hotel_booking_options/parser.ts +271 -0
  45. package/examples/google-hotels/get_hotel_booking_options/playbook.yaml +154 -0
  46. package/examples/google-hotels/get_hotel_booking_options/request-transform.ts +154 -0
  47. package/examples/google-hotels/get_hotel_booking_options/workflow.json +84 -0
  48. package/examples/google-hotels/get_hotel_reviews/index.ts +81 -0
  49. package/examples/google-hotels/get_hotel_reviews/package.json +9 -0
  50. package/examples/google-hotels/get_hotel_reviews/parser.ts +128 -0
  51. package/examples/google-hotels/get_hotel_reviews/playbook.yaml +64 -0
  52. package/examples/google-hotels/get_hotel_reviews/request-transform.ts +42 -0
  53. package/examples/google-hotels/get_hotel_reviews/workflow.json +37 -0
  54. package/examples/google-hotels/search_hotels/index.ts +207 -0
  55. package/examples/google-hotels/search_hotels/package.json +9 -0
  56. package/examples/google-hotels/search_hotels/parser.ts +260 -0
  57. package/examples/google-hotels/search_hotels/playbook.yaml +87 -0
  58. package/examples/google-hotels/search_hotels/request-transform.ts +197 -0
  59. package/examples/google-hotels/search_hotels/workflow.json +127 -0
  60. package/examples/southwest/README.md +3 -2
  61. package/examples/southwest/search_southwest_flights/index.ts +18 -1
  62. package/examples/southwest/search_southwest_flights/workflow.json +18 -1
  63. package/package.json +3 -2
  64. package/prompts/audit-agent.md +71 -0
  65. package/prompts/build-planning.md +74 -0
  66. package/prompts/compile-agent.md +131 -27
  67. package/prompts/prereq-builder.md +64 -0
  68. package/prompts/prereq-planner.md +34 -0
  69. package/prompts/tool-planning.md +39 -0
  70. package/src/cli.ts +116 -3
  71. package/src/imprint/agent.ts +5 -0
  72. package/src/imprint/audit.ts +996 -0
  73. package/src/imprint/backend-ladder.ts +1214 -184
  74. package/src/imprint/build-plan.ts +1051 -0
  75. package/src/imprint/cdp-browser-fetch.ts +592 -0
  76. package/src/imprint/cdp-jar-cache.ts +320 -0
  77. package/src/imprint/chromium.ts +414 -8
  78. package/src/imprint/claude-cli-compile.ts +125 -25
  79. package/src/imprint/codex-cli-compile.ts +26 -23
  80. package/src/imprint/compile-agent-types.ts +38 -0
  81. package/src/imprint/compile-agent.ts +63 -25
  82. package/src/imprint/compile-tools.ts +1666 -66
  83. package/src/imprint/compile.ts +13 -1
  84. package/src/imprint/concurrency.ts +87 -0
  85. package/src/imprint/cron.ts +4 -0
  86. package/src/imprint/doctor.ts +48 -3
  87. package/src/imprint/freeform-redact.ts +5 -4
  88. package/src/imprint/install.ts +79 -4
  89. package/src/imprint/integrations.ts +3 -3
  90. package/src/imprint/llm.ts +56 -8
  91. package/src/imprint/mcp-compile-server.ts +43 -10
  92. package/src/imprint/mcp-maintenance.ts +18 -102
  93. package/src/imprint/mcp-server.ts +73 -7
  94. package/src/imprint/multi-progress.ts +7 -2
  95. package/src/imprint/param-grounding.ts +367 -0
  96. package/src/imprint/paths.ts +29 -0
  97. package/src/imprint/playbook-runner.ts +101 -40
  98. package/src/imprint/prereq-builder.ts +651 -0
  99. package/src/imprint/probe-backends.ts +6 -3
  100. package/src/imprint/record.ts +10 -1
  101. package/src/imprint/redact.ts +30 -2
  102. package/src/imprint/replay-capture.ts +19 -18
  103. package/src/imprint/runtime.ts +19 -10
  104. package/src/imprint/session-diff.ts +79 -2
  105. package/src/imprint/session-merge.ts +9 -5
  106. package/src/imprint/stealth-chromium.ts +79 -0
  107. package/src/imprint/stealth-fetch.ts +309 -29
  108. package/src/imprint/stealth-token-cache.ts +88 -0
  109. package/src/imprint/teach-plan.ts +251 -0
  110. package/src/imprint/teach-state.ts +10 -0
  111. package/src/imprint/teach.ts +456 -142
  112. package/src/imprint/tool-candidates.ts +72 -14
  113. package/src/imprint/tool-plan.ts +313 -0
  114. package/src/imprint/tracing.ts +135 -6
  115. package/src/imprint/types.ts +61 -3
  116. package/examples/google-flights/search_google_flights/index.ts +0 -101
  117. package/examples/google-flights/search_google_flights/parser.test.ts +0 -140
  118. package/examples/google-flights/search_google_flights/parser.ts +0 -189
  119. package/examples/google-flights/search_google_flights/playbook.yaml +0 -130
  120. package/examples/google-flights/search_google_flights/workflow.json +0 -48
  121. package/examples/google-hotels/search_google_hotels/index.ts +0 -194
  122. package/examples/google-hotels/search_google_hotels/parser.test.ts +0 -168
  123. package/examples/google-hotels/search_google_hotels/parser.ts +0 -330
  124. package/examples/google-hotels/search_google_hotels/playbook.yaml +0 -125
  125. package/examples/google-hotels/search_google_hotels/workflow.json +0 -111
  126. package/examples/namecheap-domains/search_namecheap_domains/index.ts +0 -144
  127. package/examples/namecheap-domains/search_namecheap_domains/parser.ts +0 -380
  128. package/examples/namecheap-domains/search_namecheap_domains/playbook.yaml +0 -50
  129. package/examples/namecheap-domains/search_namecheap_domains/request-transform.ts +0 -136
  130. 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,9 @@
1
+ {
2
+ "name": "imprint-shared",
3
+ "private": true,
4
+ "devDependencies": {
5
+ "@types/bun": "latest",
6
+ "@types/node": "latest",
7
+ "bun-types": "latest"
8
+ }
9
+ }
@@ -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,9 @@
1
+ {
2
+ "name": "imprint-tool-google-hotels",
3
+ "private": true,
4
+ "devDependencies": {
5
+ "@types/bun": "latest",
6
+ "@types/node": "latest",
7
+ "bun-types": "latest"
8
+ }
9
+ }
@@ -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 };
@@ -0,0 +1,9 @@
1
+ {
2
+ "name": "imprint-tool-google-hotels",
3
+ "private": true,
4
+ "devDependencies": {
5
+ "@types/bun": "latest",
6
+ "@types/node": "latest",
7
+ "bun-types": "latest"
8
+ }
9
+ }