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 +7 -12
- package/bin/lantern-reset.js +11 -90
- package/package.json +2 -4
- package/src/index.js +10 -33
- package/src/reset.js +123 -0
- package/src/auth.js +0 -171
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. **
|
|
43
|
-
3. **
|
|
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. **
|
|
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
|
|
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
|
|
71
|
-
ngrok http
|
|
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
|
|
70
|
+
node bin/lantern-connect.js --mcp-url https://your-ngrok-url.ngrok-free.dev/mcp
|
|
76
71
|
```
|
|
77
72
|
|
|
78
73
|
## Resetting / Uninstall
|
package/bin/lantern-reset.js
CHANGED
|
@@ -1,91 +1,12 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
|
-
import {
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
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
|
+
"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:
|
|
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:
|
|
80
|
-
console.log('
|
|
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(
|
|
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
|
-
|
|
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
|
-
}
|