aipeek 0.2.6 → 0.2.8

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/index.cjs CHANGED
@@ -5,7 +5,7 @@
5
5
 
6
6
 
7
7
 
8
- var _chunk5ZZYOETFcjs = require('./chunk-5ZZYOETF.cjs');
8
+ var _chunkSDUTK75Ycjs = require('./chunk-SDUTK75Y.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 = _chunk5ZZYOETFcjs.aipeekPlugin; exports.check = _chunk5ZZYOETFcjs.check; exports.diffState = _chunk5ZZYOETFcjs.diffState; exports.emitCheck = _chunk5ZZYOETFcjs.emitCheck; exports.emitDiff = _chunk5ZZYOETFcjs.emitDiff; exports.emitSummary = _chunk5ZZYOETFcjs.emitSummary;
17
+ exports.aipeekPlugin = _chunkSDUTK75Ycjs.aipeekPlugin; exports.check = _chunkSDUTK75Ycjs.check; exports.diffState = _chunkSDUTK75Ycjs.diffState; exports.emitCheck = _chunkSDUTK75Ycjs.emitCheck; exports.emitDiff = _chunkSDUTK75Ycjs.emitDiff; exports.emitSummary = _chunkSDUTK75Ycjs.emitSummary;
package/dist/index.d.cts CHANGED
@@ -28,15 +28,58 @@ interface ErrorEntry {
28
28
  line?: number;
29
29
  column?: number;
30
30
  }
31
+ interface ActionEntry {
32
+ type: string;
33
+ target: string;
34
+ value?: string;
35
+ trusted: boolean;
36
+ view?: string;
37
+ modal?: string;
38
+ focus?: string;
39
+ ts: number;
40
+ tab?: string;
41
+ }
31
42
  interface RawState {
43
+ tab?: string;
32
44
  url: string;
45
+ title?: string;
46
+ visible?: boolean;
33
47
  ui: string;
34
48
  console: LogEntry[];
35
49
  network: NetworkRequest[];
36
50
  errors: ErrorEntry[];
51
+ actions?: ActionEntry[];
37
52
  state: Record<string, unknown>;
53
+ performance?: PerformanceData;
38
54
  timestamp: number;
39
55
  }
56
+ interface PerfStat {
57
+ total: number;
58
+ n: number;
59
+ max: number;
60
+ samples: number[];
61
+ }
62
+ interface PerfBucketData {
63
+ name: string;
64
+ components: Record<string, PerfStat>;
65
+ frames: {
66
+ total: number;
67
+ long: number;
68
+ max: number;
69
+ samples: number[];
70
+ };
71
+ lines: Record<string, PerfStat>;
72
+ }
73
+ interface PerformanceData {
74
+ windowMs: number;
75
+ hiddenFrames: number;
76
+ mobxPatched: boolean;
77
+ buckets: PerfBucketData[];
78
+ longtasks: {
79
+ count: number;
80
+ max: number;
81
+ };
82
+ }
40
83
  interface CompactState {
41
84
  url: string;
42
85
  ui: string;
@@ -44,6 +87,7 @@ interface CompactState {
44
87
  network: string;
45
88
  errors: string;
46
89
  state: string;
90
+ performance?: string;
47
91
  timestamp: number;
48
92
  counts: {
49
93
  console: number;
package/dist/index.d.ts CHANGED
@@ -28,15 +28,58 @@ interface ErrorEntry {
28
28
  line?: number;
29
29
  column?: number;
30
30
  }
31
+ interface ActionEntry {
32
+ type: string;
33
+ target: string;
34
+ value?: string;
35
+ trusted: boolean;
36
+ view?: string;
37
+ modal?: string;
38
+ focus?: string;
39
+ ts: number;
40
+ tab?: string;
41
+ }
31
42
  interface RawState {
43
+ tab?: string;
32
44
  url: string;
45
+ title?: string;
46
+ visible?: boolean;
33
47
  ui: string;
34
48
  console: LogEntry[];
35
49
  network: NetworkRequest[];
36
50
  errors: ErrorEntry[];
51
+ actions?: ActionEntry[];
37
52
  state: Record<string, unknown>;
53
+ performance?: PerformanceData;
38
54
  timestamp: number;
39
55
  }
56
+ interface PerfStat {
57
+ total: number;
58
+ n: number;
59
+ max: number;
60
+ samples: number[];
61
+ }
62
+ interface PerfBucketData {
63
+ name: string;
64
+ components: Record<string, PerfStat>;
65
+ frames: {
66
+ total: number;
67
+ long: number;
68
+ max: number;
69
+ samples: number[];
70
+ };
71
+ lines: Record<string, PerfStat>;
72
+ }
73
+ interface PerformanceData {
74
+ windowMs: number;
75
+ hiddenFrames: number;
76
+ mobxPatched: boolean;
77
+ buckets: PerfBucketData[];
78
+ longtasks: {
79
+ count: number;
80
+ max: number;
81
+ };
82
+ }
40
83
  interface CompactState {
41
84
  url: string;
42
85
  ui: string;
@@ -44,6 +87,7 @@ interface CompactState {
44
87
  network: string;
45
88
  errors: string;
46
89
  state: string;
90
+ performance?: string;
47
91
  timestamp: number;
48
92
  counts: {
49
93
  console: number;
package/dist/index.js CHANGED
@@ -5,7 +5,7 @@ import {
5
5
  emitCheck,
6
6
  emitDiff,
7
7
  emitSummary
8
- } from "./chunk-XA2LT6I4.js";
8
+ } from "./chunk-4BPXH2SW.js";
9
9
  export {
10
10
  aipeekPlugin,
11
11
  check,
package/dist/plugin.cjs CHANGED
@@ -3,11 +3,13 @@
3
3
 
4
4
 
5
5
 
6
- var _chunk5ZZYOETFcjs = require('./chunk-5ZZYOETF.cjs');
6
+
7
+ var _chunkSDUTK75Ycjs = require('./chunk-SDUTK75Y.cjs');
7
8
  require('./chunk-Z2Y65YOY.cjs');
8
9
 
9
10
 
10
11
 
11
12
 
12
13
 
13
- exports.END_TAG = _chunk5ZZYOETFcjs.END_TAG; exports.START_TAG = _chunk5ZZYOETFcjs.START_TAG; exports.aipeekPlugin = _chunk5ZZYOETFcjs.aipeekPlugin; exports.injectClaudeMd = _chunk5ZZYOETFcjs.injectClaudeMd;
14
+
15
+ exports.END_TAG = _chunkSDUTK75Ycjs.END_TAG; exports.START_TAG = _chunkSDUTK75Ycjs.START_TAG; exports.aipeekPlugin = _chunkSDUTK75Ycjs.aipeekPlugin; exports.injectClaudeMd = _chunkSDUTK75Ycjs.injectClaudeMd; exports.renderClaudeMd = _chunkSDUTK75Ycjs.renderClaudeMd;
package/dist/plugin.js CHANGED
@@ -2,11 +2,13 @@ import {
2
2
  END_TAG,
3
3
  START_TAG,
4
4
  aipeekPlugin,
5
- injectClaudeMd
6
- } from "./chunk-XA2LT6I4.js";
5
+ injectClaudeMd,
6
+ renderClaudeMd
7
+ } from "./chunk-4BPXH2SW.js";
7
8
  export {
8
9
  END_TAG,
9
10
  START_TAG,
10
11
  aipeekPlugin,
11
- injectClaudeMd
12
+ injectClaudeMd,
13
+ renderClaudeMd
12
14
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "aipeek",
3
- "version": "0.2.6",
3
+ "version": "0.2.8",
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": {
@@ -22,6 +22,7 @@
22
22
  "src"
23
23
  ],
24
24
  "scripts": {
25
+ "prebuild": "node --import ../../node_modules/tsx/dist/loader.mjs ../../packages/repodex/src/cli.ts",
25
26
  "build": "tsup",
26
27
  "test": "vitest run",
27
28
  "test:watch": "vitest",
@@ -42,6 +43,7 @@
42
43
  },
43
44
  "devDependencies": {
44
45
  "jsdom": "^29.1.1",
46
+ "repodex": "^0.3.7",
45
47
  "teact": "file:../teact",
46
48
  "tsup": "^8.4.0",
47
49
  "typescript": "~5.7.2",
@@ -0,0 +1,190 @@
1
+ import type { PluginObj, NodePath } from '@babel/core'
2
+ import type { Statement, VariableDeclaration, ReturnStatement, ExpressionStatement } from '@babel/types'
3
+ import * as t from '@babel/types'
4
+ import path from 'path'
5
+
6
+ /**
7
+ * Babel plugin: wrap statements with __line(label, () => ...) for line-level profiling.
8
+ *
9
+ * Only wraps statements that have measurable cost:
10
+ * - VariableDeclaration with initializer
11
+ * - ExpressionStatement (function calls, assignments)
12
+ * - ReturnStatement with argument
13
+ *
14
+ * Skips:
15
+ * - import/export (module syntax)
16
+ * - control flow (if/for/while/switch) — their bodies get wrapped, not the structure
17
+ * - empty statements, type declarations
18
+ */
19
+ export function lineProfilerPlugin(): PluginObj {
20
+ return {
21
+ name: 'aipeek-line-profiler',
22
+ visitor: {
23
+ VariableDeclaration(nodePath: NodePath<VariableDeclaration>, state) {
24
+ if (shouldSkip(nodePath, state)) return
25
+ const decl = nodePath.node.declarations[0]
26
+ if (!decl?.init) return // skip `let x;`
27
+ if (hasAwaitOrYield(decl.init)) return // wrapping in arrow fn breaks async semantics
28
+
29
+ const label = makeLabel(nodePath, state, decl.id.type === 'Identifier' ? decl.id.name : undefined)
30
+ const wrapped = wrapExpr(decl.init, label)
31
+ decl.init = wrapped
32
+ },
33
+
34
+ ExpressionStatement(nodePath: NodePath<ExpressionStatement>, state) {
35
+ if (shouldSkip(nodePath, state)) return
36
+ if (hasAwaitOrYield(nodePath.node.expression)) return
37
+
38
+ const label = makeLabel(nodePath, state)
39
+ const wrapped = wrapExpr(nodePath.node.expression, label)
40
+ nodePath.node.expression = wrapped
41
+ },
42
+
43
+ ReturnStatement(nodePath: NodePath<ReturnStatement>, state) {
44
+ if (shouldSkip(nodePath, state)) return
45
+ if (!nodePath.node.argument) return // skip `return;`
46
+ if (hasAwaitOrYield(nodePath.node.argument)) return
47
+
48
+ const label = makeLabel(nodePath, state)
49
+ const wrapped = wrapExpr(nodePath.node.argument, label)
50
+ nodePath.node.argument = wrapped
51
+ },
52
+ },
53
+ }
54
+ }
55
+
56
+ function shouldSkip(nodePath: NodePath<Statement>, state: any): boolean {
57
+ const filename: string = state.file?.opts?.filename ?? ''
58
+ // Skip node_modules — third-party cost isn't the app's to fix.
59
+ if (filename.includes('node_modules')) return true
60
+ // Skip aipeek's own source: the profiler reads state by walking the React fiber tree
61
+ // (client.ts walkFiber/walkChildren) on every /ui|/dom|/screen. That's MEASUREMENT cost,
62
+ // not the app's — instrumenting it lets the observer dominate its own report (290ms self-top).
63
+ if (filename.includes('packages/aipeek/')) return true
64
+
65
+ // Skip module syntax — import/export carry no runtime cost to measure
66
+ const node = nodePath.node
67
+ if (t.isImportDeclaration(node)) return true
68
+ if (t.isExportNamedDeclaration(node)) return true
69
+ if (t.isExportDefaultDeclaration(node)) return true
70
+ if (t.isExportAllDeclaration(node)) return true
71
+
72
+ // Skip if already wrapped
73
+ if (t.isExpressionStatement(node) && t.isCallExpression(node.expression)) {
74
+ const callee = node.expression.callee
75
+ if (t.isIdentifier(callee) && callee.name === '__line') return true
76
+ }
77
+
78
+ return false
79
+ }
80
+
81
+ function makeLabel(nodePath: NodePath<Statement>, state: any, varName?: string): string {
82
+ const filename = state.file.opts.filename ?? 'unknown'
83
+ const basename = path.basename(filename)
84
+ const line = resolveLine(nodePath)
85
+
86
+ if (varName) {
87
+ return `${basename}:${line}:${varName}`
88
+ }
89
+ return `${basename}:${line}`
90
+ }
91
+
92
+ // Resolve the source line a wrapped statement should point to.
93
+ //
94
+ // A node synthesized by an upstream transform (e.g. mobx-react-observer rewriting
95
+ // `function Root(){…}` → `const Root = observer(function Root(){…})`) carries NO loc on the
96
+ // outer declaration. Two recovery moves, in priority order:
97
+ //
98
+ // 1. descend — the synthesized wrapper still contains the user's ORIGINAL node
99
+ // (`function Root()` keeps its loc 59). The nearest loc-bearing descendant is the most
100
+ // specific real source location, so prefer it.
101
+ // 2. climb — only if the whole subtree is synthetic (no loc anywhere inside) do we fall back
102
+ // to the nearest ancestor's loc.
103
+ //
104
+ // Without this, observer-wrapped components — the ones most worth profiling — collapse onto
105
+ // `<file>:1` (climbing all the way to Program) or `<file>:0` (no loc at all).
106
+ function resolveLine(nodePath: NodePath<Statement>): number {
107
+ if (nodePath.node.loc) return nodePath.node.loc.start.line
108
+
109
+ const descend = firstLocLine(nodePath.node)
110
+ if (descend != null) return descend
111
+
112
+ let p: NodePath | null = nodePath.parentPath
113
+ while (p) {
114
+ if (p.node.loc) return p.node.loc.start.line
115
+ p = p.parentPath
116
+ }
117
+ return 0
118
+ }
119
+
120
+ // Pre-order DFS for the first node carrying a loc. Skips the root itself (caller already
121
+ // checked it). Source-order traversal so the earliest real line wins.
122
+ function firstLocLine(node: t.Node): number | null {
123
+ for (const key of t.VISITOR_KEYS[node.type] ?? []) {
124
+ const child = (node as any)[key]
125
+ const children = Array.isArray(child) ? child : [child]
126
+ for (const c of children) {
127
+ if (!c || typeof c.type !== 'string') continue
128
+ if (c.loc) return c.loc.start.line
129
+ const deeper = firstLocLine(c)
130
+ if (deeper != null) return deeper
131
+ }
132
+ }
133
+ return null
134
+ }
135
+
136
+ // Emit: (globalThis.__line || ((_l, f) => f()))(label, () => expr)
137
+ // NOT a bare `__line(...)`: instrumented modules eval their top-level statements at IMPORT time,
138
+ // before client-patch installs window.__line — a bare identifier then throws ReferenceError and
139
+ // kills the app. globalThis.__line is a member access (→ undefined, not a throw); the || fallback
140
+ // just runs the original expr, so the instrumentation is inert until the runtime is present.
141
+ function wrapExpr(expr: t.Expression, label: string): t.CallExpression {
142
+ const runtime = t.logicalExpression(
143
+ '||',
144
+ t.memberExpression(t.identifier('globalThis'), t.identifier('__line')),
145
+ t.arrowFunctionExpression(
146
+ [t.identifier('_l'), t.identifier('f')],
147
+ t.callExpression(t.identifier('f'), []),
148
+ ),
149
+ )
150
+ return t.callExpression(
151
+ runtime,
152
+ [
153
+ t.stringLiteral(label),
154
+ t.arrowFunctionExpression([], expr),
155
+ ]
156
+ )
157
+ }
158
+
159
+ // True if expr contains an await/yield at THIS function level (not inside a nested function).
160
+ // Wrapping such an expr in `() => expr` would either be a syntax error (await in non-async
161
+ // arrow) or silently change semantics (the arrow returns a Promise instead of awaiting).
162
+ // Nested functions are their own scope, so we stop recursing at function boundaries.
163
+ function hasAwaitOrYield(node: t.Node | null | undefined): boolean {
164
+ if (!node || typeof node !== 'object') return false
165
+ if (t.isAwaitExpression(node) || t.isYieldExpression(node)) return true
166
+ // Don't descend into nested functions — their await/yield belongs to their own scope.
167
+ if (
168
+ t.isFunctionExpression(node)
169
+ || t.isArrowFunctionExpression(node)
170
+ || t.isFunctionDeclaration(node)
171
+ || t.isObjectMethod(node)
172
+ || t.isClassMethod(node)
173
+ ) return false
174
+
175
+ for (const key of Object.keys(node)) {
176
+ if (key === 'loc' || key === 'start' || key === 'end' || key === 'leadingComments' || key === 'trailingComments') continue
177
+ const child = (node as any)[key]
178
+ if (Array.isArray(child)) {
179
+ for (const c of child) {
180
+ if (c && typeof c.type === 'string' && hasAwaitOrYield(c)) return true
181
+ }
182
+ }
183
+ else if (child && typeof child.type === 'string') {
184
+ if (hasAwaitOrYield(child)) return true
185
+ }
186
+ }
187
+ return false
188
+ }
189
+
190
+ export default lineProfilerPlugin