mongotokyo 1.0.8

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,65 @@
1
+ # ⚡ mongotokyo
2
+
3
+ > AI-powered floating overlay that solves coding questions & MCQs on any app or browser — no tab switching, no detection.
4
+
5
+ ## Features
6
+
7
+ - 🎯 **Works everywhere** — floats on top of any browser, IDE, or app
8
+ - 🔄 **Smart model rotation** — auto-switches AI models on rate limits
9
+ - 🫥 **Panic hide** — press `` ` `` to instantly hide everything
10
+ - 🆓 **Free-tier first** — Gemini 2.0 Flash, Gemini 1.5 Flash, Groq (all free)
11
+ - 💡 **Solves both** — coding questions (with full code) and MCQs (with explanations)
12
+ - 🔒 **Screen capture protection** — overlay is invisible to screen recording tools
13
+
14
+ ## Quick Start
15
+
16
+ The fastest way to use it is via `npx`:
17
+
18
+ ```bash
19
+ npx mongotokyo
20
+ ```
21
+
22
+ Or install it globally:
23
+
24
+ ```bash
25
+ npm install -g mongotokyo
26
+ mongotokyo
27
+ ```
28
+
29
+ On first launch, a setup window appears — paste your API key(s) and click **Launch**.
30
+
31
+ ## Model Priority (auto-rotates)
32
+
33
+ | Priority | Model | Free Limit |
34
+ |---|---|---|
35
+ | 1 | Gemini 2.0 Flash | 1500 req/day free |
36
+ | 2 | Gemini 1.5 Flash | 1500 req/day free |
37
+ | 3 | Groq Llama Vision | 30 RPM free |
38
+ | 4 | OpenRouter Gemma | Free tier |
39
+ | 5 | GPT-4o Mini | Pay-as-you-go |
40
+
41
+ ## Hotkeys
42
+
43
+ | Key | Action |
44
+ |---|---|
45
+ | `Ctrl+Shift+S` | Capture a region & solve |
46
+ | `` ` `` (Backtick) | **PANIC HIDE** — instantly hides all windows |
47
+ | `` ` `` again | Restore all windows |
48
+ | `Ctrl+Shift+H` | Soft toggle panel |
49
+ | `Ctrl+Shift+C` | Clear current answer |
50
+ | `Ctrl+Shift+X` | **SELF DESTRUCT** — instantly quits the app |
51
+ | `Escape` | Cancel region selection |
52
+
53
+ ## Getting Free API Keys
54
+
55
+ - **Gemini** (recommended): https://aistudio.google.com/app/apikey
56
+ - **Groq**: https://console.groq.com/keys
57
+ - **OpenRouter**: https://openrouter.ai/keys
58
+
59
+ ## Config Location
60
+
61
+ Keys are stored in `~/.mongotokyo/config.json` — fully local, never sent to any third-party server except the AI API you choose.
62
+
63
+ ## Reset Setup
64
+
65
+ Delete `~/.mongotokyo/config.json` and re-launch to redo setup.
@@ -0,0 +1,22 @@
1
+ #!/usr/bin/env node
2
+
3
+ const electron = require('electron');
4
+ const { spawn } = require('child_process');
5
+ const path = require('path');
6
+
7
+ const appPath = path.join(__dirname, '..');
8
+ const args = process.argv.slice(2);
9
+
10
+ const proc = spawn(electron, [appPath, ...args], {
11
+ stdio: 'inherit',
12
+ windowsHide: false
13
+ });
14
+
15
+ proc.on('close', (code) => {
16
+ process.exit(code ?? 0);
17
+ });
18
+
19
+ proc.on('error', (err) => {
20
+ console.error('Failed to launch stealth-solver:', err.message);
21
+ process.exit(1);
22
+ });
package/package.json ADDED
@@ -0,0 +1,34 @@
1
+ {
2
+ "name": "mongotokyo",
3
+ "version": "1.0.8",
4
+ "description": "",
5
+ "main": "src/main.js",
6
+ "bin": {
7
+ "mongotokyo": "bin/stealth-solver.js"
8
+ },
9
+ "scripts": {
10
+ "start": "electron .",
11
+ "dev": "electron . --dev"
12
+ },
13
+ "keywords": [
14
+ "ai",
15
+ "overlay",
16
+ "coding",
17
+ "mcq",
18
+ "solver",
19
+ "electron"
20
+ ],
21
+ "author": "Kashish",
22
+ "license": "MIT",
23
+ "files": [
24
+ "src",
25
+ "bin",
26
+ "README.md"
27
+ ],
28
+ "dependencies": {
29
+ "@google/generative-ai": "^0.21.0",
30
+ "dotenv": "^17.4.2",
31
+ "electron": "^31.3.0",
32
+ "openai": "^4.52.0"
33
+ }
34
+ }
@@ -0,0 +1,111 @@
1
+ const fs = require('fs');
2
+ const path = require('path');
3
+ const os = require('os');
4
+
5
+ const CONFIG_DIR = path.join(os.homedir(), '.mongotokyo');
6
+ const CONFIG_FILE = path.join(CONFIG_DIR, 'config.json');
7
+ const STATE_FILE = path.join(CONFIG_DIR, 'state.json');
8
+
9
+ const DEFAULT_CONFIG = {
10
+ hasSetup: false,
11
+ keys: {
12
+ gemini: '',
13
+ groq: '',
14
+ openrouter: '',
15
+ openai: ''
16
+ },
17
+ panicKey: '`',
18
+ captureKey: 'CommandOrControl+Shift+S'
19
+ };
20
+
21
+ // Load secrets if they exist (for npm users)
22
+ let secrets = {};
23
+ try {
24
+ secrets = require('./secrets');
25
+ } catch (e) {
26
+ // Not found
27
+ }
28
+
29
+ function ensureDir() {
30
+ if (!fs.existsSync(CONFIG_DIR)) {
31
+ fs.mkdirSync(CONFIG_DIR, { recursive: true });
32
+ }
33
+ }
34
+
35
+ function load() {
36
+ ensureDir();
37
+ let config = { ...DEFAULT_CONFIG };
38
+
39
+ // Fallback to secrets if available
40
+ if (secrets.gemini) config.keys.gemini = secrets.gemini;
41
+ if (secrets.groq) config.keys.groq = secrets.groq;
42
+ if (secrets.gemini || secrets.groq) config.hasSetup = true;
43
+
44
+ if (fs.existsSync(CONFIG_FILE)) {
45
+ try {
46
+ const saved = JSON.parse(fs.readFileSync(CONFIG_FILE, 'utf8'));
47
+ // Only merge if keys are actually set (not placeholders)
48
+ if (saved.keys) {
49
+ for (const [p, k] of Object.entries(saved.keys)) {
50
+ if (k && k !== 'USE_ENV_VARIABLE' && !k.startsWith('env:')) {
51
+ config.keys[p] = k;
52
+ }
53
+ }
54
+ }
55
+ if (saved.panicKey) config.panicKey = saved.panicKey;
56
+ if (saved.captureKey) config.captureKey = saved.captureKey;
57
+ if (saved.hasSetup !== undefined) config.hasSetup = saved.hasSetup || config.hasSetup;
58
+ } catch {
59
+ // Use defaults on error
60
+ }
61
+ }
62
+
63
+ // ─── Override with Environment Variables ──────────────────────────────────────
64
+ // This allows the user to skip setup if keys are in .env
65
+ const envKeys = {
66
+ gemini: process.env.GEMINI_KEY || process.env.GEMINI_API_KEY,
67
+ groq: process.env.GROQ_KEY || process.env.GROQ_API_KEY,
68
+ openrouter: process.env.OPENROUTER_KEY || process.env.OPENROUTER_API_KEY,
69
+ openai: process.env.OPENAI_KEY || process.env.OPENAI_API_KEY
70
+ };
71
+
72
+ // If any key is found in env, merge it and consider setup complete
73
+ let hasEnvKey = false;
74
+ for (const [provider, val] of Object.entries(envKeys)) {
75
+ if (val) {
76
+ config.keys[provider] = val;
77
+ hasEnvKey = true;
78
+ }
79
+ }
80
+
81
+ if (hasEnvKey) {
82
+ config.hasSetup = true;
83
+ }
84
+
85
+ return config;
86
+ }
87
+
88
+ function save(config) {
89
+ ensureDir();
90
+ const current = load();
91
+ const merged = { ...current, ...config, hasSetup: true };
92
+ fs.writeFileSync(CONFIG_FILE, JSON.stringify(merged, null, 2));
93
+ return merged;
94
+ }
95
+
96
+ function loadState() {
97
+ ensureDir();
98
+ if (!fs.existsSync(STATE_FILE)) return {};
99
+ try {
100
+ return JSON.parse(fs.readFileSync(STATE_FILE, 'utf8'));
101
+ } catch {
102
+ return {};
103
+ }
104
+ }
105
+
106
+ function saveState(state) {
107
+ ensureDir();
108
+ fs.writeFileSync(STATE_FILE, JSON.stringify(state, null, 2));
109
+ }
110
+
111
+ module.exports = { load, save, loadState, saveState, CONFIG_DIR };
package/src/main.js ADDED
@@ -0,0 +1,247 @@
1
+ const path = require('path');
2
+ require('dotenv').config({ path: path.join(__dirname, '..', '.env') });
3
+ const {
4
+ app,
5
+ BrowserWindow,
6
+ globalShortcut,
7
+ ipcMain,
8
+ screen,
9
+ desktopCapturer
10
+ } = require('electron');
11
+ const fs = require('fs');
12
+
13
+ const configManager = require('./configManager');
14
+ const solver = require('./solver');
15
+ const panicManager = require('./panicManager');
16
+
17
+ let overlayWindow = null;
18
+ let selectorWindow = null;
19
+ let setupWindow = null;
20
+
21
+ // ─── Window Factories ──────────────────────────────────────────────────────────
22
+
23
+ function createOverlayWindow() {
24
+ const { width, height } = screen.getPrimaryDisplay().bounds;
25
+
26
+ overlayWindow = new BrowserWindow({
27
+ width,
28
+ height,
29
+ x: 0,
30
+ y: 0,
31
+ transparent: true,
32
+ frame: false,
33
+ alwaysOnTop: true,
34
+ skipTaskbar: true,
35
+ resizable: false,
36
+ hasShadow: false,
37
+ webPreferences: {
38
+ preload: path.join(__dirname, 'preload.js'),
39
+ contextIsolation: true,
40
+ nodeIntegration: false
41
+ }
42
+ });
43
+
44
+ overlayWindow.setIgnoreMouseEvents(true, { forward: true });
45
+ overlayWindow.setContentProtection(true);
46
+ overlayWindow.loadFile(path.join(__dirname, 'renderer', 'overlay', 'index.html'));
47
+ panicManager.register(overlayWindow);
48
+ return overlayWindow;
49
+ }
50
+
51
+ function createSetupWindow() {
52
+ setupWindow = new BrowserWindow({
53
+ width: 580,
54
+ height: 680,
55
+ frame: false,
56
+ transparent: true,
57
+ resizable: false,
58
+ center: true,
59
+ webPreferences: {
60
+ preload: path.join(__dirname, 'preload.js'),
61
+ contextIsolation: true,
62
+ nodeIntegration: false
63
+ }
64
+ });
65
+
66
+ setupWindow.loadFile(path.join(__dirname, 'renderer', 'setup', 'setup.html'));
67
+ return setupWindow;
68
+ }
69
+
70
+ // Selector functionality removed for stealth mode
71
+
72
+ // ─── App Lifecycle ─────────────────────────────────────────────────────────────
73
+
74
+ app.whenReady().then(() => {
75
+ const config = configManager.load();
76
+
77
+ if (!config.hasSetup) {
78
+ createSetupWindow();
79
+ } else {
80
+ createOverlayWindow();
81
+ registerHotkeys();
82
+ console.log('\x1b[32m%s\x1b[0m', '⚡ mongotokyo Active');
83
+ const config = configManager.load();
84
+ const hasEnv = !!(process.env.GEMINI_KEY || process.env.GROQ_KEY);
85
+ if (hasEnv) {
86
+ console.log('\x1b[35m%s\x1b[0m', '🔑 Keys loaded from .env');
87
+ }
88
+ console.log('\x1b[36m%s\x1b[0m', 'Hotkeys:');
89
+ console.log(' - Ctrl+Shift+S: Capture & Solve');
90
+ console.log(' - Ctrl+Shift+M: Toggle Mouse (for scroll/copy)');
91
+ console.log(' - ` (Backtick): Panic Hide/Show');
92
+ console.log(' - Ctrl+Shift+X: Exit');
93
+ }
94
+ });
95
+
96
+ app.on('will-quit', () => {
97
+ globalShortcut.unregisterAll();
98
+ });
99
+
100
+ app.on('window-all-closed', () => {
101
+ app.quit();
102
+ });
103
+
104
+ // ─── Hotkeys ──────────────────────────────────────────────────────────────────
105
+
106
+ function registerHotkeys() {
107
+ // Capture + solve (Ctrl+Shift+S)
108
+ globalShortcut.register('CommandOrControl+Shift+S', async () => {
109
+ console.log('\x1b[33m%s\x1b[0m', '📸 Capturing screen...');
110
+ overlayWindow.webContents.send('status', { type: 'loading', message: 'Solving...' });
111
+ await captureAndSolve();
112
+ });
113
+
114
+ // Panic hide/show (backtick ` — fastest single key)
115
+ // Hides the ENTIRE overlay window from screen share / prying eyes
116
+ let panicState = false;
117
+ globalShortcut.register('`', () => {
118
+ panicState = !panicState;
119
+ if (panicState) {
120
+ if (overlayWindow && overlayWindow.isVisible()) {
121
+ overlayWindow.webContents.send('panic', 'hide');
122
+ // Small delay so the renderer can process, then hide the window
123
+ setTimeout(() => { if (overlayWindow) overlayWindow.hide(); }, 80);
124
+ }
125
+ } else {
126
+ if (overlayWindow) {
127
+ overlayWindow.show();
128
+ overlayWindow.webContents.send('panic', 'show');
129
+ }
130
+ }
131
+ });
132
+
133
+ // Toggle just the answer text visibility (Ctrl+Shift+H)
134
+ globalShortcut.register('CommandOrControl+Shift+H', () => {
135
+ if (!overlayWindow) return;
136
+ overlayWindow.webContents.send('toggle-text');
137
+ });
138
+
139
+ // Clear answer (Ctrl+Shift+C)
140
+ globalShortcut.register('CommandOrControl+Shift+C', () => {
141
+ if (overlayWindow) overlayWindow.webContents.send('clear');
142
+ });
143
+
144
+ // Toggle Mouse Interaction (Ctrl+Shift+M)
145
+ let ignoreMouse = true;
146
+ globalShortcut.register('CommandOrControl+Shift+M', () => {
147
+ ignoreMouse = !ignoreMouse;
148
+ if (overlayWindow) {
149
+ overlayWindow.setIgnoreMouseEvents(ignoreMouse, { forward: true });
150
+ overlayWindow.webContents.send('status', {
151
+ type: 'info',
152
+ message: ignoreMouse ? 'Mouse: OFF' : 'Mouse: ON'
153
+ });
154
+ console.log(`[Main] Mouse Ignore: ${ignoreMouse}`);
155
+ }
156
+ });
157
+
158
+ // SELF DESTRUCT / Finish Test (Ctrl+Shift+X)
159
+ // Instantly quits the app and unregisters all keys
160
+ globalShortcut.register('CommandOrControl+Shift+X', () => {
161
+ app.quit();
162
+ });
163
+ }
164
+
165
+ // ─── IPC Handlers ─────────────────────────────────────────────────────────────
166
+
167
+ // Setup complete
168
+ ipcMain.on('setup-complete', async (_, config) => {
169
+ configManager.save(config);
170
+ if (setupWindow) { setupWindow.close(); setupWindow = null; }
171
+ createOverlayWindow();
172
+ registerHotkeys();
173
+ });
174
+
175
+ // Get config
176
+ ipcMain.handle('get-config', () => configManager.load());
177
+
178
+ // Save config
179
+ ipcMain.on('save-config', (_, config) => configManager.save(config));
180
+
181
+ async function captureAndSolve() {
182
+ try {
183
+ const sources = await desktopCapturer.getSources({
184
+ types: ['screen'],
185
+ thumbnailSize: screen.getPrimaryDisplay().bounds
186
+ });
187
+
188
+ const source = sources[0];
189
+ const base64 = source.thumbnail.toDataURL();
190
+
191
+ if (overlayWindow) {
192
+ overlayWindow.show();
193
+ overlayWindow.setContentProtection(true);
194
+ overlayWindow.webContents.send('status', { type: 'loading', message: 'Thinking...' });
195
+ }
196
+
197
+ const config = configManager.load();
198
+ console.log('\x1b[33m%s\x1b[0m', '🧠 Thinking...');
199
+ const result = await solver.solve(base64, config);
200
+ console.log('\x1b[32m%s\x1b[0m', '✅ Result received!');
201
+
202
+ // ── Route based on question category ──────────────────────────────────────
203
+ if (result.type === 'web') {
204
+ // MERN / Web project → create folder on Desktop with all files
205
+ if (result.files && result.files.length > 0) {
206
+ const desktopPath = app.getPath('desktop');
207
+ const rawName = result.questionName || `WebProject_${Date.now()}`;
208
+ const folderName = rawName.replace(/[\\/:*?"<>|]/g, '_').trim() || `WebProject_${Date.now()}`;
209
+ const folderPath = path.join(desktopPath, folderName);
210
+
211
+ if (!fs.existsSync(folderPath)) {
212
+ fs.mkdirSync(folderPath, { recursive: true });
213
+ }
214
+
215
+ for (const file of result.files) {
216
+ if (file.name && file.content) {
217
+ const safeName = file.name.replace(/[\\/:*?"<>|]/g, '_');
218
+ // Support nested paths like "src/App.jsx"
219
+ const fullPath = path.join(folderPath, safeName);
220
+ fs.mkdirSync(path.dirname(fullPath), { recursive: true });
221
+ fs.writeFileSync(fullPath, file.content, 'utf8');
222
+ }
223
+ }
224
+ }
225
+ }
226
+ // DSA / MCQ / mixed → show on overlay screen (no folder needed)
227
+
228
+ if (overlayWindow) {
229
+ overlayWindow.webContents.send('answer', result);
230
+ }
231
+ } catch (err) {
232
+ if (overlayWindow) {
233
+ console.error('\x1b[31m%s\x1b[0m', `❌ Error: ${err.message}`);
234
+ overlayWindow.show();
235
+ overlayWindow.setContentProtection(true);
236
+ overlayWindow.webContents.send('status', { type: 'error', message: err.message });
237
+ }
238
+ }
239
+ }
240
+
241
+ // Window controls from renderer
242
+ ipcMain.on('set-ignore-mouse', (event, ignore) => {
243
+ const win = BrowserWindow.fromWebContents(event.sender);
244
+ if (win) win.setIgnoreMouseEvents(ignore, { forward: true });
245
+ });
246
+ ipcMain.on('hide-window', () => { if (overlayWindow) overlayWindow.hide(); });
247
+ ipcMain.on('quit-app', () => app.quit());
@@ -0,0 +1,94 @@
1
+ /**
2
+ * Smart model rotation pool.
3
+ * Models are tried in priority order.
4
+ * When a model hits a rate limit, it enters a cooldown period
5
+ * and the next available model is used automatically.
6
+ */
7
+
8
+ const MODEL_POOL = [
9
+ {
10
+ id: 'gemini-2.0-flash',
11
+ provider: 'gemini',
12
+ model: 'gemini-2.0-flash',
13
+ name: 'Gemini 2.0 Flash',
14
+ requiresKey: 'gemini'
15
+ },
16
+ {
17
+ id: 'gemini-1.5-flash',
18
+ provider: 'gemini',
19
+ model: 'gemini-1.5-flash',
20
+ name: 'Gemini 1.5 Flash',
21
+ requiresKey: 'gemini'
22
+ },
23
+ {
24
+ id: 'gemini-1.5-pro',
25
+ provider: 'gemini',
26
+ model: 'gemini-1.5-pro',
27
+ name: 'Gemini 1.5 Pro',
28
+ requiresKey: 'gemini'
29
+ },
30
+ {
31
+ id: 'groq-llama-4-scout',
32
+ provider: 'groq',
33
+ model: 'meta-llama/llama-4-scout-17b-16e-instruct',
34
+ name: 'Groq Llama 4 Scout',
35
+ requiresKey: 'groq'
36
+ },
37
+ {
38
+ id: 'gpt-4o-mini',
39
+ provider: 'openai',
40
+ model: 'gpt-4o-mini',
41
+ name: 'GPT-4o Mini',
42
+ requiresKey: 'openai'
43
+ }
44
+ ];
45
+
46
+ // modelId -> timestamp until which it's cooling down
47
+ const coolingModels = new Map();
48
+
49
+ function getAvailableModels(keys) {
50
+ const now = Date.now();
51
+ return MODEL_POOL.filter((m) => {
52
+ if (!keys[m.requiresKey]) return false; // no API key configured
53
+ const coolUntil = coolingModels.get(m.id) || 0;
54
+ return now > coolUntil; // not cooling
55
+ });
56
+ }
57
+
58
+ function cooldown(modelId, seconds = 60) {
59
+ coolingModels.set(modelId, Date.now() + seconds * 1000);
60
+ }
61
+
62
+ function getNextModel(keys) {
63
+ const available = getAvailableModels(keys);
64
+ return available[0] || null;
65
+ }
66
+
67
+ function isRateLimitError(err) {
68
+ const msg = (err?.message || '').toLowerCase();
69
+ const status = err?.status || err?.statusCode || 0;
70
+ return (
71
+ status === 429 ||
72
+ msg.includes('rate limit') ||
73
+ msg.includes('quota') ||
74
+ msg.includes('resource exhausted') ||
75
+ msg.includes('too many requests')
76
+ );
77
+ }
78
+
79
+ function getModelStatus(keys) {
80
+ const now = Date.now();
81
+ return MODEL_POOL.map((m) => {
82
+ const hasKey = !!keys[m.requiresKey];
83
+ const coolUntil = coolingModels.get(m.id) || 0;
84
+ const cooling = now < coolUntil;
85
+ return {
86
+ ...m,
87
+ hasKey,
88
+ cooling,
89
+ coolRemaining: cooling ? Math.ceil((coolUntil - now) / 1000) : 0
90
+ };
91
+ });
92
+ }
93
+
94
+ module.exports = { MODEL_POOL, getNextModel, cooldown, isRateLimitError, getModelStatus };
@@ -0,0 +1,46 @@
1
+ const { BrowserWindow } = require('electron');
2
+
3
+ let isPanicked = false;
4
+ const hiddenState = new Map(); // windowId -> wasVisible
5
+
6
+ function register(win) {
7
+ win.on('closed', () => hiddenState.delete(win.id));
8
+ }
9
+
10
+ function toggle() {
11
+ if (isPanicked) {
12
+ restore();
13
+ } else {
14
+ hide();
15
+ }
16
+ }
17
+
18
+ function hide() {
19
+ isPanicked = true;
20
+ hiddenState.clear();
21
+
22
+ BrowserWindow.getAllWindows().forEach((win) => {
23
+ hiddenState.set(win.id, win.isVisible());
24
+ if (win.isVisible()) {
25
+ win.hide();
26
+ }
27
+ });
28
+ }
29
+
30
+ function restore() {
31
+ isPanicked = false;
32
+
33
+ BrowserWindow.getAllWindows().forEach((win) => {
34
+ if (hiddenState.get(win.id)) {
35
+ win.show();
36
+ }
37
+ });
38
+
39
+ hiddenState.clear();
40
+ }
41
+
42
+ function isPanic() {
43
+ return isPanicked;
44
+ }
45
+
46
+ module.exports = { register, toggle, hide, restore, isPanic };
package/src/preload.js ADDED
@@ -0,0 +1,20 @@
1
+ const { contextBridge, ipcRenderer } = require('electron');
2
+
3
+ contextBridge.exposeInMainWorld('electronAPI', {
4
+ // Overlay receives
5
+ onAnswer: (cb) => ipcRenderer.on('answer', (_, data) => cb(data)),
6
+ onStatus: (cb) => ipcRenderer.on('status', (_, data) => cb(data)),
7
+ onClear: (cb) => ipcRenderer.on('clear', () => cb()),
8
+ onPanic: (cb) => ipcRenderer.on('panic', (_, state) => cb(state)),
9
+ onToggleText: (cb) => ipcRenderer.on('toggle-text', () => cb()),
10
+
11
+ // Setup
12
+ completeSetup: (config) => ipcRenderer.send('setup-complete', config),
13
+ getConfig: () => ipcRenderer.invoke('get-config'),
14
+ saveConfig: (config) => ipcRenderer.send('save-config', config),
15
+
16
+ // Window controls
17
+ setIgnoreMouse: (ignore) => ipcRenderer.send('set-ignore-mouse', ignore),
18
+ hideWindow: () => ipcRenderer.send('hide-window'),
19
+ quitApp: () => ipcRenderer.send('quit-app')
20
+ });
@@ -0,0 +1,28 @@
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8" />
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0" />
6
+ <title>Stealth Solver</title>
7
+ <link href="https://fonts.googleapis.com/css2?family=JetBrains+Mono:wght@400;500&display=swap" rel="stylesheet" />
8
+ <link rel="stylesheet" href="overlay.css" />
9
+ </head>
10
+ <body>
11
+
12
+ <!-- Tiny pulsing dot while loading (bottom-right) -->
13
+ <div id="loadingDot"></div>
14
+
15
+ <!-- Error dot (replaces loading dot on failure) -->
16
+ <div id="errorDot"></div>
17
+
18
+ <!-- Answer panel — MCQ text OR DSA code block, faint bottom-right -->
19
+ <div id="answerPanel">
20
+ <div id="answerLines"></div>
21
+ </div>
22
+
23
+ <!-- Short-lived saved notification for web/MERN questions -->
24
+ <div id="savedToast"></div>
25
+
26
+ <script src="overlay.js"></script>
27
+ </body>
28
+ </html>