aipeek 0.2.5 → 0.2.7

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.
@@ -10,7 +10,7 @@ var _url = require('url');
10
10
  var _esbuild = require('esbuild');
11
11
 
12
12
  // src/core/action.ts
13
- var TYPES = ["click", "fill", "press", "wait", "screenshot"];
13
+ var TYPES = ["click", "fill", "press", "wait", "screenshot", "realclick", "query"];
14
14
  function resolveAction(type, args) {
15
15
  if (!TYPES.includes(type))
16
16
  return { valid: false, error: `unknown action: ${type}` };
@@ -18,6 +18,8 @@ function resolveAction(type, args) {
18
18
  switch (type) {
19
19
  case "click":
20
20
  return hasTarget ? { valid: true } : { valid: false, error: "click needs sel= or text=" };
21
+ case "realclick":
22
+ return hasTarget || args.x !== void 0 && args.y !== void 0 ? { valid: true } : { valid: false, error: "realclick needs sel=, text=, or x= & y=" };
21
23
  case "fill":
22
24
  if (!hasTarget)
23
25
  return { valid: false, error: "fill needs sel= or text=" };
@@ -30,6 +32,8 @@ function resolveAction(type, args) {
30
32
  return hasTarget ? { valid: true } : { valid: false, error: "wait needs sel= or text=" };
31
33
  case "screenshot":
32
34
  return { valid: true };
35
+ case "query":
36
+ return args.sel ? { valid: true } : { valid: false, error: "query needs sel=" };
33
37
  default:
34
38
  return { valid: false, error: `unknown action: ${type}` };
35
39
  }
@@ -71,13 +75,54 @@ function check(raw) {
71
75
  function truncate(s, max) {
72
76
  return s.length > max ? `${s.slice(0, max)}\u2026` : s;
73
77
  }
78
+ function formatValue(v, seen = /* @__PURE__ */ new Set()) {
79
+ if (v === null || v === void 0)
80
+ return String(v);
81
+ const t = typeof v;
82
+ if (t === "string")
83
+ return v;
84
+ if (t === "number" || t === "boolean" || t === "bigint")
85
+ return String(v);
86
+ if (t === "symbol")
87
+ return v.toString();
88
+ if (t === "function")
89
+ return `[Function: ${v.name || "anonymous"}]`;
90
+ const obj = v;
91
+ if (seen.has(obj))
92
+ return "[Circular]";
93
+ if (v instanceof Error)
94
+ return v.stack || `${v.name}: ${v.message}`;
95
+ seen.add(obj);
96
+ if (v instanceof Map) {
97
+ const items = [...v.entries()].slice(0, 15).map(([k, val]) => `${formatValue(k, seen)} => ${formatValue(val, seen)}`);
98
+ return `Map(${v.size}) {${items.join(", ")}${v.size > 15 ? ", \u2026" : ""}}`;
99
+ }
100
+ if (v instanceof Set) {
101
+ const items = [...v.values()].slice(0, 15).map((val) => formatValue(val, seen));
102
+ return `Set(${v.size}) {${items.join(", ")}${v.size > 15 ? ", \u2026" : ""}}`;
103
+ }
104
+ if (Array.isArray(v)) {
105
+ const items = v.slice(0, 30).map((val) => formatValue(val, seen));
106
+ return `[${items.join(", ")}${v.length > 30 ? ", \u2026" : ""}]`;
107
+ }
108
+ try {
109
+ return JSON.stringify(v);
110
+ } catch (e2) {
111
+ const entries = Object.entries(v).slice(0, 15);
112
+ const parts = entries.map(([k, val]) => `${k}: ${formatValue(val, seen)}`);
113
+ return `{${parts.join(", ")}}`;
114
+ }
115
+ }
116
+ function appStackFrames(stack, max) {
117
+ return stack.split("\n").map((l) => l.trim()).filter((l) => l.startsWith("at ") && !l.includes("node_modules") && !l.includes("<anonymous>")).slice(0, max);
118
+ }
74
119
  function compactUrl(url, search) {
75
120
  try {
76
121
  const u = new URL(url);
77
122
  if (search && u.search)
78
123
  return `${u.pathname}?${truncate(u.search.slice(1), search)}`;
79
124
  return u.pathname;
80
- } catch (e2) {
125
+ } catch (e3) {
81
126
  return truncate(url, 80);
82
127
  }
83
128
  }
@@ -249,7 +294,7 @@ function isApiUrl(url) {
249
294
  try {
250
295
  const u = new URL(url);
251
296
  return u.pathname.startsWith("/api") || u.pathname.includes("/graphql");
252
- } catch (e3) {
297
+ } catch (e4) {
253
298
  return false;
254
299
  }
255
300
  }
@@ -287,17 +332,12 @@ function compactErrors(errors) {
287
332
  for (const err of seen.values()) {
288
333
  lines.push(err.message);
289
334
  if (err.stack) {
290
- const frames = filterStack(err.stack);
291
- for (const frame of frames) {
292
- lines.push(` at ${frame}`);
293
- }
335
+ for (const frame of appStackFrames(err.stack, 5))
336
+ lines.push(` ${frame}`);
294
337
  }
295
338
  }
296
339
  return lines.join("\n");
297
340
  }
298
- function filterStack(stack) {
299
- return stack.split("\n").map((l) => l.trim()).filter((l) => l.startsWith("at ")).map((l) => l.slice(3)).filter((l) => !l.includes("node_modules") && !l.includes("<anonymous>")).slice(0, 5);
300
- }
301
341
  function compactState(state) {
302
342
  if (!state || !Object.keys(state).length)
303
343
  return "";
@@ -306,7 +346,7 @@ function compactState(state) {
306
346
  lines.push(`${name}:`);
307
347
  if (typeof value === "object" && value !== null) {
308
348
  for (const [k, v] of Object.entries(value)) {
309
- lines.push(` ${k}: ${formatValue(v)}`);
349
+ lines.push(` ${k}: ${truncate(formatValue(v), 120)}`);
310
350
  }
311
351
  } else {
312
352
  lines.push(` ${String(value)}`);
@@ -314,19 +354,6 @@ function compactState(state) {
314
354
  }
315
355
  return lines.join("\n");
316
356
  }
317
- function formatValue(v) {
318
- if (v === null || v === void 0)
319
- return String(v);
320
- if (typeof v === "string")
321
- return v;
322
- if (typeof v === "number" || typeof v === "boolean")
323
- return String(v);
324
- if (typeof v === "object") {
325
- const s = JSON.stringify(v);
326
- return s.length > 120 ? `${s.slice(0, 120)}\u2026` : s;
327
- }
328
- return String(v);
329
- }
330
357
  function compact(raw) {
331
358
  return {
332
359
  url: raw.url,
@@ -463,12 +490,10 @@ function detailError(errors, index, full) {
463
490
  }
464
491
  const lines = [err.message];
465
492
  if (err.stack) {
466
- const appFrames = err.stack.split("\n").map((l) => l.trim()).filter((l) => l.startsWith("at ") && !l.includes("node_modules")).slice(0, 3);
467
- if (appFrames.length)
468
- lines.push(...appFrames);
469
- const totalApp = err.stack.split("\n").filter((l) => l.trim().startsWith("at ") && !l.includes("node_modules")).length;
470
- if (totalApp > 3)
471
- lines.push(` ... ${totalApp - 3} more app frames`);
493
+ const all = appStackFrames(err.stack, Infinity);
494
+ lines.push(...all.slice(0, 3));
495
+ if (all.length > 3)
496
+ lines.push(` ... ${all.length - 3} more app frames`);
472
497
  }
473
498
  if (err.line != null)
474
499
  lines.push(`location: ${err.source || ""}:${err.line}:${_nullishCoalesce(err.column, () => ( 0))}`);
@@ -486,9 +511,9 @@ function detailState(state, name, full) {
486
511
  const value = state[name];
487
512
  if (full) {
488
513
  try {
489
- return JSON.stringify(value, null, 2);
490
- } catch (e4) {
491
- return String(value);
514
+ return _nullishCoalesce(JSON.stringify(value, null, 2), () => ( formatValue(value)));
515
+ } catch (e5) {
516
+ return formatValue(value);
492
517
  }
493
518
  }
494
519
  if (typeof value !== "object" || value === null)
@@ -506,25 +531,14 @@ function isArraySentinel(v) {
506
531
  return digits.length > 0 && [...digits].every((c) => c >= "0" && c <= "9");
507
532
  }
508
533
  function formatSummaryValue(v) {
509
- if (v === null || v === void 0)
510
- return String(v);
511
- if (typeof v === "string") {
512
- if (isArraySentinel(v))
513
- return v;
514
- return v.length > 80 ? `${v.slice(0, 80)}\u2026` : v;
515
- }
516
- if (typeof v === "number" || typeof v === "boolean")
517
- return String(v);
518
- if (typeof v === "object") {
519
- const s = JSON.stringify(v);
520
- return s.length > 80 ? `${s.slice(0, 80)}\u2026` : s;
521
- }
522
- return String(v);
534
+ if (typeof v === "string" && isArraySentinel(v))
535
+ return v;
536
+ return truncate(formatValue(v), 80);
523
537
  }
524
538
  function jsonSchema(sample) {
525
539
  try {
526
540
  return schemaOf(JSON.parse(sample), 0);
527
- } catch (e5) {
541
+ } catch (e6) {
528
542
  return null;
529
543
  }
530
544
  }
@@ -753,6 +767,10 @@ function readBody(req) {
753
767
  req.on("end", () => resolve2(s));
754
768
  });
755
769
  }
770
+ function send(res, status, body) {
771
+ res.writeHead(status, { "Content-Type": "text/plain; charset=utf-8" });
772
+ res.end(body);
773
+ }
756
774
  var __dirname = _path.dirname.call(void 0, _url.fileURLToPath.call(void 0, _chunkZ2Y65YOYcjs.importMetaUrl));
757
775
  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
776
  var clientPath = _path.resolve.call(void 0, clientDir, "client.ts");
@@ -787,8 +805,12 @@ curl ${base}/console # console logs (errors, warnings, info)
787
805
  curl ${base}/network # fetch/XHR requests with status and timing
788
806
  curl ${base}/errors # uncaught errors and unhandled rejections
789
807
  curl ${base}/state # registered store snapshots
808
+ curl '${base}/query?sel=[role=menuitem]' # this selector right now: count + each element's text/visible/attrs
790
809
  \`\`\`
791
810
 
811
+ \`/query\` is the read-side twin of click/fill's \`sel=\` \u2014 assert on a specific element
812
+ (how many match, its text, \`data-state\`, \`aria-*\`/\`data-*\`, value, disabled) without \`/eval\`.
813
+
792
814
  \`/screen\` projects the whole UI to a few state variables \u2014 start there, not \`/ui\`. Append
793
815
  \`?full\` for untruncated output. Append \`/{index}\` for a specific item's detail.
794
816
 
@@ -825,35 +847,34 @@ curl -X POST ${base}/chain -d '[
825
847
  \`\`\`
826
848
 
827
849
  **Escape hatch.** \`curl '${base}/eval?code=...'\` (or POST the code as the body) runs arbitrary
828
- JS in the page and returns the result \u2014 for anything the typed endpoints can't do.
850
+ JS in the page and returns the result \u2014 for what the typed endpoints can't do (install listeners,
851
+ read closures, probe event flow). For count/text/state/attr assertions reach for \`/query\` first.
829
852
 
830
853
  aipeek auto-detects errors after HMR and prints them to the terminal \u2014 watch for \`[aipeek]\` messages.
831
854
  `;
832
855
  }
833
856
  var START_TAG = "<!-- AIPEEK:START -->";
834
857
  var END_TAG = "<!-- AIPEEK:END -->";
835
- function injectClaudeMd(root, port) {
836
- const path = _path.resolve.call(void 0, root, "CLAUDE.md");
858
+ function renderClaudeMd(existing, port) {
837
859
  const block = `${START_TAG}
838
860
  ${aipeekSnippet(port).trim()}
839
861
  ${END_TAG}
840
862
  `;
863
+ if (existing === null)
864
+ return block;
865
+ const si = existing.indexOf(START_TAG);
866
+ const ei = existing.indexOf(END_TAG);
867
+ if (si !== -1 && ei !== -1)
868
+ return existing.slice(0, si) + block.trimEnd() + existing.slice(ei + END_TAG.length);
869
+ const sep = existing.endsWith("\n") ? "" : "\n";
870
+ return `${existing}${sep}
871
+ ${block}`;
872
+ }
873
+ function injectClaudeMd(root, port) {
874
+ const path = _path.resolve.call(void 0, root, "CLAUDE.md");
841
875
  try {
842
- if (!_fs.existsSync.call(void 0, path)) {
843
- _fs.writeFileSync.call(void 0, path, block);
844
- return;
845
- }
846
- const content = _fs.readFileSync.call(void 0, path, "utf-8");
847
- const si = content.indexOf(START_TAG);
848
- const ei = content.indexOf(END_TAG);
849
- if (si !== -1 && ei !== -1) {
850
- _fs.writeFileSync.call(void 0, path, content.slice(0, si) + block.trimEnd() + content.slice(ei + END_TAG.length));
851
- return;
852
- }
853
- const sep = content.endsWith("\n") ? "" : "\n";
854
- _fs.writeFileSync.call(void 0, path, `${content}${sep}
855
- ${block}`);
856
- } catch (e6) {
876
+ _fs.writeFileSync.call(void 0, path, renderClaudeMd(_fs.existsSync.call(void 0, path) ? _fs.readFileSync.call(void 0, path, "utf-8") : null, port));
877
+ } catch (e7) {
857
878
  }
858
879
  }
859
880
  function aipeekPlugin() {
@@ -863,6 +884,27 @@ function aipeekPlugin() {
863
884
  let pushTimer;
864
885
  const pendingActions = /* @__PURE__ */ new Map();
865
886
  let actionId = 0;
887
+ const cdpQueue = [];
888
+ let cdpWaiter = null;
889
+ const cdpResults = /* @__PURE__ */ new Map();
890
+ let cdpId = 0;
891
+ function runCdpClick(x, y, button) {
892
+ const id = ++cdpId;
893
+ const cmd = { id, x, y, button };
894
+ return new Promise((resolve2, reject) => {
895
+ cdpResults.set(id, resolve2);
896
+ if (cdpWaiter) {
897
+ cdpWaiter(cmd);
898
+ cdpWaiter = null;
899
+ } else {
900
+ cdpQueue.push(cmd);
901
+ }
902
+ setTimeout(() => {
903
+ if (cdpResults.delete(id))
904
+ reject(new Error("cdp timeout: no extension result within 10s (is the aipeek extension loaded and the debugger attached?)"));
905
+ }, 1e4);
906
+ });
907
+ }
866
908
  let pendingDom = null;
867
909
  let pendingScreen = null;
868
910
  const pendingEvals = /* @__PURE__ */ new Map();
@@ -923,6 +965,18 @@ function aipeekPlugin() {
923
965
  };
924
966
  }, fullMs);
925
967
  }
968
+ async function runAction(type, args) {
969
+ const result = await sendAction(type, args);
970
+ lastRaw = null;
971
+ if (type === "realclick" && result.ok && result.ui === void 0) {
972
+ const cdp = await runCdpClick(result.x, result.y, _nullishCoalesce(args.button, () => ( "left")));
973
+ if (!cdp.ok)
974
+ return { ok: false, error: `cdp click failed: ${_nullishCoalesce(cdp.error, () => ( "unknown"))}` };
975
+ result.detail = `${result.detail} \u2192 clicked via extension`;
976
+ result.ui = await collectScreenFromClient();
977
+ }
978
+ return result;
979
+ }
926
980
  function evalInClient(code) {
927
981
  const id = ++evalId;
928
982
  return twoPhase("aipeek:eval", { id, code }, (resolve2) => {
@@ -1001,7 +1055,7 @@ function aipeekPlugin() {
1001
1055
  if (msg)
1002
1056
  server.config.logger.warn(msg);
1003
1057
  }
1004
- } catch (e7) {
1058
+ } catch (e8) {
1005
1059
  }
1006
1060
  }, 500);
1007
1061
  });
@@ -1015,13 +1069,11 @@ function aipeekPlugin() {
1015
1069
  if (!code && req.method === "POST")
1016
1070
  code = await readBody(req);
1017
1071
  if (!code) {
1018
- res.writeHead(400, { "Content-Type": "text/plain; charset=utf-8" });
1019
- res.end("eval needs ?code= or a POST body");
1072
+ send(res, 400, "eval needs ?code= or a POST body");
1020
1073
  return;
1021
1074
  }
1022
1075
  const r = await evalInClient(code);
1023
- res.writeHead(r.ok ? 200 : 422, { "Content-Type": "text/plain; charset=utf-8" });
1024
- res.end(r.ok ? _nullishCoalesce(r.value, () => ( "undefined")) : `error: ${r.error}`);
1076
+ send(res, r.ok ? 200 : 422, r.ok ? _nullishCoalesce(r.value, () => ( "undefined")) : `error: ${r.error}`);
1025
1077
  return;
1026
1078
  }
1027
1079
  if (parts[0] === "dom") {
@@ -1029,14 +1081,50 @@ function aipeekPlugin() {
1029
1081
  url.searchParams.get("scope") || void 0,
1030
1082
  url.searchParams.get("sel") || void 0
1031
1083
  );
1032
- res.writeHead(200, { "Content-Type": "text/plain; charset=utf-8" });
1033
- res.end(dom || "(empty)");
1084
+ send(res, 200, dom || "(empty)");
1034
1085
  return;
1035
1086
  }
1036
1087
  if (parts[0] === "screen") {
1037
1088
  const screen = await collectScreenFromClient();
1038
- res.writeHead(200, { "Content-Type": "text/plain; charset=utf-8" });
1039
- res.end(screen || "(empty)");
1089
+ send(res, 200, screen || "(empty)");
1090
+ return;
1091
+ }
1092
+ if (parts[0] === "cdp" && parts[1] === "poll") {
1093
+ const queued = cdpQueue.shift();
1094
+ if (queued) {
1095
+ send(res, 200, JSON.stringify(queued));
1096
+ return;
1097
+ }
1098
+ const cmd = await new Promise((resolve2) => {
1099
+ cdpWaiter = resolve2;
1100
+ setTimeout(() => {
1101
+ if (cdpWaiter === resolve2) {
1102
+ cdpWaiter = null;
1103
+ resolve2(null);
1104
+ }
1105
+ }, 25e3);
1106
+ });
1107
+ if (cmd)
1108
+ send(res, 200, JSON.stringify(cmd));
1109
+ else
1110
+ send(res, 204, "");
1111
+ return;
1112
+ }
1113
+ if (parts[0] === "cdp" && parts[1] === "result") {
1114
+ const body = await readBody(req);
1115
+ let data;
1116
+ try {
1117
+ data = JSON.parse(body);
1118
+ } catch (e9) {
1119
+ send(res, 400, "cdp/result needs a JSON body {id, ok, error?}");
1120
+ return;
1121
+ }
1122
+ const resolveCdp = cdpResults.get(data.id);
1123
+ if (resolveCdp) {
1124
+ cdpResults.delete(data.id);
1125
+ resolveCdp({ ok: data.ok, error: data.error });
1126
+ }
1127
+ send(res, 200, "ok");
1040
1128
  return;
1041
1129
  }
1042
1130
  if (parts[0] === "chain") {
@@ -1047,8 +1135,7 @@ function aipeekPlugin() {
1047
1135
  if (!Array.isArray(steps))
1048
1136
  throw new Error("body must be a JSON array");
1049
1137
  } catch (e) {
1050
- res.writeHead(400, { "Content-Type": "text/plain; charset=utf-8" });
1051
- res.end(`invalid chain body: ${e instanceof Error ? e.message : String(e)}`);
1138
+ send(res, 400, `invalid chain body: ${e instanceof Error ? e.message : String(e)}`);
1052
1139
  return;
1053
1140
  }
1054
1141
  lastRaw = null;
@@ -1063,7 +1150,7 @@ function aipeekPlugin() {
1063
1150
  allOk = false;
1064
1151
  break;
1065
1152
  }
1066
- const r = await sendAction(type, args);
1153
+ const r = await runAction(type, args);
1067
1154
  lines.push(`[${i}] ${r.ok ? "\u2713" : "\u2717"} ${type}: ${r.ok ? r.detail || "ok" : r.error}`);
1068
1155
  if (r.screen)
1069
1156
  lines.push(r.screen.split("\n").map((l) => ` ${l}`).join("\n"));
@@ -1074,14 +1161,13 @@ function aipeekPlugin() {
1074
1161
  break;
1075
1162
  }
1076
1163
  }
1077
- res.writeHead(allOk ? 200 : 422, { "Content-Type": "text/plain; charset=utf-8" });
1078
- res.end(lastUi ? `${lines.join("\n")}
1164
+ send(res, allOk ? 200 : 422, lastUi ? `${lines.join("\n")}
1079
1165
 
1080
1166
  --- ui after ---
1081
1167
  ${lastUi}` : lines.join("\n"));
1082
1168
  return;
1083
1169
  }
1084
- if (["click", "fill", "press", "wait", "screenshot"].includes(parts[0])) {
1170
+ if (["click", "fill", "press", "wait", "screenshot", "realclick", "query"].includes(parts[0])) {
1085
1171
  const q = url.searchParams;
1086
1172
  const args = {
1087
1173
  sel: q.get("sel") || void 0,
@@ -1089,31 +1175,30 @@ ${lastUi}` : lines.join("\n"));
1089
1175
  value: q.has("value") ? q.get("value") : void 0,
1090
1176
  key: q.get("key") || void 0,
1091
1177
  timeout: q.has("timeout") ? Number(q.get("timeout")) : void 0,
1092
- gone: q.has("gone") ? q.get("gone") !== "false" : void 0
1178
+ gone: q.has("gone") ? q.get("gone") !== "false" : void 0,
1179
+ button: q.get("button") === "right" ? "right" : q.get("button") === "left" ? "left" : void 0,
1180
+ x: q.has("x") ? Number(q.get("x")) : void 0,
1181
+ y: q.has("y") ? Number(q.get("y")) : void 0
1093
1182
  };
1094
1183
  const check2 = resolveAction(parts[0], args);
1095
1184
  if (!check2.valid) {
1096
- res.writeHead(400, { "Content-Type": "text/plain; charset=utf-8" });
1097
- res.end(check2.error);
1185
+ send(res, 400, _nullishCoalesce(check2.error, () => ( "invalid action")));
1098
1186
  return;
1099
1187
  }
1100
- const result = await sendAction(parts[0], args);
1101
- lastRaw = null;
1188
+ const result = await runAction(parts[0], args);
1102
1189
  if (parts[0] === "screenshot" && result.dataUrl) {
1103
1190
  const dir = _path.resolve.call(void 0, server.config.root, ".aipeek");
1104
1191
  _fs.mkdirSync.call(void 0, dir, { recursive: true });
1105
1192
  const name = q.get("out") || `shot-${result.dataUrl.length}.png`;
1106
1193
  const file = _path.resolve.call(void 0, dir, name);
1107
1194
  _fs.writeFileSync.call(void 0, file, _buffer.Buffer.from(result.dataUrl.split(",")[1], "base64"));
1108
- res.writeHead(200, { "Content-Type": "text/plain; charset=utf-8" });
1109
- res.end(`saved: ${file}`);
1195
+ send(res, 200, `saved: ${file}`);
1110
1196
  return;
1111
1197
  }
1112
- res.writeHead(result.ok ? 200 : 422, { "Content-Type": "text/plain; charset=utf-8" });
1113
1198
  const head = result.ok ? result.detail || "ok" : `${result.error}${result.detail ? `
1114
1199
 
1115
1200
  clickable: ${result.detail}` : ""}`;
1116
- res.end(result.ui ? `${head}
1201
+ send(res, result.ok ? 200 : 422, result.ui ? `${head}
1117
1202
 
1118
1203
  --- ui after ---
1119
1204
  ${result.ui}` : head);
@@ -1124,8 +1209,7 @@ ${result.ui}` : head);
1124
1209
  lastRaw = raw2;
1125
1210
  const result = check(raw2);
1126
1211
  const output = emitCheck(result);
1127
- res.writeHead(result.pass ? 200 : 417, { "Content-Type": "text/plain; charset=utf-8" });
1128
- res.end(output);
1212
+ send(res, result.pass ? 200 : 417, output);
1129
1213
  return;
1130
1214
  }
1131
1215
  if (parts.length >= 1) {
@@ -1133,29 +1217,22 @@ ${result.ui}` : head);
1133
1217
  lastRaw = await collectFromClient();
1134
1218
  const result = detail(lastRaw, parts[0], parts[1], full);
1135
1219
  if (result !== null) {
1136
- res.writeHead(200, { "Content-Type": "text/plain; charset=utf-8" });
1137
- res.end(result);
1220
+ send(res, 200, result);
1138
1221
  return;
1139
1222
  }
1140
- res.writeHead(404, { "Content-Type": "text/plain" });
1141
- res.end(`not found: ${parts.join("/")}`);
1223
+ send(res, 404, `not found: ${parts.join("/")}`);
1142
1224
  return;
1143
1225
  }
1144
1226
  const raw = await collectFromClient();
1145
1227
  lastRaw = raw;
1146
1228
  if (full) {
1147
1229
  const compacted = compact(raw);
1148
- const output = emit(compacted);
1149
- res.writeHead(200, { "Content-Type": "text/plain; charset=utf-8" });
1150
- res.end(output);
1230
+ send(res, 200, emit(compacted));
1151
1231
  } else {
1152
- const output = emitSummary(raw);
1153
- res.writeHead(200, { "Content-Type": "text/plain; charset=utf-8" });
1154
- res.end(output);
1232
+ send(res, 200, emitSummary(raw));
1155
1233
  }
1156
1234
  } catch (err) {
1157
- res.writeHead(504, { "Content-Type": "text/plain" });
1158
- res.end(err instanceof Error ? err.message : "unknown error");
1235
+ send(res, 504, err instanceof Error ? err.message : "unknown error");
1159
1236
  }
1160
1237
  });
1161
1238
  }
@@ -1172,4 +1249,5 @@ ${result.ui}` : head);
1172
1249
 
1173
1250
 
1174
1251
 
1175
- exports.check = check; exports.diffState = diffState; exports.emitSummary = emitSummary; exports.emitCheck = emitCheck; exports.emitDiff = emitDiff; exports.START_TAG = START_TAG; exports.END_TAG = END_TAG; exports.injectClaudeMd = injectClaudeMd; exports.aipeekPlugin = aipeekPlugin;
1252
+
1253
+ exports.check = check; exports.diffState = diffState; exports.emitSummary = emitSummary; exports.emitCheck = emitCheck; exports.emitDiff = emitDiff; exports.START_TAG = START_TAG; exports.END_TAG = END_TAG; exports.renderClaudeMd = renderClaudeMd; exports.injectClaudeMd = injectClaudeMd; exports.aipeekPlugin = aipeekPlugin;
package/dist/index.cjs CHANGED
@@ -5,7 +5,7 @@
5
5
 
6
6
 
7
7
 
8
- var _chunk6EZKMGRDcjs = require('./chunk-6EZKMGRD.cjs');
8
+ var _chunkSTYCUT23cjs = require('./chunk-STYCUT23.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 = _chunk6EZKMGRDcjs.aipeekPlugin; exports.check = _chunk6EZKMGRDcjs.check; exports.diffState = _chunk6EZKMGRDcjs.diffState; exports.emitCheck = _chunk6EZKMGRDcjs.emitCheck; exports.emitDiff = _chunk6EZKMGRDcjs.emitDiff; exports.emitSummary = _chunk6EZKMGRDcjs.emitSummary;
17
+ exports.aipeekPlugin = _chunkSTYCUT23cjs.aipeekPlugin; exports.check = _chunkSTYCUT23cjs.check; exports.diffState = _chunkSTYCUT23cjs.diffState; exports.emitCheck = _chunkSTYCUT23cjs.emitCheck; exports.emitDiff = _chunkSTYCUT23cjs.emitDiff; exports.emitSummary = _chunkSTYCUT23cjs.emitSummary;
package/dist/index.js CHANGED
@@ -5,7 +5,7 @@ import {
5
5
  emitCheck,
6
6
  emitDiff,
7
7
  emitSummary
8
- } from "./chunk-X3HAXWFJ.js";
8
+ } from "./chunk-37VLLZIU.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 _chunk6EZKMGRDcjs = require('./chunk-6EZKMGRD.cjs');
6
+
7
+ var _chunkSTYCUT23cjs = require('./chunk-STYCUT23.cjs');
7
8
  require('./chunk-Z2Y65YOY.cjs');
8
9
 
9
10
 
10
11
 
11
12
 
12
13
 
13
- exports.END_TAG = _chunk6EZKMGRDcjs.END_TAG; exports.START_TAG = _chunk6EZKMGRDcjs.START_TAG; exports.aipeekPlugin = _chunk6EZKMGRDcjs.aipeekPlugin; exports.injectClaudeMd = _chunk6EZKMGRDcjs.injectClaudeMd;
14
+
15
+ exports.END_TAG = _chunkSTYCUT23cjs.END_TAG; exports.START_TAG = _chunkSTYCUT23cjs.START_TAG; exports.aipeekPlugin = _chunkSTYCUT23cjs.aipeekPlugin; exports.injectClaudeMd = _chunkSTYCUT23cjs.injectClaudeMd; exports.renderClaudeMd = _chunkSTYCUT23cjs.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-X3HAXWFJ.js";
5
+ injectClaudeMd,
6
+ renderClaudeMd
7
+ } from "./chunk-37VLLZIU.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.5",
3
+ "version": "0.2.7",
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": {
@@ -42,6 +42,7 @@
42
42
  },
43
43
  "devDependencies": {
44
44
  "jsdom": "^29.1.1",
45
+ "teact": "file:../teact",
45
46
  "tsup": "^8.4.0",
46
47
  "typescript": "~5.7.2",
47
48
  "vitest": "^3.1.2"
@@ -39,7 +39,7 @@ for (const level of ['log', 'info', 'warn', 'error', 'debug'] as const) {
39
39
  console[level] = (...args: unknown[]) => {
40
40
  pushBounded(consoleLogs, {
41
41
  level,
42
- text: args.map(a => typeof a === 'string' ? a : tryStringify(a)).join(' '),
42
+ text: args.map(a => typeof a === 'string' ? a : formatValue(a)).join(' '),
43
43
  timestamp: Date.now(),
44
44
  }, MAX_CONSOLE)
45
45
  orig.apply(console, args)
@@ -302,11 +302,43 @@ function sampleOf(v: unknown, d: number): unknown {
302
302
  return null
303
303
  }
304
304
 
305
- function tryStringify(v: unknown): string {
305
+ // client-patch 是自包含内联脚本(esbuild transformSync,无模块解析)——不能 import core/util。
306
+ // 这是 core/util.ts formatValue 的镜像;两处契约由各自单测守,client 侧靠运行时验证。
307
+ function formatValue(v: unknown, seen: Set<object> = new Set()): string {
308
+ if (v === null || v === undefined)
309
+ return String(v)
310
+ const t = typeof v
311
+ if (t === 'string')
312
+ return v as string
313
+ if (t === 'number' || t === 'boolean' || t === 'bigint')
314
+ return String(v)
315
+ if (t === 'symbol')
316
+ return (v as symbol).toString()
317
+ if (t === 'function')
318
+ return `[Function: ${(v as { name?: string }).name || 'anonymous'}]`
319
+ const obj = v as object
320
+ if (seen.has(obj))
321
+ return '[Circular]'
322
+ if (v instanceof Error)
323
+ return v.stack || `${v.name}: ${v.message}`
324
+ seen.add(obj)
325
+ if (v instanceof Map) {
326
+ const items = [...v.entries()].slice(0, 15).map(([k, val]) => `${formatValue(k, seen)} => ${formatValue(val, seen)}`)
327
+ return `Map(${v.size}) {${items.join(', ')}${v.size > 15 ? ', …' : ''}}`
328
+ }
329
+ if (v instanceof Set) {
330
+ const items = [...v.values()].slice(0, 15).map(val => formatValue(val, seen))
331
+ return `Set(${v.size}) {${items.join(', ')}${v.size > 15 ? ', …' : ''}}`
332
+ }
333
+ if (Array.isArray(v)) {
334
+ const items = v.slice(0, 30).map(val => formatValue(val, seen))
335
+ return `[${items.join(', ')}${v.length > 30 ? ', …' : ''}]`
336
+ }
306
337
  try {
307
338
  return JSON.stringify(v)
308
339
  }
309
340
  catch {
310
- return String(v)
341
+ const entries = Object.entries(v as Record<string, unknown>).slice(0, 15)
342
+ return `{${entries.map(([k, val]) => `${k}: ${formatValue(val, seen)}`).join(', ')}}`
311
343
  }
312
344
  }