lantern-connect 0.1.3 → 0.2.1

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 CHANGED
@@ -23,9 +23,6 @@ lantern-connect
23
23
 
24
24
  # Local development with ngrok
25
25
  lantern-connect --mcp-url https://your-ngrok-url.ngrok-free.dev/mcp
26
-
27
- # Custom Lantern instance
28
- lantern-connect --lantern-url https://your-lantern.com --mcp-url https://api.your-lantern.com/mcp
29
26
  ```
30
27
 
31
28
  ## Options
@@ -33,14 +30,13 @@ lantern-connect --lantern-url https://your-lantern.com --mcp-url https://api.you
33
30
  | Option | Description | Default |
34
31
  |--------|-------------|---------|
35
32
  | `--mcp-url <url>` | MCP server URL | `https://mcp.onlantern.com/mcp` |
36
- | `--lantern-url <url>` | Lantern web URL for authentication | `https://onlantern.com` |
37
33
  | `--help, -h` | Show help message | |
38
34
 
39
35
  ## What it does
40
36
 
41
37
  1. **Detects** installed AI tools (Claude Desktop, Claude Code, Gemini CLI, Cursor)
42
- 2. **Authenticates** with your Lantern account via browser
43
- 3. **Configures** each detected tool to connect to Lantern's MCP server
38
+ 2. **Configures** each detected tool to connect to Lantern's MCP server
39
+ 3. **Each tool authenticates** with Lantern on first use (via OAuth)
44
40
 
45
41
  ## Supported Tools
46
42
 
@@ -54,7 +50,7 @@ lantern-connect --lantern-url https://your-lantern.com --mcp-url https://api.you
54
50
  ## After Installation
55
51
 
56
52
  1. **Fully quit and reopen** your AI tools (Cmd+Q on macOS)
57
- 2. **Claude Desktop only:** Go to Settings Connectors → click "lantern" to authenticate
53
+ 2. **Authenticate** when prompted by each tool on first use
58
54
  3. Start using Lantern:
59
55
  - Say "save to lantern" to export a conversation
60
56
  - Say "list my lantern conversations" to see your library
@@ -63,16 +59,15 @@ lantern-connect --lantern-url https://your-lantern.com --mcp-url https://api.you
63
59
  ## Local Development
64
60
 
65
61
  ```bash
66
- # Start your local Lantern backend and frontend
62
+ # Start your local Lantern backend and MCP server
67
63
  cd lantern && npm run dev
68
- cd lantern/frontend && npm run dev
69
64
 
70
- # Start ngrok to expose backend (required for Claude Desktop)
71
- ngrok http 3001
65
+ # Start ngrok to expose MCP server (required for Claude Desktop)
66
+ ngrok http 3002
72
67
 
73
68
  # Run the CLI with your ngrok URL
74
69
  cd lantern-connect
75
- node bin/lantern-connect.js --mcp-url https://your-ngrok-url.ngrok-free.dev/mcp --lantern-url http://localhost:3000
70
+ node bin/lantern-connect.js --mcp-url https://your-ngrok-url.ngrok-free.dev/mcp
76
71
  ```
77
72
 
78
73
  ## Resetting / Uninstall
@@ -1,91 +1,12 @@
1
1
  #!/usr/bin/env node
2
2
 
3
- import { existsSync, readFileSync, writeFileSync } from 'fs'
4
- import { homedir, platform } from 'os'
5
- import { join } from 'path'
6
- import { execSync } from 'child_process'
7
-
8
- function expandHome(path) {
9
- if (path.startsWith('~')) {
10
- return join(homedir(), path.slice(1))
11
- }
12
- return path
13
- }
14
-
15
- function getClaudeDesktopConfigPath() {
16
- const os = platform()
17
- if (os === 'darwin') {
18
- return expandHome('~/Library/Application Support/Claude/claude_desktop_config.json')
19
- } else if (os === 'win32') {
20
- return join(process.env.APPDATA || '', 'Claude', 'claude_desktop_config.json')
21
- } else {
22
- return expandHome('~/.config/Claude/claude_desktop_config.json')
23
- }
24
- }
25
-
26
- function removeFromJsonConfig(configPath, name) {
27
- if (!existsSync(configPath)) {
28
- return { found: false, reason: 'config file not found' }
29
- }
30
-
31
- try {
32
- const content = readFileSync(configPath, 'utf-8')
33
- const config = JSON.parse(content)
34
-
35
- if (!config.mcpServers || !config.mcpServers[name]) {
36
- return { found: false, reason: 'lantern not configured' }
37
- }
38
-
39
- delete config.mcpServers[name]
40
- writeFileSync(configPath, JSON.stringify(config, null, 2))
41
- return { found: true }
42
- } catch (err) {
43
- return { found: false, reason: err.message }
44
- }
45
- }
46
-
47
- function clearGeminiOAuthCache(serverName) {
48
- const cachePath = expandHome('~/.gemini/mcp-oauth-tokens.json')
49
- if (!existsSync(cachePath)) {
50
- return { cleared: false }
51
- }
52
-
53
- try {
54
- const content = readFileSync(cachePath, 'utf-8')
55
- const tokens = JSON.parse(content)
56
-
57
- if (!Array.isArray(tokens)) {
58
- return { cleared: false }
59
- }
60
-
61
- const filtered = tokens.filter(t => t.serverName !== serverName)
62
- if (filtered.length === tokens.length) {
63
- return { cleared: false }
64
- }
65
-
66
- writeFileSync(cachePath, JSON.stringify(filtered, null, 2))
67
- return { cleared: true }
68
- } catch (err) {
69
- return { cleared: false }
70
- }
71
- }
72
-
73
- function removeClaudeCode() {
74
- try {
75
- execSync('claude mcp remove lantern', { stdio: 'pipe' })
76
- return { found: true }
77
- } catch (err) {
78
- const stderr = err.stderr?.toString() || ''
79
- if (stderr.includes('not found') || stderr.includes('does not exist')) {
80
- return { found: false, reason: 'lantern not configured' }
81
- }
82
- // If claude CLI doesn't exist
83
- if (err.message.includes('ENOENT') || err.message.includes('not found')) {
84
- return { found: false, reason: 'claude CLI not installed' }
85
- }
86
- return { found: false, reason: err.message }
87
- }
88
- }
3
+ import {
4
+ expandHome,
5
+ getClaudeDesktopConfigPath,
6
+ removeFromJsonConfig,
7
+ clearGeminiOAuthCache,
8
+ removeClaudeCode,
9
+ } from '../src/reset.js'
89
10
 
90
11
  async function main() {
91
12
  console.log('')
@@ -100,11 +21,11 @@ async function main() {
100
21
  // Claude Desktop
101
22
  const claudeDesktopPath = getClaudeDesktopConfigPath()
102
23
  const claudeDesktopResult = removeFromJsonConfig(claudeDesktopPath, 'lantern')
103
- results.push({ name: 'Claude Desktop', ...claudeDesktopResult })
24
+ results.push({ name: 'Claude Desktop', ...claudeDesktopResult, reason: 'not configured' })
104
25
 
105
26
  // Claude Code
106
27
  const claudeCodeResult = removeClaudeCode()
107
- results.push({ name: 'Claude Code', ...claudeCodeResult })
28
+ results.push({ name: 'Claude Code', ...claudeCodeResult, reason: 'not configured' })
108
29
 
109
30
  // Gemini CLI
110
31
  const geminiPath = expandHome('~/.gemini/settings.json')
@@ -113,12 +34,12 @@ async function main() {
113
34
  if (geminiOAuthResult.cleared) {
114
35
  geminiResult.oauthCleared = true
115
36
  }
116
- results.push({ name: 'Gemini CLI', ...geminiResult })
37
+ results.push({ name: 'Gemini CLI', ...geminiResult, reason: 'not configured' })
117
38
 
118
39
  // Cursor
119
40
  const cursorPath = expandHome('~/.cursor/mcp.json')
120
41
  const cursorResult = removeFromJsonConfig(cursorPath, 'lantern')
121
- results.push({ name: 'Cursor', ...cursorResult })
42
+ results.push({ name: 'Cursor', ...cursorResult, reason: 'not configured' })
122
43
 
123
44
  // Print results
124
45
  let anyRemoved = false
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lantern-connect",
3
- "version": "0.1.3",
3
+ "version": "0.2.1",
4
4
  "description": "CLI installer to connect AI tools (Claude, Gemini, Cursor) to Lantern",
5
5
  "type": "module",
6
6
  "bin": {
@@ -34,7 +34,5 @@
34
34
  "engines": {
35
35
  "node": ">=18.0.0"
36
36
  },
37
- "dependencies": {
38
- "open": "^10.1.0"
39
- }
37
+ "dependencies": {}
40
38
  }
package/src/index.js CHANGED
@@ -1,9 +1,11 @@
1
1
  import { detectTools, hasAnyTools } from './detect.js'
2
- import { authenticate } from './auth.js'
3
2
  import { configureClaudeDesktop } from './configure/claude-desktop.js'
4
3
  import { configureClaudeCode } from './configure/claude-code.js'
5
4
  import { configureGeminiCli } from './configure/gemini-cli.js'
6
5
  import { configureCursor } from './configure/cursor.js'
6
+ import { resetAll } from './reset.js'
7
+
8
+ const DEFAULT_MCP_URL = 'https://mcp.onlantern.com/mcp'
7
9
 
8
10
  const configurators = {
9
11
  claudeDesktop: configureClaudeDesktop,
@@ -15,24 +17,19 @@ const configurators = {
15
17
  function parseArgs() {
16
18
  const args = process.argv.slice(2)
17
19
  const options = {
18
- mcpUrl: null,
19
- lanternUrl: null,
20
+ mcpUrl: DEFAULT_MCP_URL,
20
21
  }
21
22
 
22
23
  for (let i = 0; i < args.length; i++) {
23
24
  if (args[i] === '--mcp-url' && args[i + 1]) {
24
25
  options.mcpUrl = args[i + 1]
25
26
  i++
26
- } else if (args[i] === '--lantern-url' && args[i + 1]) {
27
- options.lanternUrl = args[i + 1]
28
- i++
29
27
  } else if (args[i] === '--help' || args[i] === '-h') {
30
28
  console.log(`
31
29
  Usage: lantern-connect [options]
32
30
 
33
31
  Options:
34
32
  --mcp-url <url> MCP server URL (default: https://mcp.onlantern.com/mcp)
35
- --lantern-url <url> Lantern web URL (default: https://onlantern.com)
36
33
  --help, -h Show this help message
37
34
  `)
38
35
  process.exit(0)
@@ -76,23 +73,9 @@ export async function main() {
76
73
  process.exit(1)
77
74
  }
78
75
 
79
- // Step 2: Authenticate
80
- console.log(' 🔐 Authenticating with Lantern...')
81
-
82
- let authResult
83
- try {
84
- authResult = await authenticate({
85
- lanternUrl: options.lanternUrl,
86
- mcpUrl: options.mcpUrl,
87
- })
88
- } catch (err) {
89
- console.log('')
90
- console.log(` ❌ Authentication failed: ${err.message}`)
91
- console.log('')
92
- process.exit(1)
93
- }
94
-
95
- console.log(` ✓ Logged in as ${authResult.email}`)
76
+ // Step 2: Reset existing configs and OAuth caches
77
+ console.log(' 🧹 Cleaning up old configs...')
78
+ resetAll()
96
79
  console.log('')
97
80
 
98
81
  // Step 3: Configure each detected tool
@@ -109,7 +92,7 @@ export async function main() {
109
92
  if (!configurator) continue
110
93
 
111
94
  try {
112
- const result = configurator(authResult.mcpUrl)
95
+ const result = configurator(options.mcpUrl)
113
96
  configured.push({ name: tool.name, path: result })
114
97
  console.log(` ✓ ${tool.name}`)
115
98
  } catch (err) {
@@ -126,14 +109,8 @@ export async function main() {
126
109
  console.log('')
127
110
  console.log(' Next steps:')
128
111
  console.log(' 1. Fully quit and reopen your AI tools (Cmd+Q on macOS)')
129
-
130
- // Check if Claude Desktop was configured
131
- if (detected.claudeDesktop?.found) {
132
- console.log(' 2. In Claude Desktop: Settings → Connectors → click "lantern" to authenticate')
133
- console.log(' 3. Say "save to lantern" to export a conversation')
134
- } else {
135
- console.log(' 2. Say "save to lantern" to export a conversation')
136
- }
112
+ console.log(' 2. Each tool will prompt you to authenticate on first use')
113
+ console.log(' 3. Say "save to lantern" to export a conversation')
137
114
  console.log('')
138
115
  }
139
116
 
package/src/reset.js ADDED
@@ -0,0 +1,123 @@
1
+ import { existsSync, readFileSync, writeFileSync } from 'fs'
2
+ import { homedir, platform } from 'os'
3
+ import { join } from 'path'
4
+ import { execSync } from 'child_process'
5
+
6
+ function expandHome(path) {
7
+ if (path.startsWith('~')) {
8
+ return join(homedir(), path.slice(1))
9
+ }
10
+ return path
11
+ }
12
+
13
+ function getClaudeDesktopConfigPath() {
14
+ const os = platform()
15
+ if (os === 'darwin') {
16
+ return expandHome('~/Library/Application Support/Claude/claude_desktop_config.json')
17
+ } else if (os === 'win32') {
18
+ return join(process.env.APPDATA || '', 'Claude', 'claude_desktop_config.json')
19
+ } else {
20
+ return expandHome('~/.config/Claude/claude_desktop_config.json')
21
+ }
22
+ }
23
+
24
+ function removeFromJsonConfig(configPath, name) {
25
+ if (!existsSync(configPath)) {
26
+ return { found: false }
27
+ }
28
+
29
+ try {
30
+ const content = readFileSync(configPath, 'utf-8')
31
+ const config = JSON.parse(content)
32
+
33
+ if (!config.mcpServers || !config.mcpServers[name]) {
34
+ return { found: false }
35
+ }
36
+
37
+ delete config.mcpServers[name]
38
+ writeFileSync(configPath, JSON.stringify(config, null, 2))
39
+ return { found: true }
40
+ } catch (err) {
41
+ return { found: false }
42
+ }
43
+ }
44
+
45
+ function clearGeminiOAuthCache(serverName) {
46
+ const cachePath = expandHome('~/.gemini/mcp-oauth-tokens.json')
47
+ if (!existsSync(cachePath)) {
48
+ return { cleared: false }
49
+ }
50
+
51
+ try {
52
+ const content = readFileSync(cachePath, 'utf-8')
53
+ const tokens = JSON.parse(content)
54
+
55
+ if (!Array.isArray(tokens)) {
56
+ return { cleared: false }
57
+ }
58
+
59
+ const filtered = tokens.filter(t => t.serverName !== serverName)
60
+ if (filtered.length === tokens.length) {
61
+ return { cleared: false }
62
+ }
63
+
64
+ writeFileSync(cachePath, JSON.stringify(filtered, null, 2))
65
+ return { cleared: true }
66
+ } catch (err) {
67
+ return { cleared: false }
68
+ }
69
+ }
70
+
71
+ function removeClaudeCode() {
72
+ try {
73
+ execSync('claude mcp remove lantern', { stdio: 'pipe' })
74
+ return { found: true }
75
+ } catch (err) {
76
+ return { found: false }
77
+ }
78
+ }
79
+
80
+ /**
81
+ * Reset all Lantern configs and OAuth caches silently
82
+ * Returns true if anything was reset
83
+ */
84
+ export function resetAll() {
85
+ let anyReset = false
86
+
87
+ // Claude Desktop
88
+ const claudeDesktopPath = getClaudeDesktopConfigPath()
89
+ if (removeFromJsonConfig(claudeDesktopPath, 'lantern').found) {
90
+ anyReset = true
91
+ }
92
+
93
+ // Claude Code
94
+ if (removeClaudeCode().found) {
95
+ anyReset = true
96
+ }
97
+
98
+ // Gemini CLI
99
+ const geminiPath = expandHome('~/.gemini/settings.json')
100
+ if (removeFromJsonConfig(geminiPath, 'lantern').found) {
101
+ anyReset = true
102
+ }
103
+ if (clearGeminiOAuthCache('lantern').cleared) {
104
+ anyReset = true
105
+ }
106
+
107
+ // Cursor
108
+ const cursorPath = expandHome('~/.cursor/mcp.json')
109
+ if (removeFromJsonConfig(cursorPath, 'lantern').found) {
110
+ anyReset = true
111
+ }
112
+
113
+ return anyReset
114
+ }
115
+
116
+ // Export individual functions for lantern-reset CLI
117
+ export {
118
+ expandHome,
119
+ getClaudeDesktopConfigPath,
120
+ removeFromJsonConfig,
121
+ clearGeminiOAuthCache,
122
+ removeClaudeCode,
123
+ }
package/src/auth.js DELETED
@@ -1,171 +0,0 @@
1
- import { createServer } from 'http'
2
- import { URL } from 'url'
3
- import open from 'open'
4
-
5
- const CALLBACK_PORT = 8765
6
- const DEFAULT_LANTERN_URL = process.env.LANTERN_URL || 'https://onlantern.com'
7
- const DEFAULT_MCP_URL = process.env.MCP_URL || 'https://mcp.onlantern.com/mcp'
8
-
9
- function createSuccessHtml(email) {
10
- return `
11
- <!DOCTYPE html>
12
- <html>
13
- <head>
14
- <meta charset="UTF-8">
15
- <title>Lantern Connected</title>
16
- <style>
17
- body {
18
- font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
19
- display: flex;
20
- align-items: center;
21
- justify-content: center;
22
- min-height: 100vh;
23
- margin: 0;
24
- background: #0a0a0a;
25
- color: #fafafa;
26
- }
27
- .container {
28
- text-align: center;
29
- padding: 2rem;
30
- }
31
- .icon {
32
- font-size: 4rem;
33
- margin-bottom: 1rem;
34
- }
35
- h1 {
36
- font-size: 1.5rem;
37
- margin-bottom: 0.5rem;
38
- }
39
- p {
40
- color: #a1a1aa;
41
- margin-bottom: 1.5rem;
42
- }
43
- .close-note {
44
- font-size: 0.875rem;
45
- color: #71717a;
46
- }
47
- </style>
48
- </head>
49
- <body>
50
- <div class="container">
51
- <div class="icon">✓</div>
52
- <h1>Connected to Lantern</h1>
53
- <p>Logged in as ${email}</p>
54
- <p class="close-note">You can close this window and return to your terminal.</p>
55
- </div>
56
- </body>
57
- </html>
58
- `
59
- }
60
-
61
- function createErrorHtml(message) {
62
- return `
63
- <!DOCTYPE html>
64
- <html>
65
- <head>
66
- <meta charset="UTF-8">
67
- <title>Lantern - Error</title>
68
- <style>
69
- body {
70
- font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
71
- display: flex;
72
- align-items: center;
73
- justify-content: center;
74
- min-height: 100vh;
75
- margin: 0;
76
- background: #0a0a0a;
77
- color: #fafafa;
78
- }
79
- .container {
80
- text-align: center;
81
- padding: 2rem;
82
- }
83
- .icon {
84
- font-size: 4rem;
85
- margin-bottom: 1rem;
86
- }
87
- h1 {
88
- font-size: 1.5rem;
89
- margin-bottom: 0.5rem;
90
- color: #ef4444;
91
- }
92
- p {
93
- color: #a1a1aa;
94
- }
95
- </style>
96
- </head>
97
- <body>
98
- <div class="container">
99
- <div class="icon">✗</div>
100
- <h1>Connection Failed</h1>
101
- <p>${message}</p>
102
- </div>
103
- </body>
104
- </html>
105
- `
106
- }
107
-
108
- export async function authenticate(options = {}) {
109
- const lanternUrl = options.lanternUrl || DEFAULT_LANTERN_URL
110
- const mcpUrlOverride = options.mcpUrl || DEFAULT_MCP_URL
111
-
112
- return new Promise((resolve, reject) => {
113
- const server = createServer((req, res) => {
114
- const url = new URL(req.url, `http://localhost:${CALLBACK_PORT}`)
115
-
116
- if (url.pathname === '/callback') {
117
- const token = url.searchParams.get('token')
118
- const mcpUrl = url.searchParams.get('mcp_url') || mcpUrlOverride
119
- const email = url.searchParams.get('email')
120
- const error = url.searchParams.get('error')
121
-
122
- if (error) {
123
- res.writeHead(200, { 'Content-Type': 'text/html' })
124
- res.end(createErrorHtml(error))
125
- server.close()
126
- reject(new Error(error))
127
- return
128
- }
129
-
130
- if (!token) {
131
- res.writeHead(200, { 'Content-Type': 'text/html' })
132
- res.end(createErrorHtml('Missing token'))
133
- server.close()
134
- reject(new Error('Missing token in callback'))
135
- return
136
- }
137
-
138
- res.writeHead(200, { 'Content-Type': 'text/html' })
139
- res.end(createSuccessHtml(email || 'unknown'))
140
-
141
- server.close()
142
- resolve({ token, mcpUrl, email })
143
- } else {
144
- res.writeHead(404)
145
- res.end('Not found')
146
- }
147
- })
148
-
149
- server.listen(CALLBACK_PORT, '127.0.0.1', () => {
150
- const authUrl = new URL(`${lanternUrl}/auth/cli`)
151
- authUrl.searchParams.set('callback_port', CALLBACK_PORT)
152
- authUrl.searchParams.set('mcp_url', mcpUrlOverride)
153
- console.log(` Opening browser...`)
154
- open(authUrl.toString())
155
- })
156
-
157
- server.on('error', (err) => {
158
- if (err.code === 'EADDRINUSE') {
159
- reject(new Error(`Port ${CALLBACK_PORT} is already in use. Please close any other lantern-connect instances.`))
160
- } else {
161
- reject(err)
162
- }
163
- })
164
-
165
- // Timeout after 5 minutes
166
- setTimeout(() => {
167
- server.close()
168
- reject(new Error('Authentication timed out. Please try again.'))
169
- }, 5 * 60 * 1000)
170
- })
171
- }