lantern-connect 0.1.0
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 +90 -0
- package/bin/lantern-connect.js +8 -0
- package/package.json +39 -0
- package/src/auth.js +171 -0
- package/src/configure/claude-code.js +17 -0
- package/src/configure/claude-desktop.js +37 -0
- package/src/configure/cursor.js +36 -0
- package/src/configure/gemini-cli.js +37 -0
- package/src/detect.js +89 -0
- package/src/index.js +147 -0
package/README.md
ADDED
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
# lantern-connect
|
|
2
|
+
|
|
3
|
+
CLI installer to connect AI tools to Lantern.
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npx lantern-connect
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
Or install globally:
|
|
12
|
+
|
|
13
|
+
```bash
|
|
14
|
+
npm install -g lantern-connect
|
|
15
|
+
lantern-connect
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
## Usage
|
|
19
|
+
|
|
20
|
+
```bash
|
|
21
|
+
# Production (default)
|
|
22
|
+
lantern-connect
|
|
23
|
+
|
|
24
|
+
# Local development with ngrok
|
|
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
|
+
```
|
|
30
|
+
|
|
31
|
+
## Options
|
|
32
|
+
|
|
33
|
+
| Option | Description | Default |
|
|
34
|
+
|--------|-------------|---------|
|
|
35
|
+
| `--mcp-url <url>` | MCP server URL | `https://api.onlantern.com/mcp` |
|
|
36
|
+
| `--lantern-url <url>` | Lantern web URL for authentication | `https://onlantern.com` |
|
|
37
|
+
| `--help, -h` | Show help message | |
|
|
38
|
+
|
|
39
|
+
## What it does
|
|
40
|
+
|
|
41
|
+
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
|
|
44
|
+
|
|
45
|
+
## Supported Tools
|
|
46
|
+
|
|
47
|
+
| Tool | Config Location | Method |
|
|
48
|
+
|------|-----------------|--------|
|
|
49
|
+
| Claude Desktop | `~/Library/Application Support/Claude/claude_desktop_config.json` | mcp-remote |
|
|
50
|
+
| Claude Code | `~/.claude.json` | `claude mcp add` |
|
|
51
|
+
| Gemini CLI | `~/.gemini/settings.json` | httpUrl config |
|
|
52
|
+
| Cursor | `~/.cursor/mcp.json` | url config |
|
|
53
|
+
|
|
54
|
+
## After Installation
|
|
55
|
+
|
|
56
|
+
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
|
|
58
|
+
3. Start using Lantern:
|
|
59
|
+
- Say "save to lantern" to export a conversation
|
|
60
|
+
- Say "list my lantern conversations" to see your library
|
|
61
|
+
- Say "load my lantern collection" to import a collection
|
|
62
|
+
|
|
63
|
+
## Local Development
|
|
64
|
+
|
|
65
|
+
```bash
|
|
66
|
+
# Start your local Lantern backend and frontend
|
|
67
|
+
cd lantern && npm run dev
|
|
68
|
+
cd lantern/frontend && npm run dev
|
|
69
|
+
|
|
70
|
+
# Start ngrok to expose backend (required for Claude Desktop)
|
|
71
|
+
ngrok http 3001
|
|
72
|
+
|
|
73
|
+
# Run the CLI with your ngrok URL
|
|
74
|
+
cd lantern-connect
|
|
75
|
+
node bin/lantern-connect.js --mcp-url https://your-ngrok-url.ngrok-free.dev/mcp --lantern-url http://localhost:3000
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
## Troubleshooting
|
|
79
|
+
|
|
80
|
+
### Claude Desktop shows "Server disconnected"
|
|
81
|
+
- Make sure you're using an ngrok URL (Claude Desktop can't reach localhost)
|
|
82
|
+
- Restart Claude Desktop completely (Cmd+Q, not just close window)
|
|
83
|
+
|
|
84
|
+
### Gemini CLI shows "Bad Request" for SSE
|
|
85
|
+
- Make sure your backend has the latest MCP session handling code
|
|
86
|
+
- Restart your backend server
|
|
87
|
+
|
|
88
|
+
### CORS errors on localhost:3000
|
|
89
|
+
- Make sure your frontend's `.env.local` has `NEXT_PUBLIC_API_URL=http://localhost:3001`
|
|
90
|
+
- The `--mcp-url` flag is for the CLI/MCP tools, not the frontend
|
package/package.json
ADDED
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "lantern-connect",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "CLI installer to connect AI tools (Claude, Gemini, Cursor) to Lantern",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"bin": {
|
|
7
|
+
"lantern-connect": "./bin/lantern-connect.js"
|
|
8
|
+
},
|
|
9
|
+
"files": [
|
|
10
|
+
"bin",
|
|
11
|
+
"src"
|
|
12
|
+
],
|
|
13
|
+
"scripts": {
|
|
14
|
+
"start": "node bin/lantern-connect.js"
|
|
15
|
+
},
|
|
16
|
+
"keywords": [
|
|
17
|
+
"lantern",
|
|
18
|
+
"mcp",
|
|
19
|
+
"claude",
|
|
20
|
+
"gemini",
|
|
21
|
+
"cursor",
|
|
22
|
+
"ai",
|
|
23
|
+
"cli",
|
|
24
|
+
"model-context-protocol"
|
|
25
|
+
],
|
|
26
|
+
"author": "Lantern",
|
|
27
|
+
"license": "MIT",
|
|
28
|
+
"repository": {
|
|
29
|
+
"type": "git",
|
|
30
|
+
"url": "https://github.com/onlantern/lantern-connect"
|
|
31
|
+
},
|
|
32
|
+
"homepage": "https://onlantern.com",
|
|
33
|
+
"engines": {
|
|
34
|
+
"node": ">=18.0.0"
|
|
35
|
+
},
|
|
36
|
+
"dependencies": {
|
|
37
|
+
"open": "^10.1.0"
|
|
38
|
+
}
|
|
39
|
+
}
|
package/src/auth.js
ADDED
|
@@ -0,0 +1,171 @@
|
|
|
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://api.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
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { execSync } from 'child_process'
|
|
2
|
+
|
|
3
|
+
export function configureClaudeCode(mcpUrl) {
|
|
4
|
+
// First, try to remove existing lantern config (ignore errors if it doesn't exist)
|
|
5
|
+
try {
|
|
6
|
+
execSync('claude mcp remove lantern', { stdio: 'ignore' })
|
|
7
|
+
} catch {
|
|
8
|
+
// Ignore - server might not exist yet
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
// Add the new MCP server
|
|
12
|
+
execSync(`claude mcp add --transport http lantern ${mcpUrl}`, {
|
|
13
|
+
stdio: 'inherit',
|
|
14
|
+
})
|
|
15
|
+
|
|
16
|
+
return 'claude mcp'
|
|
17
|
+
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { existsSync, readFileSync, writeFileSync, mkdirSync } from 'fs'
|
|
2
|
+
import { dirname } from 'path'
|
|
3
|
+
import { tools } from '../detect.js'
|
|
4
|
+
|
|
5
|
+
export function configureClaudeDesktop(mcpUrl) {
|
|
6
|
+
const configPath = tools.claudeDesktop.configPath()
|
|
7
|
+
|
|
8
|
+
// Ensure directory exists
|
|
9
|
+
const configDir = dirname(configPath)
|
|
10
|
+
if (!existsSync(configDir)) {
|
|
11
|
+
mkdirSync(configDir, { recursive: true })
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
// Read existing config or create new one
|
|
15
|
+
let config = {}
|
|
16
|
+
if (existsSync(configPath)) {
|
|
17
|
+
try {
|
|
18
|
+
const content = readFileSync(configPath, 'utf-8')
|
|
19
|
+
config = JSON.parse(content)
|
|
20
|
+
} catch {
|
|
21
|
+
// If file exists but is invalid JSON, start fresh
|
|
22
|
+
config = {}
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
// Add/update Lantern MCP server using mcp-remote to bridge HTTP
|
|
27
|
+
config.mcpServers = config.mcpServers || {}
|
|
28
|
+
config.mcpServers.lantern = {
|
|
29
|
+
command: 'npx',
|
|
30
|
+
args: ['mcp-remote', mcpUrl],
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
// Write config
|
|
34
|
+
writeFileSync(configPath, JSON.stringify(config, null, 2))
|
|
35
|
+
|
|
36
|
+
return configPath
|
|
37
|
+
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { existsSync, readFileSync, writeFileSync, mkdirSync } from 'fs'
|
|
2
|
+
import { dirname } from 'path'
|
|
3
|
+
import { tools } from '../detect.js'
|
|
4
|
+
|
|
5
|
+
export function configureCursor(mcpUrl) {
|
|
6
|
+
const configPath = tools.cursor.configPath()
|
|
7
|
+
|
|
8
|
+
// Ensure directory exists
|
|
9
|
+
const configDir = dirname(configPath)
|
|
10
|
+
if (!existsSync(configDir)) {
|
|
11
|
+
mkdirSync(configDir, { recursive: true })
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
// Read existing config or create new one
|
|
15
|
+
let config = {}
|
|
16
|
+
if (existsSync(configPath)) {
|
|
17
|
+
try {
|
|
18
|
+
const content = readFileSync(configPath, 'utf-8')
|
|
19
|
+
config = JSON.parse(content)
|
|
20
|
+
} catch {
|
|
21
|
+
// If file exists but is invalid JSON, start fresh
|
|
22
|
+
config = {}
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
// Add/update Lantern MCP server
|
|
27
|
+
config.mcpServers = config.mcpServers || {}
|
|
28
|
+
config.mcpServers.lantern = {
|
|
29
|
+
url: mcpUrl,
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
// Write config
|
|
33
|
+
writeFileSync(configPath, JSON.stringify(config, null, 2))
|
|
34
|
+
|
|
35
|
+
return configPath
|
|
36
|
+
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { existsSync, readFileSync, writeFileSync, mkdirSync } from 'fs'
|
|
2
|
+
import { dirname } from 'path'
|
|
3
|
+
import { tools } from '../detect.js'
|
|
4
|
+
|
|
5
|
+
export function configureGeminiCli(mcpUrl) {
|
|
6
|
+
const configPath = tools.geminiCli.configPath()
|
|
7
|
+
|
|
8
|
+
// Ensure directory exists
|
|
9
|
+
const configDir = dirname(configPath)
|
|
10
|
+
if (!existsSync(configDir)) {
|
|
11
|
+
mkdirSync(configDir, { recursive: true })
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
// Read existing config or create new one
|
|
15
|
+
let config = {}
|
|
16
|
+
if (existsSync(configPath)) {
|
|
17
|
+
try {
|
|
18
|
+
const content = readFileSync(configPath, 'utf-8')
|
|
19
|
+
config = JSON.parse(content)
|
|
20
|
+
} catch {
|
|
21
|
+
// If file exists but is invalid JSON, start fresh
|
|
22
|
+
config = {}
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
// Add/update Lantern MCP server
|
|
27
|
+
config.mcpServers = config.mcpServers || {}
|
|
28
|
+
config.mcpServers.lantern = {
|
|
29
|
+
httpUrl: mcpUrl,
|
|
30
|
+
timeout: 30000,
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
// Write config
|
|
34
|
+
writeFileSync(configPath, JSON.stringify(config, null, 2))
|
|
35
|
+
|
|
36
|
+
return configPath
|
|
37
|
+
}
|
package/src/detect.js
ADDED
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
import { existsSync } 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 commandExists(command) {
|
|
14
|
+
try {
|
|
15
|
+
execSync(`which ${command}`, { stdio: 'ignore' })
|
|
16
|
+
return true
|
|
17
|
+
} catch {
|
|
18
|
+
return false
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
function getClaudeDesktopPath() {
|
|
23
|
+
const os = platform()
|
|
24
|
+
if (os === 'darwin') {
|
|
25
|
+
return expandHome('~/Library/Application Support/Claude')
|
|
26
|
+
} else if (os === 'win32') {
|
|
27
|
+
return join(process.env.APPDATA || '', 'Claude')
|
|
28
|
+
} else {
|
|
29
|
+
// Linux
|
|
30
|
+
return expandHome('~/.config/Claude')
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
function getClaudeDesktopConfigPath() {
|
|
35
|
+
return join(getClaudeDesktopPath(), 'claude_desktop_config.json')
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function getCursorPath() {
|
|
39
|
+
// Cursor uses ~/.cursor for MCP config on all platforms
|
|
40
|
+
return expandHome('~/.cursor')
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function getCursorConfigPath() {
|
|
44
|
+
return join(getCursorPath(), 'mcp.json')
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
function getGeminiConfigPath() {
|
|
48
|
+
return expandHome('~/.gemini/settings.json')
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export const tools = {
|
|
52
|
+
claudeDesktop: {
|
|
53
|
+
name: 'Claude Desktop',
|
|
54
|
+
detect: () => existsSync(getClaudeDesktopPath()),
|
|
55
|
+
configPath: getClaudeDesktopConfigPath,
|
|
56
|
+
},
|
|
57
|
+
claudeCode: {
|
|
58
|
+
name: 'Claude Code',
|
|
59
|
+
detect: () => commandExists('claude'),
|
|
60
|
+
configPath: null, // Uses CLI command instead
|
|
61
|
+
},
|
|
62
|
+
geminiCli: {
|
|
63
|
+
name: 'Gemini CLI',
|
|
64
|
+
detect: () => commandExists('gemini'),
|
|
65
|
+
configPath: getGeminiConfigPath,
|
|
66
|
+
},
|
|
67
|
+
cursor: {
|
|
68
|
+
name: 'Cursor',
|
|
69
|
+
detect: () => existsSync(getCursorPath()),
|
|
70
|
+
configPath: getCursorConfigPath,
|
|
71
|
+
},
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
export function detectTools() {
|
|
75
|
+
const detected = {}
|
|
76
|
+
|
|
77
|
+
for (const [key, tool] of Object.entries(tools)) {
|
|
78
|
+
detected[key] = {
|
|
79
|
+
...tool,
|
|
80
|
+
found: tool.detect(),
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
return detected
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
export function hasAnyTools(detected) {
|
|
88
|
+
return Object.values(detected).some((tool) => tool.found)
|
|
89
|
+
}
|
package/src/index.js
ADDED
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
import { detectTools, hasAnyTools } from './detect.js'
|
|
2
|
+
import { authenticate } from './auth.js'
|
|
3
|
+
import { configureClaudeDesktop } from './configure/claude-desktop.js'
|
|
4
|
+
import { configureClaudeCode } from './configure/claude-code.js'
|
|
5
|
+
import { configureGeminiCli } from './configure/gemini-cli.js'
|
|
6
|
+
import { configureCursor } from './configure/cursor.js'
|
|
7
|
+
|
|
8
|
+
const configurators = {
|
|
9
|
+
claudeDesktop: configureClaudeDesktop,
|
|
10
|
+
claudeCode: configureClaudeCode,
|
|
11
|
+
geminiCli: configureGeminiCli,
|
|
12
|
+
cursor: configureCursor,
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
function parseArgs() {
|
|
16
|
+
const args = process.argv.slice(2)
|
|
17
|
+
const options = {
|
|
18
|
+
mcpUrl: null,
|
|
19
|
+
lanternUrl: null,
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
for (let i = 0; i < args.length; i++) {
|
|
23
|
+
if (args[i] === '--mcp-url' && args[i + 1]) {
|
|
24
|
+
options.mcpUrl = args[i + 1]
|
|
25
|
+
i++
|
|
26
|
+
} else if (args[i] === '--lantern-url' && args[i + 1]) {
|
|
27
|
+
options.lanternUrl = args[i + 1]
|
|
28
|
+
i++
|
|
29
|
+
} else if (args[i] === '--help' || args[i] === '-h') {
|
|
30
|
+
console.log(`
|
|
31
|
+
Usage: lantern-connect [options]
|
|
32
|
+
|
|
33
|
+
Options:
|
|
34
|
+
--mcp-url <url> MCP server URL (default: https://api.onlantern.com/mcp)
|
|
35
|
+
--lantern-url <url> Lantern web URL (default: https://onlantern.com)
|
|
36
|
+
--help, -h Show this help message
|
|
37
|
+
`)
|
|
38
|
+
process.exit(0)
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
return options
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export async function main() {
|
|
46
|
+
const options = parseArgs()
|
|
47
|
+
console.log('')
|
|
48
|
+
console.log(' š„ Lantern Connect')
|
|
49
|
+
console.log(' āāāāāāāāāāāāāāāāā')
|
|
50
|
+
console.log('')
|
|
51
|
+
|
|
52
|
+
// Step 1: Detect installed tools
|
|
53
|
+
console.log(' š Detecting AI tools...')
|
|
54
|
+
console.log('')
|
|
55
|
+
|
|
56
|
+
const detected = detectTools()
|
|
57
|
+
|
|
58
|
+
for (const [key, tool] of Object.entries(detected)) {
|
|
59
|
+
const icon = tool.found ? 'ā' : 'ā'
|
|
60
|
+
const color = tool.found ? '\x1b[32m' : '\x1b[90m'
|
|
61
|
+
const reset = '\x1b[0m'
|
|
62
|
+
console.log(` ${color}${icon} ${tool.name}${reset}`)
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
console.log('')
|
|
66
|
+
|
|
67
|
+
if (!hasAnyTools(detected)) {
|
|
68
|
+
console.log(' ā ļø No supported AI tools found.')
|
|
69
|
+
console.log('')
|
|
70
|
+
console.log(' Supported tools:')
|
|
71
|
+
console.log(' ⢠Claude Desktop')
|
|
72
|
+
console.log(' ⢠Claude Code (claude CLI)')
|
|
73
|
+
console.log(' ⢠Gemini CLI')
|
|
74
|
+
console.log(' ⢠Cursor')
|
|
75
|
+
console.log('')
|
|
76
|
+
process.exit(1)
|
|
77
|
+
}
|
|
78
|
+
|
|
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}`)
|
|
96
|
+
console.log('')
|
|
97
|
+
|
|
98
|
+
// Step 3: Configure each detected tool
|
|
99
|
+
console.log(' š Configuring...')
|
|
100
|
+
console.log('')
|
|
101
|
+
|
|
102
|
+
const configured = []
|
|
103
|
+
const failed = []
|
|
104
|
+
|
|
105
|
+
for (const [key, tool] of Object.entries(detected)) {
|
|
106
|
+
if (!tool.found) continue
|
|
107
|
+
|
|
108
|
+
const configurator = configurators[key]
|
|
109
|
+
if (!configurator) continue
|
|
110
|
+
|
|
111
|
+
try {
|
|
112
|
+
const result = configurator(authResult.mcpUrl)
|
|
113
|
+
configured.push({ name: tool.name, path: result })
|
|
114
|
+
console.log(` ā ${tool.name}`)
|
|
115
|
+
} catch (err) {
|
|
116
|
+
failed.push({ name: tool.name, error: err.message })
|
|
117
|
+
console.log(` ā ${tool.name}: ${err.message}`)
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
console.log('')
|
|
122
|
+
|
|
123
|
+
// Step 4: Summary
|
|
124
|
+
if (configured.length > 0) {
|
|
125
|
+
console.log(' š Done!')
|
|
126
|
+
console.log('')
|
|
127
|
+
console.log(' Next steps:')
|
|
128
|
+
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
|
+
}
|
|
137
|
+
console.log('')
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
if (failed.length > 0) {
|
|
141
|
+
console.log(' ā ļø Some tools could not be configured:')
|
|
142
|
+
for (const f of failed) {
|
|
143
|
+
console.log(` ⢠${f.name}: ${f.error}`)
|
|
144
|
+
}
|
|
145
|
+
console.log('')
|
|
146
|
+
}
|
|
147
|
+
}
|