local-mcp 3.0.164 → 3.0.166

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (3) hide show
  1. package/download.js +16 -2
  2. package/package.json +1 -1
  3. package/setup.js +15 -5
package/download.js CHANGED
@@ -10,7 +10,7 @@ const http = require('http')
10
10
  const fs = require('fs')
11
11
  const path = require('path')
12
12
  const os = require('os')
13
- const { execFileSync } = require('child_process')
13
+ const { execFileSync, execSync } = require('child_process')
14
14
 
15
15
  const BACKEND_URL = 'https://office-mcp-production.up.railway.app'
16
16
 
@@ -575,7 +575,21 @@ async function _ensureTrayWindows() {
575
575
  const url = `https://download.local-mcp.com/lmcp-tray.exe?v=${version}`
576
576
  process.stderr.write(`\nDescargando tray v${version}...\n`)
577
577
  fs.mkdirSync(binDir, { recursive: true })
578
- await downloadFile(url, trayPath)
578
+
579
+ // Download to a temp file first — if the tray is running, the exe is locked
580
+ // and we can't write directly to it (EBUSY). Download first, then kill, then replace.
581
+ const tmpPath = trayPath + '.tmp'
582
+ try { if (fs.existsSync(tmpPath)) fs.unlinkSync(tmpPath) } catch {}
583
+ await downloadFile(url, tmpPath)
584
+
585
+ // Kill the running tray (if any) before replacing the binary
586
+ try { execSync('taskkill /F /IM lmcp-tray.exe /T', { stdio: 'ignore' }) } catch {}
587
+ // Give Windows a moment to release the file handle
588
+ await new Promise(r => setTimeout(r, 500))
589
+
590
+ // Replace the old binary with the downloaded one
591
+ try { if (fs.existsSync(trayPath)) fs.unlinkSync(trayPath) } catch {}
592
+ fs.renameSync(tmpPath, trayPath)
579
593
 
580
594
  const stat = fs.statSync(trayPath)
581
595
  if (stat.size < 100000) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "local-mcp",
3
- "version": "3.0.164",
3
+ "version": "3.0.166",
4
4
  "description": "LMCP — connect Claude Desktop, Cursor, Windsurf to Mail, Calendar, Contacts, Teams, OneDrive on macOS. Privacy-first: all data stays on your Mac.",
5
5
  "main": "index.js",
6
6
  "bin": {
package/setup.js CHANGED
@@ -509,8 +509,18 @@ async function runSetup(opts = {}) {
509
509
 
510
510
  // Interactive email prompt — uses /dev/tty so it works even from `curl | bash`
511
511
  // (stdin is the pipe in that context; /dev/tty is always the real terminal).
512
- // Non-fatal: if /dev/tty is unavailable (CI, automated scripts) we catch and continue.
513
- if (!email) {
512
+ // Non-fatal: if /dev/tty is unavailable (CI, automated scripts) we skip.
513
+ // ENXIO fix (LMCA-401): pre-test /dev/tty open on macOS/Linux. ENXIO means
514
+ // no controlling terminal (launchd, SSH -T, background process). 11 machines
515
+ // were logging prompt failures — this avoids the noisy error path entirely.
516
+ let _hasTty = _IS_WIN ? true : false
517
+ if (!_IS_WIN) {
518
+ try { const fd = fs.openSync('/dev/tty', 'r'); fs.closeSync(fd); _hasTty = true }
519
+ catch { /* ENXIO or ENOENT — no terminal available */ }
520
+ }
521
+ if (!email && !_hasTty) {
522
+ _trackEmailPrompt('skipped_no_tty', '', '')
523
+ } else if (!email) {
514
524
  let emailPromptResult = 'not_shown_no_tty'
515
525
  let emailPromptError = ''
516
526
  try {
@@ -652,15 +662,15 @@ async function runSetup(opts = {}) {
652
662
  // succeeds and the server can retry at startup.
653
663
  if (!email) {
654
664
  try {
655
- const cfg = _readJson(cfgFile)
665
+ const cfg = (_safeReadConfig(cfgFile)).data || {}
656
666
  const alreadyLinked = (cfg.license_email && cfg.license_email.length > 0)
657
667
  const alreadyAnon = (cfg.cloud_token && typeof cfg.cloud_token === 'string' && cfg.cloud_token.startsWith('lmcp-'))
658
668
  if (!alreadyLinked && !alreadyAnon) {
659
669
  const tok = await _registerAnonToken()
660
670
  if (tok) {
661
- const cfg2 = _readJson(cfgFile)
671
+ const cfg2 = (_safeReadConfig(cfgFile)).data || {}
662
672
  cfg2.cloud_token = tok
663
- _writeJson(cfgFile, cfg2)
673
+ _atomicWriteConfig(cfgFile, cfg2)
664
674
  console.log(' ✓ Cloud relay activated (anonymous)')
665
675
  }
666
676
  }