aihand 0.0.1 → 0.1.0

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.
Files changed (113) hide show
  1. package/README.md +136 -2
  2. package/dist/chunk-2NTK7H4W.js +10 -0
  3. package/dist/chunk-3X4FTHLC.cjs +369 -0
  4. package/dist/chunk-BXVNR4E2.js +399 -0
  5. package/dist/chunk-C7DGE6MY.cjs +1456 -0
  6. package/dist/chunk-DUUCVLC3.cjs +254 -0
  7. package/dist/chunk-FAHI53KO.cjs +125 -0
  8. package/dist/chunk-G7KVJ7NF.js +369 -0
  9. package/dist/chunk-GNEUSRGP.js +52 -0
  10. package/dist/chunk-IGNEAOLT.cjs +130 -0
  11. package/dist/chunk-IS5XFUDB.js +125 -0
  12. package/dist/chunk-JLYC76XL.js +2448 -0
  13. package/dist/chunk-KQOABC2O.cjs +52 -0
  14. package/dist/chunk-OVMK33AC.cjs +104 -0
  15. package/dist/chunk-OWYK2IGV.js +250 -0
  16. package/dist/chunk-PQSQN4CN.js +126 -0
  17. package/dist/chunk-QF6AG3M5.cjs +410 -0
  18. package/dist/chunk-QSAMLXML.js +1456 -0
  19. package/dist/chunk-VEKYRKPF.cjs +399 -0
  20. package/dist/chunk-Y6H7W7PI.cjs +2451 -0
  21. package/dist/chunk-YKSYW77R.js +410 -0
  22. package/dist/chunk-Z2Y65YOY.cjs +7 -0
  23. package/dist/chunk-ZJQRNIK7.js +104 -0
  24. package/dist/cli-FDS2C2CZ.cjs +651 -0
  25. package/dist/cli-HHRGYPSM.js +649 -0
  26. package/dist/cli-JQEIE7RQ.js +120 -0
  27. package/dist/cli-K3OS2QQH.cjs +122 -0
  28. package/dist/cli-OSYG6LJD.cjs +89 -0
  29. package/dist/cli-TXRW5PG6.js +89 -0
  30. package/dist/cli.cjs +81 -0
  31. package/dist/cli.js +81 -0
  32. package/dist/config-5KEQLN6L.cjs +13 -0
  33. package/dist/config-PJPYKDLQ.js +13 -0
  34. package/dist/graph-IH56SCPK.js +8 -0
  35. package/dist/graph-ZUXXCJ5A.cjs +8 -0
  36. package/dist/index.cjs +481 -0
  37. package/dist/index.d.cts +461 -0
  38. package/dist/index.d.ts +461 -0
  39. package/dist/index.js +479 -0
  40. package/dist/locate-5XFSXJ5J.cjs +15 -0
  41. package/dist/locate-NKSUGL3A.js +15 -0
  42. package/dist/refactor-5FWSZIBN.cjs +19 -0
  43. package/dist/refactor-BOB3SZSA.js +19 -0
  44. package/dist/scan-4R7GQG2W.cjs +9 -0
  45. package/dist/scan-VF54GAAX.js +9 -0
  46. package/dist/ui/probe/server.cjs +505 -0
  47. package/dist/ui/probe/server.js +507 -0
  48. package/dist/vite.cjs +12 -0
  49. package/dist/vite.d.cts +12 -0
  50. package/dist/vite.d.ts +12 -0
  51. package/dist/vite.js +12 -0
  52. package/package.json +82 -9
  53. package/src/cli.ts +107 -0
  54. package/src/index.ts +54 -0
  55. package/src/read/cli.ts +650 -0
  56. package/src/read/compact.ts +286 -0
  57. package/src/read/config.ts +62 -0
  58. package/src/read/graph.ts +182 -0
  59. package/src/read/index.ts +12 -0
  60. package/src/read/inject.ts +121 -0
  61. package/src/read/locate.ts +104 -0
  62. package/src/read/panel.ts +335 -0
  63. package/src/read/pipeline.ts +78 -0
  64. package/src/read/refactor.ts +576 -0
  65. package/src/read/render.ts +1118 -0
  66. package/src/read/scan.ts +61 -0
  67. package/src/read/seam.ts +0 -0
  68. package/src/read/security.ts +171 -0
  69. package/src/read/signals.ts +333 -0
  70. package/src/read/state.ts +71 -0
  71. package/src/read/stategraph.ts +205 -0
  72. package/src/read/types.ts +162 -0
  73. package/src/read/vite.ts +77 -0
  74. package/src/ui/babel/line-profiler.ts +197 -0
  75. package/src/ui/babel/source-loc.ts +68 -0
  76. package/src/ui/bridge/cdp-bridge.ts +138 -0
  77. package/src/ui/bridge/compile-probe.ts +80 -0
  78. package/src/ui/bridge/transport.ts +26 -0
  79. package/src/ui/bridge/vite-bridge.ts +116 -0
  80. package/src/ui/client/client-patch.ts +899 -0
  81. package/src/ui/client/client.ts +2562 -0
  82. package/src/ui/core/action.ts +747 -0
  83. package/src/ui/core/candidates.ts +348 -0
  84. package/src/ui/core/canvas.ts +305 -0
  85. package/src/ui/core/check.ts +34 -0
  86. package/src/ui/core/compact.ts +314 -0
  87. package/src/ui/core/detail.ts +244 -0
  88. package/src/ui/core/diff.ts +253 -0
  89. package/src/ui/core/emit.ts +198 -0
  90. package/src/ui/core/knob-exec.ts +137 -0
  91. package/src/ui/core/perf.ts +254 -0
  92. package/src/ui/core/types.ts +164 -0
  93. package/src/ui/core/util.ts +221 -0
  94. package/src/ui/index.ts +5 -0
  95. package/src/ui/probe/cli.ts +139 -0
  96. package/src/ui/probe/server.ts +468 -0
  97. package/src/ui/self/act.ts +47 -0
  98. package/src/ui/self/discover.ts +101 -0
  99. package/src/ui/self/grow.ts +121 -0
  100. package/src/ui/self/install.ts +100 -0
  101. package/src/ui/self/probe.ts +105 -0
  102. package/src/ui/self/screen-hook.ts +44 -0
  103. package/src/ui/self/self.ts +48 -0
  104. package/src/ui/self/store-refs.ts +123 -0
  105. package/src/ui/self/store-schema.ts +65 -0
  106. package/src/ui/self/synth.ts +37 -0
  107. package/src/ui/server/cli.ts +102 -0
  108. package/src/ui/server/dispatch.ts +276 -0
  109. package/src/ui/server/help-text.ts +237 -0
  110. package/src/ui/server/knob-schema.ts +87 -0
  111. package/src/ui/server/plugin.ts +1151 -0
  112. package/src/vite.ts +39 -0
  113. package/index.js +0 -2
@@ -0,0 +1,507 @@
1
+ import {
2
+ argsFromQuery,
3
+ diagnoseEval,
4
+ dispatchAction,
5
+ dispatchChainStep,
6
+ dispatchScreen,
7
+ isDispatchAction,
8
+ isRouteChange,
9
+ runChain
10
+ } from "../../chunk-YKSYW77R.js";
11
+ import {
12
+ __dirname
13
+ } from "../../chunk-2NTK7H4W.js";
14
+
15
+ // src/ui/probe/server.ts
16
+ import http from "http";
17
+ import os from "os";
18
+ import path from "path";
19
+ import { chromium } from "playwright-core";
20
+
21
+ // src/ui/bridge/cdp-bridge.ts
22
+ function isNavTransient(err) {
23
+ const m = err?.message ?? "";
24
+ return m.includes("Execution context was destroyed") || m.includes("because of a navigation");
25
+ }
26
+ async function evalWithSettle(page2, arg, onNav) {
27
+ const fn = ([e, p]) => window.__aihandRecv?.(e, p);
28
+ try {
29
+ await page2.evaluate(fn, arg);
30
+ } catch (err) {
31
+ if (!isNavTransient(err) || !page2.settle)
32
+ throw err;
33
+ onNav?.();
34
+ await page2.settle();
35
+ await page2.evaluate(fn, arg);
36
+ }
37
+ }
38
+ function cdpBridge(page2) {
39
+ const waiters = /* @__PURE__ */ new Map();
40
+ const deliver = (event, payload) => {
41
+ const set = waiters.get(event);
42
+ if (!set)
43
+ return;
44
+ for (const w of set) {
45
+ if (w.id !== void 0 && payload.id !== w.id)
46
+ continue;
47
+ set.delete(w);
48
+ w.resolve(payload);
49
+ return;
50
+ }
51
+ };
52
+ const request = (reqEvent, replyEvent, payload, opts) => {
53
+ const fullMs = opts?.timeoutMs ?? 3e3;
54
+ const id = payload.id;
55
+ return new Promise((resolve2, reject) => {
56
+ const waiter = { id, resolve: resolve2 };
57
+ let set = waiters.get(replyEvent);
58
+ if (!set) {
59
+ set = /* @__PURE__ */ new Set();
60
+ waiters.set(replyEvent, set);
61
+ }
62
+ set.add(waiter);
63
+ let unsubNav;
64
+ let done = false;
65
+ const finish = (fn) => {
66
+ if (done)
67
+ return;
68
+ done = true;
69
+ clearTimeout(timer);
70
+ set.delete(waiter);
71
+ unsubNav?.();
72
+ fn();
73
+ };
74
+ const timer = setTimeout(() => finish(() => reject(new Error(`probe: no reply on ${replyEvent} within ${fullMs}ms`))), fullMs);
75
+ waiter.resolve = (v) => finish(() => resolve2(v));
76
+ unsubNav = opts?.onNav ? page2.onMainFrameNavIntent?.(() => finish(() => {
77
+ opts.onNav();
78
+ resolve2({ ok: true });
79
+ })) : void 0;
80
+ evalWithSettle(page2, [reqEvent, payload], opts?.onNav).catch((err) => finish(() => reject(err)));
81
+ });
82
+ };
83
+ const emit = (event, payload) => {
84
+ page2.evaluate(
85
+ ([e, p]) => window.__aihandRecv?.(e, p),
86
+ [event, payload]
87
+ ).catch(() => {
88
+ });
89
+ };
90
+ return { bridge: { request, emit }, deliver };
91
+ }
92
+
93
+ // src/ui/bridge/compile-probe.ts
94
+ import { resolve } from "path";
95
+ import { existsSync } from "fs";
96
+ import { buildSync } from "esbuild";
97
+ function findClientDir() {
98
+ const local = [resolve(__dirname, "../client"), resolve(__dirname, "client")];
99
+ for (const d of local) {
100
+ if (existsSync(resolve(d, "client.ts")))
101
+ return d;
102
+ }
103
+ let dir = __dirname;
104
+ for (let i = 0; i < 8; i++) {
105
+ const cand = resolve(dir, "src/ui/client");
106
+ if (existsSync(resolve(cand, "client.ts")))
107
+ return cand;
108
+ const parent = resolve(dir, "..");
109
+ if (parent === dir)
110
+ break;
111
+ dir = parent;
112
+ }
113
+ return resolve(__dirname, "../client");
114
+ }
115
+ var clientDir = findClientDir();
116
+ var clientPath = resolve(clientDir, "client.ts");
117
+ var BOOTSTRAP = `
118
+ import { installProbe } from ${JSON.stringify(clientPath)}
119
+ const handlers = new Map()
120
+ const transport = {
121
+ send: (event, payload) => window.__aihandSend(event, payload),
122
+ onMsg: (event, handler) => {
123
+ let list = handlers.get(event)
124
+ if (!list) { list = new Set(); handlers.set(event, list) }
125
+ list.add(handler)
126
+ return () => list.delete(handler)
127
+ },
128
+ }
129
+ window.__aihandRecv = (event, payload) => {
130
+ const list = handlers.get(event)
131
+ if (list) for (const h of list) h(payload)
132
+ }
133
+ installProbe(transport)
134
+ `;
135
+ var cached;
136
+ function compileProbe() {
137
+ if (cached !== void 0)
138
+ return cached;
139
+ const result = buildSync({
140
+ stdin: { contents: BOOTSTRAP, resolveDir: clientDir, loader: "ts" },
141
+ bundle: true,
142
+ format: "iife",
143
+ target: "es2020",
144
+ define: { "import.meta.hot": "undefined" },
145
+ // minifySyntax 开 DCE/常量折叠(不改名,bundle 仍可读):没它 define 把 import.meta.hot
146
+ // 换成 undefined 后 `if (undefined){…}` 仍原样留在 bundle——Vite shim(HMR 自愈、accept、
147
+ // hello)全被打进外部站点。开了才把整段 if(undefined) 死代码消除。
148
+ minifySyntax: true,
149
+ write: false
150
+ });
151
+ cached = result.outputFiles[0].text;
152
+ return cached;
153
+ }
154
+
155
+ // src/ui/probe/server.ts
156
+ var PORT = 5179;
157
+ var browser;
158
+ var page;
159
+ var bridge;
160
+ var onSameDocNav;
161
+ var actionId = 0;
162
+ async function launchChromium(headed) {
163
+ const headless = !headed;
164
+ try {
165
+ return await chromium.launch({ headless });
166
+ } catch (err) {
167
+ try {
168
+ return await chromium.launch({ headless, channel: "chrome" });
169
+ } catch {
170
+ throw err;
171
+ }
172
+ }
173
+ }
174
+ async function ensurePage(url, headed) {
175
+ if (browser && !browser.isConnected())
176
+ browser = page = bridge = void 0;
177
+ if (page && page.isClosed())
178
+ page = bridge = void 0;
179
+ if (!browser)
180
+ browser = await launchChromium(headed);
181
+ if (!page) {
182
+ page = await browser.newPage();
183
+ const cdp = await page.context().newCDPSession(page);
184
+ await cdp.send("Page.enable");
185
+ const mainFrameId = (await cdp.send("Page.getFrameTree")).frameTree.frame.id;
186
+ const navIntentCbs = /* @__PURE__ */ new Set();
187
+ cdp.on("Page.frameRequestedNavigation", (e) => {
188
+ if (e.frameId === mainFrameId)
189
+ for (const cb of navIntentCbs) cb();
190
+ });
191
+ const sameDocNavCbs = /* @__PURE__ */ new Set();
192
+ let lastMainUrl = url;
193
+ cdp.on("Page.navigatedWithinDocument", (e) => {
194
+ if (e.frameId !== mainFrameId)
195
+ return;
196
+ const changed = isRouteChange(lastMainUrl, e.url);
197
+ lastMainUrl = e.url;
198
+ if (changed)
199
+ for (const cb of sameDocNavCbs) cb();
200
+ });
201
+ cdp.on("Page.frameNavigated", (e) => {
202
+ if (e.frame.id === mainFrameId)
203
+ lastMainUrl = e.frame.url;
204
+ });
205
+ const wired = cdpBridge({
206
+ evaluate: (fn, arg) => page.evaluate(fn, arg),
207
+ // 整页导航撞上下文销毁时,bridge 调这个等新文档真正可查询(body 挂上)再重试。
208
+ // 只等 domcontentloaded 不够:旧文档刚销毁、新文档 body 尚未 attach 的瞬间,
209
+ // collectScreen 的 body.querySelectorAll 仍 NPE。waitForFunction 等到 body 在。
210
+ settle: async () => {
211
+ await page.waitForLoadState("domcontentloaded");
212
+ await page.waitForFunction(() => Boolean(document.body));
213
+ },
214
+ // 主帧导航意图地面真值(先于 reply ~0.3ms):动作触发整页导航时 CDP 同步 fire,锁存置位
215
+ // 调用方 navigated 标记,不 settle request(reply 仍唯一 settle)。同文档动作永不 fire → 零延迟。
216
+ onMainFrameNavIntent: (cb) => {
217
+ navIntentCbs.add(cb);
218
+ return () => navIntentCbs.delete(cb);
219
+ },
220
+ // 同文档路由切换订阅:dispatch 在动作前 arm,reply 后等(awaitSameDocNav)。出口1 无此事件。
221
+ onMainFrameSameDocNav: (cb) => {
222
+ sameDocNavCbs.add(cb);
223
+ return () => sameDocNavCbs.delete(cb);
224
+ }
225
+ });
226
+ bridge = wired.bridge;
227
+ onSameDocNav = (cb) => {
228
+ sameDocNavCbs.add(cb);
229
+ return () => sameDocNavCbs.delete(cb);
230
+ };
231
+ await page.exposeBinding("__aihandSend", (_src, event, payload) => {
232
+ wired.deliver(event, payload);
233
+ });
234
+ await page.addInitScript(compileProbe());
235
+ }
236
+ await page.goto(url, { waitUntil: "domcontentloaded" });
237
+ await page.evaluate(compileProbe());
238
+ }
239
+ function send(res, status, body) {
240
+ res.writeHead(status, { "content-type": "text/plain; charset=utf-8" });
241
+ res.end(body);
242
+ }
243
+ function readBody(req) {
244
+ return new Promise((resolve2) => {
245
+ let body = "";
246
+ req.on("data", (chunk) => body += chunk);
247
+ req.on("end", () => resolve2(body));
248
+ });
249
+ }
250
+ function withTimeout(p, ms, label) {
251
+ let timer;
252
+ const timeout = new Promise((_, reject) => {
253
+ timer = setTimeout(() => reject(new Error(`${label} timed out after ${ms}ms \u2014 page main thread may be blocked (heavy SPA / infinite loop). try a fresh \`open <url>\` to recover.`)), ms);
254
+ });
255
+ return Promise.race([p, timeout]).finally(() => clearTimeout(timer));
256
+ }
257
+ async function resolveDragCoords(srcSel, srcText, toSel) {
258
+ return page.evaluate(({ sel, text, to }) => {
259
+ const w = window;
260
+ const pick = (s, t) => {
261
+ if (s) return document.querySelector(s);
262
+ if (t) {
263
+ const els = Array.from(document.querySelectorAll("a,button,li,div,span,[role],[draggable]"));
264
+ return els.find((e) => (e.textContent || "").trim().includes(t)) ?? null;
265
+ }
266
+ return null;
267
+ };
268
+ const finder = w.__aihandFindForDrag ?? pick;
269
+ const src = finder(sel, text);
270
+ if (!src) return { ok: false, error: `no source element for ${sel || text}` };
271
+ const dst = document.querySelector(to);
272
+ if (!dst) return { ok: false, error: `no destination element for to=${to}` };
273
+ src.scrollIntoView({ block: "center", inline: "center" });
274
+ const sr = src.getBoundingClientRect();
275
+ const dr = dst.getBoundingClientRect();
276
+ const lbl = (e) => (e.getAttribute("aria-label") || (e.textContent || "").trim() || e.tagName.toLowerCase()).slice(0, 30);
277
+ return { ok: true, sx: sr.left + sr.width / 2, sy: sr.top + sr.height / 2, dx: dr.left + dr.width / 2, dy: dr.top + dr.height / 2, srcLabel: lbl(src), dstLabel: lbl(dst) };
278
+ }, { sel: srcSel, text: srcText, to: toSel });
279
+ }
280
+ async function trustedDrag(srcSel, srcText, toSel) {
281
+ const c = await resolveDragCoords(srcSel, srcText, toSel);
282
+ if (!c.ok)
283
+ return { status: 422, body: c.error };
284
+ const pause = (ms) => new Promise((r) => setTimeout(r, ms));
285
+ await page.mouse.move(c.sx, c.sy);
286
+ await page.mouse.down();
287
+ await pause(30);
288
+ const len = Math.hypot(c.dx - c.sx, c.dy - c.sy) || 1;
289
+ await page.mouse.move(c.sx + (c.dx - c.sx) / len * 6, c.sy + (c.dy - c.sy) / len * 6);
290
+ await pause(20);
291
+ const steps = 12;
292
+ for (let i = 1; i <= steps; i++) {
293
+ await page.mouse.move(c.sx + (c.dx - c.sx) * (i / steps), c.sy + (c.dy - c.sy) * (i / steps));
294
+ await pause(16);
295
+ }
296
+ await pause(60);
297
+ await page.mouse.up();
298
+ const { result } = await dispatchScreen(bridge);
299
+ return { status: 200, body: `dragged ${c.srcLabel} \u2192 ${c.dstLabel} (trusted page.mouse)
300
+
301
+ --- changed ---
302
+ ${result.body}` };
303
+ }
304
+ function navProbe() {
305
+ return {
306
+ url: async () => page.url(),
307
+ // 同文档路由切换事后等待。两段:arm 必须在动作派发前调(此刻订阅 → 监听就位,免得 @0.2s 的快
308
+ // 信号在 reply 后才订阅而漏);wait(capMs) 在 reply 后调,与 capMs 竞速。SPA 换页(URL 真变)在
309
+ // capMs 内 fire → resolve(true) 立即短路;否则到 capMs → resolve(false)。两条都退订 + 清定时器
310
+ // (幂等,无泄漏)。无订阅能力(出口1/未接线)时恒返回一个立刻 false 的 wait。
311
+ armSameDocNav: () => {
312
+ if (!onSameDocNav)
313
+ return { wait: () => Promise.resolve(false), cancel: () => {
314
+ } };
315
+ let fired = false;
316
+ let onFire;
317
+ const unsub = onSameDocNav(() => {
318
+ fired = true;
319
+ onFire?.();
320
+ });
321
+ return {
322
+ wait: (capMs) => new Promise((resolve2) => {
323
+ let done = false;
324
+ let cap;
325
+ const finish = (v) => {
326
+ if (done) return;
327
+ done = true;
328
+ if (cap) clearTimeout(cap);
329
+ unsub();
330
+ resolve2(v);
331
+ };
332
+ if (fired) return finish(true);
333
+ onFire = () => finish(true);
334
+ cap = setTimeout(() => finish(false), capMs);
335
+ }),
336
+ // 门控未命中(整页导航 / 非平凡 diff)→ 不等,直接退订,不留悬挂监听。
337
+ cancel: unsub
338
+ };
339
+ },
340
+ renavScreen: async (fromUrl) => {
341
+ await page.waitForFunction(() => Boolean(document.body));
342
+ await page.waitForFunction(() => {
343
+ const n = document.body.querySelectorAll("*").length;
344
+ const w = window;
345
+ const stable = n > 50 && w.__aihandPrevN !== void 0 && Math.abs(n - w.__aihandPrevN) < 5;
346
+ w.__aihandPrevN = n;
347
+ return stable;
348
+ }, { timeout: 3e3, polling: 150 }).catch(() => {
349
+ });
350
+ await page.evaluate(compileProbe());
351
+ const toUrl = page.url();
352
+ const { result } = await dispatchScreen(bridge);
353
+ return `navigated: ${fromUrl} \u2192 ${toUrl}
354
+ ${result.body}`;
355
+ }
356
+ };
357
+ }
358
+ var server = http.createServer(async (req, res) => {
359
+ try {
360
+ const url = new URL(req.url || "/", `http://localhost:${PORT}`);
361
+ const parts = url.pathname.split("/").filter(Boolean);
362
+ if (parts[0] === "open") {
363
+ const target = url.searchParams.get("url") || "";
364
+ if (!target) {
365
+ send(res, 400, "open needs ?url=");
366
+ return;
367
+ }
368
+ await ensurePage(target, url.searchParams.get("headed") === "1");
369
+ const { result } = await dispatchScreen(bridge);
370
+ send(res, 200, `opened ${target}
371
+ ${result.body}`);
372
+ return;
373
+ }
374
+ if (!page || !bridge) {
375
+ send(res, 409, "no page open \u2014 run `aihand probe open <url>` first");
376
+ return;
377
+ }
378
+ if (parts[0] === "screen") {
379
+ const { result } = await dispatchScreen(bridge, { form: url.searchParams.get("form") || void 0 });
380
+ send(res, 200, result.body);
381
+ return;
382
+ }
383
+ if (parts[0] === "eval") {
384
+ let code = url.searchParams.get("code") || "";
385
+ if (!code && req.method === "POST")
386
+ code = await readBody(req);
387
+ if (!code) {
388
+ send(res, 400, "eval needs ?code= or a POST body");
389
+ return;
390
+ }
391
+ let r;
392
+ const fmt = (value) => value === void 0 ? void 0 : typeof value === "string" ? value : JSON.stringify(value, null, 2);
393
+ try {
394
+ r = { ok: true, value: fmt(await withTimeout(page.evaluate(`(async () => (${code}))()`), 8e3, "eval")) };
395
+ } catch (e1) {
396
+ const m1 = e1 instanceof Error ? e1.message : String(e1);
397
+ if (m1.includes("SyntaxError")) {
398
+ try {
399
+ r = { ok: true, value: fmt(await withTimeout(page.evaluate(`(async () => { ${code} })()`), 8e3, "eval")) };
400
+ } catch (e2) {
401
+ r = { ok: false, error: e2 instanceof Error ? e2.message : String(e2) };
402
+ }
403
+ } else {
404
+ r = { ok: false, error: m1 };
405
+ }
406
+ }
407
+ const hint = diagnoseEval(code, r.ok, r.error);
408
+ const body = r.ok ? r.value ?? "undefined" : `error: ${r.error}`;
409
+ send(res, r.ok ? 200 : 422, hint ? `${body}
410
+
411
+ hint: ${hint}` : body);
412
+ return;
413
+ }
414
+ if (parts[0] === "screenshot") {
415
+ const sel = url.searchParams.get("sel");
416
+ const fullPage = url.searchParams.get("fullPage") === "1";
417
+ const file = path.join(os.tmpdir(), `aihand-shot-${++actionId}.png`);
418
+ try {
419
+ if (sel) {
420
+ const el = page.locator(sel).first();
421
+ if (await el.count() === 0) {
422
+ send(res, 404, `screenshot: no element matches ${sel}`);
423
+ return;
424
+ }
425
+ await el.screenshot({ path: file });
426
+ } else {
427
+ await page.screenshot({ path: file, fullPage });
428
+ }
429
+ send(res, 200, `screenshot saved: ${file}`);
430
+ } catch (e) {
431
+ send(res, 500, `screenshot failed: ${e instanceof Error ? e.message : String(e)}`);
432
+ }
433
+ return;
434
+ }
435
+ if (parts[0] === "chain") {
436
+ let steps;
437
+ try {
438
+ steps = JSON.parse(await readBody(req));
439
+ if (!Array.isArray(steps))
440
+ throw new Error("body must be a JSON array");
441
+ } catch (e) {
442
+ send(res, 400, `invalid chain body: ${e instanceof Error ? e.message : String(e)}`);
443
+ return;
444
+ }
445
+ const dr = await runChain(steps, (type, args) => dispatchChainStep(bridge, type, args, { nextId: () => ++actionId, afterNav: navProbe() }));
446
+ send(res, dr.status, dr.body);
447
+ return;
448
+ }
449
+ if (parts[0] === "drag") {
450
+ if (!page || !bridge) {
451
+ send(res, 400, "no page open \u2014 run `aihand probe open <url>` first");
452
+ return;
453
+ }
454
+ const to = url.searchParams.get("to");
455
+ if (!to) {
456
+ send(res, 400, "drag needs to= (destination selector)");
457
+ return;
458
+ }
459
+ const dr = await trustedDrag(url.searchParams.get("sel") || void 0, url.searchParams.get("text") || void 0, to);
460
+ send(res, dr.status, dr.body);
461
+ return;
462
+ }
463
+ if (parts[0] === "realclick") {
464
+ if (!page || !bridge) {
465
+ send(res, 400, "no page open \u2014 run `aihand probe open <url>` first");
466
+ return;
467
+ }
468
+ const c = await resolveDragCoords(url.searchParams.get("sel") || void 0, url.searchParams.get("text") || void 0, url.searchParams.get("sel") || url.searchParams.get("text") || "");
469
+ if (!c.ok) {
470
+ send(res, 422, c.error);
471
+ return;
472
+ }
473
+ await page.mouse.click(c.sx, c.sy, { button: url.searchParams.get("button") === "right" ? "right" : "left" });
474
+ const { result } = await dispatchScreen(bridge);
475
+ send(res, 200, `realclicked ${c.srcLabel} (trusted page.mouse)
476
+
477
+ --- changed ---
478
+ ${result.body}`);
479
+ return;
480
+ }
481
+ if (isDispatchAction(parts[0])) {
482
+ const args = argsFromQuery(url.searchParams);
483
+ const dr = await dispatchAction(bridge, parts[0], args, { nextId: () => ++actionId, afterNav: navProbe() });
484
+ send(res, dr.status, dr.body);
485
+ return;
486
+ }
487
+ if (parts[0] === "action" || parts[0] === "knob") {
488
+ send(res, 422, "knob green channel replays the app's own store morphisms \u2014 unavailable when driving an external site (no app store schema). use click/fill/press with text= or sel= instead.");
489
+ return;
490
+ }
491
+ send(res, 404, `unknown probe command: ${parts[0]}`);
492
+ } catch (err) {
493
+ send(res, 500, `probe error: ${err.message}`);
494
+ }
495
+ });
496
+ server.listen(PORT, () => console.log(`aihand probe server on :${PORT}`));
497
+ var exiting = false;
498
+ for (const sig of ["SIGTERM", "SIGINT"]) {
499
+ process.on(sig, () => {
500
+ if (exiting)
501
+ process.exit(0);
502
+ exiting = true;
503
+ setTimeout(() => process.exit(0), 1e3).unref();
504
+ browser?.close().catch(() => {
505
+ }).finally(() => process.exit(0));
506
+ });
507
+ }
package/dist/vite.cjs ADDED
@@ -0,0 +1,12 @@
1
+ "use strict";Object.defineProperty(exports, "__esModule", {value: true});
2
+
3
+ var _chunkY6H7W7PIcjs = require('./chunk-Y6H7W7PI.cjs');
4
+ require('./chunk-3X4FTHLC.cjs');
5
+ require('./chunk-QF6AG3M5.cjs');
6
+ require('./chunk-IGNEAOLT.cjs');
7
+ require('./chunk-KQOABC2O.cjs');
8
+ require('./chunk-DUUCVLC3.cjs');
9
+ require('./chunk-Z2Y65YOY.cjs');
10
+
11
+
12
+ exports.aihand = _chunkY6H7W7PIcjs.aihand;
@@ -0,0 +1,12 @@
1
+ import { Plugin } from 'vite';
2
+
3
+ interface RepodexPluginOptions {
4
+ /** 透传给 `repodex --watch` 的额外参数 */
5
+ args?: string[];
6
+ /** stdio 模式,默认 'inherit',嫌吵改 'ignore' */
7
+ stdio?: 'inherit' | 'ignore';
8
+ }
9
+
10
+ declare function aihand(opts?: RepodexPluginOptions): Promise<Plugin[]>;
11
+
12
+ export { type RepodexPluginOptions, aihand };
package/dist/vite.d.ts ADDED
@@ -0,0 +1,12 @@
1
+ import { Plugin } from 'vite';
2
+
3
+ interface RepodexPluginOptions {
4
+ /** 透传给 `repodex --watch` 的额外参数 */
5
+ args?: string[];
6
+ /** stdio 模式,默认 'inherit',嫌吵改 'ignore' */
7
+ stdio?: 'inherit' | 'ignore';
8
+ }
9
+
10
+ declare function aihand(opts?: RepodexPluginOptions): Promise<Plugin[]>;
11
+
12
+ export { type RepodexPluginOptions, aihand };
package/dist/vite.js ADDED
@@ -0,0 +1,12 @@
1
+ import {
2
+ aihand
3
+ } from "./chunk-JLYC76XL.js";
4
+ import "./chunk-G7KVJ7NF.js";
5
+ import "./chunk-YKSYW77R.js";
6
+ import "./chunk-PQSQN4CN.js";
7
+ import "./chunk-GNEUSRGP.js";
8
+ import "./chunk-OWYK2IGV.js";
9
+ import "./chunk-2NTK7H4W.js";
10
+ export {
11
+ aihand
12
+ };
package/package.json CHANGED
@@ -1,11 +1,84 @@
1
1
  {
2
- "name": "aihand",
3
- "version": "0.0.1",
4
- "description": "The hand of AI — read code, drive the live UI, and refactor source from one CLI. (placeholder; full release coming)",
5
- "type": "module",
6
- "main": "./index.js",
7
- "files": ["index.js", "README.md"],
8
- "keywords": ["aihand", "aidev", "codemap", "refactor", "ai-tools", "live-ui"],
9
- "license": "MIT",
10
- "author": "nightsum <lineryforjs@gmail.com>"
2
+ "name": "aihand",
3
+ "version": "0.1.0",
4
+ "description": "The hand of AI — read code, drive the live UI, and refactor source from one CLI (fuses repodex + aipeek + AST refactor)",
5
+ "type": "module",
6
+ "main": "./dist/index.cjs",
7
+ "module": "./dist/index.js",
8
+ "types": "./dist/index.d.ts",
9
+ "bin": {
10
+ "aihand": "dist/cli.js"
11
+ },
12
+ "exports": {
13
+ ".": {
14
+ "types": "./dist/index.d.ts",
15
+ "import": "./dist/index.js",
16
+ "require": "./dist/index.cjs"
17
+ },
18
+ "./vite": {
19
+ "types": "./dist/vite.d.ts",
20
+ "import": "./dist/vite.js",
21
+ "require": "./dist/vite.cjs"
22
+ }
23
+ },
24
+ "files": [
25
+ "dist",
26
+ "src",
27
+ "README.md"
28
+ ],
29
+ "scripts": {
30
+ "typecheck": "tsc --noEmit",
31
+ "build": "tsc --noEmit && tsup",
32
+ "test": "vitest run",
33
+ "test:watch": "vitest",
34
+ "prepublishOnly": "bun run build"
35
+ },
36
+ "dependencies": {
37
+ "@babel/core": "^7.27.0",
38
+ "@repomix/tree-sitter-wasms": "^0.1.17",
39
+ "esbuild": "^0.25.0",
40
+ "glob": "^13.0.6",
41
+ "html-to-image": "^1.11.13",
42
+ "ignore": "^7.0.5",
43
+ "jiti": "^2.7.0",
44
+ "launch-ide": "^1.0.0",
45
+ "mdast-util-from-markdown": "^2.0.3",
46
+ "node-html-parser": "^7.1.0",
47
+ "picocolors": "^1.1.1",
48
+ "playwright-core": "^1.61.1",
49
+ "postcss": "^8.5.3",
50
+ "postcss-less": "^6.0.0",
51
+ "postcss-scss": "^4.0.9",
52
+ "ts-morph": "^28.0.0",
53
+ "web-tree-sitter": "0.26.8",
54
+ "yaml": "^2.8.3"
55
+ },
56
+ "peerDependencies": {
57
+ "vite": ">=5.0.0"
58
+ },
59
+ "peerDependenciesMeta": {
60
+ "vite": {
61
+ "optional": true
62
+ }
63
+ },
64
+ "devDependencies": {
65
+ "@babel/types": "^7.27.0",
66
+ "@types/babel__core": "^7.20.5",
67
+ "@types/mdast": "^4.0.4",
68
+ "@types/postcss-less": "^4.0.7",
69
+ "jsdom": "^29.1.1",
70
+ "teact": "file:../teact",
71
+ "tsup": "^8.4.0",
72
+ "typescript": "~5.7.2",
73
+ "vitest": "^3.1.2"
74
+ },
75
+ "keywords": [
76
+ "aihand",
77
+ "aidev",
78
+ "codemap",
79
+ "refactor",
80
+ "ts-morph",
81
+ "ai-tools"
82
+ ],
83
+ "license": "MIT"
11
84
  }