aipeek 0.2.2 → 0.2.4

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.
@@ -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 clientPath = _path.resolve.call(void 0, __dirname, "../client/client.ts");
758
- var patchPath = _path.resolve.call(void 0, __dirname, "../client/client-patch.ts");
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 clientPath = resolve(__dirname2, "../client/client.ts");
754
- var patchPath = resolve(__dirname2, "../client/client-patch.ts");
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 _chunkGIMXNZD5cjs = require('./chunk-GIMXNZD5.cjs');
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 = _chunkGIMXNZD5cjs.aipeekPlugin; exports.check = _chunkGIMXNZD5cjs.check; exports.diffState = _chunkGIMXNZD5cjs.diffState; exports.emitCheck = _chunkGIMXNZD5cjs.emitCheck; exports.emitDiff = _chunkGIMXNZD5cjs.emitDiff; exports.emitSummary = _chunkGIMXNZD5cjs.emitSummary;
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
@@ -5,7 +5,7 @@ import {
5
5
  emitCheck,
6
6
  emitDiff,
7
7
  emitSummary
8
- } from "./chunk-JOY7QP24.js";
8
+ } from "./chunk-72ZKZ42D.js";
9
9
  export {
10
10
  aipeekPlugin,
11
11
  check,
package/dist/plugin.cjs CHANGED
@@ -1,7 +1,7 @@
1
1
  "use strict";Object.defineProperty(exports, "__esModule", {value: true});
2
2
 
3
- var _chunkGIMXNZD5cjs = require('./chunk-GIMXNZD5.cjs');
3
+ var _chunk3NVB3GGEcjs = require('./chunk-3NVB3GGE.cjs');
4
4
  require('./chunk-Z2Y65YOY.cjs');
5
5
 
6
6
 
7
- exports.aipeekPlugin = _chunkGIMXNZD5cjs.aipeekPlugin;
7
+ exports.aipeekPlugin = _chunk3NVB3GGEcjs.aipeekPlugin;
package/dist/plugin.js CHANGED
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  aipeekPlugin
3
- } from "./chunk-JOY7QP24.js";
3
+ } from "./chunk-72ZKZ42D.js";
4
4
  export {
5
5
  aipeekPlugin
6
6
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "aipeek",
3
- "version": "0.2.2",
3
+ "version": "0.2.4",
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,8 @@
18
18
  "aipeek": "dist/cli.js"
19
19
  },
20
20
  "files": [
21
- "dist"
21
+ "dist",
22
+ "src"
22
23
  ],
23
24
  "scripts": {
24
25
  "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
+ }