@emmaexcel/shakecursor 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md ADDED
@@ -0,0 +1,40 @@
1
+ # Shake Cursor Overlay Core
2
+
3
+ Framework-agnostic browser SDK for contextual AI selection overlays.
4
+
5
+ ## NPM-style usage
6
+
7
+ ```ts
8
+ import { AIOverlay } from '@shakecursor/overlay-core'
9
+
10
+ const overlay = AIOverlay.init({
11
+ siteKey: 'pk_demo_shakecursor',
12
+ apiBaseUrl: 'https://shakeai.onrender.com',
13
+ })
14
+
15
+ overlay.activate()
16
+ ```
17
+
18
+ ## Script tag usage
19
+
20
+ ```html
21
+ <script src="/overlay.global.js"></script>
22
+ <script>
23
+ window.AIOverlay.init({
24
+ siteKey: 'pk_live_xxx',
25
+ apiBaseUrl: 'https://api.yourproduct.com'
26
+ })
27
+ </script>
28
+ ```
29
+
30
+ ## Current capabilities
31
+
32
+ - Cursor shake activation
33
+ - Cmd/Ctrl + K activation
34
+ - Text selection capture
35
+ - Image metadata capture
36
+ - DOM element text capture
37
+ - Shadow DOM prompt UI
38
+ - Ollama and custom endpoint transports
39
+ - Hosted API transport with `siteKey`
40
+ - Theme, selector rules, and lifecycle callbacks
@@ -0,0 +1,27 @@
1
+ import type { AIOverlayConfig, AIOverlayInstance } from './types';
2
+ export type { AIOverlayAskPayload, AIOverlayConfig, AIOverlayInstance, AIOverlayModelConfig, AIOverlaySelection, AIOverlaySelectionConfig, AIOverlaySelectionKind, AIOverlayThemeConfig, AIOverlayTriggerConfig, } from './types';
3
+ declare class AIOverlayController implements AIOverlayInstance {
4
+ private active;
5
+ private shakeCount;
6
+ private selection;
7
+ private disposers;
8
+ private ui;
9
+ private config;
10
+ private trigger;
11
+ constructor(config: AIOverlayConfig);
12
+ activate(): void;
13
+ deactivate(): void;
14
+ destroy(): void;
15
+ isActive(): boolean;
16
+ private clearSelection;
17
+ private ask;
18
+ private bindKeyboardShortcut;
19
+ }
20
+ export declare const AIOverlay: {
21
+ init(config?: AIOverlayConfig): AIOverlayController;
22
+ };
23
+ declare global {
24
+ interface Window {
25
+ AIOverlay?: typeof AIOverlay;
26
+ }
27
+ }
@@ -0,0 +1,215 @@
1
+ (function(e){Object.defineProperty(e,Symbol.toStringTag,{value:`Module`});function t(e,t,n){return Math.min(Math.max(e,t),n)}function n(e){return e.replace(/\s+/g,` `).trim()}function r(e,t=[]){return t.some(t=>{try{return e.matches(t)||!!e.closest(t)}catch{return!1}})}function i(){return/Mac|iPhone|iPad|iPod/i.test(window.navigator.platform)}function a(e){if(e.rangeCount===0)return null;let t=e.getRangeAt(0),n=t.getBoundingClientRect();return n.width>0||n.height>0?n:t.getClientRects()[0]??null}function o(e){return{rect:e,url:window.location.href,title:document.title}}function s(e){let t=n(e.innerText??``),r=e.tagName.toLowerCase(),i=e.getAttribute(`aria-label`),a=e.getAttribute(`title`),o=[i,a,t].find(Boolean);return{label:o?`${r}: ${o.slice(0,64)}`:r,content:[`Selected element: <${r}>`,i?`ARIA label: ${i}`:``,a?`Title: ${a}`:``,t?`Visible text: ${t.slice(0,5e3)}`:``].filter(Boolean).join(`
2
+ `)}}function c(e){let t=e.currentSrc||e.src,n=e.alt||`No alt text`;return{label:`image: ${n.slice(0,64)}`,content:[`Selected image`,`Alt text: ${n}`,`Source: ${t}`,`Rendered size: ${Math.round(e.width)}x${Math.round(e.height)}`,`Natural size: ${e.naturalWidth}x${e.naturalHeight}`].join(`
3
+ `)}}function l(e,t){return!(r(e,t.blockedSelectors)||t.allowedSelectors.length>0&&!r(e,t.allowedSelectors))}function u(e){let t=t=>{if(!e.isActive())return;let n=t.target;if(!(n instanceof HTMLElement)||e.isOverlayElement(n)){e.onHover(null);return}if(!l(n,e.config)){e.onHover(null);return}e.onHover(n.getBoundingClientRect())},r=()=>{!e.isActive()||!e.config.text||window.setTimeout(()=>{let t=window.getSelection(),r=n(t?.toString()??``);if(!t||!r)return;let i=a(t),s=t.anchorNode?.parentElement;!i||!s||!l(s,e.config)||e.onSelection({kind:`text`,label:`highlighted text`,content:r,...o(i)})},0)},i=t=>{if(!e.isActive())return;let r=t.target;if(!(r instanceof HTMLElement)||e.isOverlayElement(r)||!l(r,e.config)||n(window.getSelection()?.toString()??``))return;let i=r instanceof HTMLImageElement?r:r.querySelector(`img`),a=!!i;if(a&&!e.config.images||!a&&!e.config.elements)return;t.preventDefault(),t.stopPropagation();let u=i?c(i):s(r),d=i?i.getBoundingClientRect():r.getBoundingClientRect();e.onSelection({kind:i?`image`:`element`,label:u.label,content:u.content,...o(d)})};return document.addEventListener(`pointerover`,t),document.addEventListener(`mouseup`,r),document.addEventListener(`click`,i,!0),()=>{document.removeEventListener(`pointerover`,t),document.removeEventListener(`mouseup`,r),document.removeEventListener(`click`,i,!0)}}function d(e){let t=e.windowMs??650,n=e.cooldownMs??1e3,r=e.minSamples??7,i=e.minReversals??5,a=e.minDistance??340,o=e.minDeltaX??16,s=[],c=0,l=l=>{let u=performance.now();if(s.push({x:l.clientX,y:l.clientY,time:u}),s=s.filter(e=>u-e.time<t),s.length<r||u-c<n)return;let d=0,f=0,p=0;for(let e=1;e<s.length;e+=1){let t=s[e].x-s[e-1].x,n=s[e].y-s[e-1].y;if(f+=Math.hypot(t,n),Math.abs(t)<o)continue;let r=Math.sign(t);p!==0&&r!==p&&(d+=1),p=r}d>=i&&f>a&&(c=u,s=[],e.onShake())};return document.addEventListener(`pointermove`,l),()=>{document.removeEventListener(`pointermove`,l)}}var f=`http://localhost:11434/api/chat`,p=`qwen3-coder:480b-cloud`,m=`https://shakeai.onrender.com`;async function h(e,t){let n=e.provider??`ollama`,r=e.endpoint??f,i=e.model??p;if(n===`custom`){let n=await fetch(r,{method:`POST`,headers:{"Content-Type":`application/json`,...e.headers},body:JSON.stringify(t)});if(!n.ok)throw Error(`Custom endpoint returned ${n.status}`);let i=await n.json();return typeof i==`string`?i:i&&typeof i==`object`&&`answer`in i&&typeof i.answer==`string`?i.answer:i&&typeof i==`object`&&`content`in i&&typeof i.content==`string`?i.content:JSON.stringify(i,null,2)}let a=await fetch(r,{method:`POST`,headers:{"Content-Type":`application/json`,...e.headers},body:JSON.stringify({model:i,stream:!1,messages:[{role:`system`,content:`You are an in-page AI assistant. Use the selected website context to answer or transform content. Be direct and practical.`},{role:`user`,content:[`Page title: ${t.selection.title}`,`Page URL: ${t.selection.url}`,`Selection type: ${t.selection.kind}`,`Selection label: ${t.selection.label}`,`Selected context:`,t.selection.content,``,`User request: ${t.question}`].join(`
4
+ `)}]})});if(!a.ok)throw Error(`Ollama returned ${a.status}`);let o=await a.json();if(o.error)throw Error(o.error);return o.message?.content?.trim()||`No response returned.`}async function g(e){let t=await fetch(`${e.apiBaseUrl??m}/v1/ask`,{method:`POST`,headers:{"Content-Type":`application/json`,"x-site-key":e.siteKey},body:JSON.stringify(e.payload)}),n=await t.json();if(!t.ok||n.error)throw Error(n.error??`Hosted API returned ${t.status}`);return n.answer??`No response returned.`}function _(e){let n=document.createElement(`div`);n.dataset.aiOverlayRoot=`true`;let r=document.createElement(`style`);r.dataset.aiOverlayStyle=`true`,r.textContent=`body.ai-overlay-active { cursor: crosshair; }`;let i=n.attachShadow({mode:`open`});document.head.append(r),document.body.append(n),i.innerHTML=`
5
+ <style>
6
+ :host {
7
+ all: initial;
8
+ --ai-primary: ${e.theme.primaryColor};
9
+ --ai-panel: ${e.theme.panelBackground};
10
+ --ai-text: ${e.theme.textColor};
11
+ --ai-radius: ${e.theme.borderRadius}px;
12
+ font-family: Inter, ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
13
+ }
14
+
15
+ .toast {
16
+ position: fixed;
17
+ right: 18px;
18
+ bottom: 18px;
19
+ z-index: 2147483645;
20
+ display: none;
21
+ align-items: center;
22
+ gap: 10px;
23
+ border: 1px solid color-mix(in srgb, var(--ai-primary), transparent 74%);
24
+ border-radius: var(--ai-radius);
25
+ background: var(--ai-panel);
26
+ color: var(--ai-text);
27
+ padding: 10px 12px;
28
+ font: 700 13px/1 Inter, ui-sans-serif, system-ui, sans-serif;
29
+ box-shadow: 0 16px 42px rgba(15, 23, 42, 0.18);
30
+ }
31
+
32
+ .toast.active {
33
+ display: flex;
34
+ }
35
+
36
+ .pulse {
37
+ width: 8px;
38
+ height: 8px;
39
+ border-radius: 999px;
40
+ background: var(--ai-primary);
41
+ box-shadow: 0 0 0 0 color-mix(in srgb, var(--ai-primary), transparent 38%);
42
+ animation: pulse-ring 1.3s infinite;
43
+ }
44
+
45
+ .count {
46
+ border-left: 1px solid rgba(15, 23, 42, 0.12);
47
+ padding-left: 10px;
48
+ opacity: 0.72;
49
+ }
50
+
51
+ .hover {
52
+ position: fixed;
53
+ z-index: 2147483644;
54
+ pointer-events: none;
55
+ display: none;
56
+ border: 2px solid var(--ai-primary);
57
+ border-radius: var(--ai-radius);
58
+ box-shadow:
59
+ 0 0 0 4px color-mix(in srgb, var(--ai-primary), transparent 88%),
60
+ 0 20px 52px rgba(15, 23, 42, 0.14);
61
+ }
62
+
63
+ .panel {
64
+ position: fixed;
65
+ z-index: 2147483646;
66
+ width: 380px;
67
+ display: none;
68
+ border: 1px solid rgba(17, 24, 39, 0.14);
69
+ border-radius: var(--ai-radius);
70
+ background: var(--ai-panel);
71
+ color: var(--ai-text);
72
+ text-align: left;
73
+ box-shadow: 0 26px 70px rgba(15, 23, 42, 0.22);
74
+ overflow: hidden;
75
+ }
76
+
77
+ .panel.visible {
78
+ display: block;
79
+ }
80
+
81
+ .head {
82
+ display: flex;
83
+ align-items: center;
84
+ justify-content: space-between;
85
+ gap: 14px;
86
+ border-bottom: 1px solid rgba(17, 24, 39, 0.08);
87
+ padding: 14px 14px 12px;
88
+ }
89
+
90
+ .kind {
91
+ display: block;
92
+ margin-bottom: 4px;
93
+ color: var(--ai-primary);
94
+ font: 800 11px/1 Inter, ui-sans-serif, system-ui, sans-serif;
95
+ text-transform: uppercase;
96
+ }
97
+
98
+ .label {
99
+ display: block;
100
+ color: var(--ai-text);
101
+ font: 700 14px/1.25 Inter, ui-sans-serif, system-ui, sans-serif;
102
+ }
103
+
104
+ button {
105
+ border: 1px solid rgba(17, 24, 39, 0.12);
106
+ border-radius: var(--ai-radius);
107
+ min-height: 40px;
108
+ background: #111827;
109
+ color: #ffffff;
110
+ font: 700 14px/1 Inter, ui-sans-serif, system-ui, sans-serif;
111
+ cursor: pointer;
112
+ }
113
+
114
+ button:disabled {
115
+ cursor: not-allowed;
116
+ opacity: 0.56;
117
+ }
118
+
119
+ .close {
120
+ min-width: 34px;
121
+ min-height: 34px;
122
+ padding: 0;
123
+ background: #f3f4f6;
124
+ color: #111827;
125
+ }
126
+
127
+ form {
128
+ display: grid;
129
+ gap: 10px;
130
+ padding: 14px;
131
+ }
132
+
133
+ textarea {
134
+ box-sizing: border-box;
135
+ width: 100%;
136
+ resize: vertical;
137
+ min-height: 86px;
138
+ max-height: 180px;
139
+ border: 1px solid rgba(17, 24, 39, 0.16);
140
+ border-radius: var(--ai-radius);
141
+ padding: 12px;
142
+ color: #111827;
143
+ background: #ffffff;
144
+ font: 15px/1.45 Inter, ui-sans-serif, system-ui, sans-serif;
145
+ }
146
+
147
+ textarea:focus {
148
+ border-color: var(--ai-primary);
149
+ outline: 3px solid color-mix(in srgb, var(--ai-primary), transparent 86%);
150
+ }
151
+
152
+ .error,
153
+ .answer {
154
+ display: none;
155
+ margin: 0 14px 14px;
156
+ border-radius: var(--ai-radius);
157
+ padding: 12px;
158
+ font: 14px/1.45 Inter, ui-sans-serif, system-ui, sans-serif;
159
+ white-space: pre-wrap;
160
+ }
161
+
162
+ .error.visible,
163
+ .answer.visible {
164
+ display: block;
165
+ }
166
+
167
+ .error {
168
+ background: #fef2f2;
169
+ color: #991b1b;
170
+ }
171
+
172
+ .answer {
173
+ max-height: 220px;
174
+ overflow: auto;
175
+ background: #f8fafc;
176
+ color: #111827;
177
+ }
178
+
179
+ @media (max-width: 760px) {
180
+ .panel {
181
+ left: 12px !important;
182
+ right: 12px;
183
+ top: auto !important;
184
+ bottom: 12px;
185
+ width: auto;
186
+ }
187
+ }
188
+
189
+ @keyframes pulse-ring {
190
+ 70% { box-shadow: 0 0 0 8px transparent; }
191
+ 100% { box-shadow: 0 0 0 0 transparent; }
192
+ }
193
+ </style>
194
+ <div class="toast" role="status">
195
+ <span class="pulse"></span>
196
+ <span>AI mode active</span>
197
+ <span class="count">shakes 0</span>
198
+ </div>
199
+ <div class="hover"></div>
200
+ <section class="panel" aria-label="AI prompt">
201
+ <div class="head">
202
+ <div>
203
+ <span class="kind"></span>
204
+ <strong class="label"></strong>
205
+ </div>
206
+ <button class="close" type="button" aria-label="Close AI prompt">x</button>
207
+ </div>
208
+ <form>
209
+ <textarea placeholder="Ask what to do with this selection..."></textarea>
210
+ <button class="submit" type="submit">Ask AI</button>
211
+ </form>
212
+ <p class="error"></p>
213
+ <div class="answer"></div>
214
+ </section>
215
+ `;let a=i.querySelector(`.toast`),o=i.querySelector(`.count`),s=i.querySelector(`.hover`),c=i.querySelector(`.panel`),l=i.querySelector(`.kind`),u=i.querySelector(`.label`),d=i.querySelector(`form`),f=i.querySelector(`textarea`),p=i.querySelector(`.submit`),m=i.querySelector(`.close`),h=i.querySelector(`.error`),g=i.querySelector(`.answer`);if(!a||!o||!s||!c||!l||!u||!d||!f||!p||!m||!h||!g)throw Error(`AIOverlay UI failed to initialize`);return d.addEventListener(`submit`,t=>{t.preventDefault();let n=f.value.trim();n&&e.onSubmit(n)}),m.addEventListener(`click`,()=>{e.onCancel()}),{root:n,contains:e=>e===n||n.contains(e),setActive:(e,t)=>{a.classList.toggle(`active`,e),o.textContent=`shakes ${t}`},setHoverRect:e=>{if(!e){s.style.display=`none`;return}s.style.display=`block`,s.style.left=`${e.left}px`,s.style.top=`${e.top}px`,s.style.width=`${e.width}px`,s.style.height=`${e.height}px`},showPrompt:e=>{let n=t(e.rect.left+e.rect.width/2-190,16,window.innerWidth-396),r=t(e.rect.bottom+14,16,window.innerHeight-380);l.textContent=e.kind,u.textContent=e.label,f.value=``,p.disabled=!1,p.textContent=`Ask AI`,h.classList.remove(`visible`),g.classList.remove(`visible`),c.style.left=`${n}px`,c.style.top=`${r}px`,c.classList.add(`visible`),f.focus()},setThinking:e=>{p.disabled=e,p.textContent=e?`Thinking...`:`Ask AI`},setAnswer:e=>{g.textContent=e,g.classList.toggle(`visible`,!!e)},setError:e=>{h.textContent=e,h.classList.toggle(`visible`,!!e)},clearSelection:()=>{c.classList.remove(`visible`),h.classList.remove(`visible`),g.classList.remove(`visible`),f.value=``},destroy:()=>{r.remove(),n.remove()}}}var v={primaryColor:`#14b8a6`,panelBackground:`rgba(255, 255, 255, 0.96)`,textColor:`#111827`,borderRadius:8},y={shake:!0,keyboardShortcut:`mod+k`},b={text:!0,images:!0,elements:!0,blockedSelectors:[`input[type="password"]`,`[data-ai-private]`,`[data-ai-overlay-ignore]`],allowedSelectors:[]},x=class{active=!1;shakeCount=0;selection=null;disposers=[];ui;config;trigger;constructor(e){this.config=e,this.trigger={...y,...e.trigger};let t={...b,...e.selection};this.ui=_({theme:{...v,...e.theme},onSubmit:e=>{this.ask(e)},onCancel:()=>{this.clearSelection()}}),this.trigger.shake&&this.disposers.push(d({onShake:()=>{this.shakeCount+=1,this.activate()}})),this.disposers.push(u({config:t,isActive:()=>this.active,isOverlayElement:e=>this.ui.contains(e),onHover:e=>{this.ui.setHoverRect(this.selection?null:e)},onSelection:e=>{this.selection=e,this.ui.setHoverRect(null),this.ui.showPrompt(e),this.config.onSelection?.(e)}})),this.disposers.push(this.bindKeyboardShortcut())}activate(){if(this.active){this.ui.setActive(!0,this.shakeCount);return}this.active=!0,document.body.classList.add(`ai-overlay-active`),this.ui.setActive(!0,this.shakeCount),this.config.onActivate?.()}deactivate(){this.active&&(this.active=!1,this.clearSelection(),this.ui.setHoverRect(null),this.ui.setActive(!1,this.shakeCount),document.body.classList.remove(`ai-overlay-active`),this.config.onDeactivate?.())}destroy(){for(let e of this.disposers)e();this.disposers=[],this.deactivate(),this.ui.destroy()}isActive(){return this.active}clearSelection(){this.selection=null,this.ui.clearSelection(),window.getSelection()?.removeAllRanges()}async ask(e){if(!this.selection)return;let t={question:e,selection:this.selection};this.config.onAsk?.(t),this.ui.setThinking(!0),this.ui.setAnswer(``),this.ui.setError(``);try{let e=this.config.siteKey?await g({apiBaseUrl:this.config.apiBaseUrl,siteKey:this.config.siteKey,payload:t}):await h(this.config.model??{},t);this.ui.setAnswer(e),this.config.onResponse?.(e,t)}catch(e){let t=e instanceof Error?e:Error(`Unknown AIOverlay error`);this.ui.setError(t.message),this.config.onError?.(t)}finally{this.ui.setThinking(!1)}}bindKeyboardShortcut(){let e=this.trigger.keyboardShortcut.toLowerCase(),t=t=>{let n=e.includes(`mod+`),r=e.split(`+`).at(-1),a=i()?t.metaKey:t.ctrlKey;n&&!a||r&&t.key.toLowerCase()===r&&(t.preventDefault(),this.active?this.deactivate():this.activate())};return document.addEventListener(`keydown`,t),()=>{document.removeEventListener(`keydown`,t)}}},S={init(e={}){return new x(e)}};typeof window<`u`&&(window.AIOverlay=S),e.AIOverlay=S})(this.AIOverlayBundle=this.AIOverlayBundle||{});
@@ -0,0 +1,574 @@
1
+ //#region src/utils.ts
2
+ function e(e, t, n) {
3
+ return Math.min(Math.max(e, t), n);
4
+ }
5
+ function t(e) {
6
+ return e.replace(/\s+/g, " ").trim();
7
+ }
8
+ function n(e, t = []) {
9
+ return t.some((t) => {
10
+ try {
11
+ return e.matches(t) || !!e.closest(t);
12
+ } catch {
13
+ return !1;
14
+ }
15
+ });
16
+ }
17
+ function r() {
18
+ return /Mac|iPhone|iPad|iPod/i.test(window.navigator.platform);
19
+ }
20
+ //#endregion
21
+ //#region src/selection.ts
22
+ function i(e) {
23
+ if (e.rangeCount === 0) return null;
24
+ let t = e.getRangeAt(0), n = t.getBoundingClientRect();
25
+ return n.width > 0 || n.height > 0 ? n : t.getClientRects()[0] ?? null;
26
+ }
27
+ function a(e) {
28
+ return {
29
+ rect: e,
30
+ url: window.location.href,
31
+ title: document.title
32
+ };
33
+ }
34
+ function o(e) {
35
+ let n = t(e.innerText ?? ""), r = e.tagName.toLowerCase(), i = e.getAttribute("aria-label"), a = e.getAttribute("title"), o = [
36
+ i,
37
+ a,
38
+ n
39
+ ].find(Boolean);
40
+ return {
41
+ label: o ? `${r}: ${o.slice(0, 64)}` : r,
42
+ content: [
43
+ `Selected element: <${r}>`,
44
+ i ? `ARIA label: ${i}` : "",
45
+ a ? `Title: ${a}` : "",
46
+ n ? `Visible text: ${n.slice(0, 5e3)}` : ""
47
+ ].filter(Boolean).join("\n")
48
+ };
49
+ }
50
+ function s(e) {
51
+ let t = e.currentSrc || e.src, n = e.alt || "No alt text";
52
+ return {
53
+ label: `image: ${n.slice(0, 64)}`,
54
+ content: [
55
+ "Selected image",
56
+ `Alt text: ${n}`,
57
+ `Source: ${t}`,
58
+ `Rendered size: ${Math.round(e.width)}x${Math.round(e.height)}`,
59
+ `Natural size: ${e.naturalWidth}x${e.naturalHeight}`
60
+ ].join("\n")
61
+ };
62
+ }
63
+ function c(e, t) {
64
+ return !(n(e, t.blockedSelectors) || t.allowedSelectors.length > 0 && !n(e, t.allowedSelectors));
65
+ }
66
+ function l(e) {
67
+ let n = (t) => {
68
+ if (!e.isActive()) return;
69
+ let n = t.target;
70
+ if (!(n instanceof HTMLElement) || e.isOverlayElement(n)) {
71
+ e.onHover(null);
72
+ return;
73
+ }
74
+ if (!c(n, e.config)) {
75
+ e.onHover(null);
76
+ return;
77
+ }
78
+ e.onHover(n.getBoundingClientRect());
79
+ }, r = () => {
80
+ !e.isActive() || !e.config.text || window.setTimeout(() => {
81
+ let n = window.getSelection(), r = t(n?.toString() ?? "");
82
+ if (!n || !r) return;
83
+ let o = i(n), s = n.anchorNode?.parentElement;
84
+ !o || !s || !c(s, e.config) || e.onSelection({
85
+ kind: "text",
86
+ label: "highlighted text",
87
+ content: r,
88
+ ...a(o)
89
+ });
90
+ }, 0);
91
+ }, l = (n) => {
92
+ if (!e.isActive()) return;
93
+ let r = n.target;
94
+ if (!(r instanceof HTMLElement) || e.isOverlayElement(r) || !c(r, e.config) || t(window.getSelection()?.toString() ?? "")) return;
95
+ let i = r instanceof HTMLImageElement ? r : r.querySelector("img"), l = !!i;
96
+ if (l && !e.config.images || !l && !e.config.elements) return;
97
+ n.preventDefault(), n.stopPropagation();
98
+ let u = i ? s(i) : o(r), d = i ? i.getBoundingClientRect() : r.getBoundingClientRect();
99
+ e.onSelection({
100
+ kind: i ? "image" : "element",
101
+ label: u.label,
102
+ content: u.content,
103
+ ...a(d)
104
+ });
105
+ };
106
+ return document.addEventListener("pointerover", n), document.addEventListener("mouseup", r), document.addEventListener("click", l, !0), () => {
107
+ document.removeEventListener("pointerover", n), document.removeEventListener("mouseup", r), document.removeEventListener("click", l, !0);
108
+ };
109
+ }
110
+ //#endregion
111
+ //#region src/shake.ts
112
+ function u(e) {
113
+ let t = e.windowMs ?? 650, n = e.cooldownMs ?? 1e3, r = e.minSamples ?? 7, i = e.minReversals ?? 5, a = e.minDistance ?? 340, o = e.minDeltaX ?? 16, s = [], c = 0, l = (l) => {
114
+ let u = performance.now();
115
+ if (s.push({
116
+ x: l.clientX,
117
+ y: l.clientY,
118
+ time: u
119
+ }), s = s.filter((e) => u - e.time < t), s.length < r || u - c < n) return;
120
+ let d = 0, f = 0, p = 0;
121
+ for (let e = 1; e < s.length; e += 1) {
122
+ let t = s[e].x - s[e - 1].x, n = s[e].y - s[e - 1].y;
123
+ if (f += Math.hypot(t, n), Math.abs(t) < o) continue;
124
+ let r = Math.sign(t);
125
+ p !== 0 && r !== p && (d += 1), p = r;
126
+ }
127
+ d >= i && f > a && (c = u, s = [], e.onShake());
128
+ };
129
+ return document.addEventListener("pointermove", l), () => {
130
+ document.removeEventListener("pointermove", l);
131
+ };
132
+ }
133
+ //#endregion
134
+ //#region src/transport.ts
135
+ var d = "http://localhost:11434/api/chat", f = "qwen3-coder:480b-cloud", p = "https://shakeai.onrender.com";
136
+ async function m(e, t) {
137
+ let n = e.provider ?? "ollama", r = e.endpoint ?? d, i = e.model ?? f;
138
+ if (n === "custom") {
139
+ let n = await fetch(r, {
140
+ method: "POST",
141
+ headers: {
142
+ "Content-Type": "application/json",
143
+ ...e.headers
144
+ },
145
+ body: JSON.stringify(t)
146
+ });
147
+ if (!n.ok) throw Error(`Custom endpoint returned ${n.status}`);
148
+ let i = await n.json();
149
+ return typeof i == "string" ? i : i && typeof i == "object" && "answer" in i && typeof i.answer == "string" ? i.answer : i && typeof i == "object" && "content" in i && typeof i.content == "string" ? i.content : JSON.stringify(i, null, 2);
150
+ }
151
+ let a = await fetch(r, {
152
+ method: "POST",
153
+ headers: {
154
+ "Content-Type": "application/json",
155
+ ...e.headers
156
+ },
157
+ body: JSON.stringify({
158
+ model: i,
159
+ stream: !1,
160
+ messages: [{
161
+ role: "system",
162
+ content: "You are an in-page AI assistant. Use the selected website context to answer or transform content. Be direct and practical."
163
+ }, {
164
+ role: "user",
165
+ content: [
166
+ `Page title: ${t.selection.title}`,
167
+ `Page URL: ${t.selection.url}`,
168
+ `Selection type: ${t.selection.kind}`,
169
+ `Selection label: ${t.selection.label}`,
170
+ "Selected context:",
171
+ t.selection.content,
172
+ "",
173
+ `User request: ${t.question}`
174
+ ].join("\n")
175
+ }]
176
+ })
177
+ });
178
+ if (!a.ok) throw Error(`Ollama returned ${a.status}`);
179
+ let o = await a.json();
180
+ if (o.error) throw Error(o.error);
181
+ return o.message?.content?.trim() || "No response returned.";
182
+ }
183
+ async function h(e) {
184
+ let t = await fetch(`${e.apiBaseUrl ?? p}/v1/ask`, {
185
+ method: "POST",
186
+ headers: {
187
+ "Content-Type": "application/json",
188
+ "x-site-key": e.siteKey
189
+ },
190
+ body: JSON.stringify(e.payload)
191
+ }), n = await t.json();
192
+ if (!t.ok || n.error) throw Error(n.error ?? `Hosted API returned ${t.status}`);
193
+ return n.answer ?? "No response returned.";
194
+ }
195
+ //#endregion
196
+ //#region src/ui.ts
197
+ function g(t) {
198
+ let n = document.createElement("div");
199
+ n.dataset.aiOverlayRoot = "true";
200
+ let r = document.createElement("style");
201
+ r.dataset.aiOverlayStyle = "true", r.textContent = "body.ai-overlay-active { cursor: crosshair; }";
202
+ let i = n.attachShadow({ mode: "open" });
203
+ document.head.append(r), document.body.append(n), i.innerHTML = `
204
+ <style>
205
+ :host {
206
+ all: initial;
207
+ --ai-primary: ${t.theme.primaryColor};
208
+ --ai-panel: ${t.theme.panelBackground};
209
+ --ai-text: ${t.theme.textColor};
210
+ --ai-radius: ${t.theme.borderRadius}px;
211
+ font-family: Inter, ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
212
+ }
213
+
214
+ .toast {
215
+ position: fixed;
216
+ right: 18px;
217
+ bottom: 18px;
218
+ z-index: 2147483645;
219
+ display: none;
220
+ align-items: center;
221
+ gap: 10px;
222
+ border: 1px solid color-mix(in srgb, var(--ai-primary), transparent 74%);
223
+ border-radius: var(--ai-radius);
224
+ background: var(--ai-panel);
225
+ color: var(--ai-text);
226
+ padding: 10px 12px;
227
+ font: 700 13px/1 Inter, ui-sans-serif, system-ui, sans-serif;
228
+ box-shadow: 0 16px 42px rgba(15, 23, 42, 0.18);
229
+ }
230
+
231
+ .toast.active {
232
+ display: flex;
233
+ }
234
+
235
+ .pulse {
236
+ width: 8px;
237
+ height: 8px;
238
+ border-radius: 999px;
239
+ background: var(--ai-primary);
240
+ box-shadow: 0 0 0 0 color-mix(in srgb, var(--ai-primary), transparent 38%);
241
+ animation: pulse-ring 1.3s infinite;
242
+ }
243
+
244
+ .count {
245
+ border-left: 1px solid rgba(15, 23, 42, 0.12);
246
+ padding-left: 10px;
247
+ opacity: 0.72;
248
+ }
249
+
250
+ .hover {
251
+ position: fixed;
252
+ z-index: 2147483644;
253
+ pointer-events: none;
254
+ display: none;
255
+ border: 2px solid var(--ai-primary);
256
+ border-radius: var(--ai-radius);
257
+ box-shadow:
258
+ 0 0 0 4px color-mix(in srgb, var(--ai-primary), transparent 88%),
259
+ 0 20px 52px rgba(15, 23, 42, 0.14);
260
+ }
261
+
262
+ .panel {
263
+ position: fixed;
264
+ z-index: 2147483646;
265
+ width: 380px;
266
+ display: none;
267
+ border: 1px solid rgba(17, 24, 39, 0.14);
268
+ border-radius: var(--ai-radius);
269
+ background: var(--ai-panel);
270
+ color: var(--ai-text);
271
+ text-align: left;
272
+ box-shadow: 0 26px 70px rgba(15, 23, 42, 0.22);
273
+ overflow: hidden;
274
+ }
275
+
276
+ .panel.visible {
277
+ display: block;
278
+ }
279
+
280
+ .head {
281
+ display: flex;
282
+ align-items: center;
283
+ justify-content: space-between;
284
+ gap: 14px;
285
+ border-bottom: 1px solid rgba(17, 24, 39, 0.08);
286
+ padding: 14px 14px 12px;
287
+ }
288
+
289
+ .kind {
290
+ display: block;
291
+ margin-bottom: 4px;
292
+ color: var(--ai-primary);
293
+ font: 800 11px/1 Inter, ui-sans-serif, system-ui, sans-serif;
294
+ text-transform: uppercase;
295
+ }
296
+
297
+ .label {
298
+ display: block;
299
+ color: var(--ai-text);
300
+ font: 700 14px/1.25 Inter, ui-sans-serif, system-ui, sans-serif;
301
+ }
302
+
303
+ button {
304
+ border: 1px solid rgba(17, 24, 39, 0.12);
305
+ border-radius: var(--ai-radius);
306
+ min-height: 40px;
307
+ background: #111827;
308
+ color: #ffffff;
309
+ font: 700 14px/1 Inter, ui-sans-serif, system-ui, sans-serif;
310
+ cursor: pointer;
311
+ }
312
+
313
+ button:disabled {
314
+ cursor: not-allowed;
315
+ opacity: 0.56;
316
+ }
317
+
318
+ .close {
319
+ min-width: 34px;
320
+ min-height: 34px;
321
+ padding: 0;
322
+ background: #f3f4f6;
323
+ color: #111827;
324
+ }
325
+
326
+ form {
327
+ display: grid;
328
+ gap: 10px;
329
+ padding: 14px;
330
+ }
331
+
332
+ textarea {
333
+ box-sizing: border-box;
334
+ width: 100%;
335
+ resize: vertical;
336
+ min-height: 86px;
337
+ max-height: 180px;
338
+ border: 1px solid rgba(17, 24, 39, 0.16);
339
+ border-radius: var(--ai-radius);
340
+ padding: 12px;
341
+ color: #111827;
342
+ background: #ffffff;
343
+ font: 15px/1.45 Inter, ui-sans-serif, system-ui, sans-serif;
344
+ }
345
+
346
+ textarea:focus {
347
+ border-color: var(--ai-primary);
348
+ outline: 3px solid color-mix(in srgb, var(--ai-primary), transparent 86%);
349
+ }
350
+
351
+ .error,
352
+ .answer {
353
+ display: none;
354
+ margin: 0 14px 14px;
355
+ border-radius: var(--ai-radius);
356
+ padding: 12px;
357
+ font: 14px/1.45 Inter, ui-sans-serif, system-ui, sans-serif;
358
+ white-space: pre-wrap;
359
+ }
360
+
361
+ .error.visible,
362
+ .answer.visible {
363
+ display: block;
364
+ }
365
+
366
+ .error {
367
+ background: #fef2f2;
368
+ color: #991b1b;
369
+ }
370
+
371
+ .answer {
372
+ max-height: 220px;
373
+ overflow: auto;
374
+ background: #f8fafc;
375
+ color: #111827;
376
+ }
377
+
378
+ @media (max-width: 760px) {
379
+ .panel {
380
+ left: 12px !important;
381
+ right: 12px;
382
+ top: auto !important;
383
+ bottom: 12px;
384
+ width: auto;
385
+ }
386
+ }
387
+
388
+ @keyframes pulse-ring {
389
+ 70% { box-shadow: 0 0 0 8px transparent; }
390
+ 100% { box-shadow: 0 0 0 0 transparent; }
391
+ }
392
+ </style>
393
+ <div class="toast" role="status">
394
+ <span class="pulse"></span>
395
+ <span>AI mode active</span>
396
+ <span class="count">shakes 0</span>
397
+ </div>
398
+ <div class="hover"></div>
399
+ <section class="panel" aria-label="AI prompt">
400
+ <div class="head">
401
+ <div>
402
+ <span class="kind"></span>
403
+ <strong class="label"></strong>
404
+ </div>
405
+ <button class="close" type="button" aria-label="Close AI prompt">x</button>
406
+ </div>
407
+ <form>
408
+ <textarea placeholder="Ask what to do with this selection..."></textarea>
409
+ <button class="submit" type="submit">Ask AI</button>
410
+ </form>
411
+ <p class="error"></p>
412
+ <div class="answer"></div>
413
+ </section>
414
+ `;
415
+ let a = i.querySelector(".toast"), o = i.querySelector(".count"), s = i.querySelector(".hover"), c = i.querySelector(".panel"), l = i.querySelector(".kind"), u = i.querySelector(".label"), d = i.querySelector("form"), f = i.querySelector("textarea"), p = i.querySelector(".submit"), m = i.querySelector(".close"), h = i.querySelector(".error"), g = i.querySelector(".answer");
416
+ if (!a || !o || !s || !c || !l || !u || !d || !f || !p || !m || !h || !g) throw Error("AIOverlay UI failed to initialize");
417
+ return d.addEventListener("submit", (e) => {
418
+ e.preventDefault();
419
+ let n = f.value.trim();
420
+ n && t.onSubmit(n);
421
+ }), m.addEventListener("click", () => {
422
+ t.onCancel();
423
+ }), {
424
+ root: n,
425
+ contains: (e) => e === n || n.contains(e),
426
+ setActive: (e, t) => {
427
+ a.classList.toggle("active", e), o.textContent = `shakes ${t}`;
428
+ },
429
+ setHoverRect: (e) => {
430
+ if (!e) {
431
+ s.style.display = "none";
432
+ return;
433
+ }
434
+ s.style.display = "block", s.style.left = `${e.left}px`, s.style.top = `${e.top}px`, s.style.width = `${e.width}px`, s.style.height = `${e.height}px`;
435
+ },
436
+ showPrompt: (t) => {
437
+ let n = e(t.rect.left + t.rect.width / 2 - 190, 16, window.innerWidth - 396), r = e(t.rect.bottom + 14, 16, window.innerHeight - 380);
438
+ l.textContent = t.kind, u.textContent = t.label, f.value = "", p.disabled = !1, p.textContent = "Ask AI", h.classList.remove("visible"), g.classList.remove("visible"), c.style.left = `${n}px`, c.style.top = `${r}px`, c.classList.add("visible"), f.focus();
439
+ },
440
+ setThinking: (e) => {
441
+ p.disabled = e, p.textContent = e ? "Thinking..." : "Ask AI";
442
+ },
443
+ setAnswer: (e) => {
444
+ g.textContent = e, g.classList.toggle("visible", !!e);
445
+ },
446
+ setError: (e) => {
447
+ h.textContent = e, h.classList.toggle("visible", !!e);
448
+ },
449
+ clearSelection: () => {
450
+ c.classList.remove("visible"), h.classList.remove("visible"), g.classList.remove("visible"), f.value = "";
451
+ },
452
+ destroy: () => {
453
+ r.remove(), n.remove();
454
+ }
455
+ };
456
+ }
457
+ //#endregion
458
+ //#region src/index.ts
459
+ var _ = {
460
+ primaryColor: "#14b8a6",
461
+ panelBackground: "rgba(255, 255, 255, 0.96)",
462
+ textColor: "#111827",
463
+ borderRadius: 8
464
+ }, v = {
465
+ shake: !0,
466
+ keyboardShortcut: "mod+k"
467
+ }, y = {
468
+ text: !0,
469
+ images: !0,
470
+ elements: !0,
471
+ blockedSelectors: [
472
+ "input[type=\"password\"]",
473
+ "[data-ai-private]",
474
+ "[data-ai-overlay-ignore]"
475
+ ],
476
+ allowedSelectors: []
477
+ }, b = class {
478
+ active = !1;
479
+ shakeCount = 0;
480
+ selection = null;
481
+ disposers = [];
482
+ ui;
483
+ config;
484
+ trigger;
485
+ constructor(e) {
486
+ this.config = e, this.trigger = {
487
+ ...v,
488
+ ...e.trigger
489
+ };
490
+ let t = {
491
+ ...y,
492
+ ...e.selection
493
+ };
494
+ this.ui = g({
495
+ theme: {
496
+ ..._,
497
+ ...e.theme
498
+ },
499
+ onSubmit: (e) => {
500
+ this.ask(e);
501
+ },
502
+ onCancel: () => {
503
+ this.clearSelection();
504
+ }
505
+ }), this.trigger.shake && this.disposers.push(u({ onShake: () => {
506
+ this.shakeCount += 1, this.activate();
507
+ } })), this.disposers.push(l({
508
+ config: t,
509
+ isActive: () => this.active,
510
+ isOverlayElement: (e) => this.ui.contains(e),
511
+ onHover: (e) => {
512
+ this.ui.setHoverRect(this.selection ? null : e);
513
+ },
514
+ onSelection: (e) => {
515
+ this.selection = e, this.ui.setHoverRect(null), this.ui.showPrompt(e), this.config.onSelection?.(e);
516
+ }
517
+ })), this.disposers.push(this.bindKeyboardShortcut());
518
+ }
519
+ activate() {
520
+ if (this.active) {
521
+ this.ui.setActive(!0, this.shakeCount);
522
+ return;
523
+ }
524
+ this.active = !0, document.body.classList.add("ai-overlay-active"), this.ui.setActive(!0, this.shakeCount), this.config.onActivate?.();
525
+ }
526
+ deactivate() {
527
+ this.active && (this.active = !1, this.clearSelection(), this.ui.setHoverRect(null), this.ui.setActive(!1, this.shakeCount), document.body.classList.remove("ai-overlay-active"), this.config.onDeactivate?.());
528
+ }
529
+ destroy() {
530
+ for (let e of this.disposers) e();
531
+ this.disposers = [], this.deactivate(), this.ui.destroy();
532
+ }
533
+ isActive() {
534
+ return this.active;
535
+ }
536
+ clearSelection() {
537
+ this.selection = null, this.ui.clearSelection(), window.getSelection()?.removeAllRanges();
538
+ }
539
+ async ask(e) {
540
+ if (!this.selection) return;
541
+ let t = {
542
+ question: e,
543
+ selection: this.selection
544
+ };
545
+ this.config.onAsk?.(t), this.ui.setThinking(!0), this.ui.setAnswer(""), this.ui.setError("");
546
+ try {
547
+ let e = this.config.siteKey ? await h({
548
+ apiBaseUrl: this.config.apiBaseUrl,
549
+ siteKey: this.config.siteKey,
550
+ payload: t
551
+ }) : await m(this.config.model ?? {}, t);
552
+ this.ui.setAnswer(e), this.config.onResponse?.(e, t);
553
+ } catch (e) {
554
+ let t = e instanceof Error ? e : /* @__PURE__ */ Error("Unknown AIOverlay error");
555
+ this.ui.setError(t.message), this.config.onError?.(t);
556
+ } finally {
557
+ this.ui.setThinking(!1);
558
+ }
559
+ }
560
+ bindKeyboardShortcut() {
561
+ let e = this.trigger.keyboardShortcut.toLowerCase(), t = (t) => {
562
+ let n = e.includes("mod+"), i = e.split("+").at(-1), a = r() ? t.metaKey : t.ctrlKey;
563
+ n && !a || i && t.key.toLowerCase() === i && (t.preventDefault(), this.active ? this.deactivate() : this.activate());
564
+ };
565
+ return document.addEventListener("keydown", t), () => {
566
+ document.removeEventListener("keydown", t);
567
+ };
568
+ }
569
+ }, x = { init(e = {}) {
570
+ return new b(e);
571
+ } };
572
+ typeof window < "u" && (window.AIOverlay = x);
573
+ //#endregion
574
+ export { x as AIOverlay };
@@ -0,0 +1,9 @@
1
+ import type { AIOverlaySelection, AIOverlaySelectionConfig } from './types';
2
+ export type SelectionControllerOptions = {
3
+ config: Required<AIOverlaySelectionConfig>;
4
+ isActive: () => boolean;
5
+ isOverlayElement: (element: Element) => boolean;
6
+ onHover: (rect: DOMRect | null) => void;
7
+ onSelection: (selection: AIOverlaySelection) => void;
8
+ };
9
+ export declare function createSelectionController(options: SelectionControllerOptions): () => void;
@@ -0,0 +1,10 @@
1
+ export type ShakeDetectorOptions = {
2
+ onShake: () => void;
3
+ windowMs?: number;
4
+ cooldownMs?: number;
5
+ minSamples?: number;
6
+ minReversals?: number;
7
+ minDistance?: number;
8
+ minDeltaX?: number;
9
+ };
10
+ export declare function createShakeDetector(options: ShakeDetectorOptions): () => void;
@@ -0,0 +1,7 @@
1
+ import type { AIOverlayAskPayload, AIOverlayModelConfig } from './types';
2
+ export declare function askModel(config: AIOverlayModelConfig, payload: AIOverlayAskPayload): Promise<string>;
3
+ export declare function askHostedApi(options: {
4
+ apiBaseUrl?: string;
5
+ siteKey: string;
6
+ payload: AIOverlayAskPayload;
7
+ }): Promise<string>;
@@ -0,0 +1,59 @@
1
+ export type AIOverlaySelectionKind = 'text' | 'image' | 'element';
2
+ export type AIOverlaySelection = {
3
+ kind: AIOverlaySelectionKind;
4
+ label: string;
5
+ content: string;
6
+ rect: DOMRect;
7
+ url: string;
8
+ title: string;
9
+ };
10
+ export type AIOverlayAskPayload = {
11
+ question: string;
12
+ selection: AIOverlaySelection;
13
+ };
14
+ export type AIOverlayModelConfig = {
15
+ provider?: 'ollama' | 'custom';
16
+ endpoint?: string;
17
+ model?: string;
18
+ headers?: Record<string, string>;
19
+ };
20
+ export type AIOverlayThemeConfig = {
21
+ primaryColor?: string;
22
+ panelBackground?: string;
23
+ textColor?: string;
24
+ borderRadius?: number;
25
+ };
26
+ export type AIOverlayTriggerConfig = {
27
+ shake?: boolean;
28
+ keyboardShortcut?: string;
29
+ };
30
+ export type AIOverlaySelectionConfig = {
31
+ text?: boolean;
32
+ images?: boolean;
33
+ elements?: boolean;
34
+ blockedSelectors?: string[];
35
+ allowedSelectors?: string[];
36
+ };
37
+ export type AIOverlayConfig = {
38
+ siteKey?: string;
39
+ apiBaseUrl?: string;
40
+ model?: AIOverlayModelConfig;
41
+ trigger?: AIOverlayTriggerConfig;
42
+ selection?: AIOverlaySelectionConfig;
43
+ theme?: AIOverlayThemeConfig;
44
+ analytics?: {
45
+ enabled?: boolean;
46
+ };
47
+ onActivate?: () => void;
48
+ onDeactivate?: () => void;
49
+ onSelection?: (selection: AIOverlaySelection) => void;
50
+ onAsk?: (payload: AIOverlayAskPayload) => void;
51
+ onResponse?: (response: string, payload: AIOverlayAskPayload) => void;
52
+ onError?: (error: Error) => void;
53
+ };
54
+ export type AIOverlayInstance = {
55
+ activate: () => void;
56
+ deactivate: () => void;
57
+ destroy: () => void;
58
+ isActive: () => boolean;
59
+ };
package/dist/ui.d.ts ADDED
@@ -0,0 +1,20 @@
1
+ import type { AIOverlaySelection, AIOverlayThemeConfig } from './types';
2
+ type OverlayUIOptions = {
3
+ theme: Required<AIOverlayThemeConfig>;
4
+ onSubmit: (question: string) => void;
5
+ onCancel: () => void;
6
+ };
7
+ export type OverlayUI = {
8
+ root: HTMLElement;
9
+ contains: (element: Element) => boolean;
10
+ setActive: (active: boolean, count: number) => void;
11
+ setHoverRect: (rect: DOMRect | null) => void;
12
+ showPrompt: (selection: AIOverlaySelection) => void;
13
+ setThinking: (thinking: boolean) => void;
14
+ setAnswer: (answer: string) => void;
15
+ setError: (error: string) => void;
16
+ clearSelection: () => void;
17
+ destroy: () => void;
18
+ };
19
+ export declare function createOverlayUI(options: OverlayUIOptions): OverlayUI;
20
+ export {};
@@ -0,0 +1,4 @@
1
+ export declare function clamp(value: number, min: number, max: number): number;
2
+ export declare function normalizeText(value: string): string;
3
+ export declare function matchesAnySelector(element: Element, selectors?: string[]): boolean;
4
+ export declare function isMacLike(): boolean;
package/package.json ADDED
@@ -0,0 +1,40 @@
1
+ {
2
+ "name": "@emmaexcel/shakecursor",
3
+ "version": "0.1.0",
4
+ "description": "Framework-agnostic browser SDK for contextual AI selection overlays.",
5
+ "type": "module",
6
+ "main": "./dist/overlay.global.js",
7
+ "module": "./dist/overlay.js",
8
+ "types": "./dist/index.d.ts",
9
+ "exports": {
10
+ ".": {
11
+ "types": "./dist/index.d.ts",
12
+ "import": "./dist/overlay.js",
13
+ "default": "./dist/overlay.global.js"
14
+ }
15
+ },
16
+ "files": [
17
+ "dist"
18
+ ],
19
+ "publishConfig": {
20
+ "access": "public"
21
+ },
22
+ "scripts": {
23
+ "build": "vite build --config vite.config.ts && tsc -p tsconfig.json",
24
+ "prepublishOnly": "npm run build"
25
+ },
26
+ "devDependencies": {
27
+ "vite": "^8.0.12",
28
+ "typescript": "~6.0.2"
29
+ },
30
+ "keywords": [
31
+ "ai",
32
+ "overlay",
33
+ "rag",
34
+ "contextual",
35
+ "shake",
36
+ "cursor"
37
+ ],
38
+ "author": "EmmaExcel",
39
+ "license": "MIT"
40
+ }