@elmts/cli 0.0.0-stage → 0.2.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 (59) hide show
  1. package/.claude-plugin/marketplace.json +11 -0
  2. package/LICENSE +41 -0
  3. package/README.md +18 -2
  4. package/bin/el.js +47 -0
  5. package/claude-plugin/.claude-plugin/plugin.json +6 -0
  6. package/claude-plugin/hooks/chips.ts +1 -0
  7. package/claude-plugin/hooks/hooks.json +1 -0
  8. package/claude-plugin/hooks/register.tsx +2 -0
  9. package/claude-plugin/skills/element/SKILL.md +21 -0
  10. package/claude-plugin/types/index.d.ts +15 -0
  11. package/cursor-extension/extension.js +14 -0
  12. package/cursor-extension/package.json +30 -0
  13. package/extension/background.js +2 -0
  14. package/extension/fonts/figtree-latin-400-normal.woff2 +0 -0
  15. package/extension/fonts/figtree-latin-500-normal.woff2 +0 -0
  16. package/extension/fonts/figtree-latin-600-normal.woff2 +0 -0
  17. package/extension/fonts/geist-mono-latin-400-normal.woff2 +0 -0
  18. package/extension/fonts/geist-mono-latin-500-normal.woff2 +0 -0
  19. package/extension/hotkeys.js +1 -0
  20. package/extension/icons/tile-128.png +0 -0
  21. package/extension/icons/tile-48.png +0 -0
  22. package/extension/icons/toolbar-dark-16.png +0 -0
  23. package/extension/icons/toolbar-dark-32.png +0 -0
  24. package/extension/icons/toolbar-light-16.png +0 -0
  25. package/extension/icons/toolbar-light-32.png +0 -0
  26. package/extension/logos.js +1 -0
  27. package/extension/manifest.json +57 -0
  28. package/extension/offscreen.html +4 -0
  29. package/extension/offscreen.js +1 -0
  30. package/extension/picker.js +311 -0
  31. package/extension/popup.css +59 -0
  32. package/extension/popup.html +37 -0
  33. package/extension/popup.js +1 -0
  34. package/extension/shot.js +1 -0
  35. package/extension/snapshot.js +36 -0
  36. package/opencode-plugin/chips.js +3 -0
  37. package/opencode-plugin/server.js +2 -0
  38. package/opencode-plugin/tui.js +2 -0
  39. package/package.json +36 -4
  40. package/pi-extension/index.ts +10 -0
  41. package/src/account.js +8 -0
  42. package/src/codex.js +25 -0
  43. package/src/cursor-install.js +18 -0
  44. package/src/cursor.js +8 -0
  45. package/src/format.js +7 -0
  46. package/src/hook.js +4 -0
  47. package/src/hub.js +2 -0
  48. package/src/hubclient.js +4 -0
  49. package/src/install.js +47 -0
  50. package/src/license.js +5 -0
  51. package/src/limits.js +1 -0
  52. package/src/muse.js +2 -0
  53. package/src/native.js +2 -0
  54. package/src/notify.js +1 -0
  55. package/src/paths.js +1 -0
  56. package/src/proc.js +4 -0
  57. package/src/share.js +6 -0
  58. package/src/snaptext.js +26 -0
  59. package/src/statusline.js +2 -0
@@ -0,0 +1,11 @@
1
+ {
2
+ "name": "element",
3
+ "owner": { "name": "Element" },
4
+ "plugins": [
5
+ {
6
+ "name": "element",
7
+ "source": "./claude-plugin",
8
+ "description": "Shows elements you picked in the browser above the prompt, attaches them to your next message, and teaches the agent the el CLI"
9
+ }
10
+ ]
11
+ }
package/LICENSE ADDED
@@ -0,0 +1,41 @@
1
+ Copyright (c) 2026 Randy Yu. All rights reserved.
2
+
3
+ element (the "Software") is proprietary. You may install and use the copies of the Software
4
+ distributed by its author through the npm registry and the Chrome Web Store, for your own
5
+ personal or internal business purposes, subject to any terms published at https://elmts.dev.
6
+
7
+ You may not copy, modify, merge, publish, distribute, sublicense or sell the Software or any
8
+ part of it, or reverse engineer, decompile or create derivative works from it, except as
9
+ applicable law expressly permits despite this restriction.
10
+
11
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING
12
+ BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
13
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
14
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
15
+ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
16
+
17
+ Versions 0.1.1 and earlier were released under the MIT License and remain under it.
18
+
19
+ -------------------------------------------------------------------------------------------
20
+ Third-party notices
21
+
22
+ Agent logos in extension/logos.js are from LobeHub Icons (https://github.com/lobehub/lobe-icons),
23
+ MIT License, Copyright (c) LobeHub:
24
+
25
+ Permission is hereby granted, free of charge, to any person obtaining a copy of this software
26
+ and associated documentation files (the "Software"), to deal in the Software without
27
+ restriction, including without limitation the rights to use, copy, modify, merge, publish,
28
+ distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the
29
+ Software is furnished to do so, subject to the following conditions:
30
+
31
+ The above copyright notice and this permission notice shall be included in all copies or
32
+ substantial portions of the Software.
33
+
34
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING
35
+ BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
36
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
37
+ DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
38
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
39
+
40
+ The droid mark belongs to Factory (factory.ai) and the pi mark to pi.dev; they are used only to
41
+ identify which agent a pick goes to.
package/README.md CHANGED
@@ -1,3 +1,19 @@
1
- # Temporary Holding Version
1
+ # element
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ Point at anything in your real browser and hand it to your coding agent: Claude Code, Codex,
4
+ Cursor, opencode, pi, Muse Code, or any agent that can run shell commands.
5
+
6
+ ## Install
7
+
8
+ 1. Add the extension from the [Chrome Web Store](https://chromewebstore.google.com/detail/aanpmajidbphoflakmnhcjpjfjbgohmc).
9
+ 2. In a terminal:
10
+
11
+ ```sh
12
+ npm i -g @elmts/cli
13
+ el install --claude # or --codex, --cursor, --opencode, --pi, --muse, --all-agents
14
+ ```
15
+
16
+ Then hold ⌥ (Option) and hover in Chrome, click what you mean, and tell your agent what to do
17
+ with it. macOS, with Chrome, Arc, Brave, Edge and other Chromium browsers.
18
+
19
+ More at [elmts.dev](https://elmts.dev). See LICENSE for terms.
package/bin/el.js ADDED
@@ -0,0 +1,47 @@
1
+ #!/usr/bin/env node
2
+ import A from"node:fs";import E from"node:path";import{HubClient as B}from"../src/hubclient.js";import{SNAPSHOTS as K}from"../src/paths.js";import{findAnchorPid as m}from"../src/proc.js";import{formatPicks as P,formatSession as H,pickContext as J}from"../src/format.js";import{describe as F}from"../src/notify.js";const G=`el \u2014 point at elements in your browser, hand them to your agent
3
+
4
+ el get [--all] [--json] elements picked for this agent session (new ones, else the latest)
5
+ el open <url> open a tab assigned to this session
6
+ el tabs tabs assigned to this session
7
+ el snapshot [pick-id] [--selector <css> [--tab <id>]] [-o file.html] [--text [--part <n>]] [--motion]
8
+ save a picked element as one self-contained HTML file (a pick's
9
+ stored snapshot is reused; --text: the agent's text form, to stdout,
10
+ in parts of ~60 KB when big; --motion: captured fresh, keeping the
11
+ page's animations moving, as share links do)
12
+ el share [pick-id] [--selector <css> [--tab <id>]] [--expires 1d|7d|30d|never] [--server <url>] [--json] [--no-motion]
13
+ publish a snapshot as a link for people and agents (signed in; how
14
+ many and how long depend on the plan: el whoami); it keeps the page's
15
+ animations moving unless --no-motion
16
+ [--private [--allow a@b.com,@company.com]]
17
+ a private link (Pro, Max): only you and those emails / @domains can
18
+ open it; others can ask, and you approve in the drawer or with el access
19
+ el fetch <link> [--json | --html] [--server <url>]
20
+ read a share link as an agent (markdown, JSON or the snapshot HTML);
21
+ sends your account key to the share server, so private links you're
22
+ allowed into open
23
+ el access <link> [--private | --unlisted] [--allow a,b] [--remove a,b] [--approve email] [--deny email] [--json]
24
+ who can open your link, and pending requests; changes it with the flags
25
+ el sessions connected agent sessions
26
+ el status hub / browser status
27
+ el watch [--json] stream this session's pending picks as they arrive
28
+ el take [--ids a,b] pending picks (or those ids) as prompt context, marked sent; prompt on stdin
29
+ el clear [--ids a,b] drop this session's pending picks (or just those)
30
+ el license [<key>] show the plan, or activate a Pro key (--remove drops it)
31
+ el login [--server <url>] [--no-browser]
32
+ sign in to your element account (GitHub / Google) in the browser
33
+ el logout sign out on this computer
34
+ el whoami [--json] the account you're signed in as, and its plan
35
+
36
+ el install [--claude] [--pi] [--codex] [--cursor] [--opencode] [--muse] [--all-agents] [--dir <dir>]
37
+ el uninstall remove the native host and element's agent plugins, hooks and skills
38
+ el hook [--start] | native | hub (internal entry points)`,[D,...w]=process.argv.slice(2),r=new Set(w.filter(e=>e.startsWith("--"))),O=w.filter(e=>!e.startsWith("--"));function k(e){const t=[];return w.forEach((s,o)=>{s===e&&w[o+1]&&t.push(w[o+1])}),t}function q(e=[]){const t={},s=[],o=new Set(["--selector","--tab",...e]);for(let i=0;i<w.length;i++){const n=w[i];o.has(n)?t[n.replace(/^-+/,"").replace(/^out$/,"o")]=w[++i]:n.startsWith("-")||s.push(n)}return{opt:t,pos:s}}const Y=e=>e==null?e:String(e).replace(/^(?:element\s*)?#?(\d+)$/,"el_$1");function L(e,{opt:t,pos:s},o=!1,i=!1,n=!1){return e.request("snapshot",{pid:m(),pick:Y(s[0]),selector:t.selector,tabId:t.tab,text:o,part:t.part,freshShot:i,...n?{motion:!0}:{}})}function M(e){e.dropped.length&&console.error(`el: ${e.dropped.length} resource(s) not inlined (see "dropped" in the file's #element-snapshot JSON)`)}async function u(e){const t=await B.open();try{return await e(t)}finally{t.close()}}function W(){const e=k("--ids")[0];return e?e.split(",").filter(Boolean):void 0}function C(){return new Promise(e=>{let t="";process.stdin.setEncoding("utf8"),process.stdin.on("data",s=>t+=s),process.stdin.on("end",()=>e(t))})}async function _(e){const t=m(),s=(o,i)=>console.log(e?JSON.stringify(o):i);for(setInterval(()=>{try{process.kill(t,0)}catch{process.exit(0)}},5e3);;){try{const o=await B.open(),i=new Promise(a=>o.onClose=a);o.onEvent=a=>{if(a.type==="notice")return e||a.notice?s(a,a.notice):void 0;if(a.type==="pending"){if(e)return s(a);a.picked&&console.log(`picked ${F(a.picked)}`),console.log(`${a.picks.length} pending`)}};const n=await o.request("watch",{pid:t});s({type:"session",session:n},`watching ${n.label}`),await i,s({type:"disconnected"},"hub disconnected, reconnecting\u2026")}catch(o){s({type:"error",error:o.message},`el: ${o.message}`)}await new Promise(o=>setTimeout(o,1500))}}async function R(){switch(D){case"hub":return(await import("../src/hub.js")).runHub();case"native":return(await import("../src/native.js")).runNative();case"statusline":return(await import("../src/statusline.js")).runStatusLine();case"hook":return r.has("--start")?(await import("../src/hook.js")).registerSession():(await import("../src/hook.js")).runHook(k("--format")[0]);case"install":return(await import("../src/install.js")).install({dirs:k("--dir"),...Object.fromEntries(["claude","pi","codex","cursor","opencode","muse"].map(e=>[e,r.has(`--${e}`)||r.has("--all-agents")]))});case"uninstall":return(await import("../src/install.js")).uninstall({dirs:k("--dir")});case"get":return u(async e=>{const t=await e.request("get",{pid:m(),all:r.has("--all")});if(r.has("--json"))return console.log(JSON.stringify(t,null,2));t.notice?console.log(t.picks.length?`${t.notice}
39
+
40
+ ${P(t.picks)}`:t.notice):console.log(P(t.picks))});case"open":if(!O[0])throw new Error("usage: el open <url>");return u(async e=>{const t=await e.request("open",{pid:m(),url:O[0]});console.log(`opened tab ${t.tabId}`)});case"snapshot":{const e=q(["-o","--out","--part"]);return u(async t=>{const s=await L(t,e,r.has("--text"),!1,r.has("--motion"));if(r.has("--text")&&!e.opt.o)return process.stdout.write(s.html);s.stored&&console.error(`el: reusing the stored snapshot of ${s.id} (captured ${s.meta?.capturedAt})`);const o=e.opt.o||E.join(K,`${s.id}.html`);A.mkdirSync(E.dirname(E.resolve(o)),{recursive:!0,mode:448}),A.writeFileSync(o,s.html),M(s.meta),console.log(E.resolve(o))})}case"share":{const{EXPIRIES:e,accessText:t,fetchUsage:s,planErrorText:o,shareServer:i,shareSnapshot:n,shortDay:a,signedOutMessage:$,windowWord:x}=await import("../src/share.js"),v=await(await import("../src/account.js")).shareKey();if(!v)throw Object.assign(new Error(await $()),{plain:!0});const p=q(["--expires","--server","--allow"]),d=p.opt.expires;if(d&&!e.includes(d))throw new Error(`--expires must be one of ${e.join(", ")}`);const l=p.opt.allow?p.opt.allow.split(/[\s,]+/).filter(Boolean):[],f=r.has("--private")||l.length?"private":void 0,N=i(p.opt.server);return u(async U=>{const h=await L(U,p,!1,!0,!r.has("--no-motion"));M(h.meta);let T=h.png?Buffer.from(h.png,"base64"):null;try{!T&&h.shot&&(T=A.readFileSync(h.shot))}catch{}let c;try{c=await n({html:h.html,png:T,pick:h.pick,expiresIn:d,visibility:f,allow:l,server:N,key:v})}catch(y){const I=o(y);throw I?Object.assign(new Error(I),{plain:!0}):y.status===401?new Error("the share server didn't accept your key; sign in again with `el login`"):y}const j=h.meta||{},b=h.pick?.data||{};if(await U.request("share_saved",{save:{id:c.id,viewUrl:c.viewUrl,apiUrl:c.apiUrl,screenshotUrl:c.screenshotUrl||null,expiresAt:c.expiresAt||null,deleteToken:c.deleteToken||null,visibility:c.visibility||"unlisted",allow:c.allow||[],url:b.url||j.url||"",title:b.title||j.title||"",selector:b.selector||j.selector||p.opt.selector||"",tag:b.tag||j.tag||"",name:b.name||"",via:"cli"}}).catch(()=>{}),r.has("--json"))return console.log(JSON.stringify(c,null,2));const S=c.assets;console.error(`el: ${S.total} asset(s), ${S.uploaded} uploaded, ${S.total-S.uploaded} already on the server; ${c.expiresAt?`expires ${c.expiresAt}`:"never expires"}`),c.visibility==="private"&&console.error(`el: ${t(c)} (others can ask; el access ${c.id})`),console.log(`view: ${c.viewUrl}
41
+ agent: ${c.apiUrl}`);const g=await s({key:v,server:N,timeout:3e3}).catch(()=>null);if(g&&g.limit!=null&&g.limit-g.used<=3){const y=a(g.resetsAt);console.error(`${Math.max(0,g.limit-g.used)} left ${x(g)}${y?` \xB7 resets ${y}`:""}`)}})}case"fetch":{const{fetchLink:e,shareServer:t}=await import("../src/share.js"),s=q(["--server"]);if(!s.pos[0])throw new Error("usage: el fetch <link> [--json | --html] [--server <url>]");const o=await(await import("../src/account.js")).shareKey().catch(()=>null),i=r.has("--html")?"html":r.has("--json")?"json":"md";try{const n=await e({link:s.pos[0],key:o,server:t(s.opt.server),as:i});process.stdout.write(i==="json"?JSON.stringify(n.body,null,2)+`
42
+ `:String(n.body))}catch(n){if(n.status===401||n.status===403){const a=n.body?.error?.message||n.message;throw Object.assign(new Error(`${a}${o?"":"\n(Not signed in: run `el login` with an allowed account.)"}`),{plain:!0})}throw n}return}case"access":{const{accessApi:e,accessText:t,linkId:s,planErrorText:o,shareServer:i}=await import("../src/share.js"),n=q(["--server","--allow","--remove","--approve","--deny"]),a=s(n.pos[0]);if(!a)throw new Error("usage: el access <link> [--private | --unlisted] [--allow a,b] [--remove a,b] [--approve email] [--deny email] [--json]");const $=await(await import("../src/account.js")).shareKey();if(!$)throw Object.assign(new Error("Sign in first: `el login`."),{plain:!0});const x=e({server:i(n.opt.server),key:$}),v=l=>l?l.split(/[\s,]+/).filter(Boolean):[],p={};r.has("--private")&&(p.visibility="private"),r.has("--unlisted")&&(p.visibility="unlisted");for(const[l,f]of[["allow","add"],["remove","remove"],["approve","approve"],["deny","deny"]])n.opt[l]&&(p[f]=v(n.opt[l]));let d;try{d=Object.keys(p).length?await x.update(a,p):await x.get(a)}catch(l){const f=o(l);throw Object.assign(new Error(f||l.body?.error?.message||l.message),{plain:!!(f||l.body)})}if(r.has("--json"))return console.log(JSON.stringify(d,null,2));console.log(`${d.viewUrl}
43
+ ${t(d)}`);for(const l of d.requests)console.log(` wants access: ${l.email}${l.name?` (${l.name})`:""} el access ${a} --approve ${l.email}`);return}case"tabs":return u(async e=>{const t=await e.request("tabs",{pid:m()});console.log(t.length?t.map(s=>`${s.tabId} ${s.url} ${s.title||""}`).join(`
44
+ `):"no tabs assigned to this session")});case"sessions":return u(async e=>{const t=await e.request("sessions");console.log(t.length?t.map(H).join(`
45
+ `):"no agent sessions connected")});case"status":return u(async e=>{const t=await e.request("status");console.log(`hub pid ${t.pid} \xB7 ${t.browsers.length} browser(s) \xB7 ${t.sessions.length} session(s) \xB7 ${t.picks} pick(s)`),t.plan&&console.log(` plan ${t.plan.plan}${t.plan.features.length?` \xB7 ${t.plan.features.join(", ")}`:""}`);const s=t.pickLimit;s&&console.log(` picks ${s.signIn?`need sign-in (el login) \xB7 free: ${s.limit} a week`:s.limit==null?"unlimited":`${s.used} of ${s.limit} this week \xB7 resets ${s.resetsAt.slice(0,10)}`}`);for(const o of t.browsers)console.log(` browser ${o.id}: ${o.tabs} assigned tab(s)`);for(const o of t.sessions)console.log(` ${H(o)} \xB7 ${o.pending} pending \xB7 ${o.watchers?"band connected":"no band"}`)});case"watch":return _(r.has("--json"));case"take":return u(async e=>{const t=process.stdin.isTTY?"":await C(),{picks:s,notice:o}=await e.request("take",{pid:m(),prompt:t,ids:W()});r.has("--json")?(s.length||o)&&process.stdout.write(JSON.stringify({count:s.length,context:s.length?J(s):"",shots:s.map(i=>i.shot).filter(Boolean),...o?{notice:o}:{}})):(s.length||o)&&process.stdout.write([o,s.length?J(s):""].filter(Boolean).join(`
46
+
47
+ `))});case"prompt":return u(async e=>{const t=process.stdin.isTTY?O.join(" "):await C();await e.request("prompt",{pid:m(),prompt:t})});case"clear":return u(e=>e.request("clear",{pid:m(),ids:W()}));case"license":return(await import("../src/license.js")).runLicenseCli(O,r);case"login":return(await import("../src/account.js")).runLogin({server:k("--server")[0],noBrowser:r.has("--no-browser")});case"logout":return(await import("../src/account.js")).runLogout();case"whoami":return(await import("../src/account.js")).runWhoami({json:r.has("--json")});case"stop":return u(e=>e.request("shutdown"));default:console.log(G)}}R().catch(e=>{console.error(e.plain?e.message:`el: ${e.message}`),process.exit(1)});
@@ -0,0 +1,6 @@
1
+ {
2
+ "name": "element",
3
+ "version": "0.2.1",
4
+ "description": "Shows elements you picked in the browser above the prompt, attaches them to your next message, and teaches the agent the el CLI",
5
+ "types": "./types/index.d.ts"
6
+ }
@@ -0,0 +1 @@
1
+ const p=/\[(?:element #\d+|el_\d+(?: [^\]\n]*)?)\]/g,l=/^\[(?:element #|el_)(\d+)/,s={};function u(t){return`[element #${t.id.replace(/^el_/,"")}]`}function o(t){return[...t.matchAll(p)].map(e=>({id:(()=>{const n=l.exec(e[0])?.[1];return n?`el_${n}`:""})(),start:e.index,end:e.index+e[0].length}))}function m(t){return o(t).map(({start:e,end:n})=>({start:e,end:n,...s}))}function a(t,e,n,c){if(c!==""||n-e!==1)return null;const r=o(t).find(i=>e>=i.start&&e<i.end);return r?{text:t.slice(0,r.start)+t.slice(r.end).replace(/^ /,""),cursor:r.start}:null}export{s as CHIP_STYLE,u as chip,o as chipsIn,a as deleteChip,m as paint};
@@ -0,0 +1 @@
1
+ { "modules": ["./register.tsx"] }
@@ -0,0 +1,2 @@
1
+ import{atom as h,read as k,update as u}from"claude-code";import{CHIP_STYLE as x,chip as S,chipsIn as b,deleteChip as v,paint as y}from"./chips";const m=h({plugin:"element",key:"pending"},[]),P=h({plugin:"element",key:"label"},null),p=(...r)=>["/bin/sh","-c",'exec "$HOME/.element/el" "$@"',"el",...r],g=new Set;let f=null;async function I(r,e){const{text:t,cursor:o}=await r.prompt.read(),i=o>0&&!/\s$/.test(t.slice(0,o))?" ":"",n=S(e);(await r.prompt.fill({text:`${i}${n} `,mode:"insert",decorations:[{start:i.length,end:i.length+n.length,...x}]})).isFilled?g.add(e.id):r.ui.toast(`Picked ${n}: type it into your message to send it`)}const O=r=>{r("session.start",async(e,t,o)=>{const i=await o(t);return t.isInteractive&&(async()=>{for(;;){let n="";try{for await(const c of e.process.spawn({argv:p("watch","--json")})){if(c.stream!=="stdout")continue;n+=c.text;let a;for(;(a=n.indexOf(`
2
+ `))>=0;){const d=n.slice(0,a);n=n.slice(a+1);let s;try{s=JSON.parse(d)}catch{continue}s.type==="session"?await u(e,P,()=>s.session.label):s.type==="disconnected"?await u(e,m,()=>[]):s.type==="notice"?f=s.notice:s.type==="pending"&&(await u(e,m,()=>s.picks),s.picked&&s.picks.some(w=>w.id===s.picked.id)&&!g.has(s.picked.id)&&await I(e,s.picked))}}}catch(c){e.ui.log(`element: watcher stopped: ${String(c)}`,{to:"debug"})}await e.clock.sleep(5e3)}})(),i}),r("prompt.edit",async(e,t,o)=>{const i=v(t.text,t.start,t.end,t.inputText);if(i)return{...i,decorations:y(i.text)};const n=await o(t),c=y(n.text);return c.length?{...n,decorations:[...n.decorations??[],...c]}:n}),r("prompt.submit",async(e,t,o)=>{try{const i=[...new Set(b(t.text).map(l=>l.id))],n=(await k(e,m)).map(l=>l.id).filter(l=>!i.includes(l));g.clear();const c=f;if(f=null,n.length&&e.process.run(p("clear","--ids",n.join(","))).catch(()=>{}),!i.length)return e.process.run(p("prompt"),{stdin:t.text,timeoutMs:5e3}).catch(()=>{}),o(c?{...t,context:[...t.context??[],c]}:t);const{stdout:a}=await e.process.run(p("take","--json","--ids",i.join(",")),{stdin:t.text,timeoutMs:5e3}),d=a.trim()?JSON.parse(a):null,s=[d?.notice||c,d?.context].filter(Boolean);if(s.length)return o({...t,context:[...t.context??[],...s]})}catch(i){e.ui.log(`element: could not attach picks: ${String(i)}`,{to:"debug"})}return o(t)})};export{O as register};
@@ -0,0 +1,21 @@
1
+ ---
2
+ name: element
3
+ description: "Use when the user refers to elements they picked or pointed at in their browser, an [element #N] chip in their message, or a share.elmts.dev link. Covers the `el` CLI (picks, snapshots, share links, browser tabs)."
4
+ ---
5
+
6
+ The user picks elements in their real browser with the element extension. Picks arrive in their message as `[element #N]` chips with the details attached. The `el` CLI does the rest:
7
+
8
+ - `el get`: this session's picks (selector, text, styles, component/source file, screenshot path). `--all` for recent history.
9
+ - `el snapshot <id> --text [--part <n>]`: the element's full layout as HTML + CSS, for recreating or matching its design. Big ones come in parts; the header says how many.
10
+ - `el snapshot <id>`: saves the full snapshot as one HTML file and prints its path.
11
+ - `el snapshot --selector <css> --tab <tab-id> [--text]`: capture something the user didn't pick.
12
+ - `el share <id>`: a share link for people and agents (counts toward the user's plan; only when asked).
13
+ - `el open <url>` / `el tabs`: open a tab owned by this session / list its tabs; picks there come to you.
14
+ - A share.elmts.dev link: `curl` it to read the element as markdown. A private link answers 401/403; read it with `el fetch <link>` (sends the user's element account key, only to the share server; `--json`, `--html`), which works when that account is allowed. Otherwise tell the user to ask the owner (`el fetch` prints how).
15
+ - `el share <id> --private [--allow a@b.com,@company.com]` / `el access <link>`: a link only those people can open (Pro, Max) / see or change who can open it; only when asked.
16
+
17
+ `<id>` is the pick id: `el_7` or just `7` for `[element #7]`.
18
+
19
+ If `el get` or the message says the account has used its free picks this week, or that picking needs a signed-in account, the user's pick didn't reach you: pass that on in a sentence (upgrade link, the reset, or `el login`) and don't guess what they picked.
20
+
21
+ `el` talks to a local hub over a socket. If it says a sandbox blocks the socket, run the same `el` command again outside the sandbox (request escalated permissions).
@@ -0,0 +1,15 @@
1
+ export type PickSummary = {
2
+ id: string
3
+ tag: string
4
+ name: string
5
+ selector: string
6
+ component: string | null
7
+ host: string
8
+ createdAt: number
9
+ }
10
+
11
+ declare module 'claude-code' {
12
+ interface PluginState {
13
+ element: { pending: PickSummary[]; label: string | null }
14
+ }
15
+ }
@@ -0,0 +1,14 @@
1
+ const vscode=require("vscode"),net=require("node:net"),fs=require("node:fs"),os=require("node:os"),path=require("node:path"),crypto=require("node:crypto"),{spawn}=require("node:child_process"),{pathToFileURL}=require("node:url"),HOME=process.env.ELEMENT_HOME||path.join(os.homedir(),".element"),SOCKET=path.join(HOME,"hub.sock"),SHIM=path.join(HOME,"el"),CURSOR_DIR=path.join(HOME,"cursor"),CHIPS=path.join(CURSOR_DIR,"chips"),PILLED=path.join(CURSOR_DIR,"pilled.json"),PID=process.pid;let bar,log={appendLine(){}},hub=null,session=null,pending=[],disposed=!1,spawned=!1;function connect(){return new Promise((t,e)=>{const n=net.createConnection(SOCKET),o=new Map;let i=0,c="";const s={onEvent:()=>{},onClose:()=>{},request(l,a={}){const d=++i;return new Promise((r,u)=>{o.set(d,{res:r,rej:u}),n.write(JSON.stringify({id:d,op:l,...a})+`
2
+ `)})},close:()=>n.destroy()};n.setEncoding("utf8"),n.on("data",l=>{c+=l;let a;for(;(a=c.indexOf(`
3
+ `))>=0;){const d=c.slice(0,a);c=c.slice(a+1);let r;try{r=JSON.parse(d)}catch{continue}if(r.replyTo===void 0)s.onEvent(r);else{const u=o.get(r.replyTo);o.delete(r.replyTo),r.error?u?.rej(new Error(r.error)):u?.res(r.result)}}}),n.once("connect",()=>t(s)),n.once("error",e),n.on("close",()=>{for(const l of o.values())l.rej(new Error("hub closed"));s.onClose()})})}async function run(){for(;!disposed;){try{hub=await connect()}catch(e){(hub!==null||!spawned)&&log.appendLine(`hub at ${SOCKET}: ${e.message}`),hub=null,!spawned&&fs.existsSync(SHIM)&&(spawned=!0,spawn(SHIM,["hub"],{detached:!0,stdio:"ignore"}).unref()),render("offline"),await sleep(3e3);continue}const t=new Promise(e=>hub.onClose=e);hub.onEvent=e=>{if(e.type!=="pending")return;pending=e.picks||[],render(),e.picked&&pending.some(o=>o.id===e.picked.id)&&vscode.workspace.getConfiguration("element").get("addToChat",!0)&&addToChat([e.picked.id],{quiet:!0})};try{const e=vscode.workspace.workspaceFolders?.[0]?.uri.fsPath;session=await hub.request("ensure",{pid:PID,cwd:e}),e&&writeJson(path.join(CURSOR_DIR,"ide",`${crypto.createHash("sha1").update(e).digest("hex").slice(0,16)}.json`),{pid:PID}),session=await hub.request("watch",{pid:PID})||session,log.appendLine(`watching ${session?.label} (pid ${PID}, ${e||"no folder"})`),render(),await t}catch(e){log.appendLine(`hub: ${e.message}`),hub.close()}hub=null,render("offline"),await sleep(1500)}}function tagClass(t,e){const n=String(e||"").split(/\s*[>+~]\s*|\s+/).pop()||"",o=/\.((?:\\.|[\w-])+)/.exec(n)?.[1]?.replace(/\\(.)/g,"$1");return o?`${t||"element"}.${o.slice(0,24)}`:t||"element"}const num=t=>String(t).replace(/^el_/,""),chip=t=>`[element #${num(t.id)}] ${tagClass(t.tag||t.data?.tag,t.selector||t.data?.selector)}${t.host?` \xB7 ${t.host}`:""}`;function render(t){if(!bar)return;if(t==="offline"){bar.text="$(target) element",bar.tooltip="element: not connected (run `el install --cursor`, and open your browser)";return}const e=pending.slice(0,2).map(chip).join(" ");bar.text=pending.length?`$(target) ${e}${pending.length>2?` +${pending.length-2}`:""}`:"$(target) element";const n=new vscode.MarkdownString;if(n.appendMarkdown(`**element** \xB7 ${session?.label||"this window"}
4
+
5
+ `),pending.length){n.appendMarkdown(`Waiting for your next message:
6
+
7
+ `);for(const o of pending)n.appendMarkdown(`- \`${chip(o)}\`${o.name?` ${o.name.slice(0,40)}`:""}
8
+ `)}else n.appendMarkdown("Hold \u2325 and click elements in your browser to pick them for this window.");bar.tooltip=n}async function writeChipFiles(t){const{picks:e}=await hub.request("get",{pid:PID,peek:!0}),n=e.filter(s=>t.includes(s.id)&&!s.seen),o=await formatter(),i=readJson(PILLED,{}),c=[];for(const s of n){const l=tagClass(s.data?.tag,s.data?.selector).replace(/[^\w.-]+/g,"-").slice(0,40),a=path.join(CHIPS,`element-${num(s.id)}-${l}.md`),d=o?o([s]):JSON.stringify(s.data,null,2);writeText(a,`${chip({...s,host:host(s.data?.url)})}
9
+
10
+ The user picked this element in their browser (element). They may call it "this".
11
+
12
+ ${d}
13
+ `),i[s.id]=s.createdAt,c.push(a)}return writeJson(PILLED,i),c}async function addToChat(t,{quiet:e=!1}={}){if(!(!hub||!t.length))try{const n=await writeChipFiles(t);for(const o of n){const i=vscode.Uri.file(o);try{await vscode.commands.executeCommand("composer.addfilestocomposer",i,{useExactResource:!0})}catch{await vscode.commands.executeCommand("composer.focusComposer"),await vscode.commands.executeCommand("composer.addfilestocomposer",i,{useExactResource:!0})}}}catch(n){log.appendLine(`add to chat: ${n.message}`),e||vscode.window.showWarningMessage(`element: couldn't add to chat (${n.message}). The picks still go with your next message.`)}}let formatPromise;function formatter(){return formatPromise??=(async()=>{try{const t=fs.readFileSync(SHIM,"utf8"),e=/'([^']*\/bin\/el\.js)'/.exec(t)?.[1],n=await import(pathToFileURL(path.join(path.dirname(path.dirname(e)),"src","format.js")).href);return o=>n.formatPicks(o,{html:!0})}catch{return null}})(),formatPromise}const sleep=t=>new Promise(e=>setTimeout(e,t));function host(t){try{return new URL(t).host}catch{return""}}function readJson(t,e){try{return JSON.parse(fs.readFileSync(t,"utf8"))}catch{return e}}function writeText(t,e){fs.mkdirSync(path.dirname(t),{recursive:!0,mode:448}),fs.writeFileSync(t,e)}const writeJson=(t,e)=>writeText(t,JSON.stringify(e));exports.activate=t=>{const e=vscode.window.createOutputChannel?.("element");log={appendLine(n){e?.appendLine(n);try{fs.mkdirSync(CURSOR_DIR,{recursive:!0}),fs.appendFileSync(path.join(CURSOR_DIR,"extension.log"),`${new Date().toISOString()} [${PID}] ${n}
14
+ `)}catch{}},dispose:()=>e?.dispose()},bar=vscode.window.createStatusBarItem("element.picks",vscode.StatusBarAlignment.Left,100),bar.name="element picks",bar.command="element.picks",render("offline"),bar.show(),t.subscriptions.push(bar,...log.dispose?[log]:[],vscode.commands.registerCommand("element.addToChat",()=>addToChat(pending.map(n=>n.id))),vscode.commands.registerCommand("element.clear",()=>hub?.request("clear",{pid:PID})),vscode.commands.registerCommand("element.picks",async()=>{if(!pending.length)return vscode.window.showInformationMessage(`element (${session?.label||"not connected"}): nothing picked. Hold \u2325 and click elements in your browser.`);const n=await vscode.window.showQuickPick(pending.map(i=>({label:chip(i),description:i.name||"",id:i.id,picked:!0})),{canPickMany:!0,title:"element: picks waiting for your next message"});if(!n?.length)return;const o=await vscode.window.showQuickPick(["Add to chat","Drop"],{title:`${n.length} pick(s)`});o==="Add to chat"&&addToChat(n.map(i=>i.id)),o==="Drop"&&hub?.request("clear",{pid:PID,ids:n.map(i=>i.id)})})),run()},exports.deactivate=()=>{disposed=!0,hub?.close()};
@@ -0,0 +1,30 @@
1
+ {
2
+ "name": "element-cursor",
3
+ "displayName": "element",
4
+ "description": "Shows the elements you pick in your browser (hold ⌥ and click) and puts them in Cursor's chat as chips.",
5
+ "version": "0.1.0",
6
+ "publisher": "elmts",
7
+ "license": "SEE LICENSE IN LICENSE",
8
+ "homepage": "https://elmts.dev",
9
+ "engines": { "vscode": "^1.80.0" },
10
+ "categories": ["Other"],
11
+ "main": "./extension.js",
12
+ "activationEvents": ["onStartupFinished"],
13
+ "contributes": {
14
+ "commands": [
15
+ { "command": "element.picks", "title": "element: Show picks" },
16
+ { "command": "element.addToChat", "title": "element: Add picks to chat" },
17
+ { "command": "element.clear", "title": "element: Drop waiting picks" }
18
+ ],
19
+ "configuration": {
20
+ "title": "element",
21
+ "properties": {
22
+ "element.addToChat": {
23
+ "type": "boolean",
24
+ "default": true,
25
+ "description": "Put each pick in the chat input as a file chip (element-7-div.plan.md) as soon as it's picked. Off: picks wait in the status bar and go with your next message."
26
+ }
27
+ }
28
+ }
29
+ }
30
+ }
@@ -0,0 +1,2 @@
1
+ importScripts("hotkeys.js");const HK=globalThis.ELEMENT_HOTKEYS,HOST="dev.element.bridge";let port=null,hubUp=!1,lastError="",reconnectDelay=1e3,reconnectTimer=null,sessions=[],plan=null,acct=null,acctAt=0,reqSeq=0;const pending=new Map,stashed=new Map,state={owners:{}},ready=chrome.storage.session.get(["owners","groups","sessions"]).then(async e=>{state.owners=e.owners||{},sessions=e.sessions||[];const t=Object.keys(e.groups||{}).map(Number);if(t.length){for(const n of t){const r=await chrome.tabs.query({groupId:n}).catch(()=>[]);r.length&&await chrome.tabs.ungroup(r.map(a=>a.id)).catch(()=>{})}await chrome.storage.session.remove("groups")}});function save(){chrome.storage.session.set({owners:state.owners,sessions}),pushModules()}const modulePorts=new Set;let moduleTimer=null;function pushModules({account:e=!1}={}){if(e)for(const t of modulePorts)t.accountStale=!0;moduleTimer||!modulePorts.size||(moduleTimer=setTimeout(()=>{moduleTimer=null;for(const t of modulePorts){const n=t.sender?.tab?.id;try{t.postMessage({type:"module_state",...moduleState(n),accountStale:!!t.accountStale}),t.accountStale=!1}catch{}}},50))}chrome.runtime.onConnect.addListener(e=>{e.name==="module"&&(modulePorts.add(e),e.onDisconnect.addListener(()=>modulePorts.delete(e)),refreshAccess().catch(()=>{}),accessTimer||=setInterval(()=>modulePorts.size?refreshAccess().catch(()=>{}):(clearInterval(accessTimer),accessTimer=null),6e4))});let accessTimer=null;function connect(){if(!port){clearTimeout(reconnectTimer);try{port=chrome.runtime.connectNative(HOST)}catch(e){lastError=String(e.message||e),port=null,scheduleReconnect();return}port.onMessage.addListener(onHubMessage),port.onDisconnect.addListener(()=>{lastError=chrome.runtime.lastError?.message||"disconnected",port=null,hubUp=!1,pushModules();for(const e of pending.values())e.reject(new Error("hub disconnected"));pending.clear(),scheduleReconnect()})}}function scheduleReconnect(){clearTimeout(reconnectTimer),reconnectTimer=setTimeout(connect,reconnectDelay),reconnectDelay=Math.min(reconnectDelay*2,3e4)}function request(e,t={},n=15e3,r=null){if(!port||!hubUp)return Promise.reject(new Error("Element hub not connected \u2014 run `el install` and reload the extension"));const a=++reqSeq;return new Promise((o,s)=>{const u=setTimeout(()=>{pending.delete(a)&&s(new Error("hub did not answer"))},n);pending.set(a,{onProgress:r,resolve:c=>{clearTimeout(u),o(c)},reject:c=>{clearTimeout(u),s(c)}}),port.postMessage({id:a,op:e,...t})})}function reply(e,t,n){port&&port.postMessage(n?{replyTo:e,error:n}:{replyTo:e,result:t})}async function onHubMessage(e){if(e.replyTo!==void 0&&pending.has(e.replyTo)){const t=pending.get(e.replyTo);pending.delete(e.replyTo),e.error?t.reject(Object.assign(new Error(e.error),e.code?{code:e.code,data:e.data||null}:{})):t.resolve(e.result);return}switch(e.type){case"account_changed":acct=null,pushModules({account:!0});break;case"share_progress":try{pending.get(e.reqId)?.onProgress?.(e)}catch(t){console.warn("share progress",t)}break;case"held_sent":heldSent(e.picks);break;case"saved":savedFromCli(e.save).catch(t=>console.warn("saving el share link failed",t));break;case"hub_up":{hubUp=!0,lastError="",reconnectDelay=1e3,await ready;try{const t=await request("hello",{role:"browser",owners:state.owners,tabs:await ownedTabs()});await setSessions(t.sessions,t.plan)}catch(t){lastError=t.message}pushModules({account:!0});break}case"hub_down":hubUp=!1,pushModules();break;case"sessions":await setSessions(e.sessions,e.plan);break;case"cmd":handleCmd(e).then(t=>reply(e.cmdId,t),t=>reply(e.cmdId,null,t.message||String(t)));break}}async function handleCmd(e){if(e.cmd==="open")return openForSession(e.url,e.session);if(e.cmd==="snapshot")return snapshotTab(e.tabId,e.selector,e.url,{motion:!!e.motion});if(e.cmd==="shot")return{png:await cleanShot(e.tabId,e.selector,e.url)};throw new Error(`unknown command ${e.cmd}`)}async function setSessions(e,t){await ready,sessions=e||[],t&&(JSON.stringify(t)!==JSON.stringify(plan)&&(acct=null,pushModules({account:!0})),plan=t);const n=new Set(sessions.map(a=>a.id)),r=Object.keys(state.owners).filter(a=>!n.has(state.owners[a])).map(Number);r.length&&await unassign(r),save(),refreshBadges()}function sessionById(e){return sessions.find(t=>t.id===e)}async function assign(e,t){if(!sessionById(t))throw new Error("that session is gone");for(const n of e)state.owners[n]=t;save(),refreshBadges(),pushTabs()}async function unassign(e){for(const t of e)delete state.owners[t];save(),refreshBadges(),pushTabs()}async function ownedTabs(){const e=[];for(const[t,n]of Object.entries(state.owners)){const r=await chrome.tabs.get(Number(t)).catch(()=>null);if(!r){delete state.owners[t];continue}e.push({tabId:r.id,url:r.url,title:r.title,session:n,active:r.active})}return e}let pushTimer=null;function pushTabs(){clearTimeout(pushTimer),pushTimer=setTimeout(async()=>{hubUp&&request("tabs_update",{tabs:await ownedTabs()}).catch(()=>{})},200)}async function openForSession(e,t){for(const[a,o]of Object.entries(state.owners)){if(o!==t)continue;const s=await chrome.tabs.get(Number(a)).catch(()=>null);if(s&&s.url===e)return await chrome.tabs.update(s.id,{active:!0}),await chrome.windows.update(s.windowId,{focused:!0}),{tabId:s.id,reused:!0}}const n=await chrome.windows.getLastFocused({windowTypes:["normal"]}).catch(()=>null),r=await chrome.tabs.create({url:e,active:!0,...n?{windowId:n.id}:{}});return await assign([r.id],t),{tabId:r.id}}function refreshBadges(){chrome.tabs.query({}).then(e=>{for(const t of e){const n=sessionById(state.owners[t.id]);chrome.action.setBadgeText({tabId:t.id,text:n?n.project.slice(0,4):""}).catch(()=>{})}})}let menuChain=Promise.resolve();function rebuildMenus(){return menuChain=menuChain.then(async()=>{const t=(await chrome.commands.getAll().catch(()=>[])).find(n=>n.name==="switch-session")?.shortcut;await new Promise(n=>chrome.contextMenus.removeAll(n)),await new Promise(n=>chrome.contextMenus.create({id:"el-open",title:t?`Open element (${t})`:"Open element",contexts:["page","frame","selection","link","image","editable"]},()=>{chrome.runtime.lastError,n()}))}).catch(e=>console.warn("menu build failed",e)),menuChain}function injectable(e){return/^(https?|file):/.test(e||"")&&!/^https:\/\/(chromewebstore\.google\.com|chrome\.google\.com\/webstore)(\/|$)/.test(e)}function syncPopup(e){e?.id==null||e.id<0||chrome.action.setPopup({tabId:e.id,popup:injectable(e.url)?"":"popup.html"}).catch(()=>{})}chrome.tabs.query({}).then(e=>e.forEach(syncPopup)).catch(()=>{});function injectPicker(e){return chrome.scripting.executeScript({target:{tabId:e},files:["logos.js","hotkeys.js","picker.js"]})}async function startPicker(e,t={}){await injectPicker(e),await chrome.tabs.sendMessage(e,{type:"picker_start",...t})}async function openModule(e,t=null){await ready,await injectPicker(e),await chrome.tabs.sendMessage(e,{type:"module_open",tab:t,...moduleState(e)})}function moduleState(e){return{sessions,owner:state.owners[e]||null,tabId:e,hubUp,lastError,plan}}function openModuleOrWindow(e,t=null){return e&&injectable(e.url)?openModule(e.id,t).catch(()=>openSessions(e.id)):openSessions(e?.id)}function toBase64(e){const t=new Uint8Array(e);let n="";for(let r=0;r<t.length;r+=32768)n+=String.fromCharCode(...t.subarray(r,r+32768));return btoa(n)}const CAPTURE_GAP=520;let captureChain=Promise.resolve(),lastCaptureAt=0;const captureSlot=async()=>{const e=lastCaptureAt+CAPTURE_GAP-Date.now();e>0&&await new Promise(t=>setTimeout(t,e))};function captureTab(e,t=null){const n=captureChain.then(async()=>{await captureSlot(),t!=null&&await overlay(t,!0);try{return await chrome.tabs.captureVisibleTab(e,{format:"png"})}finally{lastCaptureAt=Date.now(),t!=null&&overlay(t,!1)}});return captureChain=n.catch(()=>{}),n}function overlay(e,t){return chrome.tabs.sendMessage(e,{type:"overlay",hidden:t}).catch(()=>{})}const MAX_SHOT_BYTES=4.5*1024*1024;async function cropShot(e,t,{x:n,y:r,x2:a,y2:o}){const s=await createImageBitmap(await(await fetch(e)).blob()),u=s.width/t;if(o=Math.min(o,s.height/u),a-n<4||o-r<4)return null;const c=(a-n)*u,i=(o-r)*u;let l=Math.min(1,1400/Math.max(c,i));for(let d=0;d<4;d++,l*=.7){const h=new OffscreenCanvas(Math.max(1,Math.round(c*l)),Math.max(1,Math.round(i*l)));h.getContext("2d").drawImage(s,n*u,r*u,c,i,0,0,h.width,h.height);const f=await h.convertToBlob({type:"image/png"});if(f.size<=MAX_SHOT_BYTES)return toBase64(await f.arrayBuffer())}return null}async function captureCrop(e,t,n=!1){try{const r=await captureTab(e.windowId,n?e.id:null),a=t.viewport.w,o=t.viewport.h,s=t.rect,u=s.x+s.w/2,c=s.y+s.h/2,i=Math.max(s.w+32,240),l=Math.max(s.h+32,120);return await cropShot(r,a,{x:Math.max(0,u-i/2),y:Math.max(0,c-l/2),x2:Math.min(a,u+i/2),y2:Math.min(o,c+l/2)})}catch(r){return console.warn("screenshot failed",r),null}}let lastShotHeldMs=null,shotChain=Promise.resolve();function cleanShot(e,t,n){const r=shotChain.then(()=>cleanShotNow(e,t,n));return shotChain=r.catch(()=>{}),r}async function cleanShotNow(e,t,n){const r=await chrome.tabs.get(Number(e));if(!injectable(r.url))throw new Error(`can't capture ${r.url}`);if(n&&r.url.split("#")[0]!==String(n).split("#")[0])throw new Error("the tab has moved on");if(!r.active)throw new Error("the tab is not showing");if(!t)throw new Error("no selector");const a={tabId:r.id},o=async(l,d=[])=>(await chrome.scripting.executeScript({target:a,func:l,args:d}))[0]?.result,s=()=>o(()=>globalThis.__elementShot?.restore()).catch(()=>{});await chrome.scripting.executeScript({target:a,files:["shot.js"]}),await captureChain,await captureSlot(),await overlay(r.id,!0);let u,c;try{if(c=await o(l=>globalThis.__elementShot.prepare(l).catch(d=>({error:String(d.message||d)})),[t]),!c?.ok)throw await s(),new Error(c?.error||"shot script did not answer");try{u=await captureTab(r.windowId)}finally{lastShotHeldMs=await s()}}finally{overlay(r.id,!1)}const i=await cropShot(u,c.viewport.w,c.rect);if(!i)throw new Error("element too small to capture");return i}function frameworkProbe(e){const t=document.querySelector(`[data-el-mark="${e}"]`);if(!t)return null;t.removeAttribute("data-el-mark");const n={framework:null,components:[],source:null,sourceApprox:!1,props:null},r=c=>{if(!c||typeof c!="object")return null;const i=[];for(const[l,d]of Object.entries(c)){if(l==="children"||i.length>=12)continue;let h;d==null||typeof d=="number"||typeof d=="boolean"?h=String(d):typeof d=="string"?h=JSON.stringify(d.length>60?d.slice(0,60)+"\u2026":d):typeof d=="function"?h="\u0192":Array.isArray(d)?h=`[${d.length}]`:d.$$typeof?h="<element>":h="{\u2026}",i.push(`${l}: ${h}`)}return i.length?`{ ${i.join(", ")} }`:null},a=c=>{for(const i of String(c).split(`
2
+ `)){const l=i.match(/((?:https?|webpack-internal|file):\/\/[^\s)]+?):(\d+):(\d+)\)?\s*$/);if(!l)continue;const d=l[1];if(/node_modules|\/\.vite\/deps\/|react-dom|react\.development|jsx-dev-runtime|\/chunk-|\/_next\/static\/chunks\//.test(d))continue;return`${d.replace(/^https?:\/\/[^/]+/,"").replace(/^webpack-internal:\/\/\/(\([^)]*\)\/)?/,"").replace(/\?.*$/,"")}:${l[2]}`}return null};for(let c=t,i=0;c&&c.getAttribute&&i<15&&!n.source;c=c.parentElement,i++){const l=c.getAttribute("data-insp-path")||c.getAttribute("data-src")||c.getAttribute("data-source-file");l&&(n.source=l.split(":").slice(0,2).join(":"));const d=c.getAttribute("data-inspector-relative-path");d&&(n.source=`${d}:${c.getAttribute("data-inspector-line")||""}`)}const o=c=>c&&Object.keys(c).find(i=>i.startsWith("__reactFiber$")||i.startsWith("__reactInternalInstance$"));let s=t,u=o(s);for(;s&&!u;)s=s.parentElement,u=o(s);if(u){n.framework="react";const c=l=>!l||typeof l=="string"?null:typeof l=="function"?l.displayName||l.name||null:l.displayName||c(l.render)||c(l.type)||null;let i=s[u];for(;i&&n.components.length<6;){const l=c(i.type);l&&(n.components.length||(n.props=r(i.memoizedProps)),n.components.push(l),!n.source&&i._debugSource&&(n.source=`${i._debugSource.fileName}:${i._debugSource.lineNumber}`)),i=i.return}if(!n.source&&s[u]._debugStack){const l=a(s[u]._debugStack.stack);l&&(n.source=l,n.sourceApprox=!0)}}if(!n.framework){let c=t;for(;c&&!c.__vueParentComponent;)c=c.parentElement;let i=c&&c.__vueParentComponent;for(i&&(n.framework="vue");i&&n.components.length<6;){const l=i.type||{},d=l.__name||l.name||l.__file&&l.__file.split("/").pop().replace(/\.vue$/,"");d&&n.components.push(d),!n.source&&l.__file&&(n.source=l.__file),i=i.parent}}if(!n.source)for(let c=t,i=0;c&&i<10;c=c.parentElement,i++){const l=c.__svelte_meta;if(l&&l.loc){n.source=`${l.loc.file}:${l.loc.line+1}`,n.framework||="svelte";break}}return n.framework||n.source?n:null}async function probeFramework(e,t){try{const[n]=await chrome.scripting.executeScript({target:{tabId:e},world:"MAIN",func:frameworkProbe,args:[t]});return n?.result||null}catch(n){return console.warn("framework probe failed",n),null}}async function sendPick(e){try{const t=await request("pick",{data:e.data,react:e.react,png:e.png,session:e.session,tabId:e.tabId}),n=sessions.find(r=>r.label===t.label)?.harness;return chrome.storage.local.get("firstPick").then(r=>r.firstPick||chrome.storage.local.set({firstPick:{at:Date.now(),harness:n||null}})),t.picks&&acct&&(acct={...acct,picks:t.picks},pushModules({account:!0})),{ok:!0,id:t.id,label:t.label,harness:n}}catch(t){return t.code==="pick_signed_out"||t.code==="pick_limit"?(t.code==="pick_limit"&&acct&&t.data&&(acct={...acct,picks:{used:t.data.used,limit:t.data.limit,resetsAt:t.data.resetsAt,window:"week"}}),{ok:!1,code:t.code,data:t.data||{},error:t.message}):{ok:!1,error:t.message}}}function heldSent(e){for(const t of e||[])t.tabId!=null&&chrome.tabs.sendMessage(t.tabId,{type:"pick_sent",label:t.label,harness:t.harness,id:t.id,tag:t.tag}).catch(()=>{});acct=null,pushModules({account:!0})}async function handlePicked(e,t){const[n,r]=await Promise.all([captureCrop(e,t.data),probeFramework(e.id,t.mark)]),a={data:t.data,react:r,png:n,tabId:e.id,session:state.owners[e.id]||null};if(!a.session){if(sessions.length===1)a.session=sessions[0].id,await assign([e.id],a.session);else if(sessions.length>1){const o=Math.random().toString(36).slice(2);return stashed.set(o,a),{needChoice:!0,token:o,sessions}}}return sendPick(a)}async function handleChoice(e,t){const n=stashed.get(e);return stashed.delete(e),!n||!t?{ok:!1}:(await assign([n.tabId],t),sendPick({...n,session:t}))}const snapChains=new Map;function snapshotTab(e,t,n,r={}){const a=Number(e),o=(snapChains.get(a)||Promise.resolve()).then(()=>snapshotTabNow(e,t,n,r)),s=o.catch(()=>{});return snapChains.set(a,s),s.then(()=>snapChains.get(a)===s&&snapChains.delete(a)),o}async function snapshotTabNow(e,t,n,{motion:r=!1}={}){const a=await chrome.tabs.get(Number(e)).catch(()=>null);if(!a)throw new Error(`tab ${e} is closed`);if(!injectable(a.url))throw new Error(`can't snapshot ${a.url}`);if(!t)throw new Error("no selector to snapshot");r&&(r=(await chrome.storage.local.get("snapshotMotion").catch(()=>({}))).snapshotMotion!==!1),await chrome.scripting.executeScript({target:{tabId:a.id},files:["snapshot.js"]});const[o]=await chrome.scripting.executeScript({target:{tabId:a.id},func:(u,c)=>globalThis.__elementSnapshot(c?{selector:u,motion:!0}:{selector:u}).catch(i=>({error:String(i.message||i)})),args:[t,r]}),s=o?.result;if(!s)throw new Error("snapshot script did not answer");if(s.error){const u=n&&a.url!==n?` (the tab is now at ${a.url})`:"";throw new Error(/not found/.test(s.error)?`${t} is no longer on the page in tab ${a.id}${u}`:s.error)}return s}const MIME={png:"image/png",jpg:"image/jpeg",jpeg:"image/jpeg",gif:"image/gif",webp:"image/webp",avif:"image/avif",svg:"image/svg+xml",ico:"image/x-icon",woff2:"font/woff2",woff:"font/woff",ttf:"font/ttf",otf:"font/otf"},fetchCounts=new Map;async function snapshotFetch(e,t,n){if(n!=null&&fetchCounts.set(n,(fetchCounts.get(n)||0)+1),!/^https?:/i.test(e))throw new Error("not an http(s) URL");const r=await fetch(e,{credentials:"include",cache:"force-cache"});if(!r.ok)throw new Error(`HTTP ${r.status}`);if(t==="text")return{ok:!0,text:await r.text()};const a=await r.arrayBuffer();if(a.byteLength>10*1024*1024)throw new Error("larger than 10 MB");let o=(r.headers.get("content-type")||"").split(";")[0].trim();return(!o||o==="application/octet-stream"||o==="text/plain")&&(o=MIME[new URL(e).pathname.split(".").pop().toLowerCase()]||o||"application/octet-stream"),{ok:!0,bytes:a.byteLength,dataUrl:`data:${o};base64,${toBase64(a)}`}}const shareShots=new Map,EXPIRY_OPTIONS=["1d","7d","30d","never"],ACCOUNT_TTL=6e4;async function accountInfo(e=!1){if(!e&&acct&&Date.now()-acctAt<ACCOUNT_TTL)return acct;try{acct=await request("account",{fresh:e}),acctAt=Date.now()}catch{return acct}return acct}function shareGate(){if(plan&&!plan.signedIn)return{ok:!1,code:"signed_out",error:"Sign in to save & share",...plan.limits?{data:{limits:plan.limits}}:{}};const e=acct&&Date.now()-acctAt<ACCOUNT_TTL?acct.usage:null;return e&&e.limit!=null&&e.used>=e.limit&&!(Date.parse(e.resetsAt)<=Date.now())?{ok:!1,code:"quota_exceeded",data:{...e,plan:acct.plan},error:"out of save & shares this month"}:{ok:!0}}const MAX_SAVES=200,STALE_MS=600*1e3;let savesChain=Promise.resolve();const savePatches=new Map;let saveFlushTimer=null;const liveShares=new Map;function editSaves(e){return savesChain=savesChain.then(async()=>{const{saves:n=[]}=await chrome.storage.local.get("saves");let r=e(Array.isArray(n)?n:[])||n;for(const[o,s]of savePatches){const u=r.findIndex(c=>c.id===o);u>=0&&(r[u]={...r[u],...s})}savePatches.clear();const a=Date.now();r=r.filter(o=>o&&o.id&&!(o.expiresAt&&o.expiresAt<a)).map(o=>o.status==="active"&&a-o.createdAt>STALE_MS&&!liveShares.has(o.id)?{...o,status:"error",error:"interrupted"}:o).slice(0,MAX_SAVES),await chrome.storage.local.set({saves:r})}).catch(n=>console.warn("saves update failed",n)),savesChain}function upsertSave(e){return e={...savePatches.get(e.id),...e},savePatches.delete(e.id),editSaves(t=>{const n=t.findIndex(r=>r.id===e.id);return n>=0?t[n]={...t[n],...e}:t.unshift(e),t})}function patchSave(e,t){savePatches.set(e,{...savePatches.get(e),...t}),saveFlushTimer||(saveFlushTimer=setTimeout(()=>{saveFlushTimer=null,editSaves(n=>n)},250))}const hostOf=e=>{try{return new URL(e).host}catch{return""}},selectorLabel=e=>{const t=String(e||"").split(">").pop().trim();return t.length>48?t.slice(0,47)+"\u2026":t},expiryMs=e=>e&&(typeof e=="number"?e:Date.parse(e))||null;async function thumbOf(e){if(!e)return null;try{const t=typeof e=="string"?await(await fetch(`data:image/png;base64,${e}`)).blob():e,n=await createImageBitmap(t),r=Math.min(160,n.width),a=r/n.width,o=Math.max(1,Math.min(Math.round(n.height*a),120)),s=new OffscreenCanvas(r,o);s.getContext("2d").drawImage(n,0,0,n.width,o/a,0,0,r,o);const u=await s.convertToBlob({type:"image/webp",quality:.75});return`data:${u.type};base64,${toBase64(await u.arrayBuffer())}`}catch(t){return console.warn("thumbnail failed",t),null}}async function shareExpiry(){const{expiry:e}=await chrome.storage.local.get("expiry"),t=acct||(plan?.signedIn?null:{lifetimes:["1d"],defaultLifetime:"1d"}),n=t?.lifetimes?.length?t.lifetimes:EXPIRY_OPTIONS;return EXPIRY_OPTIONS.includes(e)&&n.includes(e)?e:t?.defaultLifetime&&n.includes(t.defaultLifetime)?t.defaultLifetime:n.includes("7d")?"7d":n[0]}async function savedFromCli(e){if(!e||typeof e.viewUrl!="string")return;const t=String(e.id||Math.random().toString(36).slice(2));if(await upsertSave({id:t,via:"cli",status:"done",progress:100,stage:"",title:String(e.title||"").slice(0,200),name:String(e.name||"").slice(0,120),url:String(e.url||""),host:hostOf(e.url)||hostOf(e.viewUrl),selector:selectorLabel(e.selector||e.tag),viewUrl:e.viewUrl,apiUrl:e.apiUrl||"",createdAt:Date.now(),expiresAt:expiryMs(e.expiresAt),...deleteTokenOf(e)}),/^https?:/.test(e.screenshotUrl||"")){const n=await fetch(e.screenshotUrl).catch(()=>null),r=n?.ok?await thumbOf(await n.blob()):null;r&&await upsertSave({id:t,thumb:r})}}const deleteTokenOf=e=>typeof e?.deleteToken=="string"&&/^eldt_[\w-]{8,200}$/.test(e.deleteToken)?{deleteToken:e.deleteToken}:{};function deleteUrlOf(e){let t;try{t=new URL(e.viewUrl)}catch{return null}if(!/^https?:$/.test(t.protocol))return null;try{const r=new URL(e.apiUrl);if(r.origin===t.origin&&/^\/v1\/snapshots\/[\w-]+$/.test(r.pathname))return r.origin+r.pathname}catch{}const n=/^\/s\/([\w-]+)$/.exec(t.pathname)?.[1];return n?`${t.origin}/v1/snapshots/${n}`:null}async function deleteLink(e){const t=deleteUrlOf(e);if(!t)throw new Error("can't tell where this link lives");let n;try{n=await fetch(t,{method:"DELETE",headers:{Authorization:`Bearer ${e.deleteToken}`},signal:AbortSignal.timeout(15e3),credentials:"omit"})}catch{throw new Error(navigator.onLine===!1?"offline":"can't reach the share server")}if(n.ok||n.status===404||n.status===410)return;let r=`HTTP ${n.status}`;throw n.status===401||n.status===403?r="not allowed":n.status>=500&&(r="share server error"),new Error(r)}async function deleteSaves(e,t){e=(Array.isArray(e)?e:[]).map(String);const{saves:n=[]}=await chrome.storage.local.get("saves"),r=(Array.isArray(n)?n:[]).filter(s=>e.includes(s.id)),a=[];t&&await Promise.all(r.filter(s=>s.deleteToken&&s.status==="done").map(s=>deleteLink(s).catch(u=>a.push({id:s.id,error:u.message}))));const o=e.filter(s=>!a.some(u=>u.id===s));return await editSaves(s=>s.filter(u=>!o.includes(u.id))),{ok:!a.length,removed:o,failed:a}}function shareProgress(e,t,n,r){n=Math.max(0,Math.min(100,Math.round(n))),patchSave(e,{progress:n,stage:r}),chrome.tabs.sendMessage(t,{type:"share_progress",token:e,progress:n,stage:r}).catch(()=>{})}async function shareShot(e,t,n={}){const r=shareGate();if(!r.ok)return r;const a=typeof n.token=="string"&&n.token?n.token.slice(0,40):Math.random().toString(36).slice(2);liveShares.set(a,{tabId:e.id}),await upsertSave({id:a,via:"click",status:"active",progress:4,stage:"capturing\u2026",title:String(t.title||e.title||"").slice(0,200),name:String(n.label||"").slice(0,120),url:t.url,host:hostOf(t.url),selector:selectorLabel(n.sel||t.selector),viewUrl:"",createdAt:Date.now(),expiresAt:null,visibility:(await shareChoice()).visibility});let o=null;try{o=await cleanShot(e.id,t.selector)}catch(u){console.warn("clean share screenshot failed; using the page as it is",u),o=await captureCrop(e,t,!0)}shareShots.set(a,o),setTimeout(()=>shareShots.delete(a),12e4);const s=await thumbOf(o);return s&&patchSave(a,{thumb:s}),shareProgress(a,e.id,10,"capturing page\u2026"),{ok:!0,token:a,thumb:s,visibility:(shareChoices.get(a)||await shareChoice()).visibility}}async function shareElement(e,{token:t,data:n}){const r=shareShots.get(t)||null;shareShots.delete(t);const a=Date.now(),o=fetchCounts.get(e.id)||0;let s=null;const u=()=>{const i=(fetchCounts.get(e.id)||0)-o;shareProgress(t,e.id,12+28*(1-Math.exp(-(Date.now()-a)/3e3)),i?`capturing page \xB7 ${i} file${i===1?"":"s"}`:"capturing page\u2026")},c=i=>{if(i.stage==="snapshot"){u(),s||=setInterval(u,300);return}clearInterval(s),i.stage==="check"?shareProgress(t,e.id,42,`checking ${i.files} file${i.files===1?"":"s"}`):i.stage==="upload"?i.total?shareProgress(t,e.id,45+45*(i.done/i.total),`uploading ${i.done} of ${i.total} files`):shareProgress(t,e.id,90,"files already uploaded"):i.stage==="publish"&&shareProgress(t,e.id,93,"publishing\u2026")};try{const i=await shareExpiry(),l=shareChoices.get(t)||await shareChoice();sharesStarted.add(t);const d=await request("share",{token:t,tabId:e.id,selector:n.selector,url:n.url,data:n,png:r,expiresIn:i,...l},18e4,c);return await upsertSave({id:t,status:"done",progress:100,stage:"",viewUrl:d.viewUrl,apiUrl:d.apiUrl||"",expiresAt:expiryMs(d.expiresAt),...deleteTokenOf(d),visibility:d.visibility||"unlisted",allow:d.allow||[],requests:[]}),d.usage&&acct&&(acct={...acct,usage:d.usage},pushModules({account:!0})),{ok:!0,url:d.viewUrl,expiresAt:d.expiresAt,usage:d.usage||null,visibility:d.visibility||"unlisted",allow:d.allow||[]}}catch(i){return i.code==="signed_out"||i.code==="quota_exceeded"||i.code==="private_not_allowed"?(i.code==="quota_exceeded"&&acct&&i.data&&(acct={...acct,usage:{used:i.data.used,limit:i.data.limit,resetsAt:i.data.resetsAt,window:i.data.window||"month"}},acctAt=Date.now(),pushModules({account:!0})),await editSaves(l=>l.filter(d=>d.id!==t)),{ok:!1,error:i.message,code:i.code,data:i.data||null}):(await upsertSave({id:t,status:"error",stage:"",error:i.message}),{ok:!1,error:i.message})}finally{clearInterval(s),liveShares.delete(t),sharesStarted.delete(t),setTimeout(()=>shareChoices.delete(t),6e4)}}async function shareChoice(){const{newVisibility:e,privateWho:t}=await chrome.storage.local.get(["newVisibility","privateWho"]);return e!=="private"?{visibility:"unlisted"}:{visibility:"private",who:t==="domain"&&acct?.privateDomain?"domain":"me"}}const shareChoices=new Map,sharesStarted=new Set,linkIdOf=e=>/\/s\/([dwmn][0-9A-Za-z]{22})$/.exec(String(e||""))?.[1]||null,accessFields=e=>({visibility:e.visibility==="private"?"private":"unlisted",allow:Array.isArray(e.allow)?e.allow:[],requests:Array.isArray(e.requests)?e.requests:[]}),codedFail=e=>({ok:!1,error:e.message||String(e),code:e.code||null,data:e.data||null});async function setShareVisibility(e,t){const n=t==="private"?{visibility:t,who:(await shareChoice()).who||"me"}:{visibility:"unlisted"};if(n.visibility==="private"&&acct&&acct.privateLinks===!1)return{ok:!1,code:"private_not_allowed",error:"Private links are part of Pro and Max",data:{plan:acct.plan}};const{saves:r=[]}=await chrome.storage.local.get("saves"),a=r.find(o=>o.id===e);if(a?.status==="done"&&a.viewUrl)return linkAccess(e,"update",n.visibility==="private"?{visibility:"private",...a.visibility==="private"?{}:{who:n.who}}:{visibility:"unlisted"});shareChoices.set(e,n),patchSave(e,{visibility:n.visibility});try{if(sharesStarted.has(e)){const o=await request("share_set",{token:e,...n},2e4);o.pending||await upsertSave({id:e,...accessFields(o)})}}catch(o){return codedFail(o)}return{ok:!0,visibility:n.visibility,pending:!0}}async function linkAccess(e,t,n){const{saves:r=[]}=await chrome.storage.local.get("saves"),a=r.find(o=>o.id===e);if(!a?.viewUrl)return{ok:!1,error:"no link yet"};try{const o=await request("link_access",{action:t,link:a.viewUrl,change:n},2e4);return await upsertSave({id:e,...accessFields(o)}),{ok:!0,access:accessFields(o)}}catch(o){return codedFail(o)}}let accessAt=0;async function refreshAccess(e=!1){if(!hubUp||!plan?.signedIn||!e&&Date.now()-accessAt<15e3)return;accessAt=Date.now();const t=await request("link_access",{action:"list"},2e4),n=new Map((t?.links||[]).map(r=>[r.id,accessFields(r)]));await editSaves(r=>r.map(a=>{const o=n.get(linkIdOf(a.viewUrl));return o?{...a,...o}:a.visibility==="private"&&a.deleteToken?{...a,visibility:"unlisted",requests:[]}:a}))}async function copyText(e){try{await chrome.offscreen.createDocument({url:"offscreen.html",reasons:["CLIPBOARD"],justification:"Copy a share link to the clipboard"})}catch(t){if(!/single offscreen|already/i.test(t.message||""))throw t}try{return await chrome.runtime.sendMessage({type:"offscreen_copy",text:e})}finally{chrome.offscreen.closeDocument().catch(()=>{})}}let fontCache=null;function fonts(){return fontCache||=Promise.all([["el-Figtree",400,"figtree-latin-400-normal"],["el-Figtree",500,"figtree-latin-500-normal"],["el-Figtree",600,"figtree-latin-600-normal"],["el-Geist-Mono",400,"geist-mono-latin-400-normal"],["el-Geist-Mono",500,"geist-mono-latin-500-normal"]].map(async([e,t,n])=>({family:e,weight:t,data:toBase64(await(await fetch(chrome.runtime.getURL(`fonts/${n}.woff2`))).arrayBuffer())}))),fontCache.catch(()=>fontCache=null),fontCache}chrome.runtime.onMessage.addListener((e,t,n)=>{const r=a=>(a.then(n,o=>n({ok:!1,error:o.message||String(o)})),!0);switch(e.type){case"picked":return r(handlePicked(t.tab,e));case"choose":return r(handleChoice(e.token,e.session));case"share_gate":return r((async()=>(plan?.signedIn&&hubUp&&await Promise.race([accountInfo(),new Promise(a=>setTimeout(a,1500))]),shareGate()))());case"account":return r(accountInfo(!!e.fresh).then(a=>({ok:!0,account:a,hubUp})));case"login_start":return r((async()=>{const a=await request("login_start");if(a.verificationUrl&&/^https?:/.test(a.verificationUrl)&&!e.noTab){let o=a.verificationUrl;if(e.provider==="github"||e.provider==="google"){const s=new URL(o);o=`${s.origin}/auth/${e.provider}?next=${encodeURIComponent(s.pathname+s.search)}`}chrome.tabs.create({url:o,...t.tab?{index:t.tab.index+1,windowId:t.tab.windowId}:{}})}return{ok:!0,...a}})());case"held_drop":return r(request("held_drop").then(a=>({ok:!0,...a})));case"held_upgrade":return r(request("held_upgrade").then(a=>({ok:!0,...a})));case"module_login_status":return r(request("login_status"));case"login_cancel":return r(request("login_cancel").then(()=>({ok:!0})));case"refresh_key":return r(request("refresh_key",{},2e4).then(a=>(a?.refreshed&&(acct=null,pushModules({account:!0})),{ok:!0,...a})));case"logout":return r(request("logout").then(a=>(acct=null,pushModules({account:!0}),{ok:!0,...a})));case"share_shot":return r(shareShot(t.tab,e.data,{token:e.token,label:e.label,sel:e.sel}));case"share":return r(shareElement(t.tab,e));case"share_visibility":return r(setShareVisibility(String(e.token||""),e.visibility==="private"?"private":"unlisted"));case"link_access":return r(linkAccess(String(e.id||""),e.action==="update"?"update":"get",e.change||{}));case"access_refresh":return r(refreshAccess(!!e.force).then(()=>({ok:!0}),codedFail));case"copy":return r(copyText(e.text).catch(a=>({ok:!1,error:a.message||String(a)})));case"snapshot_fetch":return r(snapshotFetch(e.url,e.as,t.tab?.id).catch(a=>({ok:!1,error:a.message||String(a)})));case"tab_owner":{const o=t.tab&&sessionById(state.owners[t.tab.id])||(sessions.length===1?sessions[0]:null);return n(o?{label:o.label,harness:o.harness}:null),!1}case"theme":return setToolbarIcon(e.dark),!1;case"open_sessions":return openSessions(t.tab?.id),!1;case"module_state":return r(ready.then(()=>moduleState(t.tab?.id)));case"module_assign":{const a=t.tab;return r((async()=>{const o=await chrome.tabs.query({highlighted:!0,windowId:a.windowId}),s=o.some(u=>u.id===a.id)?o.map(u=>u.id):[a.id];return await assign(s,e.session),{ok:!0,tabs:s.length}})())}case"commands":return r(chrome.commands.getAll());case"fonts":return r(fonts());case"open_url":return/^https?:/.test(e.url||"")&&chrome.tabs.create({url:e.url,...t.tab?{index:t.tab.index+1,windowId:t.tab.windowId}:{}}),!1;case"open_shortcuts":return chrome.tabs.create({url:"chrome://extensions/shortcuts"}),!1;case"delete_saves":return r(deleteSaves(e.ids,!!e.link));case"remove_saves":return r(editSaves(a=>a.filter(o=>!(e.ids||[]).includes(o.id))).then(()=>({ok:!0})));case"popup_state":return r((async()=>{await ready,connect();const a=e.tabId?await chrome.tabs.get(e.tabId).catch(()=>null):(await chrome.tabs.query({active:!0,currentWindow:!0}))[0],o=a?await chrome.tabs.query({highlighted:!0,windowId:a.windowId}):[];return{hubUp,lastError,sessions,plan,tab:a&&{id:a.id,url:a.url,owner:state.owners[a.id]||null,pickable:injectable(a.url)},highlighted:o.map(s=>s.id)}})());case"assign":return r(assign(e.tabIds,e.session).then(()=>({ok:!0})));case"unassign":return r(unassign(e.tabIds).then(()=>({ok:!0})));case"start_picker":return r(startPicker(e.tabId).then(()=>({ok:!0})))}return!1});function setToolbarIcon(e){const t=e?"dark":"light";chrome.action.setIcon({path:{16:`icons/toolbar-${t}-16.png`,32:`icons/toolbar-${t}-32.png`}}).catch(()=>{}),chrome.storage.local.set({dark:e})}chrome.storage.local.get("dark").then(({dark:e})=>{e!=null&&setToolbarIcon(e)});function syncTitle(e){const t=HK.read(e);chrome.action.setTitle({title:`element: saves, sessions and settings (hold ${HK.format(t.pick)} and click to pick; ${HK.chip(t.save).replace(" click","-click")} copies a share link)`}).catch(()=>{})}chrome.storage.local.get("hotkeys").then(({hotkeys:e})=>syncTitle(e)).catch(()=>{}),chrome.storage.onChanged.addListener((e,t)=>{t==="local"&&e.hotkeys&&syncTitle(e.hotkeys.newValue)}),chrome.action.setBadgeBackgroundColor({color:"#0A0A0A"}),chrome.action.setBadgeTextColor?.({color:"#FFFFFF"}),chrome.action.onClicked.addListener(e=>openModuleOrWindow(e));function openSessions(e){const t=e!=null?`?tab=${e}`:"";chrome.windows.create({url:chrome.runtime.getURL(`popup.html${t}`),type:"popup",width:380,height:560})}async function onCommand(e,t){if(e==="switch-session")return t||=(await chrome.tabs.query({active:!0,lastFocusedWindow:!0}))[0],openModuleOrWindow(t)}chrome.commands.onCommand.addListener(onCommand),chrome.contextMenus.onClicked.addListener((e,t)=>{e.menuItemId==="el-open"&&openModuleOrWindow(t)}),chrome.tabs.onUpdated.addListener((e,t,n)=>{(t.url||t.status)&&syncPopup(n),state.owners[e]&&(t.url||t.title)&&pushTabs(),t.status==="complete"&&state.owners[e]&&refreshBadges()}),chrome.tabs.onCreated.addListener(e=>{const t=e.openerTabId!=null&&state.owners[e.openerTabId];t&&sessionById(t)&&assign([e.id],t)}),chrome.tabs.onRemoved.addListener(e=>{state.owners[e]&&(delete state.owners[e],save(),pushTabs())}),chrome.tabs.onActivated.addListener(({tabId:e})=>{pushTabs(),chrome.tabs.get(e).then(syncPopup,()=>{})}),chrome.tabs.onCreated.addListener(syncPopup),chrome.runtime.onStartup.addListener(()=>{connect(),rebuildMenus()}),chrome.runtime.onMessageExternal.addListener((e,t,n)=>e?.type!=="element-status"?!1:(ready.then(()=>chrome.storage.local.get("firstPick")).then(({firstPick:r})=>n({version:chrome.runtime.getManifest().version,helper:!!(port&&hubUp),agents:[...new Set(sessions.map(a=>a.harness).filter(Boolean))],picked:!!r,signedIn:!!plan?.signedIn})),!0)),chrome.runtime.onInstalled.addListener(()=>{rebuildMenus(),connect()}),editSaves(e=>e),connect();
@@ -0,0 +1 @@
1
+ globalThis.ELEMENT_HOTKEYS=(()=>{const f=["ctrl","alt","shift","meta"],r=Object.freeze({pick:Object.freeze(["alt"]),save:Object.freeze(["ctrl","alt"])}),d={Control:"ctrl",Alt:"alt",AltGraph:"alt",Shift:"shift",Meta:"meta",OS:"meta"},y=String(globalThis.navigator?.userAgentData?.platform||globalThis.navigator?.platform||globalThis.navigator?.userAgent||""),u=/mac/i.test(y),v={ctrl:"\u2303",alt:"\u2325",shift:"\u21E7",meta:"\u2318"},g={ctrl:"Ctrl",alt:"Alt",shift:"Shift",meta:"Win"},o=t=>Array.isArray(t)?f.filter(e=>t.includes(e)):[],h=(t,e)=>o(t).join()===o(e).join(),m=(t,e)=>t.every(n=>e.includes(n)),c=(t,e=u)=>o(t).map(n=>(e?v:g)[n]).join(e?"":"+"),b=(t,e=u)=>`${c(t,e)}${e?" ":"+"}click`,w=t=>f.filter(e=>t[`${e}Key`]),A=t=>d[t]||null;function S(t,e){t=o(t);let n=null;for(const i of["save","pick"]){const s=e[i];!s.length||!m(s,t)||!t.every(a=>s.includes(a)||a==="shift")||(!n||s.length>e[n].length)&&(n=i)}return n}function k(t,e){const n=o(t).join("+"),i=c(t,e);return n==="shift"?`${i}-click selects text and opens links in a new window, and you hold ${i} to type capitals.`:n==="meta"?e?"\u2318-click opens links in a new tab, and you hold \u2318 for every shortcut.":"The Windows key opens the Start menu and is taken by the OS.":n==="ctrl"?e?"\u2303-click is a right-click on macOS, and \u2303 starts many shortcuts.":"Ctrl+click opens links in a new tab, and you hold Ctrl for every shortcut.":n==="ctrl+shift"||n==="shift+meta"?`${i}-click opens links in a new foreground tab, and ${i} starts many shortcuts.`:""}function p(t,e=u){const n={},i={};for(const s of["pick","save"]){const a=t[s],l=o(a);l.length?Array.isArray(a)&&a.some($=>!f.includes($))?n[s]="Modifier keys only.":k(l,e)?n[s]=`${c(l,e)} can't be used: ${k(l,e)}`:!e&&l.includes("meta")&&(i[s]="Windows may open the Start menu if you let go of Win first."):n[s]="Hold at least one modifier key: \u2303 \u2325 \u21E7 \u2318 (Ctrl, Alt, Shift, Win)."}return!n.pick&&!n.save&&h(t.pick,t.save)&&(n.save=n.pick=`Pick and Save can't both be ${c(t.pick,e)}.`),{ok:!Object.keys(n).length,errors:n,warnings:i}}function O(t){const e={pick:o(t?.pick),save:o(t?.save)};return p(e).ok?e:{pick:[...r.pick],save:[...r.save]}}return{ORDER:f,DEFAULTS:r,isMac:u,norm:o,same:h,format:c,chip:b,fromEvent:w,modOfKey:A,match:S,validate:p,read:O,isDefault:t=>h(t.pick,r.pick)&&h(t.save,r.save)}})();
Binary file
Binary file
@@ -0,0 +1 @@
1
+ globalThis.ELEMENT_ICONS=(()=>{const a={claude:'<svg viewBox="0 0 24 24" fill="currentColor" fill-rule="evenodd" aria-hidden="true"><path clip-rule="evenodd" d="M20.998 10.949H24v3.102h-3v3.028h-1.487V20H18v-2.921h-1.487V20H15v-2.921H9V20H7.488v-2.921H6V20H4.487v-2.921H3V14.05H0V10.95h3V5h17.998v5.949zM6 10.949h1.488V8.102H6v2.847zm10.51 0H18V8.102h-1.49v2.847z"></path></svg>',codex:'<svg viewBox="0 0 24 24" fill="currentColor" fill-rule="evenodd" aria-hidden="true"><path clip-rule="evenodd" d="M8.086.457a6.105 6.105 0 013.046-.415c1.333.153 2.521.72 3.564 1.7a.117.117 0 00.107.029c1.408-.346 2.762-.224 4.061.366l.063.03.154.076c1.357.703 2.33 1.77 2.918 3.198.278.679.418 1.388.421 2.126a5.655 5.655 0 01-.18 1.631.167.167 0 00.04.155 5.982 5.982 0 011.578 2.891c.385 1.901-.01 3.615-1.183 5.14l-.182.22a6.063 6.063 0 01-2.934 1.851.162.162 0 00-.108.102c-.255.736-.511 1.364-.987 1.992-1.199 1.582-2.962 2.462-4.948 2.451-1.583-.008-2.986-.587-4.21-1.736a.145.145 0 00-.14-.032c-.518.167-1.04.191-1.604.185a5.924 5.924 0 01-2.595-.622 6.058 6.058 0 01-2.146-1.781c-.203-.269-.404-.522-.551-.821a7.74 7.74 0 01-.495-1.283 6.11 6.11 0 01-.017-3.064.166.166 0 00.008-.074.115.115 0 00-.037-.064 5.958 5.958 0 01-1.38-2.202 5.196 5.196 0 01-.333-1.589 6.915 6.915 0 01.188-2.132c.45-1.484 1.309-2.648 2.577-3.493.282-.188.55-.334.802-.438.286-.12.573-.22.861-.304a.129.129 0 00.087-.087A6.016 6.016 0 015.635 2.31C6.315 1.464 7.132.846 8.086.457zm-.804 7.85a.848.848 0 00-1.473.842l1.694 2.965-1.688 2.848a.849.849 0 001.46.864l1.94-3.272a.849.849 0 00.007-.854l-1.94-3.393zm5.446 6.24a.849.849 0 000 1.695h4.848a.849.849 0 000-1.696h-4.848z"></path></svg>',gemini:'<svg viewBox="0 0 24 24" fill="currentColor" fill-rule="evenodd" aria-hidden="true"><path d="M20.616 10.835a14.147 14.147 0 01-4.45-3.001 14.111 14.111 0 01-3.678-6.452.503.503 0 00-.975 0 14.134 14.134 0 01-3.679 6.452 14.155 14.155 0 01-4.45 3.001c-.65.28-1.318.505-2.002.678a.502.502 0 000 .975c.684.172 1.35.397 2.002.677a14.147 14.147 0 014.45 3.001 14.112 14.112 0 013.679 6.453.502.502 0 00.975 0c.172-.685.397-1.351.677-2.003a14.145 14.145 0 013.001-4.45 14.113 14.113 0 016.453-3.678.503.503 0 000-.975 13.245 13.245 0 01-2.003-.678z"></path></svg>',"cursor-agent":'<svg viewBox="0 0 24 24" fill="currentColor" fill-rule="evenodd" aria-hidden="true"><path d="M22.106 5.68L12.5.135a.998.998 0 00-.998 0L1.893 5.68a.84.84 0 00-.419.726v11.186c0 .3.16.577.42.727l9.607 5.547a.999.999 0 00.998 0l9.608-5.547a.84.84 0 00.42-.727V6.407a.84.84 0 00-.42-.726zm-.603 1.176L12.228 22.92c-.063.108-.228.064-.228-.061V12.34a.59.59 0 00-.295-.51l-9.11-5.26c-.107-.062-.063-.228.062-.228h18.55c.264 0 .428.286.296.514z"></path></svg>',opencode:'<svg viewBox="0 0 24 24" fill="currentColor" fill-rule="evenodd" aria-hidden="true"><path d="M16 6H8v12h8V6zm4 16H4V2h16v20z"></path></svg>',qwen:'<svg viewBox="0 0 24 24" fill="currentColor" fill-rule="evenodd" aria-hidden="true"><path d="M12.604 1.34c.393.69.784 1.382 1.174 2.075a.18.18 0 00.157.091h5.552c.174 0 .322.11.446.327l1.454 2.57c.19.337.24.478.024.837-.26.43-.513.864-.76 1.3l-.367.658c-.106.196-.223.28-.04.512l2.652 4.637c.172.301.111.494-.043.77-.437.785-.882 1.564-1.335 2.34-.159.272-.352.375-.68.37-.777-.016-1.552-.01-2.327.016a.099.099 0 00-.081.05 575.097 575.097 0 01-2.705 4.74c-.169.293-.38.363-.725.364-.997.003-2.002.004-3.017.002a.537.537 0 01-.465-.271l-1.335-2.323a.09.09 0 00-.083-.049H4.982c-.285.03-.553-.001-.805-.092l-1.603-2.77a.543.543 0 01-.002-.54l1.207-2.12a.198.198 0 000-.197 550.951 550.951 0 01-1.875-3.272l-.79-1.395c-.16-.31-.173-.496.095-.965.465-.813.927-1.625 1.387-2.436.132-.234.304-.334.584-.335a338.3 338.3 0 012.589-.001.124.124 0 00.107-.063l2.806-4.895a.488.488 0 01.422-.246c.524-.001 1.053 0 1.583-.006L11.704 1c.341-.003.724.032.9.34zm-3.432.403a.06.06 0 00-.052.03L6.254 6.788a.157.157 0 01-.135.078H3.253c-.056 0-.07.025-.041.074l5.81 10.156c.025.042.013.062-.034.063l-2.795.015a.218.218 0 00-.2.116l-1.32 2.31c-.044.078-.021.118.068.118l5.716.008c.046 0 .08.02.104.061l1.403 2.454c.046.081.092.082.139 0l5.006-8.76.783-1.382a.055.055 0 01.096 0l1.424 2.53a.122.122 0 00.107.062l2.763-.02a.04.04 0 00.035-.02.041.041 0 000-.04l-2.9-5.086a.108.108 0 010-.113l.293-.507 1.12-1.977c.024-.041.012-.062-.035-.062H9.2c-.059 0-.073-.026-.043-.077l1.434-2.505a.107.107 0 000-.114L9.225 1.774a.06.06 0 00-.053-.031zm6.29 8.02c.046 0 .058.02.034.06l-.832 1.465-2.613 4.585a.056.056 0 01-.05.029.058.058 0 01-.05-.029L8.498 9.841c-.02-.034-.01-.052.028-.054l.216-.012 6.722-.012z"></path></svg>',kiro:'<svg viewBox="0 0 24 24" fill="currentColor" fill-rule="evenodd" aria-hidden="true"><path d="M4.594 6.677C6.67-2.226 18.746-2.211 21.16 6.632c.353 1.297 1.725 7.582-1.673 13.747-1.545 2.797-5.841 5.49-6.99 1.883C8.6 25.477 3.315 24.1 5.789 18.609l-.318.143c-3.57 1.305-3.863-1.208-3.173-2.513.45-.84.727-1.335.937-1.897.353-.975.458-1.568.593-2.498.27-1.837.277-3.607.765-5.167zm8.37.01a.92.92 0 00-.81.428c-.217.323-.33.825-.33 1.462 0 .705.15 1.89 1.14 1.89h.008c.757 0 1.214-.705 1.214-1.89 0-.622-.127-1.125-.367-1.455a1.014 1.014 0 00-.855-.435zm4.08 0a.92.92 0 00-.81.428c-.217.323-.33.825-.33 1.462 0 .705.15 1.89 1.14 1.89h.008c.757 0 1.215-.705 1.215-1.89 0-.622-.128-1.125-.368-1.455a1.014 1.014 0 00-.855-.435z"></path></svg>',goose:'<svg viewBox="0 0 24 24" fill="currentColor" fill-rule="evenodd" aria-hidden="true"><path d="M21.595 23.61c1.167-.254 2.405-.944 2.405-.944l-2.167-1.784a12.124 12.124 0 01-2.695-3.131 12.127 12.127 0 00-3.97-4.049l-.794-.462a1.115 1.115 0 01-.488-.815.844.844 0 01.154-.575c.413-.582 2.548-3.115 2.94-3.44.503-.416 1.065-.762 1.586-1.159.074-.056.148-.112.221-.17.003-.002.007-.004.009-.007.167-.131.325-.272.45-.438.453-.524.563-.988.59-1.193-.061-.197-.244-.639-.753-1.148.319.02.705.272 1.056.569.235-.376.481-.773.727-1.171.165-.266-.08-.465-.086-.471h-.001V3.22c-.007-.007-.206-.25-.471-.086-.567.35-1.134.702-1.639 1.021 0 0-.597-.012-1.305.599a2.464 2.464 0 00-.438.45l-.007.009c-.058.072-.114.147-.17.221-.397.521-.743 1.083-1.16 1.587-.323.391-2.857 2.526-3.44 2.94a.842.842 0 01-.574.153 1.115 1.115 0 01-.815-.488l-.462-.794a12.123 12.123 0 00-4.049-3.97 12.133 12.133 0 01-3.13-2.695L1.332 0S.643 1.238.39 2.405c.352.428 1.27 1.49 2.34 2.302C1.58 4.167.73 3.75.06 3.4c-.103.765-.063 1.92.043 2.816.726.317 1.961.806 3.219 1.066-1.006.236-2.11.278-2.961.262.15.554.358 1.119.64 1.688.119.263.25.52.39.77.452.125 2.222.383 3.164.171l-2.51.897a27.776 27.776 0 002.544 2.726c2.031-1.092 2.494-1.241 4.018-2.238-2.467 2.008-3.108 2.828-3.8 3.67l-.483.678c-.25.351-.469.725-.65 1.117-.61 1.31-1.47 4.1-1.47 4.1-.154.486.202.842.674.674 0 0 2.79-.861 4.1-1.47.392-.182.766-.4 1.118-.65l.677-.483c.227-.187.453-.37.701-.586 0 0 1.705 2.02 3.458 3.349l.896-2.511c-.211.942.046 2.712.17 3.163.252.142.509.272.772.392.569.28 1.134.49 1.688.64-.016-.853.026-1.956.261-2.962.26 1.258.75 2.493 1.067 3.219.895.106 2.051.146 2.816.043a73.87 73.87 0 01-1.308-2.67c.811 1.07 1.874 1.988 2.302 2.34h-.001z"></path></svg>',amp:'<svg viewBox="0 0 24 24" fill="currentColor" fill-rule="evenodd" aria-hidden="true"><path d="M15.087 23.18L12.03 24l-2.097-7.823-5.738 5.738-2.251-2.251 5.718-5.719-7.769-2.082.82-3.057 11.294 3.08 3.08 11.295z"></path><path d="M19.505 18.762l-3.057.82-2.564-9.573-9.572-2.564.819-3.057 11.295 3.079 3.08 11.295z"></path><path d="M23.893 14.374l-3.057.82-2.565-9.572L8.7 3.057 9.52 0l11.295 3.08 3.079 11.294z"></path></svg>',droid:'<svg viewBox="0 0 67 65" fill="none" aria-hidden="true"><path fill="currentColor" d="M47.75 11.15a.867.867 0 0 1-.671-.806.84.84 0 0 1 .067-.362c1.688-4.007 2.433-7.213 1.23-8.555-3.183-3.56-15.952 3.52-20.024 5.919a.9.9 0 0 1-1.273-.41c-1.711-3.998-3.51-6.78-5.334-6.9-4.833-.323-8.73 13.49-9.87 17.992a.85.85 0 0 1-.459.563.9.9 0 0 1-.737.027c-4.109-1.647-7.398-2.373-8.773-1.2-3.651 3.104 3.609 15.557 6.068 19.528a.85.85 0 0 1-.11 1.031.9.9 0 0 1-.31.21C3.455 39.856.604 41.61.478 43.389c-.329 4.713 13.834 8.513 18.452 9.625q.186.046.337.163a.87.87 0 0 1 .332.642.84.84 0 0 1-.067.362c-1.688 4.007-2.433 7.214-1.23 8.555 3.183 3.561 15.954-3.519 20.025-5.917a.9.9 0 0 1 1.058.107.9.9 0 0 1 .215.302c1.711 3.997 3.509 6.779 5.334 6.9 4.833.322 8.73-13.49 9.868-17.993a.85.85 0 0 1 .168-.33.88.88 0 0 1 .659-.324.9.9 0 0 1 .371.066c4.109 1.647 7.397 2.372 8.773 1.2 3.651-3.105-3.61-15.559-6.07-19.53a.85.85 0 0 1 .111-1.03.9.9 0 0 1 .31-.21c4.1-1.67 6.952-3.424 7.075-5.203.331-4.713-13.833-8.513-18.45-9.623m-5.546-4.518c.93 1.624-3.858 12.446-7.42 20.015a.7.7 0 0 1-.28.303.71.71 0 0 1-.796-.059.7.7 0 0 1-.23-.341c-1.439-4.921-3.082-10.704-4.841-15.612a.84.84 0 0 1 .01-.594.87.87 0 0 1 .401-.446c4.392-2.34 11.908-5.446 13.156-3.266m-21.048 1.34c1.833.507 6.294 11.46 9.264 19.268a.67.67 0 0 1-.2.754.71.71 0 0 1-.794.08c-4.589-2.485-9.94-5.444-14.743-7.702a.87.87 0 0 1-.422-.427.84.84 0 0 1-.04-.591c1.414-4.679 4.471-12.063 6.935-11.383M7.243 23.433c1.664-.906 12.762 3.763 20.522 7.235.13.058.239.154.311.274a.67.67 0 0 1-.06.776.7.7 0 0 1-.35.225c-5.045 1.403-10.976 3.006-16.01 4.721a.9.9 0 0 1-.607-.01.88.88 0 0 1-.456-.391c-2.395-4.284-5.586-11.613-3.35-12.83M8.617 43.96c.519-1.788 11.752-6.14 19.758-9.035a.72.72 0 0 1 .773.195.67.67 0 0 1 .081.774c-2.548 4.475-5.582 9.694-7.898 14.377a.87.87 0 0 1-.437.413.9.9 0 0 1-.607.039c-4.797-1.37-12.37-4.36-11.67-6.763m15.855 13.568c-.93-1.623 3.859-12.446 7.42-20.014a.7.7 0 0 1 .28-.303.715.715 0 0 1 .796.059.7.7 0 0 1 .23.34c1.439 4.92 3.083 10.705 4.841 15.613a.84.84 0 0 1-.01.593.87.87 0 0 1-.402.445c-4.391 2.335-11.908 5.447-13.15 3.267zm21.049-1.34c-1.836-.506-6.297-11.461-9.266-19.269a.67.67 0 0 1 .2-.755.71.71 0 0 1 .795-.078c4.587 2.484 9.94 5.445 14.742 7.703.189.088.339.24.423.426a.84.84 0 0 1 .039.592c-1.413 4.686-4.47 12.063-6.933 11.381m13.912-15.462c-1.665.907-12.762-3.763-20.523-7.236a.7.7 0 0 1-.311-.273.67.67 0 0 1 .06-.777.7.7 0 0 1 .35-.225c5.046-1.402 10.975-3.005 16.009-4.72a.9.9 0 0 1 .609.01.88.88 0 0 1 .457.392c2.393 4.282 5.584 11.613 3.349 12.829M58.06 20.2c-.521 1.79-11.753 6.14-19.759 9.036a.72.72 0 0 1-.774-.195.67.67 0 0 1-.08-.776c2.547-4.474 5.581-9.694 7.897-14.377a.87.87 0 0 1 .437-.412.9.9 0 0 1 .607-.038c4.797 1.377 12.37 4.359 11.672 6.762"></path></svg>',pi:'<svg viewBox="165 165 470 470" aria-hidden="true"><path fill="currentColor" d="M165.29 165.29H517.36V400H400V282.65H165.29Z"></path><path fill="currentColor" d="M165.29 282.65H282.65V400H400V517.36H282.65V634.72H165.29Z"></path><path fill="currentColor" d="M517.36 400H634.72V634.72H517.36Z"></path></svg>'},e='<svg viewBox="0 0 24 24" fill="currentColor" fill-rule="evenodd" aria-hidden="true"><rect x="2.5" y="4" width="19" height="16" rx="4" fill="none" stroke="currentColor" stroke-width="2"/><path d="M7 10l3 2.5L7 15M12.5 15.5H17" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/></svg>';return{mark:'<svg viewBox="0 0 100 100" aria-hidden="true"><g fill="none" stroke="currentColor" stroke-width="10" stroke-linecap="round" stroke-linejoin="round"><path d="M11 33 V22 Q11 11 22 11 H33"/><path d="M67 11 H78 Q89 11 89 22 V33"/><path d="M11 67 V78 Q11 89 22 89 H33"/></g><path d="M0 0 L0 29 Q0 31 1.6 29.6 L8 23.5 L12.6 33 Q13.4 34.6 15 33.9 L18 32.5 Q19.6 31.7 18.8 30.1 L14.3 21 L22.6 21 Q24.6 21 23.2 19.6 L1.6 -1 Q0 -2.4 0 0 Z" transform="translate(58 56) scale(1.25)" fill="currentColor" stroke="currentColor" stroke-width="2" stroke-linejoin="round"/></svg>',check:'<svg viewBox="0 0 24 24" aria-hidden="true"><path d="M5 12.5l4.5 4.5L19 7.5" fill="none" stroke="currentColor" stroke-width="3" stroke-linecap="round" stroke-linejoin="round"/></svg>',link:'<svg viewBox="0 0 24 24" aria-hidden="true"><g fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"><path d="M10 13a5 5 0 0 0 7.5.5l3-3a5 5 0 0 0-7-7l-1.7 1.7"/><path d="M14 11a5 5 0 0 0-7.5-.5l-3 3a5 5 0 0 0 7 7l1.7-1.7"/></g></svg>',trash:'<svg viewBox="0 0 24 24" aria-hidden="true"><g fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M4 7h16"/><path d="M10 11v6M14 11v6"/><path d="M6 7l1 12a2 2 0 0 0 2 2h6a2 2 0 0 0 2-2l1-12"/><path d="M9 7V4.5A1.5 1.5 0 0 1 10.5 3h3A1.5 1.5 0 0 1 15 4.5V7"/></g></svg>',harness:l=>a[String(l||"").toLowerCase()]||e}})();
@@ -0,0 +1,57 @@
1
+ {
2
+ "manifest_version": 3,
3
+ "name": "element: click anything for your agent",
4
+ "short_name": "element",
5
+ "version": "0.2.0",
6
+ "description": "Click anything in your browser and hand it to your coding agent: Claude Code, Codex, Cursor, opencode, pi, Muse.",
7
+ "key": "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA9FB+FKq7WWWNhOWhihw4L1PhOMlaeriDaxAydXIVLj8y0qAy90XW1Yc+hwZ3CtqdMlwnkec4rfXGwBXB6R/PmY0iVEzwPy78wfmHz2jZQ9x4UfCGGl9MIkj1/eBwScB27tZW55q9zk+XCwEbmpwdaRGy9QjewKWurSwb1hMDUxMlP4hdP2lxeO8VRhZgPnUreZ7tkZ96AQ+07uWQD9dgIyocMhMPi4js7yMe4fGrolBCmZpYrUbgH2G4A87TDnS/4pqL4GKcI9OXnd+2kWNF3+qncoZrchNeBdsXPkKu7UCKLrHbKOOgf0jLSg3CTxqniG1uO5Q9IouAjctvJsvd3QIDAQAB",
8
+ "permissions": [
9
+ "nativeMessaging",
10
+ "tabs",
11
+ "scripting",
12
+ "storage",
13
+ "contextMenus",
14
+ "offscreen",
15
+ "clipboardWrite"
16
+ ],
17
+ "host_permissions": [
18
+ "<all_urls>"
19
+ ],
20
+ "background": {
21
+ "service_worker": "background.js"
22
+ },
23
+ "action": {
24
+ "default_title": "element: saves, sessions and settings (hold ⌥ and click to pick; ⌃⌥-click copies a share link)",
25
+ "default_icon": {
26
+ "16": "icons/toolbar-light-16.png",
27
+ "32": "icons/toolbar-light-32.png"
28
+ }
29
+ },
30
+ "commands": {
31
+ "switch-session": {
32
+ "suggested_key": {
33
+ "default": "Alt+Shift+S"
34
+ },
35
+ "description": "Open element: saves, sessions and settings"
36
+ }
37
+ },
38
+ "content_scripts": [
39
+ {
40
+ "matches": [
41
+ "<all_urls>"
42
+ ],
43
+ "js": [
44
+ "logos.js",
45
+ "hotkeys.js",
46
+ "picker.js"
47
+ ],
48
+ "run_at": "document_idle"
49
+ }
50
+ ],
51
+ "icons": {
52
+ "48": "icons/tile-48.png",
53
+ "128": "icons/tile-128.png"
54
+ },
55
+ "homepage_url": "https://elmts.dev",
56
+ "externally_connectable": { "matches": ["https://elmts.dev/*", "http://localhost/*", "http://127.0.0.1/*"] }
57
+ }
@@ -0,0 +1,4 @@
1
+ <!doctype html>
2
+ <meta charset="utf-8">
3
+ <textarea id="t"></textarea>
4
+ <script src="offscreen.js"></script>
@@ -0,0 +1 @@
1
+ chrome.runtime.onMessage.addListener((t,c,n)=>{if(t.type!=="offscreen_copy")return!1;const e=document.getElementById("t");e.value=t.text,e.select();const o=document.execCommand("copy");return e.value="",n({ok:o}),!1});