aipeek 0.2.2 → 0.2.3
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/dist/{chunk-GIMXNZD5.cjs → chunk-3NVB3GGE.cjs} +3 -2
- package/dist/{chunk-JOY7QP24.js → chunk-72ZKZ42D.js} +3 -2
- package/dist/index.cjs +2 -2
- package/dist/index.js +1 -1
- package/dist/plugin.cjs +2 -2
- package/dist/plugin.js +1 -1
- package/package.json +5 -2
- package/src/client/client-patch.ts +312 -0
- package/src/client/client.ts +689 -0
- package/src/core/action.ts +272 -0
- package/src/core/types.ts +75 -0
|
@@ -754,8 +754,9 @@ function readBody(req) {
|
|
|
754
754
|
});
|
|
755
755
|
}
|
|
756
756
|
var __dirname = _path.dirname.call(void 0, _url.fileURLToPath.call(void 0, _chunkZ2Y65YOYcjs.importMetaUrl));
|
|
757
|
-
var
|
|
758
|
-
var
|
|
757
|
+
var clientDir = _fs.existsSync.call(void 0, _path.resolve.call(void 0, __dirname, "../client")) ? _path.resolve.call(void 0, __dirname, "../client") : _path.resolve.call(void 0, __dirname, "../src/client");
|
|
758
|
+
var clientPath = _path.resolve.call(void 0, clientDir, "client.ts");
|
|
759
|
+
var patchPath = _path.resolve.call(void 0, clientDir, "client-patch.ts");
|
|
759
760
|
function compilePatch() {
|
|
760
761
|
const source = _fs.readFileSync.call(void 0, patchPath, "utf-8");
|
|
761
762
|
const result = _esbuild.transformSync.call(void 0, source, {
|
|
@@ -750,8 +750,9 @@ function readBody(req) {
|
|
|
750
750
|
});
|
|
751
751
|
}
|
|
752
752
|
var __dirname2 = dirname(fileURLToPath(import.meta.url));
|
|
753
|
-
var
|
|
754
|
-
var
|
|
753
|
+
var clientDir = existsSync(resolve(__dirname2, "../client")) ? resolve(__dirname2, "../client") : resolve(__dirname2, "../src/client");
|
|
754
|
+
var clientPath = resolve(clientDir, "client.ts");
|
|
755
|
+
var patchPath = resolve(clientDir, "client-patch.ts");
|
|
755
756
|
function compilePatch() {
|
|
756
757
|
const source = readFileSync(patchPath, "utf-8");
|
|
757
758
|
const result = transformSync(source, {
|
package/dist/index.cjs
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
|
|
6
6
|
|
|
7
7
|
|
|
8
|
-
var
|
|
8
|
+
var _chunk3NVB3GGEcjs = require('./chunk-3NVB3GGE.cjs');
|
|
9
9
|
require('./chunk-Z2Y65YOY.cjs');
|
|
10
10
|
|
|
11
11
|
|
|
@@ -14,4 +14,4 @@ require('./chunk-Z2Y65YOY.cjs');
|
|
|
14
14
|
|
|
15
15
|
|
|
16
16
|
|
|
17
|
-
exports.aipeekPlugin =
|
|
17
|
+
exports.aipeekPlugin = _chunk3NVB3GGEcjs.aipeekPlugin; exports.check = _chunk3NVB3GGEcjs.check; exports.diffState = _chunk3NVB3GGEcjs.diffState; exports.emitCheck = _chunk3NVB3GGEcjs.emitCheck; exports.emitDiff = _chunk3NVB3GGEcjs.emitDiff; exports.emitSummary = _chunk3NVB3GGEcjs.emitSummary;
|
package/dist/index.js
CHANGED
package/dist/plugin.cjs
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
"use strict";Object.defineProperty(exports, "__esModule", {value: true});
|
|
2
2
|
|
|
3
|
-
var
|
|
3
|
+
var _chunk3NVB3GGEcjs = require('./chunk-3NVB3GGE.cjs');
|
|
4
4
|
require('./chunk-Z2Y65YOY.cjs');
|
|
5
5
|
|
|
6
6
|
|
|
7
|
-
exports.aipeekPlugin =
|
|
7
|
+
exports.aipeekPlugin = _chunk3NVB3GGEcjs.aipeekPlugin;
|
package/dist/plugin.js
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "aipeek",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.3",
|
|
4
4
|
"description": "Gives AI a peek into your running browser app — UI tree, console, network, errors, state",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": {
|
|
@@ -18,7 +18,10 @@
|
|
|
18
18
|
"aipeek": "dist/cli.js"
|
|
19
19
|
},
|
|
20
20
|
"files": [
|
|
21
|
-
"dist"
|
|
21
|
+
"dist",
|
|
22
|
+
"src/client",
|
|
23
|
+
"src/core/action.ts",
|
|
24
|
+
"src/core/types.ts"
|
|
22
25
|
],
|
|
23
26
|
"scripts": {
|
|
24
27
|
"build": "tsup",
|
|
@@ -0,0 +1,312 @@
|
|
|
1
|
+
// aipeek client patch — synchronous, non-module
|
|
2
|
+
// Injected as inline <script> at head-prepend to patch console/fetch/XHR/errors
|
|
3
|
+
// BEFORE any application modules execute.
|
|
4
|
+
|
|
5
|
+
import type { ErrorEntry, LogEntry, NetworkRequest } from '../core/types'
|
|
6
|
+
|
|
7
|
+
type NetworkEntry = NetworkRequest
|
|
8
|
+
|
|
9
|
+
interface AipeekBuffers {
|
|
10
|
+
consoleLogs: LogEntry[]
|
|
11
|
+
networkRequests: NetworkEntry[]
|
|
12
|
+
errorEntries: ErrorEntry[]
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
// --- Ring buffers (persist across HMR) ---
|
|
16
|
+
|
|
17
|
+
const MAX_CONSOLE = 100
|
|
18
|
+
const MAX_NETWORK = 50
|
|
19
|
+
const MAX_ERRORS = 20
|
|
20
|
+
|
|
21
|
+
const BUFFERS_KEY = '__AIPEEK_BUFFERS__'
|
|
22
|
+
const existing = (window as any)[BUFFERS_KEY] as AipeekBuffers | undefined
|
|
23
|
+
const consoleLogs: LogEntry[] = existing?.consoleLogs ?? []
|
|
24
|
+
const networkRequests: NetworkEntry[] = existing?.networkRequests ?? []
|
|
25
|
+
const errorEntries: ErrorEntry[] = existing?.errorEntries ?? []
|
|
26
|
+
;(window as any)[BUFFERS_KEY] = { consoleLogs, networkRequests, errorEntries }
|
|
27
|
+
|
|
28
|
+
function pushBounded<T>(arr: T[], item: T, max: number) {
|
|
29
|
+
arr.push(item)
|
|
30
|
+
if (arr.length > max)
|
|
31
|
+
arr.shift()
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
// --- Patch console ---
|
|
35
|
+
|
|
36
|
+
/* eslint-disable no-console -- patching console is the point of this block */
|
|
37
|
+
for (const level of ['log', 'info', 'warn', 'error', 'debug'] as const) {
|
|
38
|
+
const orig = console[level]
|
|
39
|
+
console[level] = (...args: unknown[]) => {
|
|
40
|
+
pushBounded(consoleLogs, {
|
|
41
|
+
level,
|
|
42
|
+
text: args.map(a => typeof a === 'string' ? a : tryStringify(a)).join(' '),
|
|
43
|
+
timestamp: Date.now(),
|
|
44
|
+
}, MAX_CONSOLE)
|
|
45
|
+
orig.apply(console, args)
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
/* eslint-enable no-console */
|
|
49
|
+
|
|
50
|
+
// --- Patch fetch — 透明摄像头:tee body,主流原样返回,副流后台 capture ---
|
|
51
|
+
|
|
52
|
+
const originalFetch = window.fetch
|
|
53
|
+
window.fetch = async (input, init) => {
|
|
54
|
+
const url = typeof input === 'string' ? input : input instanceof URL ? input.toString() : input.url
|
|
55
|
+
const method = init?.method || 'GET'
|
|
56
|
+
const start = performance.now()
|
|
57
|
+
|
|
58
|
+
// 立即推 entry,主线程持有同一引用,capture 完成后写回
|
|
59
|
+
const entry: NetworkEntry = {
|
|
60
|
+
method,
|
|
61
|
+
url,
|
|
62
|
+
status: 0,
|
|
63
|
+
duration: 0,
|
|
64
|
+
resourceType: 'fetch',
|
|
65
|
+
}
|
|
66
|
+
if (init?.headers) {
|
|
67
|
+
const rh: Record<string, string> = {}
|
|
68
|
+
new Headers(init.headers as HeadersInit).forEach((v, k) => {
|
|
69
|
+
rh[k] = v
|
|
70
|
+
})
|
|
71
|
+
entry.requestHeaders = rh
|
|
72
|
+
}
|
|
73
|
+
if (init?.body && typeof init.body === 'string') {
|
|
74
|
+
entry.requestBody = init.body.slice(0, 4000)
|
|
75
|
+
entry.requestSample = jsonSample(init.body)
|
|
76
|
+
}
|
|
77
|
+
pushBounded(networkRequests, entry, MAX_NETWORK)
|
|
78
|
+
|
|
79
|
+
let response: Response
|
|
80
|
+
try {
|
|
81
|
+
response = await originalFetch(input, init)
|
|
82
|
+
}
|
|
83
|
+
catch (err) {
|
|
84
|
+
entry.status = 0
|
|
85
|
+
entry.duration = Math.round(performance.now() - start)
|
|
86
|
+
entry.failed = true
|
|
87
|
+
entry.failureText = err instanceof Error ? err.message : String(err)
|
|
88
|
+
throw err
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
entry.status = response.status
|
|
92
|
+
entry.duration = Math.round(performance.now() - start)
|
|
93
|
+
const respHeaders: Record<string, string> = {}
|
|
94
|
+
response.headers.forEach((v, k) => {
|
|
95
|
+
respHeaders[k] = v
|
|
96
|
+
})
|
|
97
|
+
entry.responseHeaders = respHeaders
|
|
98
|
+
|
|
99
|
+
if (!response.body)
|
|
100
|
+
return response
|
|
101
|
+
|
|
102
|
+
// tee:主流原样给业务,副流后台读 4KB 做 capture
|
|
103
|
+
const [pass, peek] = response.body.tee()
|
|
104
|
+
captureBody(peek, entry).catch(() => {})
|
|
105
|
+
|
|
106
|
+
const proxied = new Response(pass, {
|
|
107
|
+
status: response.status,
|
|
108
|
+
statusText: response.statusText,
|
|
109
|
+
headers: response.headers,
|
|
110
|
+
})
|
|
111
|
+
Object.defineProperty(proxied, 'url', { value: response.url })
|
|
112
|
+
Object.defineProperty(proxied, 'type', { value: response.type })
|
|
113
|
+
Object.defineProperty(proxied, 'redirected', { value: response.redirected })
|
|
114
|
+
return proxied
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
async function captureBody(stream: ReadableStream<Uint8Array>, entry: NetworkEntry, MAX = 4000) {
|
|
118
|
+
const reader = stream.getReader()
|
|
119
|
+
const decoder = new TextDecoder()
|
|
120
|
+
let body = ''
|
|
121
|
+
try {
|
|
122
|
+
while (body.length < MAX) {
|
|
123
|
+
const { done, value } = await reader.read()
|
|
124
|
+
if (done)
|
|
125
|
+
break
|
|
126
|
+
body += decoder.decode(value, { stream: true })
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
catch { /* ignore */ }
|
|
130
|
+
finally {
|
|
131
|
+
try {
|
|
132
|
+
await reader.cancel()
|
|
133
|
+
}
|
|
134
|
+
catch {}
|
|
135
|
+
}
|
|
136
|
+
entry.responseBody = body.slice(0, MAX)
|
|
137
|
+
entry.responseSample = jsonSample(body)
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
// --- Patch XHR ---
|
|
141
|
+
|
|
142
|
+
const XHRProto = XMLHttpRequest.prototype
|
|
143
|
+
const origOpen = XHRProto.open
|
|
144
|
+
const origSend = XHRProto.send
|
|
145
|
+
|
|
146
|
+
XHRProto.open = function (method: string, url: string | URL, ...rest: unknown[]) {
|
|
147
|
+
(this as any).__aipeek = { method, url: String(url), start: 0 }
|
|
148
|
+
return (origOpen as any).apply(this, [method, url, ...rest])
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
XHRProto.send = function (...args: unknown[]) {
|
|
152
|
+
const meta = (this as any).__aipeek
|
|
153
|
+
if (meta) {
|
|
154
|
+
meta.start = performance.now()
|
|
155
|
+
this.addEventListener('loadend', () => {
|
|
156
|
+
const responseHeaders: Record<string, string> = {}
|
|
157
|
+
const rawHeaders = this.getAllResponseHeaders()
|
|
158
|
+
for (const line of rawHeaders.trim().split('\r\n')) {
|
|
159
|
+
const idx = line.indexOf(': ')
|
|
160
|
+
if (idx > 0)
|
|
161
|
+
responseHeaders[line.slice(0, idx)] = line.slice(idx + 2)
|
|
162
|
+
}
|
|
163
|
+
pushBounded(networkRequests, {
|
|
164
|
+
method: meta.method,
|
|
165
|
+
url: meta.url,
|
|
166
|
+
status: this.status,
|
|
167
|
+
duration: Math.round(performance.now() - meta.start),
|
|
168
|
+
resourceType: 'xhr',
|
|
169
|
+
responseHeaders,
|
|
170
|
+
}, MAX_NETWORK)
|
|
171
|
+
})
|
|
172
|
+
}
|
|
173
|
+
return (origSend as any).apply(this, args)
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
// --- Patch errors ---
|
|
177
|
+
|
|
178
|
+
window.addEventListener('error', (e) => {
|
|
179
|
+
if (e.target && e.target !== window) {
|
|
180
|
+
const el = e.target as HTMLElement
|
|
181
|
+
const src = (el as HTMLImageElement).src || (el as HTMLScriptElement).src || (el as HTMLLinkElement).href || ''
|
|
182
|
+
pushBounded(errorEntries, {
|
|
183
|
+
message: `Resource load failed: ${el.tagName.toLowerCase()} ${src}`,
|
|
184
|
+
source: src,
|
|
185
|
+
}, MAX_ERRORS)
|
|
186
|
+
return
|
|
187
|
+
}
|
|
188
|
+
pushBounded(errorEntries, {
|
|
189
|
+
message: e.message,
|
|
190
|
+
source: e.filename,
|
|
191
|
+
line: e.lineno,
|
|
192
|
+
column: e.colno,
|
|
193
|
+
stack: e.error?.stack,
|
|
194
|
+
}, MAX_ERRORS)
|
|
195
|
+
}, true)
|
|
196
|
+
|
|
197
|
+
window.addEventListener('unhandledrejection', (e) => {
|
|
198
|
+
const reason = e.reason
|
|
199
|
+
let message: string
|
|
200
|
+
let stack: string | undefined
|
|
201
|
+
if (reason instanceof Error) {
|
|
202
|
+
message = reason.message
|
|
203
|
+
stack = reason.stack
|
|
204
|
+
}
|
|
205
|
+
else if (reason instanceof Event) {
|
|
206
|
+
const target = reason.target as any
|
|
207
|
+
const tag = target?.tagName?.toLowerCase() || ''
|
|
208
|
+
const src = target?.src || target?.href || target?.currentSrc || ''
|
|
209
|
+
if (tag && src) {
|
|
210
|
+
message = `Unhandled rejection: ${tag} load failed ${src}`
|
|
211
|
+
}
|
|
212
|
+
else if (tag) {
|
|
213
|
+
message = `Unhandled rejection: ${tag} ${reason.type} event`
|
|
214
|
+
}
|
|
215
|
+
else {
|
|
216
|
+
message = `Unhandled rejection: ${reason.type} event`
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
else {
|
|
220
|
+
message = reason?.message || String(reason)
|
|
221
|
+
stack = reason?.stack
|
|
222
|
+
}
|
|
223
|
+
pushBounded(errorEntries, { message, stack }, MAX_ERRORS)
|
|
224
|
+
})
|
|
225
|
+
|
|
226
|
+
// --- Patch EventSource (SSE) ---
|
|
227
|
+
|
|
228
|
+
const OrigEventSource = window.EventSource
|
|
229
|
+
if (OrigEventSource) {
|
|
230
|
+
window.EventSource = new Proxy(OrigEventSource, {
|
|
231
|
+
construct(Target, args) {
|
|
232
|
+
const es = new Target(...args as ConstructorParameters<typeof EventSource>)
|
|
233
|
+
const url = typeof args[0] === 'string' ? args[0] : String(args[0])
|
|
234
|
+
const start = performance.now()
|
|
235
|
+
|
|
236
|
+
pushBounded(networkRequests, {
|
|
237
|
+
method: 'GET',
|
|
238
|
+
url,
|
|
239
|
+
status: 0,
|
|
240
|
+
duration: 0,
|
|
241
|
+
resourceType: 'eventsource',
|
|
242
|
+
}, MAX_NETWORK)
|
|
243
|
+
|
|
244
|
+
es.addEventListener('open', () => {
|
|
245
|
+
const entry = networkRequests.find(r => r.url === url && r.resourceType === 'eventsource' && r.status === 0)
|
|
246
|
+
if (entry) {
|
|
247
|
+
entry.status = 200
|
|
248
|
+
entry.duration = Math.round(performance.now() - start)
|
|
249
|
+
}
|
|
250
|
+
})
|
|
251
|
+
|
|
252
|
+
es.addEventListener('error', () => {
|
|
253
|
+
pushBounded(networkRequests, {
|
|
254
|
+
method: 'GET',
|
|
255
|
+
url,
|
|
256
|
+
status: es.readyState === EventSource.CLOSED ? 0 : es.readyState,
|
|
257
|
+
duration: Math.round(performance.now() - start),
|
|
258
|
+
resourceType: 'eventsource',
|
|
259
|
+
failed: true,
|
|
260
|
+
failureText: es.readyState === EventSource.CLOSED ? 'connection closed' : 'connection error',
|
|
261
|
+
}, MAX_NETWORK)
|
|
262
|
+
})
|
|
263
|
+
|
|
264
|
+
return es
|
|
265
|
+
},
|
|
266
|
+
})
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
// --- Utils ---
|
|
270
|
+
|
|
271
|
+
function jsonSample(text: string): string | undefined {
|
|
272
|
+
if (!text || (text[0] !== '{' && text[0] !== '['))
|
|
273
|
+
return undefined
|
|
274
|
+
try {
|
|
275
|
+
return JSON.stringify(sampleOf(JSON.parse(text), 0))
|
|
276
|
+
}
|
|
277
|
+
catch {
|
|
278
|
+
return undefined
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
function sampleOf(v: unknown, d: number): unknown {
|
|
283
|
+
if (d > 4)
|
|
284
|
+
return null
|
|
285
|
+
if (v === null || v === undefined)
|
|
286
|
+
return v
|
|
287
|
+
if (typeof v === 'string')
|
|
288
|
+
return v.length > 50 ? v.slice(0, 50) : v
|
|
289
|
+
if (typeof v === 'number' || typeof v === 'boolean')
|
|
290
|
+
return v
|
|
291
|
+
if (Array.isArray(v))
|
|
292
|
+
return v.length ? [sampleOf(v[0], d + 1)] : []
|
|
293
|
+
if (typeof v === 'object') {
|
|
294
|
+
const result: Record<string, unknown> = {}
|
|
295
|
+
for (const [k, val] of Object.entries(v as Record<string, unknown>).slice(0, 15)) {
|
|
296
|
+
if (typeof val === 'function')
|
|
297
|
+
continue
|
|
298
|
+
result[k] = sampleOf(val, d + 1)
|
|
299
|
+
}
|
|
300
|
+
return result
|
|
301
|
+
}
|
|
302
|
+
return null
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
function tryStringify(v: unknown): string {
|
|
306
|
+
try {
|
|
307
|
+
return JSON.stringify(v)
|
|
308
|
+
}
|
|
309
|
+
catch {
|
|
310
|
+
return String(v)
|
|
311
|
+
}
|
|
312
|
+
}
|