local-mcp 3.0.88 → 3.0.89

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 +52 -40
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "local-mcp",
3
- "version": "3.0.88",
3
+ "version": "3.0.89",
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
@@ -304,28 +304,29 @@ async function runSetup(opts = {}) {
304
304
  // Auto-launch primary configured AI client + show "Try this now" banner
305
305
  _autoLaunchClient(configured)
306
306
 
307
- // Interactive email prompt only when running in a TTY and no email set yet
307
+ // Anon-first cloud relay activation (replaces the interactive email prompt
308
+ // removed 2026-04-19). When no email is set, register an anonymous tunnel
309
+ // token keyed on machine_id so cloud relay comes online immediately. Users
310
+ // can attach an email later via the tray or /settings to unlock cross-Mac
311
+ // sync + paid-license features. Non-fatal on failure — the install still
312
+ // succeeds and the server can retry at startup.
308
313
  if (!email) {
309
- const existingEmail = (() => {
310
- try { return _readJson(cfgFile).license_email || '' } catch { return '' }
311
- })()
312
- if (!existingEmail && process.stdin.isTTY && process.stdout.isTTY) {
313
- const prompted = await _promptEmail()
314
- if (prompted) {
315
- try {
316
- const cfg = _readJson(cfgFile)
317
- if (!cfg.license_email) {
318
- cfg.license_email = prompted
319
- _writeJson(cfgFile, cfg)
320
- console.log(` ✓ Email saved — we'll notify you about important updates`)
321
- }
322
- } catch { /* config will be created on first server run */ }
323
- email = prompted
314
+ try {
315
+ const cfg = _readJson(cfgFile)
316
+ const alreadyLinked = (cfg.license_email && cfg.license_email.length > 0)
317
+ const alreadyAnon = (cfg.cloud_token && typeof cfg.cloud_token === 'string' && cfg.cloud_token.startsWith('lmcp-'))
318
+ if (!alreadyLinked && !alreadyAnon) {
319
+ const tok = await _registerAnonToken()
320
+ if (tok) {
321
+ const cfg2 = _readJson(cfgFile)
322
+ cfg2.cloud_token = tok
323
+ _writeJson(cfgFile, cfg2)
324
+ console.log(' ✓ Cloud relay activated (anonymous)')
325
+ }
324
326
  }
325
- }
327
+ } catch { /* non-fatal */ }
326
328
  }
327
329
 
328
- // Ping de tracking (fire-and-forget, no bloquea)
329
330
  _pingInstall(configured, opts.method || 'setup', email)
330
331
  }
331
332
 
@@ -407,30 +408,41 @@ function _getMachineId() {
407
408
  return ''
408
409
  }
409
410
 
410
- /// Ask the user for their email address during the terminal setup wizard.
411
- /// Returns the trimmed email string, or '' if the user skipped or timed out.
412
- /// Only called when stdin+stdout are both TTYs.
413
- async function _promptEmail() {
411
+ /// Claim an anonymous tunnel token via /tunnel/register-anon so cloud relay
412
+ /// activates without asking for an email (2026-04-19 anon-first rollout). The
413
+ /// token is keyed on the Mac's machine_id (IOPlatformUUID, shared via Keychain
414
+ /// with the Swift daemon). Returns the token string or '' on failure — callers
415
+ /// treat failure as non-fatal and fall through to the email-based path.
416
+ async function _registerAnonToken() {
414
417
  return new Promise((resolve) => {
415
- const readline = require('readline')
416
- const rl = readline.createInterface({ input: process.stdin, output: process.stdout, terminal: false })
417
- let answered = false
418
- const timer = setTimeout(() => {
419
- if (!answered) { answered = true; try { rl.close() } catch {} resolve('') }
420
- }, 30000)
421
- process.stdout.write('\n──────────────────────────────────────\n')
422
- process.stdout.write(' 📧 Your email (optional — for update notifications):\n → ')
423
- rl.once('line', (line) => {
424
- if (answered) return
425
- answered = true
426
- clearTimeout(timer)
427
- rl.close()
428
- const trimmed = (line || '').trim()
429
- resolve(trimmed.includes('@') && trimmed.includes('.') ? trimmed : '')
430
- })
431
- rl.once('close', () => {
432
- if (!answered) { answered = true; clearTimeout(timer); resolve('') }
418
+ const https = require('https')
419
+ const machineId = _getMachineId()
420
+ const payload = JSON.stringify({ machine_id: machineId })
421
+ const req = https.request({
422
+ host: BACKEND_HOST,
423
+ port: 443,
424
+ method: 'POST',
425
+ path: '/tunnel/register-anon',
426
+ headers: {
427
+ 'Content-Type': 'application/json',
428
+ 'Content-Length': Buffer.byteLength(payload),
429
+ },
430
+ timeout: 8000,
431
+ }, (res) => {
432
+ let buf = ''
433
+ res.on('data', (chunk) => { buf += chunk })
434
+ res.on('end', () => {
435
+ try {
436
+ const obj = JSON.parse(buf)
437
+ const tok = (obj && typeof obj.token === 'string') ? obj.token : ''
438
+ resolve(tok.startsWith('lmcp-') ? tok : '')
439
+ } catch { resolve('') }
440
+ })
433
441
  })
442
+ req.on('error', () => resolve(''))
443
+ req.on('timeout', () => { try { req.destroy() } catch {} resolve('') })
444
+ req.write(payload)
445
+ req.end()
434
446
  })
435
447
  }
436
448