local-mcp 3.0.254 → 3.0.255

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 (2) hide show
  1. package/package.json +1 -1
  2. package/setup.js +6 -119
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "local-mcp",
3
- "version": "3.0.254",
3
+ "version": "3.0.255",
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
@@ -535,97 +535,12 @@ async function runSetup(opts = {}) {
535
535
  } catch { /* config not created yet — will be created on first run */ }
536
536
  }
537
537
 
538
- // Interactive email prompt uses /dev/tty so it works even from `curl | bash`
539
- // (stdin is the pipe in that context; /dev/tty is always the real terminal).
540
- // Non-fatal: if /dev/tty is unavailable (CI, automated scripts) we skip.
541
- // ENXIO fix (LMCA-401): pre-test /dev/tty open on macOS/Linux. ENXIO means
542
- // no controlling terminal (launchd, SSH -T, background process).
543
- // LMCA-415: Windows also needs a TTY check (npm subprocess has no stdin.isTTY
544
- // when launched from Go tray installer). Pre-test uses 'r+' (same flags as
545
- // the actual open) to avoid a race where the pre-test passes but actual open fails.
546
- let _hasTty = false
547
- if (_IS_WIN) {
548
- // Windows: check stdin.isTTY — false when spawned as a subprocess without console
549
- _hasTty = process.stdin && process.stdin.isTTY === true
550
- } else {
551
- try { const fd = fs.openSync('/dev/tty', 'r+'); fs.closeSync(fd); _hasTty = true }
552
- catch { /* ENXIO or ENOENT — no controlling terminal */ }
553
- }
554
- if (!email && !_hasTty) {
555
- _trackEmailPrompt('skipped_no_tty', '', '')
556
- } else if (!email) {
557
- let emailPromptResult = 'not_shown_no_tty'
558
- let emailPromptError = ''
559
- try {
560
- let rl
561
- let ttyFd = null
562
-
563
- if (_IS_WIN) {
564
- // Windows: use process.stdin directly (works in PowerShell and cmd)
565
- rl = require('readline').createInterface({ input: process.stdin, output: process.stderr })
566
- } else {
567
- // macOS/Linux: open /dev/tty to read from terminal even when stdin is piped.
568
- // If this throws (ENXIO/ENOENT), treat as no-tty, not a failure.
569
- try { ttyFd = fs.openSync('/dev/tty', 'r+') }
570
- catch (ttyErr) {
571
- _trackEmailPrompt('skipped_no_tty', '', '')
572
- ttyFd = null
573
- // Skip the rest of the prompt block
574
- throw Object.assign(new Error('no_tty'), { _skipPrompt: true })
575
- }
576
- const ttyIn = require('stream').Readable.from(
577
- (function* () {
578
- const buf = Buffer.alloc(256)
579
- let line = ''
580
- while (true) {
581
- const n = fs.readSync(ttyFd, buf, 0, 1, null)
582
- if (n === 0) break
583
- const ch = buf.slice(0, n).toString()
584
- if (ch === '\n' || ch === '\r') break
585
- line += ch
586
- }
587
- yield line
588
- })()
589
- )
590
- const ttyOut = new (require('stream').Writable)({
591
- write(chunk, _enc, cb) { fs.writeSync(ttyFd, chunk); cb() },
592
- })
593
- rl = require('readline').createInterface({ input: ttyIn, output: ttyOut, terminal: true })
594
- }
595
-
596
- process.stderr.write('\n Email for update notifications (optional, press Enter to skip): ')
597
- emailPromptResult = 'shown'
598
- const ans = await Promise.race([
599
- new Promise(res => {
600
- rl.once('line', a => { rl.close(); if (ttyFd !== null) try { fs.closeSync(ttyFd) } catch {} ; res((a || '').trim()) })
601
- }),
602
- new Promise(res => setTimeout(() => {
603
- try { rl.close() } catch {}
604
- if (ttyFd !== null) try { fs.closeSync(ttyFd) } catch {}
605
- res('')
606
- }, 30000)),
607
- ])
608
-
609
- if (ans && ans.includes('@')) {
610
- email = ans
611
- emailPromptResult = 'submitted'
612
- try {
613
- const r = _safeReadConfig(cfgFile)
614
- const cfg = r.data || {}
615
- if (!cfg.license_email) { cfg.license_email = email; _atomicWriteConfig(cfgFile, cfg) }
616
- } catch {}
617
- console.log(' ✓ Email saved')
618
- } else {
619
- emailPromptResult = 'skipped'
620
- }
621
- } catch (err) {
622
- if (err && err._skipPrompt) { /* skipped_no_tty already tracked above */ }
623
- else { emailPromptResult = 'failed'; emailPromptError = (err && err.message) || String(err) }
624
- }
625
-
626
- // Track prompt outcome so we can measure conversion and diagnose failures
627
- _trackEmailPrompt(emailPromptResult, emailPromptResult === 'submitted' ? email : '', emailPromptError)
628
- }
538
+ // Interactive email prompt removed (2026-06-05) same change already made in
539
+ // install.sh on 2026-04-19. It paused `curl | bash` up to 30s on a confusing
540
+ // mid-install prompt while ground-truth telemetry showed ~98% of machines that
541
+ // hit it reached a heartbeat anyway (no conversion impact). Email is still
542
+ // captured non-interactively: via LMCP_EMAIL above, the anon tunnel register,
543
+ // and the tray/settings UI later.
629
544
 
630
545
  // Health check — verify binary works before showing success
631
546
  const healthOk = _runHealthCheck()
@@ -950,34 +865,6 @@ function _trackConfigWritten(clientId, clientName) {
950
865
  } catch { /* non-fatal */ }
951
866
  }
952
867
 
953
- function _trackEmailPrompt(result, email, errorMsg) {
954
- // result: 'shown' | 'submitted' | 'skipped' | 'not_shown_no_tty' | 'failed'
955
- try {
956
- const https = require('https')
957
- const machineId = _getMachineId()
958
- const payload = {
959
- stage: 'email_prompt',
960
- email_prompt_result: result,
961
- machine_id: machineId,
962
- install_id: process.env.INSTALL_ID || '',
963
- }
964
- if (email) payload.email = email
965
- if (errorMsg) payload.ref = errorMsg.slice(0, 200) // reuse ref field; fits in existing schema
966
- const data = JSON.stringify(payload)
967
- const req = https.request({
968
- hostname: BACKEND_HOST,
969
- path: '/install-event',
970
- method: 'POST',
971
- headers: { 'Content-Type': 'application/json', 'Content-Length': data.length },
972
- timeout: 5000,
973
- })
974
- req.on('response', (res) => res.resume())
975
- req.on('error', () => {})
976
- req.write(data)
977
- req.end()
978
- } catch { /* non-fatal */ }
979
- }
980
-
981
868
  function _trackRestartPrompted(clientName) {
982
869
  try {
983
870
  const https = require('https')