imprint-mcp 0.7.0 → 0.7.2

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 (58) hide show
  1. package/README.md +66 -2
  2. package/package.json +1 -1
  3. package/prompts/auth-compile-agent.md +173 -22
  4. package/prompts/compile-agent.md +45 -8
  5. package/prompts/component-recovery.md +40 -0
  6. package/prompts/master-teach-decision.md +28 -3
  7. package/prompts/master-teach-focused-planner.md +18 -5
  8. package/src/cli.ts +26 -18
  9. package/src/imprint/agent-tool.ts +15 -0
  10. package/src/imprint/agent.ts +2 -11
  11. package/src/imprint/audit.ts +35 -13
  12. package/src/imprint/auth-browser-discovery.ts +276 -0
  13. package/src/imprint/auth-compile-agent.ts +513 -224
  14. package/src/imprint/auth-compile-tools.ts +213 -25
  15. package/src/imprint/auth-discovery-evidence.ts +81 -0
  16. package/src/imprint/auth-plan.ts +28 -0
  17. package/src/imprint/auth-verifier.ts +234 -20
  18. package/src/imprint/authentication-interaction.ts +137 -0
  19. package/src/imprint/backend-ladder.ts +271 -43
  20. package/src/imprint/build-plan.ts +2 -22
  21. package/src/imprint/cdp-browser-fetch.ts +242 -60
  22. package/src/imprint/chromium.ts +93 -29
  23. package/src/imprint/claude-cli-compile.ts +2 -0
  24. package/src/imprint/codex-cli-compile.ts +99 -46
  25. package/src/imprint/compile-agent-types.ts +9 -5
  26. package/src/imprint/compile-agent.ts +33 -0
  27. package/src/imprint/compile-tool-executor.ts +783 -0
  28. package/src/imprint/compile-tools.ts +42 -9
  29. package/src/imprint/compile-verification.ts +2 -0
  30. package/src/imprint/compile.ts +3 -0
  31. package/src/imprint/compiler-log.ts +16 -0
  32. package/src/imprint/component-recovery.ts +120 -0
  33. package/src/imprint/cookie-jar.ts +117 -7
  34. package/src/imprint/credential-extract.ts +9 -2
  35. package/src/imprint/credential-lock.ts +147 -0
  36. package/src/imprint/credential-observation.ts +261 -0
  37. package/src/imprint/credential-store.ts +284 -44
  38. package/src/imprint/live-component-recovery.ts +73 -0
  39. package/src/imprint/llm.ts +4 -3
  40. package/src/imprint/log.ts +16 -1
  41. package/src/imprint/login.ts +9 -0
  42. package/src/imprint/master-teach-agent-contracts.ts +40 -3
  43. package/src/imprint/master-teach-agents.ts +229 -14
  44. package/src/imprint/master-teach-checks.ts +8 -1
  45. package/src/imprint/master-teach-controller.ts +681 -138
  46. package/src/imprint/master-teach-prompt-projections.ts +2 -0
  47. package/src/imprint/mcp-cdp-sessions.ts +7 -2
  48. package/src/imprint/mcp-compile-server.ts +62 -850
  49. package/src/imprint/mcp-server.ts +667 -91
  50. package/src/imprint/mcp-session-recovery.ts +167 -0
  51. package/src/imprint/native-teach-agents.ts +232 -108
  52. package/src/imprint/runtime.ts +416 -47
  53. package/src/imprint/stealth-chromium.ts +67 -7
  54. package/src/imprint/teach-browser-resources.ts +88 -0
  55. package/src/imprint/teach-stage-progress.ts +38 -0
  56. package/src/imprint/terminal-user-prompt.ts +42 -0
  57. package/src/imprint/types.ts +51 -18
  58. package/src/imprint/user-interaction.ts +836 -0
@@ -0,0 +1,261 @@
1
+ /** Optional, value-free credential path diagnostics. A private per-run key makes
2
+ * identities comparable across child processes without stable secret hashes. */
3
+ import { createHmac, randomBytes } from 'node:crypto';
4
+ import { appendFileSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs';
5
+ import { dirname } from 'node:path';
6
+
7
+ interface CredentialMaterial {
8
+ site: string;
9
+ cookies: unknown[];
10
+ values: Record<string, string>;
11
+ storage?: unknown[];
12
+ }
13
+
14
+ export interface CredentialObservation {
15
+ scope: string;
16
+ revision: string;
17
+ source: string;
18
+ cookieCount: number;
19
+ valueCount: number;
20
+ storageCount: number;
21
+ }
22
+
23
+ export interface DispatchObservation {
24
+ invocationId: string;
25
+ requestIndex: number;
26
+ credentials: CredentialObservation;
27
+ /** Runtime jar after mechanical normalization and earlier response updates. */
28
+ effectiveCredentials?: CredentialObservation;
29
+ /** Exact runtime-prepared headers/url/body, before transport-owned changes. */
30
+ preparedRequestIdentity: string;
31
+ requestTargetIdentity: string;
32
+ preparedCookieHeaderIdentity?: string;
33
+ cookieSource: 'browser-owned' | 'runtime-prepared';
34
+ }
35
+
36
+ interface BrowserCookieStateObservation extends CredentialObservation {
37
+ cookieStates: Array<{ scopeIdentity: string; valueIdentity: string; stateIdentity: string }>;
38
+ seed?: { attempted: number; accepted: number; rejected: number; failed: number };
39
+ }
40
+
41
+ function canonical(value: unknown): string {
42
+ if (Array.isArray(value)) return `[${value.map(canonical).join(',')}]`;
43
+ if (value && typeof value === 'object') {
44
+ return `{${Object.entries(value)
45
+ .sort(([a], [b]) => a.localeCompare(b))
46
+ .filter(([, item]) => item !== undefined)
47
+ .map(([key, item]) => `${JSON.stringify(key)}:${canonical(item)}`)
48
+ .join(',')}}`;
49
+ }
50
+ return JSON.stringify(value) ?? 'null';
51
+ }
52
+
53
+ function diagnosticKey(): Buffer | undefined {
54
+ const path = process.env.IMPRINT_CREDENTIAL_DIAGNOSTICS_PATH;
55
+ if (!path) return undefined;
56
+ try {
57
+ const keyPath = `${path}.key`;
58
+ mkdirSync(dirname(path), { recursive: true });
59
+ try {
60
+ writeFileSync(keyPath, randomBytes(32), { flag: 'wx', mode: 0o600 });
61
+ } catch (error) {
62
+ if ((error as NodeJS.ErrnoException).code !== 'EEXIST') return undefined;
63
+ }
64
+ const key = readFileSync(keyPath);
65
+ return key.length === 32 ? key : undefined;
66
+ } catch {
67
+ return undefined;
68
+ }
69
+ }
70
+
71
+ function identity(key: Buffer, value: unknown): string {
72
+ return createHmac('sha256', key).update(canonical(value)).digest('hex');
73
+ }
74
+
75
+ export function credentialObservation(
76
+ material: CredentialMaterial,
77
+ source: string,
78
+ ): CredentialObservation | undefined {
79
+ const key = diagnosticKey();
80
+ if (!key) return undefined;
81
+ return {
82
+ scope: identity(key, 'credential-observation-scope'),
83
+ revision: identity(key, {
84
+ site: material.site,
85
+ cookies: material.cookies,
86
+ values: material.values,
87
+ storage: material.storage ?? [],
88
+ }),
89
+ source,
90
+ cookieCount: material.cookies.length,
91
+ valueCount: Object.keys(material.values).length,
92
+ storageCount: material.storage?.length ?? 0,
93
+ };
94
+ }
95
+
96
+ /** Only accept typed metadata at this boundary; never raw credentials/requests. */
97
+ export function recordCredentialEvent(
98
+ event:
99
+ | 'load'
100
+ | 'commit'
101
+ | 'dispatch'
102
+ | 'response'
103
+ | 'browser-request'
104
+ | 'browser-wire'
105
+ | 'browser-response'
106
+ | 'browser-state',
107
+ facts:
108
+ | CredentialObservation
109
+ | DispatchObservation
110
+ | {
111
+ invocationId: string;
112
+ requestIndex: number;
113
+ status: number;
114
+ }
115
+ | BrowserCredentialObservation
116
+ | BrowserCookieStateObservation,
117
+ ): void {
118
+ const path = process.env.IMPRINT_CREDENTIAL_DIAGNOSTICS_PATH;
119
+ if (!path) return;
120
+ try {
121
+ appendFileSync(
122
+ path,
123
+ `${JSON.stringify({
124
+ event,
125
+ at: new Date().toISOString(),
126
+ monotonicMs: performance.now(),
127
+ pid: process.pid,
128
+ ...facts,
129
+ })}\n`,
130
+ { mode: 0o600 },
131
+ );
132
+ } catch {
133
+ // Optional diagnostics cannot alter persistence or runtime outcomes.
134
+ }
135
+ }
136
+
137
+ /** Compare exact cookie scopes across seed/bootstrap/request boundaries without
138
+ * recording their names or values. These are facts, never auth verdicts. */
139
+ export function recordBrowserCookieState(
140
+ material: CredentialMaterial,
141
+ phase: 'cdp-before-seed' | 'cdp-after-bootstrap' | 'cdp-after-request',
142
+ seed?: BrowserCookieStateObservation['seed'],
143
+ ): void {
144
+ const key = diagnosticKey();
145
+ const observation = credentialObservation(material, phase);
146
+ if (!key || !observation) return;
147
+ recordCredentialEvent('browser-state', {
148
+ ...observation,
149
+ cookieStates: material.cookies.map((cookie) => {
150
+ const entry = cookie as Record<string, unknown>;
151
+ const domain = String(entry.domain ?? '');
152
+ const scope = {
153
+ name: entry.name,
154
+ domain: domain.replace(/^\./, ''),
155
+ path: entry.path ?? '/',
156
+ hostOnly: entry.hostOnly ?? !domain.startsWith('.'),
157
+ };
158
+ return {
159
+ scopeIdentity: identity(key, scope),
160
+ valueIdentity: identity(key, entry.value),
161
+ stateIdentity: identity(key, {
162
+ ...scope,
163
+ value: entry.value,
164
+ secure: Boolean(entry.secure),
165
+ httpOnly: Boolean(entry.httpOnly),
166
+ sameSite: typeof entry.sameSite === 'string' ? entry.sameSite.toLowerCase() : undefined,
167
+ expires:
168
+ typeof entry.expires === 'number' && entry.expires > 0 ? entry.expires : undefined,
169
+ }),
170
+ };
171
+ }),
172
+ ...(seed ? { seed } : {}),
173
+ });
174
+ }
175
+
176
+ export function dispatchObservation(input: {
177
+ invocationId: string;
178
+ requestIndex: number;
179
+ material: CredentialMaterial;
180
+ effectiveMaterial?: CredentialMaterial;
181
+ source: string;
182
+ request: { method: string; url: string; headers: Record<string, string>; body?: string };
183
+ browserOwned: boolean;
184
+ }): DispatchObservation | undefined {
185
+ const key = diagnosticKey();
186
+ const credentials = credentialObservation(input.material, input.source);
187
+ if (!key || !credentials) return undefined;
188
+ const observation: DispatchObservation = {
189
+ invocationId: input.invocationId,
190
+ requestIndex: input.requestIndex,
191
+ credentials,
192
+ ...(input.effectiveMaterial
193
+ ? { effectiveCredentials: credentialObservation(input.effectiveMaterial, 'runtime-jar') }
194
+ : {}),
195
+ preparedRequestIdentity: identity(key, input.request),
196
+ requestTargetIdentity: identity(key, {
197
+ method: input.request.method.toUpperCase(),
198
+ url: input.request.url,
199
+ }),
200
+ ...(Object.entries(input.request.headers).find(
201
+ ([name]) => name.toLowerCase() === 'cookie',
202
+ )?.[1] === undefined
203
+ ? {}
204
+ : {
205
+ preparedCookieHeaderIdentity: identity(
206
+ key,
207
+ Object.entries(input.request.headers).find(
208
+ ([name]) => name.toLowerCase() === 'cookie',
209
+ )?.[1],
210
+ ),
211
+ }),
212
+ cookieSource: input.browserOwned ? 'browser-owned' : 'runtime-prepared',
213
+ };
214
+ recordCredentialEvent('dispatch', observation);
215
+ return observation;
216
+ }
217
+
218
+ interface BrowserCredentialObservation {
219
+ transport: 'cdp' | 'browser-http';
220
+ browserRequestIdentity: string;
221
+ requestTargetIdentity?: string;
222
+ wireHeadersIdentity?: string;
223
+ cookieHeaderIdentity?: string;
224
+ status?: number;
225
+ }
226
+
227
+ /** CDP's actual network header event; never retains header/cookie values or URL.
228
+ * Request and extra-info events may arrive in either order, so join opaque IDs. */
229
+ export function recordBrowserCredentialObservation(input: {
230
+ event: 'browser-request' | 'browser-wire' | 'browser-response';
231
+ browserId: string;
232
+ requestId: string;
233
+ transport?: 'cdp' | 'browser-http';
234
+ request?: { method?: string; url?: string };
235
+ headers?: Record<string, unknown>;
236
+ status?: number;
237
+ }): void {
238
+ const key = diagnosticKey();
239
+ if (!key) return;
240
+ const cookie = Object.entries(input.headers ?? {}).find(
241
+ ([name]) => name.toLowerCase() === 'cookie',
242
+ )?.[1];
243
+ recordCredentialEvent(input.event, {
244
+ transport: input.transport ?? 'cdp',
245
+ browserRequestIdentity: identity(key, {
246
+ browserId: input.browserId,
247
+ requestId: input.requestId,
248
+ }),
249
+ ...(input.request?.url
250
+ ? {
251
+ requestTargetIdentity: identity(key, {
252
+ method: (input.request.method ?? 'GET').toUpperCase(),
253
+ url: input.request.url,
254
+ }),
255
+ }
256
+ : {}),
257
+ ...(input.headers ? { wireHeadersIdentity: identity(key, input.headers) } : {}),
258
+ ...(cookie === undefined ? {} : { cookieHeaderIdentity: identity(key, cookie) }),
259
+ ...(input.status === undefined ? {} : { status: input.status }),
260
+ });
261
+ }