@pheem49/mint 1.2.4 → 1.3.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.
@@ -0,0 +1,88 @@
1
+ const { exec } = require('child_process');
2
+ const { screen } = require('electron');
3
+
4
+ /**
5
+ * GranularAutomation handles low-level OS input via xdotool.
6
+ * It uses a normalized coordinate system (0-1000).
7
+ */
8
+ class GranularAutomation {
9
+ constructor() {
10
+ this.screenWidth = 1920; // Default fallback
11
+ this.screenHeight = 1080;
12
+ this.updateScreenSize();
13
+ }
14
+
15
+ updateScreenSize() {
16
+ try {
17
+ // In Electron main process, we can use the screen module
18
+ const primaryDisplay = screen.getPrimaryDisplay();
19
+ if (primaryDisplay && primaryDisplay.size) {
20
+ this.screenWidth = primaryDisplay.size.width;
21
+ this.screenHeight = primaryDisplay.size.height;
22
+ console.log(`[Automation] Screen detected: ${this.screenWidth}x${this.screenHeight}`);
23
+ }
24
+ } catch (e) {
25
+ // Fallback for CLI or cases where screen module is unavailable
26
+ exec('xdpyinfo | grep dimensions', (err, stdout) => {
27
+ if (!err && stdout) {
28
+ const match = stdout.match(/(\d+)x(\d+) pixels/);
29
+ if (match) {
30
+ this.screenWidth = parseInt(match[1]);
31
+ this.screenHeight = parseInt(match[2]);
32
+ console.log(`[Automation] Screen detected via xdpyinfo: ${this.screenWidth}x${this.screenHeight}`);
33
+ }
34
+ }
35
+ });
36
+ }
37
+ }
38
+
39
+ scaleX(x) {
40
+ return Math.round((x / 1000) * this.screenWidth);
41
+ }
42
+
43
+ scaleY(y) {
44
+ return Math.round((y / 1000) * this.screenHeight);
45
+ }
46
+
47
+ run(command) {
48
+ return new Promise((resolve, reject) => {
49
+ exec(command, (err, stdout, stderr) => {
50
+ if (err) {
51
+ console.error(`[Automation] xdotool error: ${stderr}`);
52
+ reject(err);
53
+ } else {
54
+ resolve(stdout);
55
+ }
56
+ });
57
+ });
58
+ }
59
+
60
+ async mouseMove(x, y) {
61
+ const sx = this.scaleX(x);
62
+ const sy = this.scaleY(y);
63
+ console.log(`[Automation] Moving mouse to ${sx}, ${sy}`);
64
+ return this.run(`xdotool mousemove ${sx} ${sy}`);
65
+ }
66
+
67
+ async mouseClick(x, y, button = 1) {
68
+ const sx = this.scaleX(x);
69
+ const sy = this.scaleY(y);
70
+ console.log(`[Automation] Clicking ${button} at ${sx}, ${sy}`);
71
+ // move first then click to be safe
72
+ return this.run(`xdotool mousemove ${sx} ${sy} click ${button}`);
73
+ }
74
+
75
+ async typeText(text) {
76
+ console.log(`[Automation] Typing: ${text}`);
77
+ // Escape double quotes for shell
78
+ const escaped = text.replace(/"/g, '\\"');
79
+ return this.run(`xdotool type "${escaped}"`);
80
+ }
81
+
82
+ async keyTap(key) {
83
+ console.log(`[Automation] Key tap: ${key}`);
84
+ return this.run(`xdotool key "${key}"`);
85
+ }
86
+ }
87
+
88
+ module.exports = new GranularAutomation();
@@ -352,6 +352,30 @@
352
352
  </div>
353
353
  </div>
354
354
  </section>
355
+
356
+ <!-- MCP Servers Management -->
357
+ <section class="setting-section" style="margin-top: 30px;">
358
+ <h2 class="section-title">🌐 MCP Servers (Model Context Protocol)</h2>
359
+ <p class="hint" style="margin-bottom: 16px;">Connect Mint to external tools like Google Search, GitHub, or Filesystem.</p>
360
+
361
+ <div class="mcp-list" id="mcp-server-list" style="display: flex; flex-direction: column; gap: 12px; margin-bottom: 20px;">
362
+ <!-- MCP items will be injected here -->
363
+ </div>
364
+
365
+ <div class="add-mcp-box" style="padding: 16px; background: rgba(255,255,255,0.05); border-radius: 12px; border: 1px dashed var(--border);">
366
+ <h3 style="font-size: 1rem; margin-bottom: 12px; color: var(--accent);">+ Add New MCP Server</h3>
367
+ <div style="display: grid; grid-template-columns: 1fr 1fr; gap: 10px; margin-bottom: 10px;">
368
+ <input type="text" id="mcp-new-name" placeholder="Server Name (e.g. google-search)">
369
+ <input type="text" id="mcp-new-command" placeholder="Command (e.g. npx)">
370
+ </div>
371
+ <input type="text" id="mcp-new-args" placeholder="Arguments (e.g. -y @modelcontextprotocol/server-brave-search)" style="width: 100%; margin-bottom: 10px;">
372
+ <textarea id="mcp-new-env" placeholder='Env (JSON format, e.g. {"BRAVE_API_KEY": "..."})' style="width: 100%; min-height: 60px; background: var(--input-bg); color: var(--text-main); border: 1px solid var(--border); border-radius: 8px; padding: 10px; font-family: monospace; font-size: 0.8rem;"></textarea>
373
+
374
+ <button class="btn-primary" id="btn-add-mcp" style="margin-top: 10px; width: 100%;">Add MCP Server</button>
375
+ </div>
376
+
377
+ <p class="hint" style="margin-top: 12px;">Browse more servers at <a href="https://github.com/modelcontextprotocol/servers" target="_blank">MCP Servers Registry</a></p>
378
+ </section>
355
379
  </div>
356
380
  <div class="tab-pane" id="sect-shortcuts">
357
381
  <!-- Keyboard Shortcuts -->
@@ -23,7 +23,8 @@ const DEFAULT_CONFIG = {
23
23
  pluginSpotifyEnabled: true,
24
24
  pluginCalendarEnabled: false,
25
25
  pluginDiscordEnabled: false,
26
- showDesktopWidget: true
26
+ showDesktopWidget: true,
27
+ mcpServers: {}
27
28
  };
28
29
 
29
30
  let currentConfig = { ...DEFAULT_CONFIG };
@@ -153,6 +154,9 @@ function applyConfig(config) {
153
154
  document.getElementById('proactive-cooldown').value = cooldown;
154
155
  updateIntervalDisplay(interval);
155
156
  updateCooldownDisplay(cooldown);
157
+
158
+ // MCP Servers
159
+ renderMcpServers();
156
160
  }
157
161
 
158
162
  function lightenColor(hex, amount) {
@@ -433,12 +437,100 @@ document.getElementById('save-btn').addEventListener('click', async () => {
433
437
  currentConfig.customBgEnd = document.getElementById('custom-bg-end').value;
434
438
  currentConfig.customPanelBg = document.getElementById('custom-panel-bg').value;
435
439
 
440
+ // Ensure mcpServers is part of the saved config
441
+ if (!currentConfig.mcpServers) currentConfig.mcpServers = {};
442
+
443
+ console.log('[Settings] Saving config with MCP servers:', Object.keys(currentConfig.mcpServers).length);
436
444
  await window.settingsApi.saveSettings(currentConfig);
437
445
  const btn = document.getElementById('save-btn');
438
446
  btn.textContent = '✅ Saved!';
439
447
  setTimeout(() => { btn.textContent = 'Save Settings'; }, 1500);
440
448
  });
441
449
 
450
+ // --- MCP Management Functions ---
451
+ function renderMcpServers() {
452
+ console.log('[Settings] Rendering MCP Servers UI...');
453
+ const list = document.getElementById('mcp-server-list');
454
+ if (!list) {
455
+ console.warn('[Settings] MCP list element not found in DOM.');
456
+ return;
457
+ }
458
+ list.innerHTML = '';
459
+
460
+ const servers = currentConfig.mcpServers || {};
461
+ const entries = Object.entries(servers);
462
+ console.log(`[Settings] Found ${entries.length} servers in currentConfig.`);
463
+
464
+ if (entries.length === 0) {
465
+ list.innerHTML = '<p class="hint" style="text-align: center; padding: 10px;">No MCP servers connected.</p>';
466
+ return;
467
+ }
468
+
469
+ for (const [name, cfg] of entries) {
470
+ const item = document.createElement('div');
471
+ item.style.cssText = 'display: flex; justify-content: space-between; align-items: center; padding: 12px; background: rgba(0,0,0,0.2); border-radius: 10px; border: 1px solid var(--border);';
472
+
473
+ item.innerHTML = `
474
+ <div style="display: flex; flex-direction: column; gap: 4px;">
475
+ <div style="font-weight: 600; color: var(--accent); display: flex; align-items: center; gap: 6px;">
476
+ <span>🌐 ${name}</span>
477
+ </div>
478
+ <div style="font-size: 0.75rem; opacity: 0.7; font-family: monospace;">${cfg.command} ${cfg.args.join(' ')}</div>
479
+ </div>
480
+ <button class="btn-danger" style="padding: 6px 12px; font-size: 0.8rem;" onclick="removeMcpServer('${name}')">Remove</button>
481
+ `;
482
+ list.appendChild(item);
483
+ }
484
+ }
485
+
486
+ window.removeMcpServer = function(name) {
487
+ if (confirm(`Remove MCP server "${name}"?`)) {
488
+ delete currentConfig.mcpServers[name];
489
+ renderMcpServers();
490
+ }
491
+ };
492
+
493
+ document.getElementById('btn-add-mcp').addEventListener('click', () => {
494
+ const nameInput = document.getElementById('mcp-new-name');
495
+ const cmdInput = document.getElementById('mcp-new-command');
496
+ const argsInput = document.getElementById('mcp-new-args');
497
+ const envInput = document.getElementById('mcp-new-env');
498
+
499
+ const name = nameInput.value.trim();
500
+ const command = cmdInput.value.trim();
501
+ const argsStr = argsInput.value.trim();
502
+ const envStr = envInput.value.trim();
503
+
504
+ if (!name || !command) {
505
+ alert('Name and Command are required!');
506
+ return;
507
+ }
508
+
509
+ // Basic args split (by space, but respecting some quotes if possible - simple for now)
510
+ const args = argsStr ? argsStr.split(/\s+/) : [];
511
+
512
+ let env = {};
513
+ if (envStr) {
514
+ try {
515
+ env = JSON.parse(envStr);
516
+ } catch (e) {
517
+ alert('Invalid JSON in Environment Variables field!');
518
+ return;
519
+ }
520
+ }
521
+
522
+ if (!currentConfig.mcpServers) currentConfig.mcpServers = {};
523
+ currentConfig.mcpServers[name] = { command, args, env };
524
+
525
+ // Clear inputs
526
+ nameInput.value = '';
527
+ cmdInput.value = '';
528
+ argsInput.value = '';
529
+ envInput.value = '';
530
+
531
+ renderMcpServers();
532
+ });
533
+
442
534
  // Custom Workflows functionality
443
535
  const openWorkflowsBtn = document.getElementById('open-workflows-btn');
444
536
  const reloadWorkflowsBtn = document.getElementById('reload-workflows-btn');
@@ -508,6 +600,11 @@ document.querySelectorAll('.tab-btn').forEach(btn => {
508
600
  btn.classList.add('active');
509
601
  const pane = document.getElementById(target);
510
602
  if (pane) pane.classList.add('active');
603
+
604
+ // Re-render MCP list if switching to plugins tab
605
+ if (target === 'sect-plugins') {
606
+ renderMcpServers();
607
+ }
511
608
  });
512
609
  });
513
610