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.
- package/README.md +2 -1
- package/dist/{chunk-X3HAXWFJ.js → chunk-37VLLZIU.js} +176 -98
- package/dist/{chunk-6EZKMGRD.cjs → chunk-STYCUT23.cjs} +183 -105
- package/dist/index.cjs +2 -2
- package/dist/index.js +1 -1
- package/dist/plugin.cjs +4 -2
- package/dist/plugin.js +5 -3
- package/package.json +2 -1
- package/src/client/client-patch.ts +35 -3
- package/src/client/client.ts +19 -18
- package/src/core/action.ts +136 -4
- package/src/core/compact.ts +4 -30
- package/src/core/detail.ts +10 -26
- package/src/core/util.ts +53 -0
- package/src/server/plugin.ts +146 -56
|
@@ -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 (
|
|
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 (
|
|
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
|
|
291
|
-
|
|
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
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
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 (
|
|
491
|
-
return
|
|
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 ===
|
|
510
|
-
return
|
|
511
|
-
|
|
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 (
|
|
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
|
|
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
|
|
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
|
-
|
|
843
|
-
|
|
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 (
|
|
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
|
|
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
|
|
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
|
|
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
|
|
1039
|
-
|
|
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
|
|
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
|
|
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
|
|
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
|
|
1097
|
-
res.end(check2.error);
|
|
1185
|
+
send(res, 400, _nullishCoalesce(check2.error, () => ( "invalid action")));
|
|
1098
1186
|
return;
|
|
1099
1187
|
}
|
|
1100
|
-
const result = await
|
|
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
|
|
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.
|
|
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
|
|
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
|
|
1137
|
-
res.end(result);
|
|
1220
|
+
send(res, 200, result);
|
|
1138
1221
|
return;
|
|
1139
1222
|
}
|
|
1140
|
-
res
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
|
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 =
|
|
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
package/dist/plugin.cjs
CHANGED
|
@@ -3,11 +3,13 @@
|
|
|
3
3
|
|
|
4
4
|
|
|
5
5
|
|
|
6
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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.
|
|
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 :
|
|
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
|
-
|
|
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
|
-
|
|
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
|
}
|