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
package/README.md
CHANGED
|
@@ -39,9 +39,10 @@ All endpoints are available on your Vite dev server:
|
|
|
39
39
|
| `GET /__aipeek/{section}/{index}` | Detail for a specific item in a section |
|
|
40
40
|
| `GET /__aipeek/{section}?full` | Full detail (no truncation) |
|
|
41
41
|
| `GET /__aipeek/dom[?scope=Name\|?sel=css]` | Semantic DOM — UI as text (see below) |
|
|
42
|
+
| `GET /__aipeek/query?sel=css` | Read-side twin of `sel=`: a selector's live `count` + each match's `text`/`visible`/`attrs` (role, `data-state`, `aria-*`/`data-*`, value, disabled). Per-element assertions without `/eval`. |
|
|
42
43
|
| `GET /__aipeek/{action}?...` | Drive the page (see Actions) |
|
|
43
44
|
| `POST /__aipeek/chain` | Run a JSON array of actions in one round-trip (see Actions) |
|
|
44
|
-
| `GET\|POST /__aipeek/eval` | Run arbitrary JS in the page (`?code=` or POST body); returns the result. Escape hatch for what typed endpoints can't do. |
|
|
45
|
+
| `GET\|POST /__aipeek/eval` | Run arbitrary JS in the page (`?code=` or POST body); returns the result. Escape hatch for what typed endpoints can't do — for count/text/state/attr checks reach for `/query` first. |
|
|
45
46
|
|
|
46
47
|
### Perception layers — UI as text, not pixels
|
|
47
48
|
|
|
@@ -6,7 +6,7 @@ import { fileURLToPath } from "url";
|
|
|
6
6
|
import { transformSync } from "esbuild";
|
|
7
7
|
|
|
8
8
|
// src/core/action.ts
|
|
9
|
-
var TYPES = ["click", "fill", "press", "wait", "screenshot"];
|
|
9
|
+
var TYPES = ["click", "fill", "press", "wait", "screenshot", "realclick", "query"];
|
|
10
10
|
function resolveAction(type, args) {
|
|
11
11
|
if (!TYPES.includes(type))
|
|
12
12
|
return { valid: false, error: `unknown action: ${type}` };
|
|
@@ -14,6 +14,8 @@ function resolveAction(type, args) {
|
|
|
14
14
|
switch (type) {
|
|
15
15
|
case "click":
|
|
16
16
|
return hasTarget ? { valid: true } : { valid: false, error: "click needs sel= or text=" };
|
|
17
|
+
case "realclick":
|
|
18
|
+
return hasTarget || args.x !== void 0 && args.y !== void 0 ? { valid: true } : { valid: false, error: "realclick needs sel=, text=, or x= & y=" };
|
|
17
19
|
case "fill":
|
|
18
20
|
if (!hasTarget)
|
|
19
21
|
return { valid: false, error: "fill needs sel= or text=" };
|
|
@@ -26,6 +28,8 @@ function resolveAction(type, args) {
|
|
|
26
28
|
return hasTarget ? { valid: true } : { valid: false, error: "wait needs sel= or text=" };
|
|
27
29
|
case "screenshot":
|
|
28
30
|
return { valid: true };
|
|
31
|
+
case "query":
|
|
32
|
+
return args.sel ? { valid: true } : { valid: false, error: "query needs sel=" };
|
|
29
33
|
default:
|
|
30
34
|
return { valid: false, error: `unknown action: ${type}` };
|
|
31
35
|
}
|
|
@@ -67,6 +71,47 @@ function check(raw) {
|
|
|
67
71
|
function truncate(s, max) {
|
|
68
72
|
return s.length > max ? `${s.slice(0, max)}\u2026` : s;
|
|
69
73
|
}
|
|
74
|
+
function formatValue(v, seen = /* @__PURE__ */ new Set()) {
|
|
75
|
+
if (v === null || v === void 0)
|
|
76
|
+
return String(v);
|
|
77
|
+
const t = typeof v;
|
|
78
|
+
if (t === "string")
|
|
79
|
+
return v;
|
|
80
|
+
if (t === "number" || t === "boolean" || t === "bigint")
|
|
81
|
+
return String(v);
|
|
82
|
+
if (t === "symbol")
|
|
83
|
+
return v.toString();
|
|
84
|
+
if (t === "function")
|
|
85
|
+
return `[Function: ${v.name || "anonymous"}]`;
|
|
86
|
+
const obj = v;
|
|
87
|
+
if (seen.has(obj))
|
|
88
|
+
return "[Circular]";
|
|
89
|
+
if (v instanceof Error)
|
|
90
|
+
return v.stack || `${v.name}: ${v.message}`;
|
|
91
|
+
seen.add(obj);
|
|
92
|
+
if (v instanceof Map) {
|
|
93
|
+
const items = [...v.entries()].slice(0, 15).map(([k, val]) => `${formatValue(k, seen)} => ${formatValue(val, seen)}`);
|
|
94
|
+
return `Map(${v.size}) {${items.join(", ")}${v.size > 15 ? ", \u2026" : ""}}`;
|
|
95
|
+
}
|
|
96
|
+
if (v instanceof Set) {
|
|
97
|
+
const items = [...v.values()].slice(0, 15).map((val) => formatValue(val, seen));
|
|
98
|
+
return `Set(${v.size}) {${items.join(", ")}${v.size > 15 ? ", \u2026" : ""}}`;
|
|
99
|
+
}
|
|
100
|
+
if (Array.isArray(v)) {
|
|
101
|
+
const items = v.slice(0, 30).map((val) => formatValue(val, seen));
|
|
102
|
+
return `[${items.join(", ")}${v.length > 30 ? ", \u2026" : ""}]`;
|
|
103
|
+
}
|
|
104
|
+
try {
|
|
105
|
+
return JSON.stringify(v);
|
|
106
|
+
} catch {
|
|
107
|
+
const entries = Object.entries(v).slice(0, 15);
|
|
108
|
+
const parts = entries.map(([k, val]) => `${k}: ${formatValue(val, seen)}`);
|
|
109
|
+
return `{${parts.join(", ")}}`;
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
function appStackFrames(stack, max) {
|
|
113
|
+
return stack.split("\n").map((l) => l.trim()).filter((l) => l.startsWith("at ") && !l.includes("node_modules") && !l.includes("<anonymous>")).slice(0, max);
|
|
114
|
+
}
|
|
70
115
|
function compactUrl(url, search) {
|
|
71
116
|
try {
|
|
72
117
|
const u = new URL(url);
|
|
@@ -283,17 +328,12 @@ function compactErrors(errors) {
|
|
|
283
328
|
for (const err of seen.values()) {
|
|
284
329
|
lines.push(err.message);
|
|
285
330
|
if (err.stack) {
|
|
286
|
-
const
|
|
287
|
-
|
|
288
|
-
lines.push(` at ${frame}`);
|
|
289
|
-
}
|
|
331
|
+
for (const frame of appStackFrames(err.stack, 5))
|
|
332
|
+
lines.push(` ${frame}`);
|
|
290
333
|
}
|
|
291
334
|
}
|
|
292
335
|
return lines.join("\n");
|
|
293
336
|
}
|
|
294
|
-
function filterStack(stack) {
|
|
295
|
-
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);
|
|
296
|
-
}
|
|
297
337
|
function compactState(state) {
|
|
298
338
|
if (!state || !Object.keys(state).length)
|
|
299
339
|
return "";
|
|
@@ -302,7 +342,7 @@ function compactState(state) {
|
|
|
302
342
|
lines.push(`${name}:`);
|
|
303
343
|
if (typeof value === "object" && value !== null) {
|
|
304
344
|
for (const [k, v] of Object.entries(value)) {
|
|
305
|
-
lines.push(` ${k}: ${formatValue(v)}`);
|
|
345
|
+
lines.push(` ${k}: ${truncate(formatValue(v), 120)}`);
|
|
306
346
|
}
|
|
307
347
|
} else {
|
|
308
348
|
lines.push(` ${String(value)}`);
|
|
@@ -310,19 +350,6 @@ function compactState(state) {
|
|
|
310
350
|
}
|
|
311
351
|
return lines.join("\n");
|
|
312
352
|
}
|
|
313
|
-
function formatValue(v) {
|
|
314
|
-
if (v === null || v === void 0)
|
|
315
|
-
return String(v);
|
|
316
|
-
if (typeof v === "string")
|
|
317
|
-
return v;
|
|
318
|
-
if (typeof v === "number" || typeof v === "boolean")
|
|
319
|
-
return String(v);
|
|
320
|
-
if (typeof v === "object") {
|
|
321
|
-
const s = JSON.stringify(v);
|
|
322
|
-
return s.length > 120 ? `${s.slice(0, 120)}\u2026` : s;
|
|
323
|
-
}
|
|
324
|
-
return String(v);
|
|
325
|
-
}
|
|
326
353
|
function compact(raw) {
|
|
327
354
|
return {
|
|
328
355
|
url: raw.url,
|
|
@@ -459,12 +486,10 @@ function detailError(errors, index, full) {
|
|
|
459
486
|
}
|
|
460
487
|
const lines = [err.message];
|
|
461
488
|
if (err.stack) {
|
|
462
|
-
const
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
if (totalApp > 3)
|
|
467
|
-
lines.push(` ... ${totalApp - 3} more app frames`);
|
|
489
|
+
const all = appStackFrames(err.stack, Infinity);
|
|
490
|
+
lines.push(...all.slice(0, 3));
|
|
491
|
+
if (all.length > 3)
|
|
492
|
+
lines.push(` ... ${all.length - 3} more app frames`);
|
|
468
493
|
}
|
|
469
494
|
if (err.line != null)
|
|
470
495
|
lines.push(`location: ${err.source || ""}:${err.line}:${err.column ?? 0}`);
|
|
@@ -482,9 +507,9 @@ function detailState(state, name, full) {
|
|
|
482
507
|
const value = state[name];
|
|
483
508
|
if (full) {
|
|
484
509
|
try {
|
|
485
|
-
return JSON.stringify(value, null, 2);
|
|
510
|
+
return JSON.stringify(value, null, 2) ?? formatValue(value);
|
|
486
511
|
} catch {
|
|
487
|
-
return
|
|
512
|
+
return formatValue(value);
|
|
488
513
|
}
|
|
489
514
|
}
|
|
490
515
|
if (typeof value !== "object" || value === null)
|
|
@@ -502,20 +527,9 @@ function isArraySentinel(v) {
|
|
|
502
527
|
return digits.length > 0 && [...digits].every((c) => c >= "0" && c <= "9");
|
|
503
528
|
}
|
|
504
529
|
function formatSummaryValue(v) {
|
|
505
|
-
if (v ===
|
|
506
|
-
return
|
|
507
|
-
|
|
508
|
-
if (isArraySentinel(v))
|
|
509
|
-
return v;
|
|
510
|
-
return v.length > 80 ? `${v.slice(0, 80)}\u2026` : v;
|
|
511
|
-
}
|
|
512
|
-
if (typeof v === "number" || typeof v === "boolean")
|
|
513
|
-
return String(v);
|
|
514
|
-
if (typeof v === "object") {
|
|
515
|
-
const s = JSON.stringify(v);
|
|
516
|
-
return s.length > 80 ? `${s.slice(0, 80)}\u2026` : s;
|
|
517
|
-
}
|
|
518
|
-
return String(v);
|
|
530
|
+
if (typeof v === "string" && isArraySentinel(v))
|
|
531
|
+
return v;
|
|
532
|
+
return truncate(formatValue(v), 80);
|
|
519
533
|
}
|
|
520
534
|
function jsonSchema(sample) {
|
|
521
535
|
try {
|
|
@@ -749,6 +763,10 @@ function readBody(req) {
|
|
|
749
763
|
req.on("end", () => resolve2(s));
|
|
750
764
|
});
|
|
751
765
|
}
|
|
766
|
+
function send(res, status, body) {
|
|
767
|
+
res.writeHead(status, { "Content-Type": "text/plain; charset=utf-8" });
|
|
768
|
+
res.end(body);
|
|
769
|
+
}
|
|
752
770
|
var __dirname2 = dirname(fileURLToPath(import.meta.url));
|
|
753
771
|
var clientDir = existsSync(resolve(__dirname2, "../client")) ? resolve(__dirname2, "../client") : resolve(__dirname2, "../src/client");
|
|
754
772
|
var clientPath = resolve(clientDir, "client.ts");
|
|
@@ -783,8 +801,12 @@ curl ${base}/console # console logs (errors, warnings, info)
|
|
|
783
801
|
curl ${base}/network # fetch/XHR requests with status and timing
|
|
784
802
|
curl ${base}/errors # uncaught errors and unhandled rejections
|
|
785
803
|
curl ${base}/state # registered store snapshots
|
|
804
|
+
curl '${base}/query?sel=[role=menuitem]' # this selector right now: count + each element's text/visible/attrs
|
|
786
805
|
\`\`\`
|
|
787
806
|
|
|
807
|
+
\`/query\` is the read-side twin of click/fill's \`sel=\` \u2014 assert on a specific element
|
|
808
|
+
(how many match, its text, \`data-state\`, \`aria-*\`/\`data-*\`, value, disabled) without \`/eval\`.
|
|
809
|
+
|
|
788
810
|
\`/screen\` projects the whole UI to a few state variables \u2014 start there, not \`/ui\`. Append
|
|
789
811
|
\`?full\` for untruncated output. Append \`/{index}\` for a specific item's detail.
|
|
790
812
|
|
|
@@ -821,34 +843,33 @@ curl -X POST ${base}/chain -d '[
|
|
|
821
843
|
\`\`\`
|
|
822
844
|
|
|
823
845
|
**Escape hatch.** \`curl '${base}/eval?code=...'\` (or POST the code as the body) runs arbitrary
|
|
824
|
-
JS in the page and returns the result \u2014 for
|
|
846
|
+
JS in the page and returns the result \u2014 for what the typed endpoints can't do (install listeners,
|
|
847
|
+
read closures, probe event flow). For count/text/state/attr assertions reach for \`/query\` first.
|
|
825
848
|
|
|
826
849
|
aipeek auto-detects errors after HMR and prints them to the terminal \u2014 watch for \`[aipeek]\` messages.
|
|
827
850
|
`;
|
|
828
851
|
}
|
|
829
852
|
var START_TAG = "<!-- AIPEEK:START -->";
|
|
830
853
|
var END_TAG = "<!-- AIPEEK:END -->";
|
|
831
|
-
function
|
|
832
|
-
const path = resolve(root, "CLAUDE.md");
|
|
854
|
+
function renderClaudeMd(existing, port) {
|
|
833
855
|
const block = `${START_TAG}
|
|
834
856
|
${aipeekSnippet(port).trim()}
|
|
835
857
|
${END_TAG}
|
|
836
858
|
`;
|
|
859
|
+
if (existing === null)
|
|
860
|
+
return block;
|
|
861
|
+
const si = existing.indexOf(START_TAG);
|
|
862
|
+
const ei = existing.indexOf(END_TAG);
|
|
863
|
+
if (si !== -1 && ei !== -1)
|
|
864
|
+
return existing.slice(0, si) + block.trimEnd() + existing.slice(ei + END_TAG.length);
|
|
865
|
+
const sep = existing.endsWith("\n") ? "" : "\n";
|
|
866
|
+
return `${existing}${sep}
|
|
867
|
+
${block}`;
|
|
868
|
+
}
|
|
869
|
+
function injectClaudeMd(root, port) {
|
|
870
|
+
const path = resolve(root, "CLAUDE.md");
|
|
837
871
|
try {
|
|
838
|
-
|
|
839
|
-
writeFileSync(path, block);
|
|
840
|
-
return;
|
|
841
|
-
}
|
|
842
|
-
const content = readFileSync(path, "utf-8");
|
|
843
|
-
const si = content.indexOf(START_TAG);
|
|
844
|
-
const ei = content.indexOf(END_TAG);
|
|
845
|
-
if (si !== -1 && ei !== -1) {
|
|
846
|
-
writeFileSync(path, content.slice(0, si) + block.trimEnd() + content.slice(ei + END_TAG.length));
|
|
847
|
-
return;
|
|
848
|
-
}
|
|
849
|
-
const sep = content.endsWith("\n") ? "" : "\n";
|
|
850
|
-
writeFileSync(path, `${content}${sep}
|
|
851
|
-
${block}`);
|
|
872
|
+
writeFileSync(path, renderClaudeMd(existsSync(path) ? readFileSync(path, "utf-8") : null, port));
|
|
852
873
|
} catch {
|
|
853
874
|
}
|
|
854
875
|
}
|
|
@@ -859,6 +880,27 @@ function aipeekPlugin() {
|
|
|
859
880
|
let pushTimer;
|
|
860
881
|
const pendingActions = /* @__PURE__ */ new Map();
|
|
861
882
|
let actionId = 0;
|
|
883
|
+
const cdpQueue = [];
|
|
884
|
+
let cdpWaiter = null;
|
|
885
|
+
const cdpResults = /* @__PURE__ */ new Map();
|
|
886
|
+
let cdpId = 0;
|
|
887
|
+
function runCdpClick(x, y, button) {
|
|
888
|
+
const id = ++cdpId;
|
|
889
|
+
const cmd = { id, x, y, button };
|
|
890
|
+
return new Promise((resolve2, reject) => {
|
|
891
|
+
cdpResults.set(id, resolve2);
|
|
892
|
+
if (cdpWaiter) {
|
|
893
|
+
cdpWaiter(cmd);
|
|
894
|
+
cdpWaiter = null;
|
|
895
|
+
} else {
|
|
896
|
+
cdpQueue.push(cmd);
|
|
897
|
+
}
|
|
898
|
+
setTimeout(() => {
|
|
899
|
+
if (cdpResults.delete(id))
|
|
900
|
+
reject(new Error("cdp timeout: no extension result within 10s (is the aipeek extension loaded and the debugger attached?)"));
|
|
901
|
+
}, 1e4);
|
|
902
|
+
});
|
|
903
|
+
}
|
|
862
904
|
let pendingDom = null;
|
|
863
905
|
let pendingScreen = null;
|
|
864
906
|
const pendingEvals = /* @__PURE__ */ new Map();
|
|
@@ -919,6 +961,18 @@ function aipeekPlugin() {
|
|
|
919
961
|
};
|
|
920
962
|
}, fullMs);
|
|
921
963
|
}
|
|
964
|
+
async function runAction(type, args) {
|
|
965
|
+
const result = await sendAction(type, args);
|
|
966
|
+
lastRaw = null;
|
|
967
|
+
if (type === "realclick" && result.ok && result.ui === void 0) {
|
|
968
|
+
const cdp = await runCdpClick(result.x, result.y, args.button ?? "left");
|
|
969
|
+
if (!cdp.ok)
|
|
970
|
+
return { ok: false, error: `cdp click failed: ${cdp.error ?? "unknown"}` };
|
|
971
|
+
result.detail = `${result.detail} \u2192 clicked via extension`;
|
|
972
|
+
result.ui = await collectScreenFromClient();
|
|
973
|
+
}
|
|
974
|
+
return result;
|
|
975
|
+
}
|
|
922
976
|
function evalInClient(code) {
|
|
923
977
|
const id = ++evalId;
|
|
924
978
|
return twoPhase("aipeek:eval", { id, code }, (resolve2) => {
|
|
@@ -1011,13 +1065,11 @@ function aipeekPlugin() {
|
|
|
1011
1065
|
if (!code && req.method === "POST")
|
|
1012
1066
|
code = await readBody(req);
|
|
1013
1067
|
if (!code) {
|
|
1014
|
-
res
|
|
1015
|
-
res.end("eval needs ?code= or a POST body");
|
|
1068
|
+
send(res, 400, "eval needs ?code= or a POST body");
|
|
1016
1069
|
return;
|
|
1017
1070
|
}
|
|
1018
1071
|
const r = await evalInClient(code);
|
|
1019
|
-
res
|
|
1020
|
-
res.end(r.ok ? r.value ?? "undefined" : `error: ${r.error}`);
|
|
1072
|
+
send(res, r.ok ? 200 : 422, r.ok ? r.value ?? "undefined" : `error: ${r.error}`);
|
|
1021
1073
|
return;
|
|
1022
1074
|
}
|
|
1023
1075
|
if (parts[0] === "dom") {
|
|
@@ -1025,14 +1077,50 @@ function aipeekPlugin() {
|
|
|
1025
1077
|
url.searchParams.get("scope") || void 0,
|
|
1026
1078
|
url.searchParams.get("sel") || void 0
|
|
1027
1079
|
);
|
|
1028
|
-
res
|
|
1029
|
-
res.end(dom || "(empty)");
|
|
1080
|
+
send(res, 200, dom || "(empty)");
|
|
1030
1081
|
return;
|
|
1031
1082
|
}
|
|
1032
1083
|
if (parts[0] === "screen") {
|
|
1033
1084
|
const screen = await collectScreenFromClient();
|
|
1034
|
-
res
|
|
1035
|
-
|
|
1085
|
+
send(res, 200, screen || "(empty)");
|
|
1086
|
+
return;
|
|
1087
|
+
}
|
|
1088
|
+
if (parts[0] === "cdp" && parts[1] === "poll") {
|
|
1089
|
+
const queued = cdpQueue.shift();
|
|
1090
|
+
if (queued) {
|
|
1091
|
+
send(res, 200, JSON.stringify(queued));
|
|
1092
|
+
return;
|
|
1093
|
+
}
|
|
1094
|
+
const cmd = await new Promise((resolve2) => {
|
|
1095
|
+
cdpWaiter = resolve2;
|
|
1096
|
+
setTimeout(() => {
|
|
1097
|
+
if (cdpWaiter === resolve2) {
|
|
1098
|
+
cdpWaiter = null;
|
|
1099
|
+
resolve2(null);
|
|
1100
|
+
}
|
|
1101
|
+
}, 25e3);
|
|
1102
|
+
});
|
|
1103
|
+
if (cmd)
|
|
1104
|
+
send(res, 200, JSON.stringify(cmd));
|
|
1105
|
+
else
|
|
1106
|
+
send(res, 204, "");
|
|
1107
|
+
return;
|
|
1108
|
+
}
|
|
1109
|
+
if (parts[0] === "cdp" && parts[1] === "result") {
|
|
1110
|
+
const body = await readBody(req);
|
|
1111
|
+
let data;
|
|
1112
|
+
try {
|
|
1113
|
+
data = JSON.parse(body);
|
|
1114
|
+
} catch {
|
|
1115
|
+
send(res, 400, "cdp/result needs a JSON body {id, ok, error?}");
|
|
1116
|
+
return;
|
|
1117
|
+
}
|
|
1118
|
+
const resolveCdp = cdpResults.get(data.id);
|
|
1119
|
+
if (resolveCdp) {
|
|
1120
|
+
cdpResults.delete(data.id);
|
|
1121
|
+
resolveCdp({ ok: data.ok, error: data.error });
|
|
1122
|
+
}
|
|
1123
|
+
send(res, 200, "ok");
|
|
1036
1124
|
return;
|
|
1037
1125
|
}
|
|
1038
1126
|
if (parts[0] === "chain") {
|
|
@@ -1043,8 +1131,7 @@ function aipeekPlugin() {
|
|
|
1043
1131
|
if (!Array.isArray(steps))
|
|
1044
1132
|
throw new Error("body must be a JSON array");
|
|
1045
1133
|
} catch (e) {
|
|
1046
|
-
res
|
|
1047
|
-
res.end(`invalid chain body: ${e instanceof Error ? e.message : String(e)}`);
|
|
1134
|
+
send(res, 400, `invalid chain body: ${e instanceof Error ? e.message : String(e)}`);
|
|
1048
1135
|
return;
|
|
1049
1136
|
}
|
|
1050
1137
|
lastRaw = null;
|
|
@@ -1059,7 +1146,7 @@ function aipeekPlugin() {
|
|
|
1059
1146
|
allOk = false;
|
|
1060
1147
|
break;
|
|
1061
1148
|
}
|
|
1062
|
-
const r = await
|
|
1149
|
+
const r = await runAction(type, args);
|
|
1063
1150
|
lines.push(`[${i}] ${r.ok ? "\u2713" : "\u2717"} ${type}: ${r.ok ? r.detail || "ok" : r.error}`);
|
|
1064
1151
|
if (r.screen)
|
|
1065
1152
|
lines.push(r.screen.split("\n").map((l) => ` ${l}`).join("\n"));
|
|
@@ -1070,14 +1157,13 @@ function aipeekPlugin() {
|
|
|
1070
1157
|
break;
|
|
1071
1158
|
}
|
|
1072
1159
|
}
|
|
1073
|
-
res
|
|
1074
|
-
res.end(lastUi ? `${lines.join("\n")}
|
|
1160
|
+
send(res, allOk ? 200 : 422, lastUi ? `${lines.join("\n")}
|
|
1075
1161
|
|
|
1076
1162
|
--- ui after ---
|
|
1077
1163
|
${lastUi}` : lines.join("\n"));
|
|
1078
1164
|
return;
|
|
1079
1165
|
}
|
|
1080
|
-
if (["click", "fill", "press", "wait", "screenshot"].includes(parts[0])) {
|
|
1166
|
+
if (["click", "fill", "press", "wait", "screenshot", "realclick", "query"].includes(parts[0])) {
|
|
1081
1167
|
const q = url.searchParams;
|
|
1082
1168
|
const args = {
|
|
1083
1169
|
sel: q.get("sel") || void 0,
|
|
@@ -1085,31 +1171,30 @@ ${lastUi}` : lines.join("\n"));
|
|
|
1085
1171
|
value: q.has("value") ? q.get("value") : void 0,
|
|
1086
1172
|
key: q.get("key") || void 0,
|
|
1087
1173
|
timeout: q.has("timeout") ? Number(q.get("timeout")) : void 0,
|
|
1088
|
-
gone: q.has("gone") ? q.get("gone") !== "false" : void 0
|
|
1174
|
+
gone: q.has("gone") ? q.get("gone") !== "false" : void 0,
|
|
1175
|
+
button: q.get("button") === "right" ? "right" : q.get("button") === "left" ? "left" : void 0,
|
|
1176
|
+
x: q.has("x") ? Number(q.get("x")) : void 0,
|
|
1177
|
+
y: q.has("y") ? Number(q.get("y")) : void 0
|
|
1089
1178
|
};
|
|
1090
1179
|
const check2 = resolveAction(parts[0], args);
|
|
1091
1180
|
if (!check2.valid) {
|
|
1092
|
-
res
|
|
1093
|
-
res.end(check2.error);
|
|
1181
|
+
send(res, 400, check2.error ?? "invalid action");
|
|
1094
1182
|
return;
|
|
1095
1183
|
}
|
|
1096
|
-
const result = await
|
|
1097
|
-
lastRaw = null;
|
|
1184
|
+
const result = await runAction(parts[0], args);
|
|
1098
1185
|
if (parts[0] === "screenshot" && result.dataUrl) {
|
|
1099
1186
|
const dir = resolve(server.config.root, ".aipeek");
|
|
1100
1187
|
mkdirSync(dir, { recursive: true });
|
|
1101
1188
|
const name = q.get("out") || `shot-${result.dataUrl.length}.png`;
|
|
1102
1189
|
const file = resolve(dir, name);
|
|
1103
1190
|
writeFileSync(file, Buffer.from(result.dataUrl.split(",")[1], "base64"));
|
|
1104
|
-
res
|
|
1105
|
-
res.end(`saved: ${file}`);
|
|
1191
|
+
send(res, 200, `saved: ${file}`);
|
|
1106
1192
|
return;
|
|
1107
1193
|
}
|
|
1108
|
-
res.writeHead(result.ok ? 200 : 422, { "Content-Type": "text/plain; charset=utf-8" });
|
|
1109
1194
|
const head = result.ok ? result.detail || "ok" : `${result.error}${result.detail ? `
|
|
1110
1195
|
|
|
1111
1196
|
clickable: ${result.detail}` : ""}`;
|
|
1112
|
-
res.
|
|
1197
|
+
send(res, result.ok ? 200 : 422, result.ui ? `${head}
|
|
1113
1198
|
|
|
1114
1199
|
--- ui after ---
|
|
1115
1200
|
${result.ui}` : head);
|
|
@@ -1120,8 +1205,7 @@ ${result.ui}` : head);
|
|
|
1120
1205
|
lastRaw = raw2;
|
|
1121
1206
|
const result = check(raw2);
|
|
1122
1207
|
const output = emitCheck(result);
|
|
1123
|
-
res
|
|
1124
|
-
res.end(output);
|
|
1208
|
+
send(res, result.pass ? 200 : 417, output);
|
|
1125
1209
|
return;
|
|
1126
1210
|
}
|
|
1127
1211
|
if (parts.length >= 1) {
|
|
@@ -1129,29 +1213,22 @@ ${result.ui}` : head);
|
|
|
1129
1213
|
lastRaw = await collectFromClient();
|
|
1130
1214
|
const result = detail(lastRaw, parts[0], parts[1], full);
|
|
1131
1215
|
if (result !== null) {
|
|
1132
|
-
res
|
|
1133
|
-
res.end(result);
|
|
1216
|
+
send(res, 200, result);
|
|
1134
1217
|
return;
|
|
1135
1218
|
}
|
|
1136
|
-
res
|
|
1137
|
-
res.end(`not found: ${parts.join("/")}`);
|
|
1219
|
+
send(res, 404, `not found: ${parts.join("/")}`);
|
|
1138
1220
|
return;
|
|
1139
1221
|
}
|
|
1140
1222
|
const raw = await collectFromClient();
|
|
1141
1223
|
lastRaw = raw;
|
|
1142
1224
|
if (full) {
|
|
1143
1225
|
const compacted = compact(raw);
|
|
1144
|
-
|
|
1145
|
-
res.writeHead(200, { "Content-Type": "text/plain; charset=utf-8" });
|
|
1146
|
-
res.end(output);
|
|
1226
|
+
send(res, 200, emit(compacted));
|
|
1147
1227
|
} else {
|
|
1148
|
-
|
|
1149
|
-
res.writeHead(200, { "Content-Type": "text/plain; charset=utf-8" });
|
|
1150
|
-
res.end(output);
|
|
1228
|
+
send(res, 200, emitSummary(raw));
|
|
1151
1229
|
}
|
|
1152
1230
|
} catch (err) {
|
|
1153
|
-
res
|
|
1154
|
-
res.end(err instanceof Error ? err.message : "unknown error");
|
|
1231
|
+
send(res, 504, err instanceof Error ? err.message : "unknown error");
|
|
1155
1232
|
}
|
|
1156
1233
|
});
|
|
1157
1234
|
}
|
|
@@ -1166,6 +1243,7 @@ export {
|
|
|
1166
1243
|
emitDiff,
|
|
1167
1244
|
START_TAG,
|
|
1168
1245
|
END_TAG,
|
|
1246
|
+
renderClaudeMd,
|
|
1169
1247
|
injectClaudeMd,
|
|
1170
1248
|
aipeekPlugin
|
|
1171
1249
|
};
|