pi-ele-picker 0.1.1 → 0.1.3
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/client/picker.js +31 -3
- package/dist/index.cjs +13 -3
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +14 -4
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/client/picker.js
CHANGED
|
@@ -125,6 +125,9 @@
|
|
|
125
125
|
var picking = false;
|
|
126
126
|
var target = null;
|
|
127
127
|
var shots = [];
|
|
128
|
+
// Set when a pick commits on pointerdown; swallows the follow-up click so the
|
|
129
|
+
// page (links, buttons) never reacts to the click that finished the pick.
|
|
130
|
+
var swallowClick = false;
|
|
128
131
|
|
|
129
132
|
document.documentElement.appendChild(host);
|
|
130
133
|
|
|
@@ -288,15 +291,34 @@
|
|
|
288
291
|
showHighlight(el);
|
|
289
292
|
}
|
|
290
293
|
|
|
291
|
-
|
|
294
|
+
/**
|
|
295
|
+
* Commit the pick on `pointerdown`, not `click`: disabled form controls never
|
|
296
|
+
* dispatch `click` (so they could never be picked), but `pointerdown` still
|
|
297
|
+
* fires on them with the correct target.
|
|
298
|
+
*/
|
|
299
|
+
function onPointerDown(e) {
|
|
300
|
+
if (e.button !== 0) return;
|
|
292
301
|
var el = elFromEvent(e);
|
|
293
302
|
if (!el) return;
|
|
294
303
|
e.preventDefault();
|
|
295
304
|
e.stopPropagation();
|
|
305
|
+
// preventDefault above only cancels the compat mousedown/mouseup, not the
|
|
306
|
+
// click, so flag it for the one-shot click swallower below. The flag is
|
|
307
|
+
// cleared at the start of every pointerdown (see the always-on listener at
|
|
308
|
+
// the bottom), so it can never strand on a disabled target — which fires
|
|
309
|
+
// pointerdown but no click at all — and eat an unrelated later click.
|
|
310
|
+
swallowClick = true;
|
|
296
311
|
setPicking(false);
|
|
297
312
|
select(el);
|
|
298
313
|
}
|
|
299
314
|
|
|
315
|
+
function onClick(e) {
|
|
316
|
+
if (!swallowClick) return;
|
|
317
|
+
swallowClick = false;
|
|
318
|
+
e.preventDefault();
|
|
319
|
+
e.stopPropagation();
|
|
320
|
+
}
|
|
321
|
+
|
|
300
322
|
function onKey(e) {
|
|
301
323
|
if (e.key === 'Escape') {
|
|
302
324
|
setPicking(false);
|
|
@@ -311,11 +333,11 @@
|
|
|
311
333
|
trigger.style.display = on ? 'none' : 'flex';
|
|
312
334
|
if (on) {
|
|
313
335
|
window.addEventListener('mousemove', onMove, true);
|
|
314
|
-
window.addEventListener('
|
|
336
|
+
window.addEventListener('pointerdown', onPointerDown, true);
|
|
315
337
|
window.addEventListener('keydown', onKey, true);
|
|
316
338
|
} else {
|
|
317
339
|
window.removeEventListener('mousemove', onMove, true);
|
|
318
|
-
window.removeEventListener('
|
|
340
|
+
window.removeEventListener('pointerdown', onPointerDown, true);
|
|
319
341
|
window.removeEventListener('keydown', onKey, true);
|
|
320
342
|
hideHighlight();
|
|
321
343
|
}
|
|
@@ -562,6 +584,12 @@
|
|
|
562
584
|
});
|
|
563
585
|
}
|
|
564
586
|
|
|
587
|
+
// Always-on. The first one resets the swallow flag before `onPointerDown`
|
|
588
|
+
// runs (registered earlier, so same-node capture listeners fire in order);
|
|
589
|
+
// the second consumes the flag to cancel the click that finishes a pick.
|
|
590
|
+
window.addEventListener('pointerdown', function () { swallowClick = false; }, true);
|
|
591
|
+
window.addEventListener('click', onClick, true);
|
|
592
|
+
|
|
565
593
|
trigger.addEventListener('click', function () {
|
|
566
594
|
if (suppressClick) {
|
|
567
595
|
suppressClick = false;
|
package/dist/index.cjs
CHANGED
|
@@ -35,7 +35,16 @@ var import_node_os = require("os");
|
|
|
35
35
|
var import_node_url = require("url");
|
|
36
36
|
var REG_DIR = (0, import_node_path.join)((0, import_node_os.homedir)(), ".pi", "agent", "pi-picker");
|
|
37
37
|
var ENDPOINT = "/__pi_picker__";
|
|
38
|
-
var
|
|
38
|
+
var OWN_CLIENT_FILE = (0, import_node_url.fileURLToPath)(new URL("../client/picker.js", importMetaUrl));
|
|
39
|
+
function resolveClientFile(projectRoot) {
|
|
40
|
+
for (let dir = projectRoot; dir; ) {
|
|
41
|
+
const candidate = (0, import_node_path.join)(dir, "node_modules", "pi-ele-picker", "client", "picker.js");
|
|
42
|
+
if ((0, import_node_fs.existsSync)(candidate)) return candidate;
|
|
43
|
+
const parent = (0, import_node_path.dirname)(dir);
|
|
44
|
+
dir = parent === dir ? "" : parent;
|
|
45
|
+
}
|
|
46
|
+
return (0, import_node_fs.existsSync)(OWN_CLIENT_FILE) ? OWN_CLIENT_FILE : null;
|
|
47
|
+
}
|
|
39
48
|
function isPidAlive(pid) {
|
|
40
49
|
try {
|
|
41
50
|
process.kill(pid, 0);
|
|
@@ -88,14 +97,15 @@ function piElePicker(options = {}) {
|
|
|
88
97
|
return;
|
|
89
98
|
}
|
|
90
99
|
if (url === "/picker.js") {
|
|
91
|
-
|
|
100
|
+
const clientFile = resolveClientFile(server.config.root);
|
|
101
|
+
if (!clientFile) {
|
|
92
102
|
res.statusCode = 404;
|
|
93
103
|
res.end("pi-ele-picker client missing");
|
|
94
104
|
return;
|
|
95
105
|
}
|
|
96
106
|
res.setHeader("content-type", "text/javascript");
|
|
97
107
|
res.setHeader("cache-control", "no-store");
|
|
98
|
-
res.end((0, import_node_fs.readFileSync)(
|
|
108
|
+
res.end((0, import_node_fs.readFileSync)(clientFile));
|
|
99
109
|
return;
|
|
100
110
|
}
|
|
101
111
|
next();
|
package/dist/index.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/index.ts","../node_modules/tsup/assets/cjs_shims.js"],"sourcesContent":["import type { Plugin } from 'vite';\nimport { existsSync, readFileSync, readdirSync } from 'node:fs';\nimport { join } from 'node:path';\nimport { homedir } from 'node:os';\nimport { fileURLToPath } from 'node:url';\n\n/**\n * pi-ele-picker\n *\n * A Vite dev-only plugin that injects the pi element picker into the page and\n * exposes a same-origin endpoint the picker uses to discover the running pi\n * bridge.\n *\n * The bridge registry is written by the pi extension at\n * `~/.pi/agent/extensions/pi-picker.ts` (or any extension implementing the same\n * `~/.pi/agent/pi-picker/<pid>.json` registry + `POST /pick` protocol).\n */\n\n/** Directory where pi bridge extensions register themselves. */\nconst REG_DIR = join(homedir(), '.pi', 'agent', 'pi-picker');\n\n/** URL prefix under which the picker client and discovery endpoint are served. */\nconst ENDPOINT = '/__pi_picker__';\n\n/**
|
|
1
|
+
{"version":3,"sources":["../src/index.ts","../node_modules/tsup/assets/cjs_shims.js"],"sourcesContent":["import type { Plugin } from 'vite';\nimport { existsSync, readFileSync, readdirSync } from 'node:fs';\nimport { dirname, join } from 'node:path';\nimport { homedir } from 'node:os';\nimport { fileURLToPath } from 'node:url';\n\n/**\n * pi-ele-picker\n *\n * A Vite dev-only plugin that injects the pi element picker into the page and\n * exposes a same-origin endpoint the picker uses to discover the running pi\n * bridge.\n *\n * The bridge registry is written by the pi extension at\n * `~/.pi/agent/extensions/pi-picker.ts` (or any extension implementing the same\n * `~/.pi/agent/pi-picker/<pid>.json` registry + `POST /pick` protocol).\n */\n\n/** Directory where pi bridge extensions register themselves. */\nconst REG_DIR = join(homedir(), '.pi', 'agent', 'pi-picker');\n\n/** URL prefix under which the picker client and discovery endpoint are served. */\nconst ENDPOINT = '/__pi_picker__';\n\n/** Path to this package's own browser client, anchored at the plugin's install location. */\nconst OWN_CLIENT_FILE = fileURLToPath(new URL('../client/picker.js', import.meta.url));\n\n/**\n * Resolve the browser client to serve. Called per request, never cached.\n *\n * The plugin module is evaluated once when the dev server boots, so anything\n * captured at module scope is frozen for the life of that process. Upgrading\n * pi-ele-picker while a dev server is running therefore used to keep serving the\n * old client forever: the frozen path still pointed at\n * `node_modules/.pnpm/pi-ele-picker@<old>/...`, pnpm leaves that directory on\n * disk, and the stale client was handed out silently — refreshing never helped.\n *\n * Resolving from the project root instead means the currently installed copy\n * wins, with no server restart.\n *\n * Two traps this deliberately avoids:\n *\n * 1. Anchoring at `import.meta.url`. From inside `.../pi-ele-picker@<old>/dist/`\n * Node resolves `pi-ele-picker` straight back to that same old copy.\n * 2. `require.resolve()`. Node memoises resolutions in `Module._pathCache` and\n * never invalidates them when `node_modules` changes underneath a running\n * process, which pins the first lookup forever — the same staleness, moved.\n *\n * So the lookup is a plain filesystem walk, mirroring Node's own node_modules\n * search (upward from the project root, for workspace/monorepo layouts), and it\n * falls back to this package's own copy when nothing matches.\n */\nfunction resolveClientFile(projectRoot: string): string | null {\n for (let dir = projectRoot; dir; ) {\n const candidate = join(dir, 'node_modules', 'pi-ele-picker', 'client', 'picker.js');\n if (existsSync(candidate)) return candidate;\n const parent = dirname(dir);\n dir = parent === dir ? '' : parent; // stop at the filesystem root\n }\n return existsSync(OWN_CLIENT_FILE) ? OWN_CLIENT_FILE : null;\n}\n\nexport interface PiElePickerOptions {\n /**\n * Directory whose pi bridge to discover. Must match the `cwd` the pi session\n * was started in. Defaults to `process.cwd()`.\n */\n root?: string;\n}\n\ninterface BridgeInfo {\n port: number;\n pid: number;\n cwd: string;\n token: string;\n updatedAt: number;\n}\n\nfunction isPidAlive(pid: number): boolean {\n try {\n process.kill(pid, 0);\n return true;\n } catch {\n return false;\n }\n}\n\n/** Find the freshest live bridge registered for the given project root. */\nfunction discoverBridge(root: string): BridgeInfo | null {\n let files: string[];\n try {\n files = readdirSync(REG_DIR).filter((f) => f.endsWith('.json'));\n } catch {\n return null;\n }\n let best: BridgeInfo | null = null;\n for (const f of files) {\n try {\n const info = JSON.parse(readFileSync(join(REG_DIR, f), 'utf8')) as BridgeInfo;\n if (info.cwd !== root) continue;\n if (!isPidAlive(info.pid)) continue;\n if (!best || info.updatedAt > best.updatedAt) best = info;\n } catch {\n /* skip malformed */\n }\n }\n return best;\n}\n\nexport default function piElePicker(options: PiElePickerOptions = {}): Plugin {\n const root = options.root ?? process.cwd();\n\n return {\n name: 'pi-ele-picker',\n apply: 'serve',\n transformIndexHtml() {\n return [\n {\n tag: 'script',\n attrs: { type: 'module', src: `${ENDPOINT}/picker.js` },\n injectTo: 'body',\n },\n ];\n },\n configureServer(server) {\n server.middlewares.use(ENDPOINT, (req, res, next) => {\n const url = (req.url ?? '').split('?')[0];\n\n if (url === '/config') {\n const info = discoverBridge(root);\n res.setHeader('content-type', 'application/json');\n res.setHeader('cache-control', 'no-store');\n res.end(JSON.stringify(info ? { port: info.port, token: info.token } : { port: null }));\n return;\n }\n\n if (url === '/picker.js') {\n const clientFile = resolveClientFile(server.config.root);\n if (!clientFile) {\n res.statusCode = 404;\n res.end('pi-ele-picker client missing');\n return;\n }\n res.setHeader('content-type', 'text/javascript');\n res.setHeader('cache-control', 'no-store');\n res.end(readFileSync(clientFile));\n return;\n }\n\n next();\n });\n },\n };\n}\n","// Shim globals in cjs bundle\n// There's a weird bug that esbuild will always inject importMetaUrl\n// if we export it as `const importMetaUrl = ... __filename ...`\n// But using a function will not cause this issue\n\nconst getImportMetaUrl = () => \n typeof document === \"undefined\" \n ? new URL(`file:${__filename}`).href \n : (document.currentScript && document.currentScript.tagName.toUpperCase() === 'SCRIPT') \n ? document.currentScript.src \n : new URL(\"main.js\", document.baseURI).href;\n\nexport const importMetaUrl = /* @__PURE__ */ getImportMetaUrl()\n"],"mappings":"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","names":[]}
|
package/dist/index.js
CHANGED
|
@@ -1,11 +1,20 @@
|
|
|
1
1
|
// src/index.ts
|
|
2
2
|
import { existsSync, readFileSync, readdirSync } from "fs";
|
|
3
|
-
import { join } from "path";
|
|
3
|
+
import { dirname, join } from "path";
|
|
4
4
|
import { homedir } from "os";
|
|
5
5
|
import { fileURLToPath } from "url";
|
|
6
6
|
var REG_DIR = join(homedir(), ".pi", "agent", "pi-picker");
|
|
7
7
|
var ENDPOINT = "/__pi_picker__";
|
|
8
|
-
var
|
|
8
|
+
var OWN_CLIENT_FILE = fileURLToPath(new URL("../client/picker.js", import.meta.url));
|
|
9
|
+
function resolveClientFile(projectRoot) {
|
|
10
|
+
for (let dir = projectRoot; dir; ) {
|
|
11
|
+
const candidate = join(dir, "node_modules", "pi-ele-picker", "client", "picker.js");
|
|
12
|
+
if (existsSync(candidate)) return candidate;
|
|
13
|
+
const parent = dirname(dir);
|
|
14
|
+
dir = parent === dir ? "" : parent;
|
|
15
|
+
}
|
|
16
|
+
return existsSync(OWN_CLIENT_FILE) ? OWN_CLIENT_FILE : null;
|
|
17
|
+
}
|
|
9
18
|
function isPidAlive(pid) {
|
|
10
19
|
try {
|
|
11
20
|
process.kill(pid, 0);
|
|
@@ -58,14 +67,15 @@ function piElePicker(options = {}) {
|
|
|
58
67
|
return;
|
|
59
68
|
}
|
|
60
69
|
if (url === "/picker.js") {
|
|
61
|
-
|
|
70
|
+
const clientFile = resolveClientFile(server.config.root);
|
|
71
|
+
if (!clientFile) {
|
|
62
72
|
res.statusCode = 404;
|
|
63
73
|
res.end("pi-ele-picker client missing");
|
|
64
74
|
return;
|
|
65
75
|
}
|
|
66
76
|
res.setHeader("content-type", "text/javascript");
|
|
67
77
|
res.setHeader("cache-control", "no-store");
|
|
68
|
-
res.end(readFileSync(
|
|
78
|
+
res.end(readFileSync(clientFile));
|
|
69
79
|
return;
|
|
70
80
|
}
|
|
71
81
|
next();
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/index.ts"],"sourcesContent":["import type { Plugin } from 'vite';\nimport { existsSync, readFileSync, readdirSync } from 'node:fs';\nimport { join } from 'node:path';\nimport { homedir } from 'node:os';\nimport { fileURLToPath } from 'node:url';\n\n/**\n * pi-ele-picker\n *\n * A Vite dev-only plugin that injects the pi element picker into the page and\n * exposes a same-origin endpoint the picker uses to discover the running pi\n * bridge.\n *\n * The bridge registry is written by the pi extension at\n * `~/.pi/agent/extensions/pi-picker.ts` (or any extension implementing the same\n * `~/.pi/agent/pi-picker/<pid>.json` registry + `POST /pick` protocol).\n */\n\n/** Directory where pi bridge extensions register themselves. */\nconst REG_DIR = join(homedir(), '.pi', 'agent', 'pi-picker');\n\n/** URL prefix under which the picker client and discovery endpoint are served. */\nconst ENDPOINT = '/__pi_picker__';\n\n/**
|
|
1
|
+
{"version":3,"sources":["../src/index.ts"],"sourcesContent":["import type { Plugin } from 'vite';\nimport { existsSync, readFileSync, readdirSync } from 'node:fs';\nimport { dirname, join } from 'node:path';\nimport { homedir } from 'node:os';\nimport { fileURLToPath } from 'node:url';\n\n/**\n * pi-ele-picker\n *\n * A Vite dev-only plugin that injects the pi element picker into the page and\n * exposes a same-origin endpoint the picker uses to discover the running pi\n * bridge.\n *\n * The bridge registry is written by the pi extension at\n * `~/.pi/agent/extensions/pi-picker.ts` (or any extension implementing the same\n * `~/.pi/agent/pi-picker/<pid>.json` registry + `POST /pick` protocol).\n */\n\n/** Directory where pi bridge extensions register themselves. */\nconst REG_DIR = join(homedir(), '.pi', 'agent', 'pi-picker');\n\n/** URL prefix under which the picker client and discovery endpoint are served. */\nconst ENDPOINT = '/__pi_picker__';\n\n/** Path to this package's own browser client, anchored at the plugin's install location. */\nconst OWN_CLIENT_FILE = fileURLToPath(new URL('../client/picker.js', import.meta.url));\n\n/**\n * Resolve the browser client to serve. Called per request, never cached.\n *\n * The plugin module is evaluated once when the dev server boots, so anything\n * captured at module scope is frozen for the life of that process. Upgrading\n * pi-ele-picker while a dev server is running therefore used to keep serving the\n * old client forever: the frozen path still pointed at\n * `node_modules/.pnpm/pi-ele-picker@<old>/...`, pnpm leaves that directory on\n * disk, and the stale client was handed out silently — refreshing never helped.\n *\n * Resolving from the project root instead means the currently installed copy\n * wins, with no server restart.\n *\n * Two traps this deliberately avoids:\n *\n * 1. Anchoring at `import.meta.url`. From inside `.../pi-ele-picker@<old>/dist/`\n * Node resolves `pi-ele-picker` straight back to that same old copy.\n * 2. `require.resolve()`. Node memoises resolutions in `Module._pathCache` and\n * never invalidates them when `node_modules` changes underneath a running\n * process, which pins the first lookup forever — the same staleness, moved.\n *\n * So the lookup is a plain filesystem walk, mirroring Node's own node_modules\n * search (upward from the project root, for workspace/monorepo layouts), and it\n * falls back to this package's own copy when nothing matches.\n */\nfunction resolveClientFile(projectRoot: string): string | null {\n for (let dir = projectRoot; dir; ) {\n const candidate = join(dir, 'node_modules', 'pi-ele-picker', 'client', 'picker.js');\n if (existsSync(candidate)) return candidate;\n const parent = dirname(dir);\n dir = parent === dir ? '' : parent; // stop at the filesystem root\n }\n return existsSync(OWN_CLIENT_FILE) ? OWN_CLIENT_FILE : null;\n}\n\nexport interface PiElePickerOptions {\n /**\n * Directory whose pi bridge to discover. Must match the `cwd` the pi session\n * was started in. Defaults to `process.cwd()`.\n */\n root?: string;\n}\n\ninterface BridgeInfo {\n port: number;\n pid: number;\n cwd: string;\n token: string;\n updatedAt: number;\n}\n\nfunction isPidAlive(pid: number): boolean {\n try {\n process.kill(pid, 0);\n return true;\n } catch {\n return false;\n }\n}\n\n/** Find the freshest live bridge registered for the given project root. */\nfunction discoverBridge(root: string): BridgeInfo | null {\n let files: string[];\n try {\n files = readdirSync(REG_DIR).filter((f) => f.endsWith('.json'));\n } catch {\n return null;\n }\n let best: BridgeInfo | null = null;\n for (const f of files) {\n try {\n const info = JSON.parse(readFileSync(join(REG_DIR, f), 'utf8')) as BridgeInfo;\n if (info.cwd !== root) continue;\n if (!isPidAlive(info.pid)) continue;\n if (!best || info.updatedAt > best.updatedAt) best = info;\n } catch {\n /* skip malformed */\n }\n }\n return best;\n}\n\nexport default function piElePicker(options: PiElePickerOptions = {}): Plugin {\n const root = options.root ?? process.cwd();\n\n return {\n name: 'pi-ele-picker',\n apply: 'serve',\n transformIndexHtml() {\n return [\n {\n tag: 'script',\n attrs: { type: 'module', src: `${ENDPOINT}/picker.js` },\n injectTo: 'body',\n },\n ];\n },\n configureServer(server) {\n server.middlewares.use(ENDPOINT, (req, res, next) => {\n const url = (req.url ?? '').split('?')[0];\n\n if (url === '/config') {\n const info = discoverBridge(root);\n res.setHeader('content-type', 'application/json');\n res.setHeader('cache-control', 'no-store');\n res.end(JSON.stringify(info ? { port: info.port, token: info.token } : { port: null }));\n return;\n }\n\n if (url === '/picker.js') {\n const clientFile = resolveClientFile(server.config.root);\n if (!clientFile) {\n res.statusCode = 404;\n res.end('pi-ele-picker client missing');\n return;\n }\n res.setHeader('content-type', 'text/javascript');\n res.setHeader('cache-control', 'no-store');\n res.end(readFileSync(clientFile));\n return;\n }\n\n next();\n });\n },\n };\n}\n"],"mappings":";AACA,SAAS,YAAY,cAAc,mBAAmB;AACtD,SAAS,SAAS,YAAY;AAC9B,SAAS,eAAe;AACxB,SAAS,qBAAqB;AAe9B,IAAM,UAAU,KAAK,QAAQ,GAAG,OAAO,SAAS,WAAW;AAG3D,IAAM,WAAW;AAGjB,IAAM,kBAAkB,cAAc,IAAI,IAAI,uBAAuB,YAAY,GAAG,CAAC;AA2BrF,SAAS,kBAAkB,aAAoC;AAC7D,WAAS,MAAM,aAAa,OAAO;AACjC,UAAM,YAAY,KAAK,KAAK,gBAAgB,iBAAiB,UAAU,WAAW;AAClF,QAAI,WAAW,SAAS,EAAG,QAAO;AAClC,UAAM,SAAS,QAAQ,GAAG;AAC1B,UAAM,WAAW,MAAM,KAAK;AAAA,EAC9B;AACA,SAAO,WAAW,eAAe,IAAI,kBAAkB;AACzD;AAkBA,SAAS,WAAW,KAAsB;AACxC,MAAI;AACF,YAAQ,KAAK,KAAK,CAAC;AACnB,WAAO;AAAA,EACT,QAAQ;AACN,WAAO;AAAA,EACT;AACF;AAGA,SAAS,eAAe,MAAiC;AACvD,MAAI;AACJ,MAAI;AACF,YAAQ,YAAY,OAAO,EAAE,OAAO,CAAC,MAAM,EAAE,SAAS,OAAO,CAAC;AAAA,EAChE,QAAQ;AACN,WAAO;AAAA,EACT;AACA,MAAI,OAA0B;AAC9B,aAAW,KAAK,OAAO;AACrB,QAAI;AACF,YAAM,OAAO,KAAK,MAAM,aAAa,KAAK,SAAS,CAAC,GAAG,MAAM,CAAC;AAC9D,UAAI,KAAK,QAAQ,KAAM;AACvB,UAAI,CAAC,WAAW,KAAK,GAAG,EAAG;AAC3B,UAAI,CAAC,QAAQ,KAAK,YAAY,KAAK,UAAW,QAAO;AAAA,IACvD,QAAQ;AAAA,IAER;AAAA,EACF;AACA,SAAO;AACT;AAEe,SAAR,YAA6B,UAA8B,CAAC,GAAW;AAC5E,QAAM,OAAO,QAAQ,QAAQ,QAAQ,IAAI;AAEzC,SAAO;AAAA,IACL,MAAM;AAAA,IACN,OAAO;AAAA,IACP,qBAAqB;AACnB,aAAO;AAAA,QACL;AAAA,UACE,KAAK;AAAA,UACL,OAAO,EAAE,MAAM,UAAU,KAAK,GAAG,QAAQ,aAAa;AAAA,UACtD,UAAU;AAAA,QACZ;AAAA,MACF;AAAA,IACF;AAAA,IACA,gBAAgB,QAAQ;AACtB,aAAO,YAAY,IAAI,UAAU,CAAC,KAAK,KAAK,SAAS;AACnD,cAAM,OAAO,IAAI,OAAO,IAAI,MAAM,GAAG,EAAE,CAAC;AAExC,YAAI,QAAQ,WAAW;AACrB,gBAAM,OAAO,eAAe,IAAI;AAChC,cAAI,UAAU,gBAAgB,kBAAkB;AAChD,cAAI,UAAU,iBAAiB,UAAU;AACzC,cAAI,IAAI,KAAK,UAAU,OAAO,EAAE,MAAM,KAAK,MAAM,OAAO,KAAK,MAAM,IAAI,EAAE,MAAM,KAAK,CAAC,CAAC;AACtF;AAAA,QACF;AAEA,YAAI,QAAQ,cAAc;AACxB,gBAAM,aAAa,kBAAkB,OAAO,OAAO,IAAI;AACvD,cAAI,CAAC,YAAY;AACf,gBAAI,aAAa;AACjB,gBAAI,IAAI,8BAA8B;AACtC;AAAA,UACF;AACA,cAAI,UAAU,gBAAgB,iBAAiB;AAC/C,cAAI,UAAU,iBAAiB,UAAU;AACzC,cAAI,IAAI,aAAa,UAAU,CAAC;AAChC;AAAA,QACF;AAEA,aAAK;AAAA,MACP,CAAC;AAAA,IACH;AAAA,EACF;AACF;","names":[]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pi-ele-picker",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.3",
|
|
4
4
|
"description": "Pick an element in your running page, describe the change, and send it to a pi session. Ships a Vite dev plugin (browser picker + discovery endpoint) and the matching pi bridge extension.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|