ai-panel 1.0.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,47 @@
1
+ # AI Dev Panel
2
+
3
+ Uma ferramenta revolucionária para edição de aplicações web em tempo real usando IA.
4
+
5
+ ## 🧩 Arquitetura
6
+
7
+ O projeto é um monorepo TypeScript dividido em:
8
+
9
+ - `packages/core`: Tipagens e contratos compartilhados.
10
+ - `packages/overlay`: Script injetado no navegador que fornece a interface de seleção e chat.
11
+ - `packages/plugin`: Plugin Vite que serve o overlay e processa as alterações no código.
12
+ - `packages/cli-connector`: Módulo responsável pela comunicação com CLIs de IA (Claude, OpenAI, etc).
13
+
14
+ ## 🚀 Como usar (Demo)
15
+
16
+ 1. Instale as dependências:
17
+ ```bash
18
+ npm install
19
+ ```
20
+
21
+ 2. Inicie o projeto de exemplo:
22
+ ```bash
23
+ npm run dev
24
+ ```
25
+
26
+ 3. Abra o navegador em `http://localhost:3000`.
27
+
28
+ 4. Use o atalho **Ctrl + Espaço** para ativar o modo IA.
29
+
30
+ 5. Selecione um elemento (ex: o botão azul) e solicite uma alteração no painel lateral.
31
+
32
+ ## 🔥 Funcionalidades Implementadas
33
+
34
+ - [x] Overlay flutuante com suporte a seleção de elementos.
35
+ - [x] Highlight visual de elementos em hover.
36
+ - [x] Painel de chat lateral integrado.
37
+ - [x] Plugin Vite para injeção automática e Hot Reload.
38
+ - [x] Middleware de servidor para processar comandos da IA.
39
+ - [x] Mock de conector de IA com suporte a diffs.
40
+ - [x] Aplicação automática de mudanças no sistema de arquivos.
41
+
42
+ ## 🛠️ Próximos Passos
43
+
44
+ - Integração real com `claude-cli` ou `ollama`.
45
+ - Uso de AST (Babel/TS Morph) para injeção de metadados de origem de forma robusta.
46
+ - Preview de diff antes de aplicar.
47
+ - Undo/Redo de alterações.
package/dist/core.d.ts ADDED
@@ -0,0 +1,9 @@
1
+ export declare class AIPanelCore {
2
+ private projectRoot;
3
+ private historyFile;
4
+ constructor(projectRoot?: string);
5
+ handleRequest(body: any): Promise<any>;
6
+ private getHistory;
7
+ private saveToHistory;
8
+ private executeClaude;
9
+ }
package/dist/core.js ADDED
@@ -0,0 +1,160 @@
1
+ import { spawn } from 'child_process';
2
+ import * as fs from 'fs';
3
+ import * as path from 'path';
4
+ import * as os from 'os';
5
+ export class AIPanelCore {
6
+ projectRoot;
7
+ historyFile;
8
+ constructor(projectRoot = process.cwd()) {
9
+ this.projectRoot = projectRoot;
10
+ this.historyFile = path.join(this.projectRoot, 'ai_history.json');
11
+ }
12
+ async handleRequest(body) {
13
+ const { action } = body;
14
+ if (action === 'getHistory') {
15
+ return this.getHistory();
16
+ }
17
+ if (action === 'execute') {
18
+ const command = body;
19
+ const response = await this.executeClaude(command);
20
+ // Save to history
21
+ await this.saveToHistory(command, response);
22
+ return response;
23
+ }
24
+ throw new Error(`Unknown action: ${action}`);
25
+ }
26
+ getHistory() {
27
+ if (fs.existsSync(this.historyFile)) {
28
+ try {
29
+ return JSON.parse(fs.readFileSync(this.historyFile, 'utf-8'));
30
+ }
31
+ catch (e) {
32
+ console.error('[AI Core] Error parsing history file:', e);
33
+ }
34
+ }
35
+ return { messages: [] };
36
+ }
37
+ async saveToHistory(command, response) {
38
+ try {
39
+ const history = this.getHistory();
40
+ history.messages.push({
41
+ role: 'user',
42
+ content: command.instruction,
43
+ element: command.element,
44
+ timestamp: new Date().toISOString()
45
+ });
46
+ history.messages.push({
47
+ role: 'ai',
48
+ content: response.message,
49
+ timestamp: new Date().toISOString()
50
+ });
51
+ fs.writeFileSync(this.historyFile, JSON.stringify(history, null, 2));
52
+ }
53
+ catch (e) {
54
+ console.error('[AI Core] Error saving history:', e);
55
+ }
56
+ }
57
+ async executeClaude(command) {
58
+ const { instruction, element, context } = command;
59
+ const absolutePath = element?.source?.file ? path.resolve(this.projectRoot, element.source.file) : null;
60
+ const prompt = `
61
+ USER INSTRUCTION: ${instruction}
62
+ ${element ? `TARGET ELEMENT: ${element.tagName}${element.className ? `.${element.className}` : ''}` : ''}
63
+ ${element?.source ? `FILE: ${element.source.file}` : ''}
64
+
65
+ CONTEXT:
66
+ ${context.targetFileContent ? `Current file content:\n${context.targetFileContent}` : ''}
67
+ ${context.projectContext ? `Project context:\n${context.projectContext}` : ''}
68
+
69
+ FINAL DIRECTIVE: ${instruction}
70
+ `;
71
+ const tempPromptFile = path.join(os.tmpdir(), `claude_prompt_${Date.now()}.txt`);
72
+ const tempScriptFile = path.join(os.tmpdir(), `claude_run_${Date.now()}.ps1`);
73
+ try {
74
+ // Sync IDE content to disk before running Claude if possible
75
+ if (absolutePath && context.targetFileContent && fs.existsSync(absolutePath)) {
76
+ fs.writeFileSync(absolutePath, context.targetFileContent, 'utf8');
77
+ }
78
+ fs.writeFileSync(tempPromptFile, prompt, 'utf8');
79
+ const scriptContent = `
80
+ $ErrorActionPreference = 'SilentlyContinue'
81
+ $PromptText = Get-Content -Path "${tempPromptFile.replace(/\\/g, '\\\\')}" -Raw
82
+
83
+ Write-Output "---CLAUDE-RESPONSE-START---"
84
+ $input = $PromptText + [System.Environment]::NewLine + "exit"
85
+ $input | & claude --dangerously-skip-permissions
86
+ Write-Output "---CLAUDE-RESPONSE-END---"
87
+ `;
88
+ fs.writeFileSync(tempScriptFile, scriptContent, 'utf8');
89
+ let capturedStdout = '';
90
+ await new Promise((resolve) => {
91
+ const child = spawn('powershell.exe', ['-NoProfile', '-ExecutionPolicy', 'Bypass', '-File', tempScriptFile], {
92
+ stdio: ['pipe', 'pipe', 'inherit'],
93
+ cwd: this.projectRoot,
94
+ shell: false
95
+ });
96
+ child.stdout?.on('data', (data) => {
97
+ const str = data.toString();
98
+ capturedStdout += str;
99
+ process.stdout.write(str);
100
+ if (capturedStdout.includes("---CLAUDE-RESPONSE-END---")) {
101
+ resolve(null);
102
+ child.kill();
103
+ }
104
+ });
105
+ child.on('exit', () => resolve(null));
106
+ setTimeout(() => { try {
107
+ child.kill();
108
+ }
109
+ catch (e) { } resolve(null); }, 60000);
110
+ });
111
+ const stdout = capturedStdout.replace(/[\u001b\u009b][[()#;?]*(?:[0-9]{1,4}(?:;[0-9]{0,4})*)?[0-9A-ORZcf-nqry=><]/g, '');
112
+ // Parse diffs (simplified: read the file back)
113
+ let diffs = [];
114
+ let newContent = context.targetFileContent || '';
115
+ if (absolutePath && fs.existsSync(absolutePath)) {
116
+ newContent = fs.readFileSync(absolutePath, 'utf8');
117
+ if (newContent !== context.targetFileContent) {
118
+ diffs.push({
119
+ path: element?.source?.file || 'unknown',
120
+ original: context.targetFileContent || '',
121
+ modified: newContent,
122
+ patch: ''
123
+ });
124
+ }
125
+ }
126
+ // Extract message
127
+ const startMarker = "---CLAUDE-RESPONSE-START---";
128
+ const endMarker = "---CLAUDE-RESPONSE-END---";
129
+ const startIndex = stdout.indexOf(startMarker);
130
+ const endIndex = stdout.indexOf(endMarker);
131
+ let responseMessage = "";
132
+ if (startIndex !== -1) {
133
+ if (endIndex !== -1) {
134
+ responseMessage = stdout.substring(startIndex + startMarker.length, endIndex).trim();
135
+ }
136
+ else {
137
+ responseMessage = stdout.substring(startIndex + startMarker.length).trim();
138
+ }
139
+ }
140
+ responseMessage = responseMessage
141
+ .replace(/USER INSTRUCTION:[\s\S]*?FINAL DIRECTIVE:.*\n/g, '')
142
+ .replace(/\(To exit, type 'exit' or press Ctrl\+C\)/g, '')
143
+ .trim();
144
+ if (!responseMessage) {
145
+ responseMessage = diffs.length > 0 ? `Updated ${element?.source?.file}` : "Claude finished execution.";
146
+ }
147
+ return { message: responseMessage, diffs };
148
+ }
149
+ finally {
150
+ try {
151
+ fs.unlinkSync(tempPromptFile);
152
+ }
153
+ catch (e) { }
154
+ try {
155
+ fs.unlinkSync(tempScriptFile);
156
+ }
157
+ catch (e) { }
158
+ }
159
+ }
160
+ }
@@ -0,0 +1,5 @@
1
+ export * from './types';
2
+ export * from './core';
3
+ export * from './overlay';
4
+ export { aiPanelVite } from './vite';
5
+ export { createNextAIPanelHandler } from './next';
package/dist/index.js ADDED
@@ -0,0 +1,5 @@
1
+ export * from './types';
2
+ export * from './core';
3
+ export * from './overlay';
4
+ export { aiPanelVite } from './vite';
5
+ export { createNextAIPanelHandler } from './next';
package/dist/next.d.ts ADDED
@@ -0,0 +1,3 @@
1
+ export declare function createNextAIPanelHandler(options?: {
2
+ projectRoot?: string;
3
+ }): (req: any, res: any) => Promise<any>;
package/dist/next.js ADDED
@@ -0,0 +1,30 @@
1
+ import { AIPanelCore } from './core';
2
+ export function createNextAIPanelHandler(options = {}) {
3
+ const core = new AIPanelCore(options.projectRoot);
4
+ return async function handler(req, res) {
5
+ if (req.method !== 'POST') {
6
+ if (res.status)
7
+ return res.status(405).json({ error: 'Method not allowed' });
8
+ return new Response(JSON.stringify({ error: 'Method not allowed' }), { status: 405 });
9
+ }
10
+ try {
11
+ // Handle both Pages API (req.body) and App Router (req.json())
12
+ const body = req.body || (await req.json());
13
+ const result = await core.handleRequest(body);
14
+ if (res.status) {
15
+ return res.status(200).json(result);
16
+ }
17
+ return new Response(JSON.stringify(result), {
18
+ status: 200,
19
+ headers: { 'Content-Type': 'application/json' }
20
+ });
21
+ }
22
+ catch (e) {
23
+ const error = e;
24
+ if (res.status) {
25
+ return res.status(500).json({ error: error.message });
26
+ }
27
+ return new Response(JSON.stringify({ error: error.message }), { status: 500 });
28
+ }
29
+ };
30
+ }
@@ -0,0 +1,21 @@
1
+ export declare class AIOverlay {
2
+ private container;
3
+ private chatContent;
4
+ private input;
5
+ private selectedElement;
6
+ private isProcessing;
7
+ private endpoint;
8
+ constructor(options?: {
9
+ endpoint?: string;
10
+ });
11
+ private init;
12
+ private createStyles;
13
+ private createUI;
14
+ private loadHistory;
15
+ private addMessageToChat;
16
+ private startInspecting;
17
+ private setupElementPicker;
18
+ private handleSend;
19
+ private getElementMetadata;
20
+ private getFileContent;
21
+ }
@@ -0,0 +1,203 @@
1
+ export class AIOverlay {
2
+ container = null;
3
+ chatContent = null;
4
+ input = null;
5
+ selectedElement = null;
6
+ isProcessing = false;
7
+ endpoint;
8
+ constructor(options = {}) {
9
+ this.endpoint = options.endpoint || '/__ai_dev_api';
10
+ this.init();
11
+ }
12
+ init() {
13
+ if (typeof document === 'undefined')
14
+ return;
15
+ this.createStyles();
16
+ this.createUI();
17
+ this.setupElementPicker();
18
+ this.loadHistory();
19
+ }
20
+ createStyles() {
21
+ const style = document.createElement('style');
22
+ style.textContent = `
23
+ #ai-panel-root {
24
+ position: fixed; top: 0; right: 0; width: 350px; height: 100vh;
25
+ background: #1e1e1e; color: white; z-index: 99999;
26
+ display: flex; flex-direction: column; font-family: sans-serif;
27
+ box-shadow: -2px 0 10px rgba(0,0,0,0.5); transform: translateX(100%);
28
+ transition: transform 0.3s ease; border-left: 1px solid #333;
29
+ }
30
+ #ai-panel-root.open { transform: translateX(0); }
31
+ #ai-panel-toggle {
32
+ position: fixed; bottom: 20px; right: 20px; width: 50px; height: 50px;
33
+ background: #007acc; border-radius: 25px; cursor: pointer; z-index: 100000;
34
+ display: flex; align-items: center; justify-content: center; box-shadow: 0 2px 10px rgba(0,0,0,0.3);
35
+ }
36
+ #ai-panel-header { padding: 15px; border-bottom: 1px solid #333; font-weight: bold; display: flex; justify-content: space-between; }
37
+ #ai-panel-chat { flex: 1; overflow-y: auto; padding: 15px; display: flex; flex-direction: column; gap: 10px; }
38
+ .chat-msg { padding: 8px 12px; border-radius: 8px; max-width: 85%; font-size: 14px; line-height: 1.4; }
39
+ .chat-msg.user { background: #007acc; align-self: flex-end; }
40
+ .chat-msg.ai { background: #333; align-self: flex-start; }
41
+ #ai-panel-input-container { padding: 15px; border-top: 1px solid #333; }
42
+ #ai-panel-input { width: 100%; background: #2d2d2d; border: 1px solid #444; color: white; padding: 10px; border-radius: 4px; resize: none; box-sizing: border-box; }
43
+ #ai-panel-footer { padding: 0 15px 15px; display: flex; gap: 10px; }
44
+ .ai-btn { padding: 8px 12px; border-radius: 4px; border: none; cursor: pointer; font-size: 12px; }
45
+ .ai-btn-primary { background: #007acc; color: white; }
46
+ .ai-btn-secondary { background: #444; color: white; }
47
+ .inspecting { cursor: crosshair !important; }
48
+ .inspecting * { cursor: crosshair !important; }
49
+ .ai-highlight { outline: 2px solid #007acc !important; outline-offset: 2px; }
50
+ `;
51
+ document.head.appendChild(style);
52
+ }
53
+ createUI() {
54
+ this.container = document.createElement('div');
55
+ this.container.id = 'ai-panel-root';
56
+ this.container.innerHTML = `
57
+ <div id="ai-panel-header">
58
+ AI Assistant
59
+ <span style="cursor:pointer" onclick="document.getElementById('ai-panel-root').classList.remove('open')">×</span>
60
+ </div>
61
+ <div id="ai-panel-chat"></div>
62
+ <div id="ai-panel-input-container">
63
+ <div id="selected-element-info" style="font-size:10px; color:#aaa; margin-bottom:5px; white-space:nowrap; overflow:hidden; text-overflow:ellipsis"></div>
64
+ <textarea id="ai-panel-input" placeholder="O que deseja alterar?" rows="3"></textarea>
65
+ </div>
66
+ <div id="ai-panel-footer">
67
+ <button id="ai-btn-inspect" class="ai-btn ai-btn-secondary">Selecionar Elemento</button>
68
+ <button id="ai-btn-send" class="ai-btn ai-btn-primary">Enviar</button>
69
+ </div>
70
+ `;
71
+ document.body.appendChild(this.container);
72
+ const toggle = document.createElement('div');
73
+ toggle.id = 'ai-panel-toggle';
74
+ toggle.innerHTML = 'AI';
75
+ toggle.onclick = () => this.container?.classList.toggle('open');
76
+ document.body.appendChild(toggle);
77
+ this.chatContent = this.container.querySelector('#ai-panel-chat');
78
+ this.input = this.container.querySelector('#ai-panel-input');
79
+ this.container.querySelector('#ai-btn-send')?.addEventListener('click', () => this.handleSend());
80
+ this.container.querySelector('#ai-btn-inspect')?.addEventListener('click', () => this.startInspecting());
81
+ this.input?.addEventListener('keydown', (e) => {
82
+ if (e.key === 'Enter' && !e.shiftKey) {
83
+ e.preventDefault();
84
+ this.handleSend();
85
+ }
86
+ });
87
+ }
88
+ async loadHistory() {
89
+ try {
90
+ const response = await fetch(this.endpoint, {
91
+ method: 'POST',
92
+ headers: { 'Content-Type': 'application/json' },
93
+ body: JSON.stringify({ action: 'getHistory' }),
94
+ });
95
+ const data = await response.json();
96
+ if (data.messages) {
97
+ data.messages.forEach((msg) => {
98
+ this.addMessageToChat(msg.role, msg.content);
99
+ });
100
+ }
101
+ }
102
+ catch (e) {
103
+ console.error('Failed to load history:', e);
104
+ }
105
+ }
106
+ addMessageToChat(role, content) {
107
+ if (!this.chatContent)
108
+ return;
109
+ const msg = document.createElement('div');
110
+ msg.className = `chat-msg ${role}`;
111
+ msg.textContent = content;
112
+ this.chatContent.appendChild(msg);
113
+ this.chatContent.scrollTop = this.chatContent.scrollHeight;
114
+ }
115
+ startInspecting() {
116
+ document.body.classList.add('inspecting');
117
+ this.container?.classList.remove('open');
118
+ const onMouseOver = (e) => {
119
+ e.target.classList.add('ai-highlight');
120
+ };
121
+ const onMouseOut = (e) => {
122
+ e.target.classList.remove('ai-highlight');
123
+ };
124
+ const onClick = (e) => {
125
+ e.preventDefault();
126
+ e.stopPropagation();
127
+ this.selectedElement = e.target;
128
+ const info = this.container?.querySelector('#selected-element-info');
129
+ if (info)
130
+ info.textContent = `Selecionado: <${this.selectedElement.tagName.toLowerCase()}>`;
131
+ stop();
132
+ this.container?.classList.add('open');
133
+ };
134
+ const stop = () => {
135
+ document.body.classList.remove('inspecting');
136
+ document.removeEventListener('mouseover', onMouseOver);
137
+ document.removeEventListener('mouseout', onMouseOut);
138
+ document.removeEventListener('click', onClick, true);
139
+ };
140
+ document.addEventListener('mouseover', onMouseOver);
141
+ document.addEventListener('mouseout', onMouseOut);
142
+ document.addEventListener('click', onClick, true);
143
+ }
144
+ setupElementPicker() {
145
+ // Already handled in startInspecting
146
+ }
147
+ async handleSend() {
148
+ const text = this.input?.value.trim();
149
+ if (!text || this.isProcessing)
150
+ return;
151
+ this.isProcessing = true;
152
+ this.addMessageToChat('user', text);
153
+ if (this.input)
154
+ this.input.value = '';
155
+ const aiMsg = document.createElement('div');
156
+ aiMsg.className = 'chat-msg ai';
157
+ aiMsg.textContent = 'AI está pensando...';
158
+ this.chatContent?.appendChild(aiMsg);
159
+ try {
160
+ const metadata = this.selectedElement ? this.getElementMetadata(this.selectedElement) : undefined;
161
+ const response = await fetch(this.endpoint, {
162
+ method: 'POST',
163
+ headers: { 'Content-Type': 'application/json' },
164
+ body: JSON.stringify({
165
+ action: 'execute',
166
+ instruction: text,
167
+ element: metadata,
168
+ context: {
169
+ targetFileContent: metadata?.source?.file ? await this.getFileContent(metadata.source.file) : undefined
170
+ }
171
+ }),
172
+ });
173
+ const result = await response.json();
174
+ aiMsg.textContent = result.message;
175
+ // If code was changed, we might want to notify or reload parts of the page
176
+ if (result.diffs && result.diffs.length > 0) {
177
+ console.log('Changes applied:', result.diffs);
178
+ }
179
+ }
180
+ catch (e) {
181
+ aiMsg.textContent = 'Erro ao processar solicitação.';
182
+ console.error(e);
183
+ }
184
+ finally {
185
+ this.isProcessing = false;
186
+ }
187
+ }
188
+ getElementMetadata(el) {
189
+ // This is a placeholder - in a real lib, we'd use a Vite plugin to inject source info
190
+ return {
191
+ tagName: el.tagName,
192
+ className: el.className,
193
+ id: el.id,
194
+ outerHTML: el.outerHTML,
195
+ source: el.__source
196
+ };
197
+ }
198
+ async getFileContent(file) {
199
+ // In a development environment, we might have an endpoint to read files
200
+ // For now, return empty or handle via core
201
+ return "";
202
+ }
203
+ }
@@ -0,0 +1,39 @@
1
+ export interface ElementMetadata {
2
+ tagName: string;
3
+ className?: string;
4
+ id?: string;
5
+ textContent?: string;
6
+ outerHTML: string;
7
+ source?: {
8
+ file: string;
9
+ line?: number;
10
+ column?: number;
11
+ };
12
+ }
13
+ export interface AIDiff {
14
+ path: string;
15
+ original: string;
16
+ modified: string;
17
+ patch: string;
18
+ }
19
+ export interface AIResponse {
20
+ message: string;
21
+ diffs: AIDiff[];
22
+ }
23
+ export interface AICommand {
24
+ instruction: string;
25
+ element?: ElementMetadata;
26
+ context: {
27
+ targetFileContent?: string;
28
+ projectContext?: string;
29
+ };
30
+ }
31
+ export interface ChatMessage {
32
+ role: 'user' | 'ai';
33
+ content: string;
34
+ element?: ElementMetadata;
35
+ timestamp: string;
36
+ }
37
+ export interface ChatHistory {
38
+ messages: ChatMessage[];
39
+ }
package/dist/types.js ADDED
@@ -0,0 +1 @@
1
+ export {};
package/dist/vite.d.ts ADDED
@@ -0,0 +1,2 @@
1
+ import { Plugin } from 'vite';
2
+ export declare function aiPanelVite(): Plugin;
package/dist/vite.js ADDED
@@ -0,0 +1,36 @@
1
+ import { AIPanelCore } from './core';
2
+ export function aiPanelVite() {
3
+ const core = new AIPanelCore();
4
+ return {
5
+ name: 'vite-plugin-ai-panel',
6
+ // Inject source information during transformation (simplified)
7
+ transform(code, id) {
8
+ if (!id.endsWith('.tsx') && !id.endsWith('.jsx') && !id.endsWith('.html'))
9
+ return;
10
+ // In a real implementation, we would use a parser to inject data-source attributes
11
+ return code;
12
+ },
13
+ configureServer(server) {
14
+ server.middlewares.use(async (req, res, next) => {
15
+ if (req.url === '/__ai_dev_api') {
16
+ let body = '';
17
+ req.on('data', chunk => body += chunk);
18
+ req.on('end', async () => {
19
+ try {
20
+ const data = JSON.parse(body);
21
+ const result = await core.handleRequest(data);
22
+ res.setHeader('Content-Type', 'application/json');
23
+ res.end(JSON.stringify(result));
24
+ }
25
+ catch (e) {
26
+ res.statusCode = 500;
27
+ res.end(JSON.stringify({ error: e.message }));
28
+ }
29
+ });
30
+ return;
31
+ }
32
+ next();
33
+ });
34
+ }
35
+ };
36
+ }
package/package.json ADDED
@@ -0,0 +1,48 @@
1
+ {
2
+ "name": "ai-panel",
3
+ "version": "1.0.0",
4
+ "description": "",
5
+ "main": "dist/index.js",
6
+ "module": "dist/index.mjs",
7
+ "types": "dist/index.d.ts",
8
+ "files": [
9
+ "dist"
10
+ ],
11
+ "exports": {
12
+ ".": {
13
+ "types": "./dist/index.d.ts",
14
+ "import": "./dist/index.mjs",
15
+ "require": "./dist/index.js"
16
+ },
17
+ "./client": {
18
+ "types": "./dist/overlay.d.ts",
19
+ "import": "./dist/overlay.mjs",
20
+ "require": "./dist/overlay.js"
21
+ },
22
+ "./server": {
23
+ "types": "./dist/core.d.ts",
24
+ "import": "./dist/core.mjs",
25
+ "require": "./dist/core.js"
26
+ }
27
+ },
28
+ "scripts": {
29
+ "build": "tsc",
30
+ "dev": "vite example",
31
+ "test": "echo \"Error: no test specified\" && exit 1"
32
+ },
33
+ "keywords": [],
34
+ "author": "",
35
+ "license": "ISC",
36
+ "type": "commonjs",
37
+ "devDependencies": {
38
+ "@types/dotenv": "^6.1.1",
39
+ "@types/node": "^25.2.3",
40
+ "ts-node": "^10.9.2",
41
+ "typescript": "^5.9.3",
42
+ "vite": "^7.3.1"
43
+ },
44
+ "dependencies": {
45
+ "dotenv": "^17.2.4",
46
+ "gsap": "^3.14.2"
47
+ }
48
+ }