imprint-mcp 0.2.1 → 0.3.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.
Files changed (126) hide show
  1. package/README.md +165 -201
  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 +76 -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/package.json +3 -2
  61. package/prompts/audit-agent.md +71 -0
  62. package/prompts/build-planning.md +74 -0
  63. package/prompts/compile-agent.md +131 -27
  64. package/prompts/prereq-builder.md +64 -0
  65. package/prompts/prereq-planner.md +34 -0
  66. package/prompts/tool-planning.md +39 -0
  67. package/src/cli.ts +109 -2
  68. package/src/imprint/agent.ts +5 -0
  69. package/src/imprint/audit.ts +996 -0
  70. package/src/imprint/backend-ladder.ts +1214 -184
  71. package/src/imprint/build-plan.ts +1051 -0
  72. package/src/imprint/cdp-browser-fetch.ts +589 -0
  73. package/src/imprint/cdp-jar-cache.ts +320 -0
  74. package/src/imprint/chromium.ts +135 -0
  75. package/src/imprint/claude-cli-compile.ts +125 -25
  76. package/src/imprint/codex-cli-compile.ts +26 -23
  77. package/src/imprint/compile-agent-types.ts +38 -0
  78. package/src/imprint/compile-agent.ts +63 -25
  79. package/src/imprint/compile-tools.ts +1656 -64
  80. package/src/imprint/compile.ts +13 -1
  81. package/src/imprint/concurrency.ts +87 -0
  82. package/src/imprint/cron.ts +1 -0
  83. package/src/imprint/doctor.ts +39 -0
  84. package/src/imprint/freeform-redact.ts +5 -4
  85. package/src/imprint/integrations.ts +2 -2
  86. package/src/imprint/llm.ts +56 -8
  87. package/src/imprint/mcp-compile-server.ts +43 -10
  88. package/src/imprint/mcp-maintenance.ts +9 -101
  89. package/src/imprint/mcp-server.ts +73 -7
  90. package/src/imprint/multi-progress.ts +7 -2
  91. package/src/imprint/param-grounding.ts +367 -0
  92. package/src/imprint/paths.ts +29 -0
  93. package/src/imprint/playbook-runner.ts +101 -40
  94. package/src/imprint/prereq-builder.ts +651 -0
  95. package/src/imprint/probe-backends.ts +6 -3
  96. package/src/imprint/record.ts +10 -1
  97. package/src/imprint/redact.ts +30 -2
  98. package/src/imprint/replay-capture.ts +19 -18
  99. package/src/imprint/runtime.ts +19 -10
  100. package/src/imprint/session-diff.ts +79 -2
  101. package/src/imprint/session-merge.ts +9 -5
  102. package/src/imprint/stealth-chromium.ts +81 -0
  103. package/src/imprint/stealth-fetch.ts +309 -29
  104. package/src/imprint/stealth-token-cache.ts +88 -0
  105. package/src/imprint/teach-plan.ts +251 -0
  106. package/src/imprint/teach-state.ts +10 -0
  107. package/src/imprint/teach.ts +456 -142
  108. package/src/imprint/tool-candidates.ts +72 -14
  109. package/src/imprint/tool-plan.ts +313 -0
  110. package/src/imprint/tracing.ts +135 -6
  111. package/src/imprint/types.ts +61 -3
  112. package/examples/google-flights/search_google_flights/index.ts +0 -101
  113. package/examples/google-flights/search_google_flights/parser.test.ts +0 -140
  114. package/examples/google-flights/search_google_flights/parser.ts +0 -189
  115. package/examples/google-flights/search_google_flights/playbook.yaml +0 -130
  116. package/examples/google-flights/search_google_flights/workflow.json +0 -48
  117. package/examples/google-hotels/search_google_hotels/index.ts +0 -194
  118. package/examples/google-hotels/search_google_hotels/parser.test.ts +0 -168
  119. package/examples/google-hotels/search_google_hotels/parser.ts +0 -330
  120. package/examples/google-hotels/search_google_hotels/playbook.yaml +0 -125
  121. package/examples/google-hotels/search_google_hotels/workflow.json +0 -111
  122. package/examples/namecheap-domains/search_namecheap_domains/index.ts +0 -144
  123. package/examples/namecheap-domains/search_namecheap_domains/parser.ts +0 -380
  124. package/examples/namecheap-domains/search_namecheap_domains/playbook.yaml +0 -50
  125. package/examples/namecheap-domains/search_namecheap_domains/request-transform.ts +0 -136
  126. package/examples/namecheap-domains/search_namecheap_domains/workflow.json +0 -97
@@ -0,0 +1,251 @@
1
+ /**
2
+ * Plan-prereqs step for multi-tool `imprint teach`.
3
+ *
4
+ * Runs once per teach, after candidate selection + the replay/diff join and
5
+ * before the per-tool compile fan-out, when ≥2 tools are selected. It:
6
+ * 1. generates a BuildPlan (shared modules + per-tool guidance + auth recipe),
7
+ * 2. builds + verifies the shared modules under `~/.imprint/<site>/_shared/`
8
+ * level-by-level (independent modules concurrently, dependents after their
9
+ * dependencies), so the files exist when the per-tool agents import them,
10
+ * 3. persists the plan to `.build-plan.json` and returns the manifest.
11
+ *
12
+ * A module the builder can't verify is marked unverified and pruned from every
13
+ * tool's `usesSharedModules`, so the per-tool import-assertion never fails on a
14
+ * module that was never written (tools fall back to inlining — today's behavior).
15
+ */
16
+
17
+ import { existsSync, mkdirSync, rmSync, writeFileSync } from 'node:fs';
18
+ import { join as pathJoin } from 'node:path';
19
+ import {
20
+ type BuildPlan,
21
+ type SharedModuleManifestEntry,
22
+ type SharedModuleSpec,
23
+ generateBuildPlan,
24
+ topoLevels,
25
+ writeBuildPlanSidecar,
26
+ } from './build-plan.ts';
27
+ import { mapLimit } from './concurrency.ts';
28
+ import type { ProviderName } from './llm.ts';
29
+ import { loadJsonFile } from './load-json.ts';
30
+ import { createLog } from './log.ts';
31
+ import { imprintHomeDir, localSharedDir } from './paths.ts';
32
+ import { buildSharedModule } from './prereq-builder.ts';
33
+ import { ensureImprintRuntimeLink } from './runtime-link.ts';
34
+ import type { ClassifiedValue } from './session-diff.ts';
35
+ import type { SharedCompileContext, ToolCandidate } from './tool-candidates.ts';
36
+ import { SessionSchema } from './types.ts';
37
+
38
+ const log = createLog('teach-plan');
39
+
40
+ /** Wall-clock cap on the single planner LLM call. A throttled/hung provider
41
+ * must not block the per-tool fan-out indefinitely; on timeout we degrade to
42
+ * independent per-tool compilation. */
43
+ const PLANNER_TIMEOUT_MS = 10 * 60_000;
44
+
45
+ /** Max shared modules built concurrently within one dependency level. Each build
46
+ * spawns an LLM child + `bun test` + `tsc`, so this is capped (matching the
47
+ * per-tool compile fan-out) to bound peak load and avoid provider throttling. */
48
+ const SHARED_BUILD_CONCURRENCY = 2;
49
+
50
+ interface PlanAndBuildPrereqsResult {
51
+ /** Absolute path to the persisted plan sidecar, or '' when planning was skipped. */
52
+ buildPlanPath: string;
53
+ /** Build manifest (one entry per shared module, with verified flags). */
54
+ sharedModules: SharedModuleManifestEntry[];
55
+ /** The plan that was used (after pruning unverified modules), if any. */
56
+ plan?: BuildPlan;
57
+ /** Set when planning was attempted but failed/timed out, so the caller can
58
+ * surface the reason in the TUI (not raw stderr). Absent on success and when
59
+ * planning was deliberately skipped (disabled / <2 tools). */
60
+ skippedReason?: string;
61
+ }
62
+
63
+ function buildPlanDisabled(): boolean {
64
+ const v = process.env.IMPRINT_NO_BUILD_PLAN;
65
+ return !!v && !['0', 'false', 'no', 'off'].includes(v.toLowerCase());
66
+ }
67
+
68
+ export async function planAndBuildPrereqs(opts: {
69
+ site: string;
70
+ /** Redacted session path (also used as IMPRINT_SESSION_PATH when testing modules). */
71
+ redactedSessionPath: string;
72
+ candidates: ToolCandidate[];
73
+ sharedContext?: SharedCompileContext;
74
+ siteClassifications?: ClassifiedValue[];
75
+ providerName: ProviderName;
76
+ model?: string;
77
+ maxCyclesPerModule?: number;
78
+ onProgress?: (msg: string) => void;
79
+ }): Promise<PlanAndBuildPrereqsResult> {
80
+ // Gate: shared prereqs only make sense across ≥2 tools.
81
+ if (opts.candidates.length < 2) return { buildPlanPath: '', sharedModules: [] };
82
+ if (buildPlanDisabled()) {
83
+ log('IMPRINT_NO_BUILD_PLAN set — skipping build plan + shared prereqs');
84
+ return { buildPlanPath: '', sharedModules: [] };
85
+ }
86
+
87
+ const session = loadJsonFile(
88
+ opts.redactedSessionPath,
89
+ SessionSchema,
90
+ {
91
+ notFound: 'Redacted session file not found before build planning.',
92
+ badSchema: 'Redacted session file is malformed.',
93
+ },
94
+ 'session',
95
+ );
96
+
97
+ // 1. Plan. Bounded by a wall-clock timeout AND made non-fatal: a throttled or
98
+ // hung LLM provider (or a malformed plan) must never wedge or abort the
99
+ // whole multi-tool teach. On any failure we degrade to independent per-tool
100
+ // compilation (the pre-feature behavior) instead of shared modules.
101
+ opts.onProgress?.('Planning shared modules');
102
+ let generated: Awaited<ReturnType<typeof generateBuildPlan>>;
103
+ try {
104
+ generated = await generateBuildPlan({
105
+ session,
106
+ candidates: opts.candidates,
107
+ sharedContext: opts.sharedContext,
108
+ classifications: opts.siteClassifications,
109
+ llmConfig: { provider: opts.providerName, model: opts.model },
110
+ timeoutMs: PLANNER_TIMEOUT_MS,
111
+ onProgress: opts.onProgress,
112
+ });
113
+ } catch (err) {
114
+ return {
115
+ buildPlanPath: '',
116
+ sharedModules: [],
117
+ skippedReason: `Build planning failed or timed out (${err instanceof Error ? err.message : String(err)}) — compiling tools independently (no shared modules).`,
118
+ };
119
+ }
120
+ const plan: BuildPlan = {
121
+ sharedModules: generated.sharedModules,
122
+ perTool: generated.perTool,
123
+ };
124
+
125
+ // Persist immediately so a crash mid-build still leaves a readable plan.
126
+ const buildPlanPath = writeBuildPlanSidecar(opts.site, plan);
127
+
128
+ if (plan.sharedModules.length === 0) {
129
+ log('build plan declared no shared modules — per-tool guidance only');
130
+ return { buildPlanPath, sharedModules: [], plan };
131
+ }
132
+
133
+ // 2. Prepare a clean _shared dir + its toolchain (a stale module from a
134
+ // differently-shaped prior run must not be silently imported).
135
+ const sharedDir = localSharedDir(opts.site);
136
+ rmSync(sharedDir, { recursive: true, force: true });
137
+ mkdirSync(sharedDir, { recursive: true });
138
+ ensureSharedDirToolchain(sharedDir);
139
+
140
+ // 3. Build the modules level-by-level. Modules in the same dependency level are
141
+ // independent, so each level builds concurrently (bounded by
142
+ // SHARED_BUILD_CONCURRENCY); a module that dependsOn another waits for its
143
+ // dependency's level. Only VERIFIED dependencies are accumulated into
144
+ // builtSpecs between levels, so a dependent of a pruned module degrades to
145
+ // inlining (today's behavior) rather than importing something never written.
146
+ const levels = topoLevels(plan.sharedModules);
147
+ const manifest: SharedModuleManifestEntry[] = [];
148
+ const builtSpecs: SharedModuleSpec[] = [];
149
+ for (const level of levels) {
150
+ const results = await mapLimit(level, SHARED_BUILD_CONCURRENCY, (module) => {
151
+ opts.onProgress?.(`Building ${module.path}`);
152
+ return buildSharedModule({
153
+ site: opts.site,
154
+ module,
155
+ session,
156
+ sessionPath: opts.redactedSessionPath,
157
+ sharedDir,
158
+ builtModules: builtSpecs,
159
+ llmConfig: { provider: opts.providerName, model: opts.model },
160
+ maxCycles: opts.maxCyclesPerModule,
161
+ onProgress: opts.onProgress,
162
+ });
163
+ });
164
+ for (const result of results) {
165
+ manifest.push({
166
+ path: result.module.path,
167
+ kind: result.module.kind,
168
+ verified: result.ok,
169
+ });
170
+ if (result.ok) {
171
+ builtSpecs.push(result.module);
172
+ log(`shared module ${result.module.path} built + verified in ${result.cycles} cycle(s)`);
173
+ } else {
174
+ log(
175
+ `shared module ${result.module.path} could not be verified — pruning from tools. Failures:\n${result.failures.join('\n')}`,
176
+ );
177
+ }
178
+ }
179
+ }
180
+
181
+ // 4. Prune unverified modules from every tool, then re-persist.
182
+ const verifiedPaths = new Set(manifest.filter((m) => m.verified).map((m) => m.path));
183
+ const prunedPlan: BuildPlan = {
184
+ sharedModules: plan.sharedModules.filter((m) => verifiedPaths.has(m.path)),
185
+ perTool: plan.perTool.map((t) => ({
186
+ ...t,
187
+ usesSharedModules: t.usesSharedModules.filter((p) => verifiedPaths.has(p)),
188
+ parserGuidance: correctGuidanceForPrunedModules(t.parserGuidance, verifiedPaths),
189
+ })),
190
+ };
191
+ writeBuildPlanSidecar(opts.site, prunedPlan);
192
+
193
+ return { buildPlanPath, sharedModules: manifest, plan: prunedPlan };
194
+ }
195
+
196
+ /** Shared-module reference pattern, mirrors build-plan.ts SHARED_MODULE_PATH_RE. */
197
+ const SHARED_MODULE_REF_RE = /_shared\/[A-Za-z0-9._-]+\.ts/g;
198
+
199
+ /** Append a correction note to a tool's free-text `parserGuidance` for any shared
200
+ * module the guidance still names but that was NOT verified/built (and therefore
201
+ * pruned). Without this, the planner's prose (e.g. "Call decodeBatchExecute from
202
+ * _shared/batchexecute.ts") reaches the compile LLM via read_build_plan and tells
203
+ * it to import a module that was never written. Pure + unit-testable; appends
204
+ * rather than rewrites, so still-valid guidance is preserved. */
205
+ export function correctGuidanceForPrunedModules(
206
+ guidance: string,
207
+ verifiedPaths: ReadonlySet<string>,
208
+ ): string {
209
+ const referenced = new Set(guidance.match(SHARED_MODULE_REF_RE) ?? []);
210
+ const pruned = [...referenced].filter((p) => !verifiedPaths.has(p));
211
+ if (pruned.length === 0) return guidance;
212
+ const notes = pruned
213
+ .map(
214
+ (p) =>
215
+ `NOTE: shared module ${p} was NOT built — implement its logic inline in this tool's parser.ts; do not import it.`,
216
+ )
217
+ .join('\n');
218
+ return guidance ? `${guidance}\n\n${notes}` : notes;
219
+ }
220
+
221
+ // ─── Toolchain bootstrap ────────────────────────────────────────────────────
222
+
223
+ /** Bootstrap `_shared/` with the type deps + runtime symlink so `bun test`,
224
+ * `tsc`, and `imprint/*` imports resolve — mirrors the compile-agent tool-dir
225
+ * bootstrap. */
226
+ function ensureSharedDirToolchain(sharedDir: string): void {
227
+ ensureImprintRuntimeLink(imprintHomeDir());
228
+ const pkgPath = pathJoin(sharedDir, 'package.json');
229
+ if (!existsSync(pkgPath)) {
230
+ writeFileSync(
231
+ pkgPath,
232
+ `${JSON.stringify(
233
+ {
234
+ name: 'imprint-shared',
235
+ private: true,
236
+ devDependencies: {
237
+ '@types/bun': 'latest',
238
+ '@types/node': 'latest',
239
+ 'bun-types': 'latest',
240
+ },
241
+ },
242
+ null,
243
+ 2,
244
+ )}\n`,
245
+ 'utf8',
246
+ );
247
+ }
248
+ if (!existsSync(pathJoin(sharedDir, 'node_modules'))) {
249
+ Bun.spawnSync(['bun', 'install'], { cwd: sharedDir });
250
+ }
251
+ }
@@ -22,6 +22,7 @@ import {
22
22
  join as pathJoin,
23
23
  resolve as pathResolve,
24
24
  } from 'node:path';
25
+ import type { SharedModuleManifestEntry } from './build-plan.ts';
25
26
  import {
26
27
  localSessionsDir,
27
28
  localSiteDir,
@@ -36,6 +37,7 @@ export const TEACH_STEPS = [
36
37
  'replay-and-diff',
37
38
  'triage',
38
39
  'detect-candidates',
40
+ 'plan-prereqs',
39
41
  'generate',
40
42
  'compile-playbook',
41
43
  'emit',
@@ -55,6 +57,14 @@ export interface WorkflowState {
55
57
  updatedAt: string;
56
58
  candidate?: ToolCandidate;
57
59
  sharedContext?: SharedCompileContext;
60
+ /** Site-relative path to the multi-tool build plan sidecar (.build-plan.json),
61
+ * set at the plan-prereqs step. Threaded into the per-tool compile drivers so
62
+ * each agent reads its slice via the read_build_plan tool. */
63
+ buildPlanPath?: string;
64
+ /** Shared modules built + verified before the per-tool fan-out. The verifier
65
+ * asserts a tool imports the modules the plan assigned it; entries with
66
+ * `verified: false` are excluded from that assertion. */
67
+ sharedModules?: SharedModuleManifestEntry[];
58
68
  /** Non-fatal flags raised by upstream stages that downstream stages (and
59
69
  * the user) should know about. Currently used by the redact stage to
60
70
  * record `'credentials_not_paired'` when a password-shaped body field