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.
- package/README.md +66 -2
- package/package.json +1 -1
- package/prompts/auth-compile-agent.md +173 -22
- package/prompts/compile-agent.md +45 -8
- package/prompts/component-recovery.md +40 -0
- package/prompts/master-teach-decision.md +28 -3
- package/prompts/master-teach-focused-planner.md +18 -5
- package/src/cli.ts +26 -18
- package/src/imprint/agent-tool.ts +15 -0
- package/src/imprint/agent.ts +2 -11
- package/src/imprint/audit.ts +35 -13
- package/src/imprint/auth-browser-discovery.ts +276 -0
- package/src/imprint/auth-compile-agent.ts +513 -224
- package/src/imprint/auth-compile-tools.ts +213 -25
- package/src/imprint/auth-discovery-evidence.ts +81 -0
- package/src/imprint/auth-plan.ts +28 -0
- package/src/imprint/auth-verifier.ts +234 -20
- package/src/imprint/authentication-interaction.ts +137 -0
- package/src/imprint/backend-ladder.ts +271 -43
- package/src/imprint/build-plan.ts +2 -22
- package/src/imprint/cdp-browser-fetch.ts +242 -60
- package/src/imprint/chromium.ts +93 -29
- package/src/imprint/claude-cli-compile.ts +2 -0
- package/src/imprint/codex-cli-compile.ts +99 -46
- package/src/imprint/compile-agent-types.ts +9 -5
- package/src/imprint/compile-agent.ts +33 -0
- package/src/imprint/compile-tool-executor.ts +783 -0
- package/src/imprint/compile-tools.ts +42 -9
- package/src/imprint/compile-verification.ts +2 -0
- package/src/imprint/compile.ts +3 -0
- package/src/imprint/compiler-log.ts +16 -0
- package/src/imprint/component-recovery.ts +120 -0
- package/src/imprint/cookie-jar.ts +117 -7
- package/src/imprint/credential-extract.ts +9 -2
- package/src/imprint/credential-lock.ts +147 -0
- package/src/imprint/credential-observation.ts +261 -0
- package/src/imprint/credential-store.ts +284 -44
- package/src/imprint/live-component-recovery.ts +73 -0
- package/src/imprint/llm.ts +4 -3
- package/src/imprint/log.ts +16 -1
- package/src/imprint/login.ts +9 -0
- package/src/imprint/master-teach-agent-contracts.ts +40 -3
- package/src/imprint/master-teach-agents.ts +229 -14
- package/src/imprint/master-teach-checks.ts +8 -1
- package/src/imprint/master-teach-controller.ts +681 -138
- package/src/imprint/master-teach-prompt-projections.ts +2 -0
- package/src/imprint/mcp-cdp-sessions.ts +7 -2
- package/src/imprint/mcp-compile-server.ts +62 -850
- package/src/imprint/mcp-server.ts +667 -91
- package/src/imprint/mcp-session-recovery.ts +167 -0
- package/src/imprint/native-teach-agents.ts +232 -108
- package/src/imprint/runtime.ts +416 -47
- package/src/imprint/stealth-chromium.ts +67 -7
- package/src/imprint/teach-browser-resources.ts +88 -0
- package/src/imprint/teach-stage-progress.ts +38 -0
- package/src/imprint/terminal-user-prompt.ts +42 -0
- package/src/imprint/types.ts +51 -18
- 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
|
+
}
|