imprint-mcp 0.3.0 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -29,6 +29,24 @@ imprint teach southwest --url https://www.southwest.com
29
29
 
30
30
  A browser opens. You drive the workflow and narrate what you're doing. Imprint records every request and interaction, then compiles a deterministic **MCP tool** your agent can call forever.
31
31
 
32
+ Want to try a finished MCP before recording anything?
33
+
34
+ ```bash
35
+ imprint install google-flights --source examples --platform claude-desktop
36
+ ```
37
+
38
+ That registers the checked-in Google Flights example as an MCP server in your client. Swap `claude-desktop` for `claude-code`, `codex`, `openclaw`, or `hermes`, or add `--print` to see the config without changing anything.
39
+
40
+ On a Hermes agent or Docker host, register the examples directly into Hermes:
41
+
42
+ ```bash
43
+ for site in google-flights google-hotels southwest discoverandgo echo; do
44
+ imprint install "$site" --source examples --platform hermes --no-interactive
45
+ done
46
+ ```
47
+
48
+ When `HERMES_HOME` is set, Imprint writes Hermes MCP entries to `$HERMES_HOME/config.yaml`; outside Hermes it uses `~/.hermes/config.yaml`. For browser-backed MCPs, `imprint install` also installs Playwright Chromium into `$HERMES_HOME/.cache/ms-playwright` and writes `PLAYWRIGHT_BROWSERS_PATH` into the MCP config so Hermes can find it. Use `--skip-browser-install` only for offline builds where you preinstall the browser yourself. In a fresh Linux image that is missing browser system libraries, install those during image build with `bunx playwright install --with-deps chromium`.
49
+
32
50
  ---
33
51
 
34
52
  ## See It in Action
@@ -118,7 +136,7 @@ bun install && bun link
118
136
 
119
137
  <br>
120
138
 
121
- **Browser commands** (`teach`, `record`, `login`, `playbook`) need Playwright's Chromium:
139
+ **Browser commands** (`teach`, `record`, `login`, `playbook`) and browser-backed `imprint install` targets auto-install Playwright Chromium when it is missing. For offline CI or prebuilt Linux images where you pass `--skip-browser-install`, preinstall it ahead of time:
122
140
 
123
141
  ```bash
124
142
  bunx playwright install chromium
@@ -207,6 +225,21 @@ Install any example into your MCP client:
207
225
  imprint install google-flights --source examples --platform claude-desktop
208
226
  ```
209
227
 
228
+ Examples are real generated MCPs, not handwritten SDK samples. `imprint install <site> --source examples` points the MCP server at this repo's `examples/` directory with `IMPRINT_HOME`, ensures Playwright Chromium for browser-backed tools, and lets your client list and call the checked-in tools immediately:
229
+
230
+ ```bash
231
+ imprint install google-hotels --source examples --platform codex
232
+ imprint install southwest --source examples --platform claude-code
233
+ imprint install echo --source examples --platform claude-desktop --print
234
+ ```
235
+
236
+ For your own generated tools, leave off `--source examples`:
237
+
238
+ ```bash
239
+ imprint install mysite --platform claude-code
240
+ imprint install mysite --platform codex
241
+ ```
242
+
210
243
  ---
211
244
 
212
245
  ## CLI Reference
@@ -227,7 +260,14 @@ imprint <command> --help # per-command options
227
260
 
228
261
  ## Sharing Skills
229
262
 
230
- Teach on your laptop, ship to a remote agent. Skill folders contain **zero plaintext credentials** only `${credential.NAME}` placeholders and a manifest listing what the receiver must provision.
263
+ Teach on your laptop, ship to a remote agent. Generated MCP folders contain the portable tool artifacts: `workflow.json`, `playbook.yaml`, `index.ts`, optional shared modules, and cron/backend metadata. Copy `~/.imprint/<site>` into the receiver's `~/.imprint/<site>` or commit it to a private repo, install Imprint there, then register it:
264
+
265
+ ```bash
266
+ bun install -g imprint-mcp
267
+ imprint install mysite --platform claude-code
268
+ ```
269
+
270
+ Credentials stay separate. Skill folders contain **zero plaintext credentials** — only `${credential.NAME}` placeholders and a manifest listing what the receiver must provision.
231
271
 
232
272
  ```bash
233
273
  # Export (encrypted with libsodium + argon2id)
@@ -75,7 +75,7 @@ const WORKFLOW: Workflow = {
75
75
  {
76
76
  "name": "f_sid",
77
77
  "required": false,
78
- "capability": "ordinary_http",
78
+ "capability": "browser_bootstrap",
79
79
  "source": "html_regex",
80
80
  "pattern": "\"FdrFJe\":\"([^\"]+)\"",
81
81
  "group": 1
@@ -83,7 +83,7 @@ const WORKFLOW: Workflow = {
83
83
  {
84
84
  "name": "bl",
85
85
  "required": false,
86
- "capability": "ordinary_http",
86
+ "capability": "browser_bootstrap",
87
87
  "source": "html_regex",
88
88
  "pattern": "\"cfb2h\":\"([^\"]+)\"",
89
89
  "group": 1
@@ -44,14 +44,16 @@
44
44
  "name": "f_sid",
45
45
  "pattern": "\"FdrFJe\":\"([^\"]+)\"",
46
46
  "group": 1,
47
- "required": false
47
+ "required": false,
48
+ "capability": "browser_bootstrap"
48
49
  },
49
50
  {
50
51
  "source": "html_regex",
51
52
  "name": "bl",
52
53
  "pattern": "\"cfb2h\":\"([^\"]+)\"",
53
54
  "group": 1,
54
- "required": false
55
+ "required": false,
56
+ "capability": "browser_bootstrap"
55
57
  }
56
58
  ]
57
59
  },
@@ -8,13 +8,14 @@
8
8
  - **`probe-backends` skipping the futile rung.** The cached `backends.json` orders the ladder `stealth-fetch → playbook` so cron doesn't burn 200ms on a fetch attempt every tick.
9
9
  - **`notifyWhen: price_below`** pushing only on real drops, with the `pricePath` extracting from real Southwest response shape.
10
10
  - **The fresh-UUID header trick** — Southwest rejects stale `X-User-Experience-ID`; stealth-fetch regenerates per call.
11
+ - **Public bootstrap config capture.** The workflow fetches Southwest's public bootstrap `data.js` and captures the current `api-keys.prod` value, so installed examples do not require a `SOUTHWEST_API_KEY` environment variable.
11
12
  - **Multi-path `pricePath`** — `cron.json`'s notifyWhen lists both the raw API shape (when stealth-fetch wins) and the playbook's reshaped output, so the push fires regardless of which backend produced the result.
12
13
 
13
14
  ## Run it
14
15
 
15
16
  ```bash
16
- # One-time setup (if you haven't already)
17
- bunx playwright install chromium
17
+ # One-time setup: registers the example MCP and installs Playwright Chromium if missing
18
+ imprint install southwest --source examples --platform claude-desktop
18
19
 
19
20
  # Run a single tick (verifies everything still works)
20
21
  imprint cron southwest --once
@@ -55,13 +55,30 @@ const WORKFLOW: Workflow = {
55
55
  }
56
56
  ],
57
57
  "requests": [
58
+ {
59
+ "method": "GET",
60
+ "url": "https://www.southwest.com/swa-ui/bootstrap/landing-home-page-v2/1/data.js",
61
+ "headers": {
62
+ "Accept": "application/javascript, text/javascript, */*; q=0.01"
63
+ },
64
+ "captures": [
65
+ {
66
+ "name": "southwest_api_key",
67
+ "source": "text_regex",
68
+ "pattern": "\"swa-bootstrap-landing-home-page-v2/api-keys\":\\[function\\(require,module,exports\\)\\{\\s*module\\.exports = \\{[^}]*\"prod\":\"([^\"]+)\"",
69
+ "group": 1,
70
+ "required": true,
71
+ "capability": "ordinary_http"
72
+ }
73
+ ]
74
+ },
58
75
  {
59
76
  "method": "POST",
60
77
  "url": "https://www.southwest.com/api/air-booking/v1/air-booking/page/air/booking/shopping",
61
78
  "headers": {
62
79
  "Content-Type": "application/json",
63
80
  "Accept": "application/json, text/javascript, */*; q=0.01",
64
- "X-API-Key": "${env.SOUTHWEST_API_KEY}",
81
+ "X-API-Key": "${state.southwest_api_key}",
65
82
  "X-App-ID": "air-booking",
66
83
  "X-Channel-ID": "southwest"
67
84
  },
@@ -37,13 +37,30 @@
37
37
  }
38
38
  ],
39
39
  "requests": [
40
+ {
41
+ "method": "GET",
42
+ "url": "https://www.southwest.com/swa-ui/bootstrap/landing-home-page-v2/1/data.js",
43
+ "headers": {
44
+ "Accept": "application/javascript, text/javascript, */*; q=0.01"
45
+ },
46
+ "captures": [
47
+ {
48
+ "name": "southwest_api_key",
49
+ "source": "text_regex",
50
+ "pattern": "\"swa-bootstrap-landing-home-page-v2/api-keys\":\\[function\\(require,module,exports\\)\\{\\s*module\\.exports = \\{[^}]*\"prod\":\"([^\"]+)\"",
51
+ "group": 1,
52
+ "required": true,
53
+ "capability": "ordinary_http"
54
+ }
55
+ ]
56
+ },
40
57
  {
41
58
  "method": "POST",
42
59
  "url": "https://www.southwest.com/api/air-booking/v1/air-booking/page/air/booking/shopping",
43
60
  "headers": {
44
61
  "Content-Type": "application/json",
45
62
  "Accept": "application/json, text/javascript, */*; q=0.01",
46
- "X-API-Key": "${env.SOUTHWEST_API_KEY}",
63
+ "X-API-Key": "${state.southwest_api_key}",
47
64
  "X-App-ID": "air-booking",
48
65
  "X-Channel-ID": "southwest"
49
66
  },
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "imprint-mcp",
3
- "version": "0.3.0",
3
+ "version": "0.4.0",
4
4
  "description": "Teach an AI agent how to use any website. Once. Records a real browser session + narration; generates a deterministic MCP tool plus a DOM-replay playbook fallback.",
5
5
  "type": "module",
6
6
  "exports": {
package/src/cli.ts CHANGED
@@ -62,6 +62,10 @@ INSTALL
62
62
  install [<site>] Install an emitted MCP server into an AI platform.
63
63
  uninstall [<site>] Remove an installed Imprint MCP server from an AI platform.
64
64
 
65
+ SHARE
66
+ export <site> [<site2>] Bundle site tools into a portable .tar.gz archive.
67
+ import <archive.tar.gz> Unpack an archive into ~/.imprint and set up tools.
68
+
65
69
  RUN
66
70
  mcp-server <site> Serve one site's tools as MCP (stdio default).
67
71
  cron <site> Polling daemon for ~/.imprint/<site>/<toolName>/cron.json.
@@ -238,7 +242,7 @@ export const VERB_HELP: Record<string, VerbHelp> = {
238
242
  summary:
239
243
  'Install an already-emitted MCP server into Claude Code, Codex, Claude Desktop, OpenClaw, or Hermes.',
240
244
  usage: [
241
- 'imprint install [<site>] [--platform <name>] [--source local|examples] [--print] [--no-interactive]',
245
+ 'imprint install [<site>] [--platform <name>] [--source local|examples] [--print] [--no-interactive] [--skip-browser-install]',
242
246
  ],
243
247
  flags: [
244
248
  {
@@ -257,6 +261,10 @@ export const VERB_HELP: Record<string, VerbHelp> = {
257
261
  name: '--no-interactive',
258
262
  description: 'Do not prompt; requires <site> and --platform.',
259
263
  },
264
+ {
265
+ name: '--skip-browser-install',
266
+ description: 'Do not auto-install Playwright Chromium for browser-backed tools.',
267
+ },
260
268
  ],
261
269
  example: 'imprint install google-flights --source examples --platform claude-desktop',
262
270
  },
@@ -280,6 +288,39 @@ export const VERB_HELP: Record<string, VerbHelp> = {
280
288
  ],
281
289
  example: 'imprint uninstall google-flights --platform claude-desktop',
282
290
  },
291
+ export: {
292
+ summary:
293
+ 'Bundle one or more site tool sets into a portable .tar.gz archive for sharing across machines.',
294
+ usage: ['imprint export <site> [<site2> ...] [--out <path>] [--include-credentials]'],
295
+ flags: [
296
+ {
297
+ name: '--out <path>',
298
+ description:
299
+ 'Output path. Defaults to ./imprint-export-<site>.tar.gz (single) or ./imprint-export-<timestamp>.tar.gz (multi).',
300
+ },
301
+ {
302
+ name: '--include-credentials',
303
+ description: 'Embed encrypted credential bundles (prompts for a passphrase per site).',
304
+ },
305
+ ],
306
+ example: 'imprint export avis southwest marriott --out tools.tar.gz --include-credentials',
307
+ },
308
+ import: {
309
+ summary: 'Unpack an imprint export archive into ~/.imprint and set up tools for use.',
310
+ usage: ['imprint import <archive.tar.gz> [--force] [--platform <name>]'],
311
+ flags: [
312
+ {
313
+ name: '--force',
314
+ description: 'Overwrite existing sites instead of skipping them.',
315
+ },
316
+ {
317
+ name: '--platform <name>',
318
+ description:
319
+ 'Auto-install MCP servers after import: claude-code, codex, claude-desktop, openclaw, hermes.',
320
+ },
321
+ ],
322
+ example: 'imprint import tools.tar.gz --force --platform claude-code',
323
+ },
283
324
  login: {
284
325
  summary: 'Persist auth cookies for <site> from a captured session.',
285
326
  usage: ['imprint login <site> --from-session <session.json>'],
@@ -822,6 +863,7 @@ async function main(argv: string[]): Promise<number> {
822
863
  source: { type: 'string' },
823
864
  print: { type: 'boolean' },
824
865
  'no-interactive': { type: 'boolean' },
866
+ 'skip-browser-install': { type: 'boolean' },
825
867
  },
826
868
  allowPositionals: false,
827
869
  });
@@ -850,6 +892,7 @@ async function main(argv: string[]): Promise<number> {
850
892
  source: values.source as (typeof sources)[number] | undefined,
851
893
  print: values.print,
852
894
  noInteractive: values['no-interactive'],
895
+ skipBrowserInstall: values['skip-browser-install'],
853
896
  });
854
897
  console.log(`[imprint] ${result.message}`);
855
898
  if ('source' in result)
@@ -890,6 +933,114 @@ async function main(argv: string[]): Promise<number> {
890
933
  return 0;
891
934
  }
892
935
 
936
+ case 'export': {
937
+ const sites: string[] = [];
938
+ let i = 1;
939
+ for (; i < argv.length; i++) {
940
+ const arg = argv[i];
941
+ if (!arg || arg.startsWith('-')) break;
942
+ sites.push(arg);
943
+ }
944
+ if (sites.length === 0) {
945
+ console.error('error: `imprint export` requires at least one <site> argument.');
946
+ return 2;
947
+ }
948
+ const { values } = parseArgs({
949
+ args: argv.slice(i),
950
+ options: {
951
+ out: { type: 'string' },
952
+ 'include-credentials': { type: 'boolean' },
953
+ },
954
+ allowPositionals: false,
955
+ });
956
+ const defaultOut =
957
+ sites.length === 1
958
+ ? `imprint-export-${sites[0]}.tar.gz`
959
+ : `imprint-export-${new Date().toISOString().replace(/[:.]/g, '-').slice(0, 19)}.tar.gz`;
960
+ const out = values.out ?? defaultOut;
961
+ const { exportArchive } = await import('./imprint/export-archive.ts');
962
+ const result = await exportArchive({
963
+ sites,
964
+ out,
965
+ includeCredentials: values['include-credentials'],
966
+ });
967
+ console.log(`[imprint] exported → ${result.archivePath}`);
968
+ for (const s of result.sites) {
969
+ console.log(
970
+ `[imprint] ${s.name}: ${s.tools.length} tool${s.tools.length === 1 ? '' : 's'} (${s.tools.join(', ')})`,
971
+ );
972
+ }
973
+ const kb = (result.byteSize / 1024).toFixed(1);
974
+ console.log(`[imprint] archive size: ${kb} KB`);
975
+ console.log('');
976
+ console.log('next step:');
977
+ console.log(` imprint import ${out} # on the target machine`);
978
+ return 0;
979
+ }
980
+
981
+ case 'import': {
982
+ const archivePath = requirePositional(argv, 'import', 'an <archive.tar.gz> argument');
983
+ if (archivePath === null) return 2;
984
+ const { values } = parseArgs({
985
+ args: argv.slice(2),
986
+ options: {
987
+ force: { type: 'boolean' },
988
+ platform: { type: 'string' },
989
+ },
990
+ allowPositionals: false,
991
+ });
992
+
993
+ if (values.platform) {
994
+ const { PLATFORMS } = await import('./imprint/integrations.ts');
995
+ if (!PLATFORMS.includes(values.platform as (typeof PLATFORMS)[number])) {
996
+ console.error(
997
+ `error: unknown platform '${values.platform}' — valid: ${PLATFORMS.join(', ')}`,
998
+ );
999
+ return 2;
1000
+ }
1001
+ }
1002
+
1003
+ const { importArchive } = await import('./imprint/export-archive.ts');
1004
+ const result = await importArchive({
1005
+ archivePath,
1006
+ force: values.force,
1007
+ });
1008
+
1009
+ for (const s of result.sites) {
1010
+ if (s.skipped) {
1011
+ console.log(`[imprint] ${s.name}: skipped (already exists)`);
1012
+ } else {
1013
+ console.log(
1014
+ `[imprint] ${s.name}: imported ${s.tools.length} tool${s.tools.length === 1 ? '' : 's'} (${s.tools.join(', ')})${s.credentialsImported ? ' + credentials' : ''}`,
1015
+ );
1016
+ }
1017
+ }
1018
+
1019
+ const imported = result.sites.filter((s) => !s.skipped);
1020
+ if (imported.length > 0 && !values.platform) {
1021
+ console.log('');
1022
+ console.log('next steps:');
1023
+ for (const s of imported) {
1024
+ console.log(` imprint install ${s.name} # register MCP server`);
1025
+ }
1026
+ }
1027
+
1028
+ if (values.platform) {
1029
+ const { install } = await import('./imprint/install.ts');
1030
+ const { PLATFORMS } = await import('./imprint/integrations.ts');
1031
+ for (const s of imported) {
1032
+ const installResult = await install({
1033
+ site: s.name,
1034
+ platform: values.platform as (typeof PLATFORMS)[number],
1035
+ noInteractive: true,
1036
+ });
1037
+ console.log(`[imprint] ${installResult.message}`);
1038
+ }
1039
+ }
1040
+
1041
+ return 0;
1042
+ }
1043
+
893
1044
  case 'login': {
894
1045
  const site = requirePositional(argv, 'login', 'a <site> argument');
895
1046
  if (site === null) return 2;
@@ -283,7 +283,10 @@ export function createCdpBrowserFetch(opts: CdpBrowserFetchOptions): CdpBrowserF
283
283
  throw new Error('navigate timeout');
284
284
  }),
285
285
  ]);
286
- await Page.loadEventFired().catch(() => {});
286
+ await Promise.race([
287
+ Page.loadEventFired(),
288
+ sleep(Math.min(abckWaitMs, 5000)).then(() => undefined),
289
+ ]).catch(() => {});
287
290
  } catch (err) {
288
291
  log(`navigation issue (continuing): ${err instanceof Error ? err.message : String(err)}`);
289
292
  }