aipeek 0.2.1 → 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.
@@ -0,0 +1,7 @@
1
+ "use strict";Object.defineProperty(exports, "__esModule", {value: true});// node_modules/tsup/assets/cjs_shims.js
2
+ var getImportMetaUrl = () => typeof document === "undefined" ? new URL(`file:${__filename}`).href : document.currentScript && document.currentScript.tagName.toUpperCase() === "SCRIPT" ? document.currentScript.src : new URL("main.js", document.baseURI).href;
3
+ var importMetaUrl = /* @__PURE__ */ getImportMetaUrl();
4
+
5
+
6
+
7
+ exports.importMetaUrl = importMetaUrl;
package/dist/cli.cjs CHANGED
@@ -1,4 +1,6 @@
1
- "use strict"; function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } function _nullishCoalesce(lhs, rhsFn) { if (lhs != null) { return lhs; } else { return rhsFn(); } }// src/server/cli.ts
1
+ "use strict"; function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } function _nullishCoalesce(lhs, rhsFn) { if (lhs != null) { return lhs; } else { return rhsFn(); } }require('./chunk-Z2Y65YOY.cjs');
2
+
3
+ // src/server/cli.ts
2
4
  var _process = require('process'); var _process2 = _interopRequireDefault(_process);
3
5
  var _picocolors = require('picocolors'); var _picocolors2 = _interopRequireDefault(_picocolors);
4
6
  var args = _process2.default.argv.slice(2);
package/dist/index.cjs CHANGED
@@ -5,7 +5,8 @@
5
5
 
6
6
 
7
7
 
8
- var _chunkIOK7MMPTcjs = require('./chunk-IOK7MMPT.cjs');
8
+ var _chunk3NVB3GGEcjs = require('./chunk-3NVB3GGE.cjs');
9
+ require('./chunk-Z2Y65YOY.cjs');
9
10
 
10
11
 
11
12
 
@@ -13,4 +14,4 @@ var _chunkIOK7MMPTcjs = require('./chunk-IOK7MMPT.cjs');
13
14
 
14
15
 
15
16
 
16
- exports.aipeekPlugin = _chunkIOK7MMPTcjs.aipeekPlugin; exports.check = _chunkIOK7MMPTcjs.check; exports.diffState = _chunkIOK7MMPTcjs.diffState; exports.emitCheck = _chunkIOK7MMPTcjs.emitCheck; exports.emitDiff = _chunkIOK7MMPTcjs.emitDiff; exports.emitSummary = _chunkIOK7MMPTcjs.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-E7YXT6HW.js";
8
+ } from "./chunk-72ZKZ42D.js";
9
9
  export {
10
10
  aipeekPlugin,
11
11
  check,
package/dist/plugin.cjs CHANGED
@@ -1,6 +1,7 @@
1
1
  "use strict";Object.defineProperty(exports, "__esModule", {value: true});
2
2
 
3
- var _chunkIOK7MMPTcjs = require('./chunk-IOK7MMPT.cjs');
3
+ var _chunk3NVB3GGEcjs = require('./chunk-3NVB3GGE.cjs');
4
+ require('./chunk-Z2Y65YOY.cjs');
4
5
 
5
6
 
6
- exports.aipeekPlugin = _chunkIOK7MMPTcjs.aipeekPlugin;
7
+ exports.aipeekPlugin = _chunk3NVB3GGEcjs.aipeekPlugin;
package/dist/plugin.js CHANGED
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  aipeekPlugin
3
- } from "./chunk-E7YXT6HW.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.1",
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",
@@ -27,6 +30,7 @@
27
30
  "prepublishOnly": "bun run build"
28
31
  },
29
32
  "dependencies": {
33
+ "esbuild": "^0.25.0",
30
34
  "html-to-image": "^1.11.13",
31
35
  "picocolors": "^1.1.1"
32
36
  },
@@ -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
+ }