neon-init 0.9.1 → 0.10.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 +19 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +55 -148
- package/dist/index.js.map +1 -1
- package/dist/lib/auth.d.ts +13 -0
- package/dist/lib/auth.d.ts.map +1 -0
- package/dist/lib/auth.js +72 -0
- package/dist/lib/auth.js.map +1 -0
- package/dist/lib/editors.d.ts +15 -0
- package/dist/lib/editors.d.ts.map +1 -0
- package/dist/lib/editors.js +47 -0
- package/dist/lib/editors.js.map +1 -0
- package/dist/lib/install.d.ts +14 -0
- package/dist/lib/install.d.ts.map +1 -0
- package/dist/lib/install.js +78 -0
- package/dist/lib/install.js.map +1 -0
- package/dist/lib/mcp-config.d.ts +24 -0
- package/dist/lib/mcp-config.d.ts.map +1 -0
- package/dist/lib/mcp-config.js +51 -0
- package/dist/lib/mcp-config.js.map +1 -0
- package/dist/lib/types.d.ts +22 -0
- package/dist/lib/types.d.ts.map +1 -0
- package/dist/lib/types.js +1 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# neon-init
|
|
2
2
|
|
|
3
|
-
Set up
|
|
3
|
+
Set up Neon's MCP Server for AI-powered database operations in VS Code, Cursor, and Claude CLI.
|
|
4
4
|
|
|
5
5
|
## Installation
|
|
6
6
|
|
|
@@ -16,16 +16,32 @@ import { init } from "neon-init";
|
|
|
16
16
|
await init();
|
|
17
17
|
```
|
|
18
18
|
|
|
19
|
+
Or via CLI:
|
|
20
|
+
|
|
21
|
+
```sh
|
|
22
|
+
npx neon-init
|
|
23
|
+
```
|
|
24
|
+
|
|
19
25
|
Then:
|
|
20
26
|
|
|
21
|
-
1. Restart your
|
|
27
|
+
1. Restart your editor (VS Code, Cursor, or Claude CLI)
|
|
22
28
|
2. Type **"Get started with Neon using MCP Resource"** in your AI chat
|
|
23
29
|
|
|
24
30
|
## What It Does
|
|
25
31
|
|
|
26
32
|
### Configures Neon MCP Server
|
|
27
33
|
|
|
28
|
-
Creates `~/.cursor/mcp.json` (global config
|
|
34
|
+
- **Cursor**: Creates or updates `~/.cursor/mcp.json` (global config - works across all projects)
|
|
35
|
+
- **VS Code**: Creates or updates global `mcp.json` if VS Code is installed, otherwise falls back to `.vscode/mcp.json` (workspace config)
|
|
36
|
+
- **Claude CLI**: Creates or updates `~/.claude.json` (global config - works across all projects)
|
|
37
|
+
|
|
38
|
+
**Supported Editors:**
|
|
39
|
+
|
|
40
|
+
- **VS Code** with GitHub Copilot
|
|
41
|
+
- **Cursor**
|
|
42
|
+
- **Claude CLI**
|
|
43
|
+
|
|
44
|
+
The tool automatically detects which editors are installed on your system and you'll be prompted to choose which one(s) to configure.
|
|
29
45
|
|
|
30
46
|
**Authentication:** Uses OAuth via `neonctl` and creates an API key for you - opens your browser, no manual API keys needed.
|
|
31
47
|
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","names":[],"sources":["../src/index.ts"],"sourcesContent":[],"mappings":";;
|
|
1
|
+
{"version":3,"file":"index.d.ts","names":[],"sources":["../src/index.ts"],"sourcesContent":[],"mappings":";;AAiBA;;iBAAsB,IAAA,CAAA,GAAQ"}
|
package/dist/index.js
CHANGED
|
@@ -1,169 +1,76 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
import { confirm, intro, isCancel, log, note, outro, spinner } from "@clack/prompts";
|
|
5
|
-
import { execa } from "execa";
|
|
1
|
+
import { detectAvailableEditors } from "./lib/editors.js";
|
|
2
|
+
import { installMCPServer } from "./lib/install.js";
|
|
3
|
+
import { confirm, intro, isCancel, log, multiselect, note, outro } from "@clack/prompts";
|
|
6
4
|
import { bold, cyan } from "yoctocolors";
|
|
7
5
|
|
|
8
6
|
//#region src/index.ts
|
|
9
|
-
dirname(fileURLToPath(import.meta.url));
|
|
10
7
|
/**
|
|
11
|
-
*
|
|
12
|
-
* This will automatically start the OAuth flow if the user isn't already authenticated
|
|
13
|
-
*/
|
|
14
|
-
async function ensureNeonctlAuth() {
|
|
15
|
-
try {
|
|
16
|
-
await execa("npx", [
|
|
17
|
-
"-y",
|
|
18
|
-
"neonctl",
|
|
19
|
-
"me",
|
|
20
|
-
"--no-analytics"
|
|
21
|
-
], { stdio: "inherit" });
|
|
22
|
-
return true;
|
|
23
|
-
} catch (error) {
|
|
24
|
-
log.error(`Authentication failed: ${error instanceof Error ? error.message : "Unknown error"}`);
|
|
25
|
-
return false;
|
|
26
|
-
}
|
|
27
|
-
}
|
|
28
|
-
/**
|
|
29
|
-
* Gets the OAuth access token from neonctl's stored credentials
|
|
30
|
-
*/
|
|
31
|
-
async function getNeonctlAccessToken() {
|
|
32
|
-
try {
|
|
33
|
-
const homeDir = process.env.HOME || process.env.USERPROFILE;
|
|
34
|
-
if (!homeDir) return null;
|
|
35
|
-
const credentialsPath = resolve(homeDir, ".config", "neonctl", "credentials.json");
|
|
36
|
-
if (!existsSync(credentialsPath)) return null;
|
|
37
|
-
return JSON.parse(readFileSync(credentialsPath, "utf-8")).access_token || null;
|
|
38
|
-
} catch {
|
|
39
|
-
return null;
|
|
40
|
-
}
|
|
41
|
-
}
|
|
42
|
-
/**
|
|
43
|
-
* Creates an API key using the Neon API with the OAuth token from neonctl
|
|
44
|
-
*/
|
|
45
|
-
async function createApiKeyFromNeonctl() {
|
|
46
|
-
try {
|
|
47
|
-
const accessToken = await getNeonctlAccessToken();
|
|
48
|
-
if (!accessToken) {
|
|
49
|
-
log.error("Could not find OAuth token from neonctl");
|
|
50
|
-
return null;
|
|
51
|
-
}
|
|
52
|
-
const keyName = `neonctl-init-${(/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-").slice(0, -5)}`;
|
|
53
|
-
const response = await fetch("https://console.neon.tech/api/v2/api_keys", {
|
|
54
|
-
method: "POST",
|
|
55
|
-
headers: {
|
|
56
|
-
Authorization: `Bearer ${accessToken}`,
|
|
57
|
-
"Content-Type": "application/json"
|
|
58
|
-
},
|
|
59
|
-
body: JSON.stringify({ key_name: keyName })
|
|
60
|
-
});
|
|
61
|
-
if (!response.ok) {
|
|
62
|
-
const errorText = await response.text();
|
|
63
|
-
log.error(`Failed to create API key: ${response.status} ${errorText}`);
|
|
64
|
-
return null;
|
|
65
|
-
}
|
|
66
|
-
return (await response.json()).key || null;
|
|
67
|
-
} catch (error) {
|
|
68
|
-
log.error(`Failed to create API key: ${error instanceof Error ? error.message : "Unknown error"}`);
|
|
69
|
-
return null;
|
|
70
|
-
}
|
|
71
|
-
}
|
|
72
|
-
/**
|
|
73
|
-
* Gets or creates the .cursor/mcp.json configuration
|
|
74
|
-
*/
|
|
75
|
-
function getMCPConfig(cursorDir) {
|
|
76
|
-
const mcpConfigPath = resolve(cursorDir, "mcp.json");
|
|
77
|
-
if (existsSync(mcpConfigPath)) try {
|
|
78
|
-
const content = readFileSync(mcpConfigPath, "utf-8");
|
|
79
|
-
return JSON.parse(content);
|
|
80
|
-
} catch (_error) {
|
|
81
|
-
log.warn("Failed to parse existing mcp.json. Creating a new configuration.");
|
|
82
|
-
return { mcpServers: {} };
|
|
83
|
-
}
|
|
84
|
-
return { mcpServers: {} };
|
|
85
|
-
}
|
|
86
|
-
/**
|
|
87
|
-
* Writes the MCP configuration to .cursor/mcp.json
|
|
88
|
-
*/
|
|
89
|
-
function writeMCPConfig(cursorDir, config) {
|
|
90
|
-
const mcpConfigPath = resolve(cursorDir, "mcp.json");
|
|
91
|
-
if (!existsSync(cursorDir)) mkdirSync(cursorDir, { recursive: true });
|
|
92
|
-
writeFileSync(mcpConfigPath, JSON.stringify(config, null, 2), "utf-8");
|
|
93
|
-
}
|
|
94
|
-
/**
|
|
95
|
-
* Installs Neon's MCP Server by configuring it globally in ~/.cursor/mcp.json
|
|
8
|
+
* Initialize Neon projects with MCP Server
|
|
96
9
|
*/
|
|
97
|
-
async function
|
|
10
|
+
async function init() {
|
|
11
|
+
intro("Adding Neon to your project");
|
|
98
12
|
const homeDir = process.env.HOME || process.env.USERPROFILE;
|
|
99
13
|
if (!homeDir) {
|
|
100
14
|
log.error("Could not determine home directory");
|
|
101
|
-
|
|
15
|
+
outro("📣 Is this unexpected? Email us at feedback@neon.tech");
|
|
16
|
+
process.exit(1);
|
|
102
17
|
}
|
|
103
|
-
const
|
|
104
|
-
const
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
18
|
+
const workspaceDir = process.cwd();
|
|
19
|
+
const availableEditors = await detectAvailableEditors(homeDir);
|
|
20
|
+
if (availableEditors.length === 0) {
|
|
21
|
+
log.warn("No supported editors detected on your system.");
|
|
22
|
+
log.info("Supported editors:");
|
|
23
|
+
log.info(" • VS Code (with GitHub Copilot)");
|
|
24
|
+
log.info(" • Cursor");
|
|
25
|
+
log.info(" • Claude CLI");
|
|
26
|
+
const continueAnyway = await confirm({
|
|
27
|
+
message: "Would you like to configure MCP anyway? (You can manually select your editor)",
|
|
110
28
|
initialValue: true
|
|
111
29
|
});
|
|
112
|
-
if (isCancel(
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
log.info("Keeping existing global configuration.");
|
|
116
|
-
return true;
|
|
30
|
+
if (isCancel(continueAnyway) || !continueAnyway) {
|
|
31
|
+
outro("Installation cancelled");
|
|
32
|
+
process.exit(0);
|
|
117
33
|
}
|
|
118
34
|
}
|
|
119
|
-
const
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
}
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
};
|
|
136
|
-
try {
|
|
137
|
-
writeMCPConfig(cursorDir, config);
|
|
138
|
-
return true;
|
|
139
|
-
} catch (error) {
|
|
140
|
-
log.error(`Failed to write global configuration: ${error instanceof Error ? error.message : "Unknown error"}`);
|
|
141
|
-
return false;
|
|
35
|
+
const response = await multiselect({
|
|
36
|
+
message: "Which editor(s) would you like to configure? (Space to toggle each option, Enter to confirm your selection)",
|
|
37
|
+
options: [
|
|
38
|
+
"Cursor",
|
|
39
|
+
"VS Code",
|
|
40
|
+
"Claude CLI"
|
|
41
|
+
].map((editor) => ({
|
|
42
|
+
value: editor,
|
|
43
|
+
label: editor
|
|
44
|
+
})),
|
|
45
|
+
initialValues: availableEditors,
|
|
46
|
+
required: true
|
|
47
|
+
});
|
|
48
|
+
if (isCancel(response)) {
|
|
49
|
+
outro("Installation cancelled");
|
|
50
|
+
process.exit(0);
|
|
142
51
|
}
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
intro("Adding Neon to your project");
|
|
149
|
-
const homeDir = process.env.HOME || process.env.USERPROFILE;
|
|
150
|
-
if (!homeDir) {
|
|
151
|
-
log.error("Could not determine home directory");
|
|
152
|
-
process.exit(1);
|
|
52
|
+
const selectedEditors = response;
|
|
53
|
+
if (selectedEditors.length === 0) {
|
|
54
|
+
log.warn("No editors selected.");
|
|
55
|
+
outro("Installation cancelled");
|
|
56
|
+
process.exit(0);
|
|
153
57
|
}
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
58
|
+
const results = await installMCPServer(homeDir, workspaceDir, selectedEditors);
|
|
59
|
+
const successful = [];
|
|
60
|
+
const failed = [];
|
|
61
|
+
for (const [editor, status] of results.entries()) if (status === "success") successful.push(editor);
|
|
62
|
+
else failed.push(editor);
|
|
63
|
+
const successList = successful.join(" / ");
|
|
64
|
+
if (successful.length > 0) {
|
|
65
|
+
log.step("Installed Neon MCP server");
|
|
66
|
+
log.step(`Success! Neon is now ready to use with ${successList}.\n`);
|
|
159
67
|
}
|
|
160
|
-
if (
|
|
161
|
-
|
|
68
|
+
if (failed.length > 0) log.error(`Failed to configure: ${failed.join(" / ")}`);
|
|
69
|
+
if (successful.length === 0) {
|
|
70
|
+
outro("Installation cancelled or failed. Please check the output above and try again.");
|
|
162
71
|
process.exit(1);
|
|
163
72
|
}
|
|
164
|
-
|
|
165
|
-
log.step("Success! Neon is now ready to use with Cursor.\n");
|
|
166
|
-
note(`\x1b[0mRestart Cursor and ask Cursor to "${bold(cyan("Get started with Neon using MCP Resource"))}\x1b[0m" in the chat`, "What's next?");
|
|
73
|
+
note(`\x1b[0mRestart ${successList} and type in "${bold(cyan("Get started with Neon using MCP resource"))}\x1b[0m" in the chat`, "What's next?");
|
|
167
74
|
outro("Have feedback? Email us at feedback@neon.tech");
|
|
168
75
|
}
|
|
169
76
|
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","names":[],"sources":["../src/index.ts"],"sourcesContent":["import { existsSync, mkdirSync, readFileSync, writeFileSync } from \"node:fs\";\nimport { dirname, resolve } from \"node:path\";\nimport { fileURLToPath } from \"node:url\";\nimport {\n\tconfirm,\n\tintro,\n\tisCancel,\n\tlog,\n\tnote,\n\toutro,\n\tspinner,\n} from \"@clack/prompts\";\nimport { execa } from \"execa\";\nimport { bold, cyan } from \"yoctocolors\";\n\nconst __filename = fileURLToPath(import.meta.url);\nconst __dirname = dirname(__filename);\n\ninterface MCPConfig {\n\tmcpServers: {\n\t\t[key: string]: {\n\t\t\turl: string;\n\t\t\theaders?: Record<string, string>;\n\t\t};\n\t};\n}\n\n/**\n * Ensures neonctl is authenticated by running a command that triggers auth if needed\n * This will automatically start the OAuth flow if the user isn't already authenticated\n */\nasync function ensureNeonctlAuth(): Promise<boolean> {\n\ttry {\n\t\t// Use execa to authenticate with neonctl\n\t\tawait execa(\"npx\", [\"-y\", \"neonctl\", \"me\", \"--no-analytics\"], {\n\t\t\tstdio: \"inherit\", // Shows OAuth URL and prompts to the user\n\t\t});\n\n\t\treturn true;\n\t} catch (error) {\n\t\tlog.error(\n\t\t\t`Authentication failed: ${error instanceof Error ? error.message : \"Unknown error\"}`,\n\t\t);\n\t\treturn false;\n\t}\n}\n\n/**\n * Gets the OAuth access token from neonctl's stored credentials\n */\nasync function getNeonctlAccessToken(): Promise<string | null> {\n\ttry {\n\t\tconst homeDir = process.env.HOME || process.env.USERPROFILE;\n\t\tif (!homeDir) return null;\n\n\t\tconst credentialsPath = resolve(\n\t\t\thomeDir,\n\t\t\t\".config\",\n\t\t\t\"neonctl\",\n\t\t\t\"credentials.json\",\n\t\t);\n\t\tif (!existsSync(credentialsPath)) return null;\n\n\t\tconst credentials = JSON.parse(readFileSync(credentialsPath, \"utf-8\"));\n\t\treturn credentials.access_token || null;\n\t} catch {\n\t\treturn null;\n\t}\n}\n\n/**\n * Creates an API key using the Neon API with the OAuth token from neonctl\n */\nasync function createApiKeyFromNeonctl(): Promise<string | null> {\n\ttry {\n\t\tconst accessToken = await getNeonctlAccessToken();\n\t\tif (!accessToken) {\n\t\t\tlog.error(\"Could not find OAuth token from neonctl\");\n\t\t\treturn null;\n\t\t}\n\n\t\t// Generate a unique key name with timestamp\n\t\tconst timestamp = new Date()\n\t\t\t.toISOString()\n\t\t\t.replace(/[:.]/g, \"-\")\n\t\t\t.slice(0, -5); // e.g., 2024-10-08T15-30-45\n\t\tconst keyName = `neonctl-init-${timestamp}`;\n\n\t\t// Call Neon API to create an API key\n\t\tconst response = await fetch(\n\t\t\t\"https://console.neon.tech/api/v2/api_keys\",\n\t\t\t{\n\t\t\t\tmethod: \"POST\",\n\t\t\t\theaders: {\n\t\t\t\t\tAuthorization: `Bearer ${accessToken}`,\n\t\t\t\t\t\"Content-Type\": \"application/json\",\n\t\t\t\t},\n\t\t\t\tbody: JSON.stringify({\n\t\t\t\t\tkey_name: keyName,\n\t\t\t\t}),\n\t\t\t},\n\t\t);\n\n\t\tif (!response.ok) {\n\t\t\tconst errorText = await response.text();\n\t\t\tlog.error(\n\t\t\t\t`Failed to create API key: ${response.status} ${errorText}`,\n\t\t\t);\n\t\t\treturn null;\n\t\t}\n\n\t\tconst data = await response.json();\n\t\treturn data.key || null;\n\t} catch (error) {\n\t\tlog.error(\n\t\t\t`Failed to create API key: ${error instanceof Error ? error.message : \"Unknown error\"}`,\n\t\t);\n\t\treturn null;\n\t}\n}\n\n/**\n * Gets or creates the .cursor/mcp.json configuration\n */\nfunction getMCPConfig(cursorDir: string): MCPConfig {\n\tconst mcpConfigPath = resolve(cursorDir, \"mcp.json\");\n\n\tif (existsSync(mcpConfigPath)) {\n\t\ttry {\n\t\t\tconst content = readFileSync(mcpConfigPath, \"utf-8\");\n\t\t\treturn JSON.parse(content);\n\t\t} catch (_error) {\n\t\t\tlog.warn(\n\t\t\t\t\"Failed to parse existing mcp.json. Creating a new configuration.\",\n\t\t\t);\n\t\t\treturn { mcpServers: {} };\n\t\t}\n\t}\n\n\treturn { mcpServers: {} };\n}\n\n/**\n * Writes the MCP configuration to .cursor/mcp.json\n */\nfunction writeMCPConfig(cursorDir: string, config: MCPConfig): void {\n\tconst mcpConfigPath = resolve(cursorDir, \"mcp.json\");\n\n\tif (!existsSync(cursorDir)) {\n\t\tmkdirSync(cursorDir, { recursive: true });\n\t}\n\n\twriteFileSync(mcpConfigPath, JSON.stringify(config, null, 2), \"utf-8\");\n}\n\n/**\n * Installs Neon's MCP Server by configuring it globally in ~/.cursor/mcp.json\n */\nasync function installMCPServer(): Promise<boolean> {\n\tconst homeDir = process.env.HOME || process.env.USERPROFILE;\n\tif (!homeDir) {\n\t\tlog.error(\"Could not determine home directory\");\n\t\treturn false;\n\t}\n\tconst cursorDir = resolve(homeDir, \".cursor\");\n\tconst config = getMCPConfig(cursorDir);\n\n\t// Check if already configured\n\tconst alreadyConfigured = Boolean(config.mcpServers.Neon);\n\tlet shouldReconfigure = false;\n\n\tif (alreadyConfigured) {\n\t\tconst response = await confirm({\n\t\t\tmessage:\n\t\t\t\t\"Neon MCP Server is already configured. Would you like to reconfigure it? (Y/n)\",\n\t\t\tinitialValue: true,\n\t\t});\n\n\t\tif (isCancel(response)) {\n\t\t\treturn false;\n\t\t}\n\n\t\tshouldReconfigure = response as boolean;\n\n\t\tif (!shouldReconfigure) {\n\t\t\tlog.info(\"Keeping existing global configuration.\");\n\t\t\treturn true;\n\t\t}\n\t}\n\n\t// Ensure authentication (will trigger OAuth if needed)\n\tconst authSpinner = spinner();\n\tauthSpinner.start(\"Authenticating...\");\n\n\tconst authSuccess = await ensureNeonctlAuth();\n\n\tif (!authSuccess) {\n\t\tauthSpinner.stop(\"Authentication failed\");\n\t\treturn false;\n\t}\n\n\tauthSpinner.stop(\"Authentication successful ✓\");\n\n\t// Create API key using the OAuth token\n\tconst apiKey = await createApiKeyFromNeonctl();\n\n\tif (!apiKey) {\n\t\tlog.error(\"Could not create API key after authentication.\");\n\t\tlog.info(\n\t\t\t\"You can manually create one at: https://console.neon.tech/app/settings/api-keys\",\n\t\t);\n\t\treturn false;\n\t}\n\n\t// Configure Neon MCP Server\n\t// Using remote MCP server with API key authentication\n\t// Ref: https://neon.com/docs/ai/neon-mcp-server#api-key-based-authentication\n\tconfig.mcpServers.Neon = {\n\t\turl: \"https://mcp.neon.tech/mcp\",\n\t\theaders: {\n\t\t\tAuthorization: `Bearer ${apiKey}`,\n\t\t},\n\t};\n\n\t// Write configuration\n\ttry {\n\t\twriteMCPConfig(cursorDir, config);\n\t\treturn true;\n\t} catch (error) {\n\t\tlog.error(\n\t\t\t`Failed to write global configuration: ${error instanceof Error ? error.message : \"Unknown error\"}`,\n\t\t);\n\t\treturn false;\n\t}\n}\n\n/**\n * Initialize Neon projects with MCP Server\n */\nexport async function init(): Promise<void> {\n\tintro(\"Adding Neon to your project\");\n\n\t// Check if Cursor is installed\n\tconst homeDir = process.env.HOME || process.env.USERPROFILE;\n\tif (!homeDir) {\n\t\tlog.error(\"Could not determine home directory\");\n\t\tprocess.exit(1);\n\t}\n\n\tconst cursorDir = resolve(homeDir, \".cursor\");\n\tif (!existsSync(cursorDir)) {\n\t\tlog.warn(\"Cursor not found.\");\n\t\tlog.warn(\n\t\t\t\"Error: Cursor is required to continue. Support for additional agents is coming soon.\",\n\t\t);\n\t\toutro(\"📣 Is this unexpected? Email us at feedback@neon.tech\");\n\t\tprocess.exit(1);\n\t}\n\n\tconst mcpSuccess = await installMCPServer();\n\n\tif (!mcpSuccess) {\n\t\toutro(\n\t\t\t\"Initialization cancelled or failed. Please check the output above and try again.\",\n\t\t);\n\t\tprocess.exit(1);\n\t}\n\n\tlog.step(\"Installed Neon MCP server\");\n\tlog.step(\"Success! Neon is now ready to use with Cursor.\\n\");\n\n\t// \\x1b[0m is the ANSI escape code for \"reset all styles\" to clear any dimming/fading that clack's note() applies\n\tnote(\n\t\t`\\x1b[0mRestart Cursor and ask Cursor to \"${bold(cyan(\"Get started with Neon using MCP Resource\"))}\\x1b[0m\" in the chat`,\n\t\t\"What's next?\",\n\t);\n\n\toutro(\"Have feedback? Email us at feedback@neon.tech\");\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"index.js","names":["successful: Editor[]","failed: Editor[]"],"sources":["../src/index.ts"],"sourcesContent":["import {\n\tconfirm,\n\tintro,\n\tisCancel,\n\tlog,\n\tmultiselect,\n\tnote,\n\toutro,\n} from \"@clack/prompts\";\nimport { bold, cyan } from \"yoctocolors\";\nimport { detectAvailableEditors } from \"./lib/editors.js\";\nimport { installMCPServer } from \"./lib/install.js\";\nimport type { Editor } from \"./lib/types.js\";\n\n/**\n * Initialize Neon projects with MCP Server\n */\nexport async function init(): Promise<void> {\n\tintro(\"Adding Neon to your project\");\n\n\t// Get the home directory\n\tconst homeDir = process.env.HOME || process.env.USERPROFILE;\n\tif (!homeDir) {\n\t\tlog.error(\"Could not determine home directory\");\n\t\toutro(\"📣 Is this unexpected? Email us at feedback@neon.tech\");\n\t\tprocess.exit(1);\n\t}\n\n\t// Get the current workspace directory\n\tconst workspaceDir = process.cwd();\n\n\t// Detect available editors\n\tconst availableEditors = await detectAvailableEditors(homeDir);\n\n\t// If no editors detected, offer to continue anyway\n\tif (availableEditors.length === 0) {\n\t\tlog.warn(\"No supported editors detected on your system.\");\n\t\tlog.info(\"Supported editors:\");\n\t\tlog.info(\" • VS Code (with GitHub Copilot)\");\n\t\tlog.info(\" • Cursor\");\n\t\tlog.info(\" • Claude CLI\");\n\n\t\tconst continueAnyway = await confirm({\n\t\t\tmessage:\n\t\t\t\t\"Would you like to configure MCP anyway? (You can manually select your editor)\",\n\t\t\tinitialValue: true,\n\t\t});\n\n\t\tif (isCancel(continueAnyway) || !continueAnyway) {\n\t\t\toutro(\"Installation cancelled\");\n\t\t\tprocess.exit(0);\n\t\t}\n\t}\n\n\t// Determine which editors to configure\n\tconst response = await multiselect({\n\t\tmessage:\n\t\t\t\"Which editor(s) would you like to configure? (Space to toggle each option, Enter to confirm your selection)\",\n\t\toptions: [\"Cursor\", \"VS Code\", \"Claude CLI\"].map((editor) => ({\n\t\t\tvalue: editor,\n\t\t\tlabel: editor,\n\t\t})),\n\t\tinitialValues: availableEditors, // Select detected editors by default\n\t\trequired: true,\n\t});\n\n\tif (isCancel(response)) {\n\t\toutro(\"Installation cancelled\");\n\t\tprocess.exit(0);\n\t}\n\n\tconst selectedEditors = response as Editor[];\n\n\tif (selectedEditors.length === 0) {\n\t\tlog.warn(\"No editors selected.\");\n\t\toutro(\"Installation cancelled\");\n\t\tprocess.exit(0);\n\t}\n\n\t// Install MCP server for selected editors\n\tconst results = await installMCPServer(\n\t\thomeDir,\n\t\tworkspaceDir,\n\t\tselectedEditors,\n\t);\n\n\tconst successful: Editor[] = [];\n\tconst failed: Editor[] = [];\n\n\tfor (const [editor, status] of results.entries()) {\n\t\tif (status === \"success\") {\n\t\t\tsuccessful.push(editor);\n\t\t} else {\n\t\t\tfailed.push(editor);\n\t\t}\n\t}\n\tconst successList = successful.join(\" / \");\n\tif (successful.length > 0) {\n\t\tlog.step(\"Installed Neon MCP server\");\n\t\tlog.step(`Success! Neon is now ready to use with ${successList}.\\n`);\n\t}\n\n\tif (failed.length > 0) {\n\t\tlog.error(`Failed to configure: ${failed.join(\" / \")}`);\n\t}\n\n\t// Exit with error if all failed\n\tif (successful.length === 0) {\n\t\toutro(\n\t\t\t\"Installation cancelled or failed. Please check the output above and try again.\",\n\t\t);\n\t\tprocess.exit(1);\n\t}\n\n\tnote(\n\t\t`\\x1b[0mRestart ${successList} and type in \"${bold(cyan(\"Get started with Neon using MCP resource\"))}\\x1b[0m\" in the chat`,\n\t\t\"What's next?\",\n\t);\n\n\toutro(\"Have feedback? Email us at feedback@neon.tech\");\n}\n"],"mappings":";;;;;;;;;AAiBA,eAAsB,OAAsB;AAC3C,OAAM,8BAA8B;CAGpC,MAAM,UAAU,QAAQ,IAAI,QAAQ,QAAQ,IAAI;AAChD,KAAI,CAAC,SAAS;AACb,MAAI,MAAM,qCAAqC;AAC/C,QAAM,wDAAwD;AAC9D,UAAQ,KAAK,EAAE;;CAIhB,MAAM,eAAe,QAAQ,KAAK;CAGlC,MAAM,mBAAmB,MAAM,uBAAuB,QAAQ;AAG9D,KAAI,iBAAiB,WAAW,GAAG;AAClC,MAAI,KAAK,gDAAgD;AACzD,MAAI,KAAK,qBAAqB;AAC9B,MAAI,KAAK,oCAAoC;AAC7C,MAAI,KAAK,aAAa;AACtB,MAAI,KAAK,iBAAiB;EAE1B,MAAM,iBAAiB,MAAM,QAAQ;GACpC,SACC;GACD,cAAc;GACd,CAAC;AAEF,MAAI,SAAS,eAAe,IAAI,CAAC,gBAAgB;AAChD,SAAM,yBAAyB;AAC/B,WAAQ,KAAK,EAAE;;;CAKjB,MAAM,WAAW,MAAM,YAAY;EAClC,SACC;EACD,SAAS;GAAC;GAAU;GAAW;GAAa,CAAC,KAAK,YAAY;GAC7D,OAAO;GACP,OAAO;GACP,EAAE;EACH,eAAe;EACf,UAAU;EACV,CAAC;AAEF,KAAI,SAAS,SAAS,EAAE;AACvB,QAAM,yBAAyB;AAC/B,UAAQ,KAAK,EAAE;;CAGhB,MAAM,kBAAkB;AAExB,KAAI,gBAAgB,WAAW,GAAG;AACjC,MAAI,KAAK,uBAAuB;AAChC,QAAM,yBAAyB;AAC/B,UAAQ,KAAK,EAAE;;CAIhB,MAAM,UAAU,MAAM,iBACrB,SACA,cACA,gBACA;CAED,MAAMA,aAAuB,EAAE;CAC/B,MAAMC,SAAmB,EAAE;AAE3B,MAAK,MAAM,CAAC,QAAQ,WAAW,QAAQ,SAAS,CAC/C,KAAI,WAAW,UACd,YAAW,KAAK,OAAO;KAEvB,QAAO,KAAK,OAAO;CAGrB,MAAM,cAAc,WAAW,KAAK,MAAM;AAC1C,KAAI,WAAW,SAAS,GAAG;AAC1B,MAAI,KAAK,4BAA4B;AACrC,MAAI,KAAK,0CAA0C,YAAY,KAAK;;AAGrE,KAAI,OAAO,SAAS,EACnB,KAAI,MAAM,wBAAwB,OAAO,KAAK,MAAM,GAAG;AAIxD,KAAI,WAAW,WAAW,GAAG;AAC5B,QACC,iFACA;AACD,UAAQ,KAAK,EAAE;;AAGhB,MACC,kBAAkB,YAAY,gBAAgB,KAAK,KAAK,2CAA2C,CAAC,CAAC,uBACrG,eACA;AAED,OAAM,gDAAgD"}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
//#region src/lib/auth.d.ts
|
|
2
|
+
/**
|
|
3
|
+
* Ensures neonctl is authenticated by running a command that triggers auth if needed
|
|
4
|
+
* This will automatically start the OAuth flow if the user isn't already authenticated
|
|
5
|
+
*/
|
|
6
|
+
declare function ensureNeonctlAuth(): Promise<boolean>;
|
|
7
|
+
/**
|
|
8
|
+
* Creates an API key using the Neon API with the OAuth token from neonctl
|
|
9
|
+
*/
|
|
10
|
+
declare function createApiKeyFromNeonctl(): Promise<string | null>;
|
|
11
|
+
//#endregion
|
|
12
|
+
export { createApiKeyFromNeonctl, ensureNeonctlAuth };
|
|
13
|
+
//# sourceMappingURL=auth.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"auth.d.ts","names":[],"sources":["../../src/lib/auth.ts"],"sourcesContent":[],"mappings":";;AASA;AA0CA;;iBA1CsB,iBAAA,CAAA,GAAqB;;;;iBA0CrB,uBAAA,CAAA,GAA2B"}
|
package/dist/lib/auth.js
ADDED
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import { log } from "@clack/prompts";
|
|
2
|
+
import { existsSync, readFileSync } from "node:fs";
|
|
3
|
+
import { resolve } from "node:path";
|
|
4
|
+
import { execa } from "execa";
|
|
5
|
+
|
|
6
|
+
//#region src/lib/auth.ts
|
|
7
|
+
/**
|
|
8
|
+
* Ensures neonctl is authenticated by running a command that triggers auth if needed
|
|
9
|
+
* This will automatically start the OAuth flow if the user isn't already authenticated
|
|
10
|
+
*/
|
|
11
|
+
async function ensureNeonctlAuth() {
|
|
12
|
+
try {
|
|
13
|
+
await execa("npx", [
|
|
14
|
+
"-y",
|
|
15
|
+
"neonctl",
|
|
16
|
+
"me",
|
|
17
|
+
"--no-analytics"
|
|
18
|
+
], { stdio: "inherit" });
|
|
19
|
+
return true;
|
|
20
|
+
} catch (error) {
|
|
21
|
+
log.error(`Authentication failed: ${error instanceof Error ? error.message : "Unknown error"}`);
|
|
22
|
+
return false;
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* Gets the OAuth access token from neonctl's stored credentials
|
|
27
|
+
*/
|
|
28
|
+
async function getNeonctlAccessToken() {
|
|
29
|
+
try {
|
|
30
|
+
const homeDir = process.env.HOME || process.env.USERPROFILE;
|
|
31
|
+
if (!homeDir) return null;
|
|
32
|
+
const credentialsPath = resolve(homeDir, ".config", "neonctl", "credentials.json");
|
|
33
|
+
if (!existsSync(credentialsPath)) return null;
|
|
34
|
+
return JSON.parse(readFileSync(credentialsPath, "utf-8")).access_token || null;
|
|
35
|
+
} catch {
|
|
36
|
+
return null;
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* Creates an API key using the Neon API with the OAuth token from neonctl
|
|
41
|
+
*/
|
|
42
|
+
async function createApiKeyFromNeonctl() {
|
|
43
|
+
try {
|
|
44
|
+
const accessToken = await getNeonctlAccessToken();
|
|
45
|
+
if (!accessToken) {
|
|
46
|
+
log.error("Could not find OAuth token from neonctl");
|
|
47
|
+
return null;
|
|
48
|
+
}
|
|
49
|
+
const keyName = `neonctl-init-${(/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-").slice(0, -5)}`;
|
|
50
|
+
const response = await fetch("https://console.neon.tech/api/v2/api_keys", {
|
|
51
|
+
method: "POST",
|
|
52
|
+
headers: {
|
|
53
|
+
Authorization: `Bearer ${accessToken}`,
|
|
54
|
+
"Content-Type": "application/json"
|
|
55
|
+
},
|
|
56
|
+
body: JSON.stringify({ key_name: keyName })
|
|
57
|
+
});
|
|
58
|
+
if (!response.ok) {
|
|
59
|
+
const errorText = await response.text();
|
|
60
|
+
log.error(`Failed to create API key: ${response.status} ${errorText}`);
|
|
61
|
+
return null;
|
|
62
|
+
}
|
|
63
|
+
return (await response.json()).key || null;
|
|
64
|
+
} catch (error) {
|
|
65
|
+
log.error(`Failed to create API key: ${error instanceof Error ? error.message : "Unknown error"}`);
|
|
66
|
+
return null;
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
//#endregion
|
|
71
|
+
export { createApiKeyFromNeonctl, ensureNeonctlAuth };
|
|
72
|
+
//# sourceMappingURL=auth.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"auth.js","names":[],"sources":["../../src/lib/auth.ts"],"sourcesContent":["import { existsSync, readFileSync } from \"node:fs\";\nimport { resolve } from \"node:path\";\nimport { log } from \"@clack/prompts\";\nimport { execa } from \"execa\";\n\n/**\n * Ensures neonctl is authenticated by running a command that triggers auth if needed\n * This will automatically start the OAuth flow if the user isn't already authenticated\n */\nexport async function ensureNeonctlAuth(): Promise<boolean> {\n\ttry {\n\t\t// Use execa to authenticate with neonctl\n\t\tawait execa(\"npx\", [\"-y\", \"neonctl\", \"me\", \"--no-analytics\"], {\n\t\t\tstdio: \"inherit\", // Shows OAuth URL and prompts to the user\n\t\t});\n\n\t\treturn true;\n\t} catch (error) {\n\t\tlog.error(\n\t\t\t`Authentication failed: ${error instanceof Error ? error.message : \"Unknown error\"}`,\n\t\t);\n\t\treturn false;\n\t}\n}\n\n/**\n * Gets the OAuth access token from neonctl's stored credentials\n */\nasync function getNeonctlAccessToken(): Promise<string | null> {\n\ttry {\n\t\tconst homeDir = process.env.HOME || process.env.USERPROFILE;\n\t\tif (!homeDir) return null;\n\n\t\tconst credentialsPath = resolve(\n\t\t\thomeDir,\n\t\t\t\".config\",\n\t\t\t\"neonctl\",\n\t\t\t\"credentials.json\",\n\t\t);\n\t\tif (!existsSync(credentialsPath)) return null;\n\n\t\tconst credentials = JSON.parse(readFileSync(credentialsPath, \"utf-8\"));\n\t\treturn credentials.access_token || null;\n\t} catch {\n\t\treturn null;\n\t}\n}\n\n/**\n * Creates an API key using the Neon API with the OAuth token from neonctl\n */\nexport async function createApiKeyFromNeonctl(): Promise<string | null> {\n\ttry {\n\t\tconst accessToken = await getNeonctlAccessToken();\n\t\tif (!accessToken) {\n\t\t\tlog.error(\"Could not find OAuth token from neonctl\");\n\t\t\treturn null;\n\t\t}\n\n\t\t// Generate a unique key name with timestamp\n\t\tconst timestamp = new Date()\n\t\t\t.toISOString()\n\t\t\t.replace(/[:.]/g, \"-\")\n\t\t\t.slice(0, -5); // e.g., 2024-10-08T15-30-45\n\t\tconst keyName = `neonctl-init-${timestamp}`;\n\n\t\t// Call Neon API to create an API key\n\t\tconst response = await fetch(\n\t\t\t\"https://console.neon.tech/api/v2/api_keys\",\n\t\t\t{\n\t\t\t\tmethod: \"POST\",\n\t\t\t\theaders: {\n\t\t\t\t\tAuthorization: `Bearer ${accessToken}`,\n\t\t\t\t\t\"Content-Type\": \"application/json\",\n\t\t\t\t},\n\t\t\t\tbody: JSON.stringify({\n\t\t\t\t\tkey_name: keyName,\n\t\t\t\t}),\n\t\t\t},\n\t\t);\n\n\t\tif (!response.ok) {\n\t\t\tconst errorText = await response.text();\n\t\t\tlog.error(\n\t\t\t\t`Failed to create API key: ${response.status} ${errorText}`,\n\t\t\t);\n\t\t\treturn null;\n\t\t}\n\n\t\tconst data = await response.json();\n\t\treturn data.key || null;\n\t} catch (error) {\n\t\tlog.error(\n\t\t\t`Failed to create API key: ${error instanceof Error ? error.message : \"Unknown error\"}`,\n\t\t);\n\t\treturn null;\n\t}\n}\n"],"mappings":";;;;;;;;;;AASA,eAAsB,oBAAsC;AAC3D,KAAI;AAEH,QAAM,MAAM,OAAO;GAAC;GAAM;GAAW;GAAM;GAAiB,EAAE,EAC7D,OAAO,WACP,CAAC;AAEF,SAAO;UACC,OAAO;AACf,MAAI,MACH,0BAA0B,iBAAiB,QAAQ,MAAM,UAAU,kBACnE;AACD,SAAO;;;;;;AAOT,eAAe,wBAAgD;AAC9D,KAAI;EACH,MAAM,UAAU,QAAQ,IAAI,QAAQ,QAAQ,IAAI;AAChD,MAAI,CAAC,QAAS,QAAO;EAErB,MAAM,kBAAkB,QACvB,SACA,WACA,WACA,mBACA;AACD,MAAI,CAAC,WAAW,gBAAgB,CAAE,QAAO;AAGzC,SADoB,KAAK,MAAM,aAAa,iBAAiB,QAAQ,CAAC,CACnD,gBAAgB;SAC5B;AACP,SAAO;;;;;;AAOT,eAAsB,0BAAkD;AACvE,KAAI;EACH,MAAM,cAAc,MAAM,uBAAuB;AACjD,MAAI,CAAC,aAAa;AACjB,OAAI,MAAM,0CAA0C;AACpD,UAAO;;EAQR,MAAM,UAAU,iCAJE,IAAI,MAAM,EAC1B,aAAa,CACb,QAAQ,SAAS,IAAI,CACrB,MAAM,GAAG,GAAG;EAId,MAAM,WAAW,MAAM,MACtB,6CACA;GACC,QAAQ;GACR,SAAS;IACR,eAAe,UAAU;IACzB,gBAAgB;IAChB;GACD,MAAM,KAAK,UAAU,EACpB,UAAU,SACV,CAAC;GACF,CACD;AAED,MAAI,CAAC,SAAS,IAAI;GACjB,MAAM,YAAY,MAAM,SAAS,MAAM;AACvC,OAAI,MACH,6BAA6B,SAAS,OAAO,GAAG,YAChD;AACD,UAAO;;AAIR,UADa,MAAM,SAAS,MAAM,EACtB,OAAO;UACX,OAAO;AACf,MAAI,MACH,6BAA6B,iBAAiB,QAAQ,MAAM,UAAU,kBACtE;AACD,SAAO"}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { Editor } from "./types.js";
|
|
2
|
+
|
|
3
|
+
//#region src/lib/editors.d.ts
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Gets VS Code's global config directory based on the platform
|
|
7
|
+
*/
|
|
8
|
+
declare function getVSCodeGlobalConfigDir(homeDir: string): string | null;
|
|
9
|
+
/**
|
|
10
|
+
* Detects which editors are installed on the system
|
|
11
|
+
*/
|
|
12
|
+
declare function detectAvailableEditors(homeDir: string): Promise<Editor[]>;
|
|
13
|
+
//#endregion
|
|
14
|
+
export { detectAvailableEditors, getVSCodeGlobalConfigDir };
|
|
15
|
+
//# sourceMappingURL=editors.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"editors.d.ts","names":[],"sources":["../../src/lib/editors.ts"],"sourcesContent":[],"mappings":";;;;;;AAQA;AA8CsB,iBA9CN,wBAAA,CA8C4B,OAAA,EAAA,MAAA,CAAA,EAAA,MAAA,GAAA,IAAA;;;;AAElC,iBAFY,sBAAA,CAEZ,OAAA,EAAA,MAAA,CAAA,EAAP,OAAO,CAAC,MAAD,EAAA,CAAA"}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import { existsSync } from "node:fs";
|
|
2
|
+
import { resolve } from "node:path";
|
|
3
|
+
import { execa } from "execa";
|
|
4
|
+
|
|
5
|
+
//#region src/lib/editors.ts
|
|
6
|
+
/**
|
|
7
|
+
* Gets VS Code's global config directory based on the platform
|
|
8
|
+
*/
|
|
9
|
+
function getVSCodeGlobalConfigDir(homeDir) {
|
|
10
|
+
const platform = process.platform;
|
|
11
|
+
if (platform === "darwin") return resolve(homeDir, "Library", "Application Support", "Code", "User");
|
|
12
|
+
if (platform === "linux") return resolve(homeDir, ".config", "Code", "User");
|
|
13
|
+
if (platform === "win32") {
|
|
14
|
+
const appData = process.env.APPDATA;
|
|
15
|
+
if (appData) return resolve(appData, "Code", "User");
|
|
16
|
+
}
|
|
17
|
+
return null;
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* Checks if Claude CLI is installed
|
|
21
|
+
*/
|
|
22
|
+
async function isClaudeCLIInstalled() {
|
|
23
|
+
try {
|
|
24
|
+
await execa("claude", ["--version"], {
|
|
25
|
+
stdio: "ignore",
|
|
26
|
+
timeout: 5e3
|
|
27
|
+
});
|
|
28
|
+
return true;
|
|
29
|
+
} catch {
|
|
30
|
+
return false;
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Detects which editors are installed on the system
|
|
35
|
+
*/
|
|
36
|
+
async function detectAvailableEditors(homeDir) {
|
|
37
|
+
const editors = [];
|
|
38
|
+
if (existsSync(resolve(homeDir, ".cursor"))) editors.push("Cursor");
|
|
39
|
+
const vscodeGlobalDir = getVSCodeGlobalConfigDir(homeDir);
|
|
40
|
+
if (vscodeGlobalDir && existsSync(vscodeGlobalDir)) editors.push("VS Code");
|
|
41
|
+
if (await isClaudeCLIInstalled()) editors.push("Claude CLI");
|
|
42
|
+
return editors;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
//#endregion
|
|
46
|
+
export { detectAvailableEditors, getVSCodeGlobalConfigDir };
|
|
47
|
+
//# sourceMappingURL=editors.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"editors.js","names":["editors: Editor[]"],"sources":["../../src/lib/editors.ts"],"sourcesContent":["import { existsSync } from \"node:fs\";\nimport { resolve } from \"node:path\";\nimport { execa } from \"execa\";\nimport type { Editor } from \"./types.js\";\n\n/**\n * Gets VS Code's global config directory based on the platform\n */\nexport function getVSCodeGlobalConfigDir(homeDir: string): string | null {\n\tconst platform = process.platform;\n\n\tif (platform === \"darwin\") {\n\t\t// macOS: ~/Library/Application Support/Code/User\n\t\treturn resolve(\n\t\t\thomeDir,\n\t\t\t\"Library\",\n\t\t\t\"Application Support\",\n\t\t\t\"Code\",\n\t\t\t\"User\",\n\t\t);\n\t}\n\tif (platform === \"linux\") {\n\t\t// Linux: ~/.config/Code/User\n\t\treturn resolve(homeDir, \".config\", \"Code\", \"User\");\n\t}\n\tif (platform === \"win32\") {\n\t\t// Windows: %APPDATA%\\Code\\User\n\t\tconst appData = process.env.APPDATA;\n\t\tif (appData) {\n\t\t\treturn resolve(appData, \"Code\", \"User\");\n\t\t}\n\t}\n\n\treturn null;\n}\n\n/**\n * Checks if Claude CLI is installed\n */\nasync function isClaudeCLIInstalled(): Promise<boolean> {\n\ttry {\n\t\tawait execa(\"claude\", [\"--version\"], {\n\t\t\tstdio: \"ignore\",\n\t\t\ttimeout: 5000,\n\t\t});\n\t\treturn true;\n\t} catch {\n\t\treturn false;\n\t}\n}\n\n/**\n * Detects which editors are installed on the system\n */\nexport async function detectAvailableEditors(\n\thomeDir: string,\n): Promise<Editor[]> {\n\tconst editors: Editor[] = [];\n\n\t// Check for Cursor (global config directory)\n\tconst cursorDir = resolve(homeDir, \".cursor\");\n\tif (existsSync(cursorDir)) {\n\t\teditors.push(\"Cursor\");\n\t}\n\n\t// Check if VS Code's global config directory exists\n\tconst vscodeGlobalDir = getVSCodeGlobalConfigDir(homeDir);\n\tif (vscodeGlobalDir && existsSync(vscodeGlobalDir)) {\n\t\teditors.push(\"VS Code\");\n\t}\n\n\t// Check for Claude CLI by running the command\n\tconst claudeInstalled = await isClaudeCLIInstalled();\n\tif (claudeInstalled) {\n\t\teditors.push(\"Claude CLI\");\n\t}\n\n\treturn editors;\n}\n"],"mappings":";;;;;;;;AAQA,SAAgB,yBAAyB,SAAgC;CACxE,MAAM,WAAW,QAAQ;AAEzB,KAAI,aAAa,SAEhB,QAAO,QACN,SACA,WACA,uBACA,QACA,OACA;AAEF,KAAI,aAAa,QAEhB,QAAO,QAAQ,SAAS,WAAW,QAAQ,OAAO;AAEnD,KAAI,aAAa,SAAS;EAEzB,MAAM,UAAU,QAAQ,IAAI;AAC5B,MAAI,QACH,QAAO,QAAQ,SAAS,QAAQ,OAAO;;AAIzC,QAAO;;;;;AAMR,eAAe,uBAAyC;AACvD,KAAI;AACH,QAAM,MAAM,UAAU,CAAC,YAAY,EAAE;GACpC,OAAO;GACP,SAAS;GACT,CAAC;AACF,SAAO;SACA;AACP,SAAO;;;;;;AAOT,eAAsB,uBACrB,SACoB;CACpB,MAAMA,UAAoB,EAAE;AAI5B,KAAI,WADc,QAAQ,SAAS,UAAU,CACpB,CACxB,SAAQ,KAAK,SAAS;CAIvB,MAAM,kBAAkB,yBAAyB,QAAQ;AACzD,KAAI,mBAAmB,WAAW,gBAAgB,CACjD,SAAQ,KAAK,UAAU;AAKxB,KADwB,MAAM,sBAAsB,CAEnD,SAAQ,KAAK,aAAa;AAG3B,QAAO"}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { Editor, InstallStatus } from "./types.js";
|
|
2
|
+
|
|
3
|
+
//#region src/lib/install.d.ts
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Installs Neon's MCP Server
|
|
7
|
+
* - Cursor: Global config
|
|
8
|
+
* - VS Code: Global config (preferred) or workspace config (fallback)
|
|
9
|
+
* - Claude CLI: Global config
|
|
10
|
+
*/
|
|
11
|
+
declare function installMCPServer(homeDir: string, workspaceDir: string, selectedEditors: Editor[]): Promise<Map<Editor, InstallStatus>>;
|
|
12
|
+
//#endregion
|
|
13
|
+
export { installMCPServer };
|
|
14
|
+
//# sourceMappingURL=install.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"install.d.ts","names":[],"sources":["../../src/lib/install.ts"],"sourcesContent":[],"mappings":";;;;;;AAmFA;;;;AAIuB,iBAJD,gBAAA,CAIC,OAAA,EAAA,MAAA,EAAA,YAAA,EAAA,MAAA,EAAA,eAAA,EADL,MACK,EAAA,CAAA,EAApB,OAAoB,CAAZ,GAAY,CAAR,MAAQ,EAAA,aAAA,CAAA,CAAA"}
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import { createApiKeyFromNeonctl, ensureNeonctlAuth } from "./auth.js";
|
|
2
|
+
import { getMCPConfig, writeMCPConfig } from "./mcp-config.js";
|
|
3
|
+
import { confirm, isCancel, log, spinner } from "@clack/prompts";
|
|
4
|
+
|
|
5
|
+
//#region src/lib/install.ts
|
|
6
|
+
/**
|
|
7
|
+
* Installs Neon's MCP Server for a specific editor
|
|
8
|
+
* - Cursor: Global config
|
|
9
|
+
* - VS Code: Global config (preferred) or workspace config (fallback)
|
|
10
|
+
* - Claude CLI: Global config
|
|
11
|
+
*/
|
|
12
|
+
async function installMCPServerForEditor(homeDir, workspaceDir, editor, apiKey) {
|
|
13
|
+
const { config, configPath } = getMCPConfig(homeDir, workspaceDir, editor);
|
|
14
|
+
const serverKey = editor === "VS Code" ? config.servers : config.mcpServers;
|
|
15
|
+
if (Boolean(serverKey?.Neon)) {
|
|
16
|
+
const response = await confirm({
|
|
17
|
+
message: `Neon MCP Server is already configured for ${editor}. Would you like to reconfigure it? (Y/n)`,
|
|
18
|
+
initialValue: true
|
|
19
|
+
});
|
|
20
|
+
if (isCancel(response)) return "failed";
|
|
21
|
+
if (!response) {
|
|
22
|
+
log.info(`Keeping existing configuration for ${editor}.`);
|
|
23
|
+
return "success";
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
const neonServerConfig = {
|
|
27
|
+
type: "http",
|
|
28
|
+
url: "https://mcp.neon.tech/mcp",
|
|
29
|
+
headers: { Authorization: `Bearer ${apiKey}` }
|
|
30
|
+
};
|
|
31
|
+
if (editor === "VS Code") {
|
|
32
|
+
if (!config.servers) config.servers = {};
|
|
33
|
+
config.servers.Neon = neonServerConfig;
|
|
34
|
+
} else {
|
|
35
|
+
if (!config.mcpServers) config.mcpServers = {};
|
|
36
|
+
config.mcpServers.Neon = neonServerConfig;
|
|
37
|
+
}
|
|
38
|
+
try {
|
|
39
|
+
writeMCPConfig(configPath, config);
|
|
40
|
+
return "success";
|
|
41
|
+
} catch (error) {
|
|
42
|
+
log.error(`Failed to write configuration for ${editor}: ${error instanceof Error ? error.message : "Unknown error"}`);
|
|
43
|
+
return "failed";
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* Installs Neon's MCP Server
|
|
48
|
+
* - Cursor: Global config
|
|
49
|
+
* - VS Code: Global config (preferred) or workspace config (fallback)
|
|
50
|
+
* - Claude CLI: Global config
|
|
51
|
+
*/
|
|
52
|
+
async function installMCPServer(homeDir, workspaceDir, selectedEditors) {
|
|
53
|
+
const results = /* @__PURE__ */ new Map();
|
|
54
|
+
const authSpinner = spinner();
|
|
55
|
+
authSpinner.start("Authenticating...");
|
|
56
|
+
if (!await ensureNeonctlAuth()) {
|
|
57
|
+
authSpinner.stop("Authentication failed");
|
|
58
|
+
for (const editor of selectedEditors) results.set(editor, "failed");
|
|
59
|
+
return results;
|
|
60
|
+
}
|
|
61
|
+
authSpinner.stop("Authentication successful ✓");
|
|
62
|
+
const apiKey = await createApiKeyFromNeonctl();
|
|
63
|
+
if (!apiKey) {
|
|
64
|
+
log.error("Could not create API key after authentication.");
|
|
65
|
+
log.info("You can manually create one at: https://console.neon.tech/app/settings/api-keys");
|
|
66
|
+
for (const editor of selectedEditors) results.set(editor, "failed");
|
|
67
|
+
return results;
|
|
68
|
+
}
|
|
69
|
+
for (const editor of selectedEditors) {
|
|
70
|
+
const status = await installMCPServerForEditor(homeDir, workspaceDir, editor, apiKey);
|
|
71
|
+
results.set(editor, status);
|
|
72
|
+
}
|
|
73
|
+
return results;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
//#endregion
|
|
77
|
+
export { installMCPServer };
|
|
78
|
+
//# sourceMappingURL=install.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"install.js","names":[],"sources":["../../src/lib/install.ts"],"sourcesContent":["import { confirm, isCancel, log, spinner } from \"@clack/prompts\";\nimport { createApiKeyFromNeonctl, ensureNeonctlAuth } from \"./auth.js\";\nimport { getMCPConfig, writeMCPConfig } from \"./mcp-config.js\";\nimport type { Editor, InstallStatus } from \"./types.js\";\n\n/**\n * Installs Neon's MCP Server for a specific editor\n * - Cursor: Global config\n * - VS Code: Global config (preferred) or workspace config (fallback)\n * - Claude CLI: Global config\n */\nasync function installMCPServerForEditor(\n\thomeDir: string,\n\tworkspaceDir: string,\n\teditor: Editor,\n\tapiKey: string,\n): Promise<InstallStatus> {\n\tconst { config, configPath } = getMCPConfig(homeDir, workspaceDir, editor);\n\n\t// Check if already configured\n\tconst serverKey = editor === \"VS Code\" ? config.servers : config.mcpServers;\n\tconst alreadyConfigured = Boolean(serverKey?.Neon);\n\n\tif (alreadyConfigured) {\n\t\tconst response = await confirm({\n\t\t\tmessage: `Neon MCP Server is already configured for ${editor}. Would you like to reconfigure it? (Y/n)`,\n\t\t\tinitialValue: true,\n\t\t});\n\n\t\tif (isCancel(response)) {\n\t\t\treturn \"failed\";\n\t\t}\n\n\t\tconst shouldReconfigure = response as boolean;\n\n\t\tif (!shouldReconfigure) {\n\t\t\tlog.info(`Keeping existing configuration for ${editor}.`);\n\t\t\treturn \"success\";\n\t\t}\n\t}\n\n\t// Configure Neon MCP Server\n\t// Using remote MCP server with API key authentication\n\t// Ref: https://neon.com/docs/ai/neon-mcp-server#api-key-based-authentication\n\tconst neonServerConfig = {\n\t\ttype: \"http\",\n\t\turl: \"https://mcp.neon.tech/mcp\",\n\t\theaders: {\n\t\t\tAuthorization: `Bearer ${apiKey}`,\n\t\t},\n\t};\n\n\t// VS Code uses \"servers\" key, Cursor and Claude CLI use \"mcpServers\" key\n\tif (editor === \"VS Code\") {\n\t\tif (!config.servers) {\n\t\t\tconfig.servers = {};\n\t\t}\n\t\tconfig.servers.Neon = neonServerConfig;\n\t} else {\n\t\tif (!config.mcpServers) {\n\t\t\tconfig.mcpServers = {};\n\t\t}\n\t\tconfig.mcpServers.Neon = neonServerConfig;\n\t}\n\n\t// Write configuration\n\ttry {\n\t\twriteMCPConfig(configPath, config);\n\t\treturn \"success\";\n\t} catch (error) {\n\t\tlog.error(\n\t\t\t`Failed to write configuration for ${editor}: ${error instanceof Error ? error.message : \"Unknown error\"}`,\n\t\t);\n\t\treturn \"failed\";\n\t}\n}\n\n/**\n * Installs Neon's MCP Server\n * - Cursor: Global config\n * - VS Code: Global config (preferred) or workspace config (fallback)\n * - Claude CLI: Global config\n */\nexport async function installMCPServer(\n\thomeDir: string,\n\tworkspaceDir: string,\n\tselectedEditors: Editor[],\n): Promise<Map<Editor, InstallStatus>> {\n\tconst results = new Map<Editor, InstallStatus>();\n\n\t// Ensure authentication (will trigger OAuth if needed)\n\tconst authSpinner = spinner();\n\tauthSpinner.start(\"Authenticating...\");\n\n\tconst authSuccess = await ensureNeonctlAuth();\n\n\tif (!authSuccess) {\n\t\tauthSpinner.stop(\"Authentication failed\");\n\t\t// Mark all editors as failed due to auth failure\n\t\tfor (const editor of selectedEditors) {\n\t\t\tresults.set(editor, \"failed\");\n\t\t}\n\t\treturn results;\n\t}\n\n\tauthSpinner.stop(\"Authentication successful ✓\");\n\n\t// Create API key using the OAuth token\n\tconst apiKey = await createApiKeyFromNeonctl();\n\n\tif (!apiKey) {\n\t\tlog.error(\"Could not create API key after authentication.\");\n\t\tlog.info(\n\t\t\t\"You can manually create one at: https://console.neon.tech/app/settings/api-keys\",\n\t\t);\n\t\t// Mark all editors as failed due to API key creation failure\n\t\tfor (const editor of selectedEditors) {\n\t\t\tresults.set(editor, \"failed\");\n\t\t}\n\t\treturn results;\n\t}\n\n\t// Install MCP server for each selected editor\n\tfor (const editor of selectedEditors) {\n\t\tconst status = await installMCPServerForEditor(\n\t\t\thomeDir,\n\t\t\tworkspaceDir,\n\t\t\teditor,\n\t\t\tapiKey,\n\t\t);\n\t\tresults.set(editor, status);\n\t}\n\n\treturn results;\n}\n"],"mappings":";;;;;;;;;;;AAWA,eAAe,0BACd,SACA,cACA,QACA,QACyB;CACzB,MAAM,EAAE,QAAQ,eAAe,aAAa,SAAS,cAAc,OAAO;CAG1E,MAAM,YAAY,WAAW,YAAY,OAAO,UAAU,OAAO;AAGjE,KAF0B,QAAQ,WAAW,KAAK,EAE3B;EACtB,MAAM,WAAW,MAAM,QAAQ;GAC9B,SAAS,6CAA6C,OAAO;GAC7D,cAAc;GACd,CAAC;AAEF,MAAI,SAAS,SAAS,CACrB,QAAO;AAKR,MAAI,CAFsB,UAEF;AACvB,OAAI,KAAK,sCAAsC,OAAO,GAAG;AACzD,UAAO;;;CAOT,MAAM,mBAAmB;EACxB,MAAM;EACN,KAAK;EACL,SAAS,EACR,eAAe,UAAU,UACzB;EACD;AAGD,KAAI,WAAW,WAAW;AACzB,MAAI,CAAC,OAAO,QACX,QAAO,UAAU,EAAE;AAEpB,SAAO,QAAQ,OAAO;QAChB;AACN,MAAI,CAAC,OAAO,WACX,QAAO,aAAa,EAAE;AAEvB,SAAO,WAAW,OAAO;;AAI1B,KAAI;AACH,iBAAe,YAAY,OAAO;AAClC,SAAO;UACC,OAAO;AACf,MAAI,MACH,qCAAqC,OAAO,IAAI,iBAAiB,QAAQ,MAAM,UAAU,kBACzF;AACD,SAAO;;;;;;;;;AAUT,eAAsB,iBACrB,SACA,cACA,iBACsC;CACtC,MAAM,0BAAU,IAAI,KAA4B;CAGhD,MAAM,cAAc,SAAS;AAC7B,aAAY,MAAM,oBAAoB;AAItC,KAAI,CAFgB,MAAM,mBAAmB,EAE3B;AACjB,cAAY,KAAK,wBAAwB;AAEzC,OAAK,MAAM,UAAU,gBACpB,SAAQ,IAAI,QAAQ,SAAS;AAE9B,SAAO;;AAGR,aAAY,KAAK,8BAA8B;CAG/C,MAAM,SAAS,MAAM,yBAAyB;AAE9C,KAAI,CAAC,QAAQ;AACZ,MAAI,MAAM,iDAAiD;AAC3D,MAAI,KACH,kFACA;AAED,OAAK,MAAM,UAAU,gBACpB,SAAQ,IAAI,QAAQ,SAAS;AAE9B,SAAO;;AAIR,MAAK,MAAM,UAAU,iBAAiB;EACrC,MAAM,SAAS,MAAM,0BACpB,SACA,cACA,QACA,OACA;AACD,UAAQ,IAAI,QAAQ,OAAO;;AAG5B,QAAO"}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { Editor, MCPConfig } from "./types.js";
|
|
2
|
+
|
|
3
|
+
//#region src/lib/mcp-config.d.ts
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Gets or creates the MCP configuration for a specific editor
|
|
7
|
+
* - Cursor: Global config at ~/.cursor/mcp.json
|
|
8
|
+
* - VS Code: Try global config first, then workspace
|
|
9
|
+
* - Claude CLI: Global config at ~/.claude.json
|
|
10
|
+
*/
|
|
11
|
+
declare function getMCPConfig(homeDir: string, workspaceDir: string, editor: Editor): {
|
|
12
|
+
config: MCPConfig;
|
|
13
|
+
configPath: string;
|
|
14
|
+
};
|
|
15
|
+
/**
|
|
16
|
+
* Writes the MCP configuration to the appropriate location
|
|
17
|
+
* - Cursor: Global config at ~/.cursor/mcp.json
|
|
18
|
+
* - VS Code: Global config (preferred) or workspace config (fallback)
|
|
19
|
+
* - Claude CLI: Global config at ~/.claude.json
|
|
20
|
+
*/
|
|
21
|
+
declare function writeMCPConfig(configPath: string, config: MCPConfig): void;
|
|
22
|
+
//#endregion
|
|
23
|
+
export { getMCPConfig, writeMCPConfig };
|
|
24
|
+
//# sourceMappingURL=mcp-config.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"mcp-config.d.ts","names":[],"sources":["../../src/lib/mcp-config.ts"],"sourcesContent":[],"mappings":";;;;;;AAWA;;;;AAIsB,iBAJN,YAAA,CAIM,OAAA,EAAA,MAAA,EAAA,YAAA,EAAA,MAAA,EAAA,MAAA,EADb,MACa,CAAA,EAAA;EAgDN,MAAA,EAhDH,SAgDiB;;;;;;;;;iBAAd,cAAA,6BAA2C"}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import { getVSCodeGlobalConfigDir } from "./editors.js";
|
|
2
|
+
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
3
|
+
import { dirname, resolve } from "node:path";
|
|
4
|
+
|
|
5
|
+
//#region src/lib/mcp-config.ts
|
|
6
|
+
/**
|
|
7
|
+
* Gets or creates the MCP configuration for a specific editor
|
|
8
|
+
* - Cursor: Global config at ~/.cursor/mcp.json
|
|
9
|
+
* - VS Code: Try global config first, then workspace
|
|
10
|
+
* - Claude CLI: Global config at ~/.claude.json
|
|
11
|
+
*/
|
|
12
|
+
function getMCPConfig(homeDir, workspaceDir, editor) {
|
|
13
|
+
let mcpConfigPath;
|
|
14
|
+
if (editor === "VS Code") {
|
|
15
|
+
const vscodeGlobalDir = getVSCodeGlobalConfigDir(homeDir);
|
|
16
|
+
if (vscodeGlobalDir && existsSync(vscodeGlobalDir)) mcpConfigPath = resolve(vscodeGlobalDir, "mcp.json");
|
|
17
|
+
else mcpConfigPath = resolve(workspaceDir, ".vscode", "mcp.json");
|
|
18
|
+
} else if (editor === "Claude CLI") mcpConfigPath = resolve(homeDir, ".claude.json");
|
|
19
|
+
else mcpConfigPath = resolve(homeDir, ".cursor", "mcp.json");
|
|
20
|
+
if (existsSync(mcpConfigPath)) try {
|
|
21
|
+
const content = readFileSync(mcpConfigPath, "utf-8");
|
|
22
|
+
return {
|
|
23
|
+
config: JSON.parse(content),
|
|
24
|
+
configPath: mcpConfigPath
|
|
25
|
+
};
|
|
26
|
+
} catch (_error) {
|
|
27
|
+
return {
|
|
28
|
+
config: editor === "VS Code" ? { servers: {} } : { mcpServers: {} },
|
|
29
|
+
configPath: mcpConfigPath
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
return {
|
|
33
|
+
config: editor === "VS Code" ? { servers: {} } : { mcpServers: {} },
|
|
34
|
+
configPath: mcpConfigPath
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* Writes the MCP configuration to the appropriate location
|
|
39
|
+
* - Cursor: Global config at ~/.cursor/mcp.json
|
|
40
|
+
* - VS Code: Global config (preferred) or workspace config (fallback)
|
|
41
|
+
* - Claude CLI: Global config at ~/.claude.json
|
|
42
|
+
*/
|
|
43
|
+
function writeMCPConfig(configPath, config) {
|
|
44
|
+
const editorDir = dirname(configPath);
|
|
45
|
+
if (!existsSync(editorDir)) mkdirSync(editorDir, { recursive: true });
|
|
46
|
+
writeFileSync(configPath, JSON.stringify(config, null, 2), "utf-8");
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
//#endregion
|
|
50
|
+
export { getMCPConfig, writeMCPConfig };
|
|
51
|
+
//# sourceMappingURL=mcp-config.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"mcp-config.js","names":["mcpConfigPath: string"],"sources":["../../src/lib/mcp-config.ts"],"sourcesContent":["import { existsSync, mkdirSync, readFileSync, writeFileSync } from \"node:fs\";\nimport { dirname, resolve } from \"node:path\";\nimport { getVSCodeGlobalConfigDir } from \"./editors.js\";\nimport type { Editor, MCPConfig } from \"./types.js\";\n\n/**\n * Gets or creates the MCP configuration for a specific editor\n * - Cursor: Global config at ~/.cursor/mcp.json\n * - VS Code: Try global config first, then workspace\n * - Claude CLI: Global config at ~/.claude.json\n */\nexport function getMCPConfig(\n\thomeDir: string,\n\tworkspaceDir: string,\n\teditor: Editor,\n): { config: MCPConfig; configPath: string } {\n\tlet mcpConfigPath: string;\n\n\tif (editor === \"VS Code\") {\n\t\t// Try global config first\n\t\tconst vscodeGlobalDir = getVSCodeGlobalConfigDir(homeDir);\n\t\tif (vscodeGlobalDir && existsSync(vscodeGlobalDir)) {\n\t\t\tmcpConfigPath = resolve(vscodeGlobalDir, \"mcp.json\");\n\t\t} else {\n\t\t\t// Fall back to workspace\n\t\t\tmcpConfigPath = resolve(workspaceDir, \".vscode\", \"mcp.json\");\n\t\t}\n\t} else if (editor === \"Claude CLI\") {\n\t\t// Claude CLI uses ~/.claude.json\n\t\tmcpConfigPath = resolve(homeDir, \".claude.json\");\n\t} else {\n\t\t// Cursor uses ~/.cursor/mcp.json\n\t\tmcpConfigPath = resolve(homeDir, \".cursor\", \"mcp.json\");\n\t}\n\n\tif (existsSync(mcpConfigPath)) {\n\t\ttry {\n\t\t\tconst content = readFileSync(mcpConfigPath, \"utf-8\");\n\t\t\treturn {\n\t\t\t\tconfig: JSON.parse(content),\n\t\t\t\tconfigPath: mcpConfigPath,\n\t\t\t};\n\t\t} catch (_error) {\n\t\t\treturn {\n\t\t\t\tconfig:\n\t\t\t\t\teditor === \"VS Code\" ? { servers: {} } : { mcpServers: {} },\n\t\t\t\tconfigPath: mcpConfigPath,\n\t\t\t};\n\t\t}\n\t}\n\n\treturn {\n\t\tconfig: editor === \"VS Code\" ? { servers: {} } : { mcpServers: {} },\n\t\tconfigPath: mcpConfigPath,\n\t};\n}\n\n/**\n * Writes the MCP configuration to the appropriate location\n * - Cursor: Global config at ~/.cursor/mcp.json\n * - VS Code: Global config (preferred) or workspace config (fallback)\n * - Claude CLI: Global config at ~/.claude.json\n */\nexport function writeMCPConfig(configPath: string, config: MCPConfig): void {\n\tconst editorDir = dirname(configPath);\n\n\tif (!existsSync(editorDir)) {\n\t\tmkdirSync(editorDir, { recursive: true });\n\t}\n\n\twriteFileSync(configPath, JSON.stringify(config, null, 2), \"utf-8\");\n}\n"],"mappings":";;;;;;;;;;;AAWA,SAAgB,aACf,SACA,cACA,QAC4C;CAC5C,IAAIA;AAEJ,KAAI,WAAW,WAAW;EAEzB,MAAM,kBAAkB,yBAAyB,QAAQ;AACzD,MAAI,mBAAmB,WAAW,gBAAgB,CACjD,iBAAgB,QAAQ,iBAAiB,WAAW;MAGpD,iBAAgB,QAAQ,cAAc,WAAW,WAAW;YAEnD,WAAW,aAErB,iBAAgB,QAAQ,SAAS,eAAe;KAGhD,iBAAgB,QAAQ,SAAS,WAAW,WAAW;AAGxD,KAAI,WAAW,cAAc,CAC5B,KAAI;EACH,MAAM,UAAU,aAAa,eAAe,QAAQ;AACpD,SAAO;GACN,QAAQ,KAAK,MAAM,QAAQ;GAC3B,YAAY;GACZ;UACO,QAAQ;AAChB,SAAO;GACN,QACC,WAAW,YAAY,EAAE,SAAS,EAAE,EAAE,GAAG,EAAE,YAAY,EAAE,EAAE;GAC5D,YAAY;GACZ;;AAIH,QAAO;EACN,QAAQ,WAAW,YAAY,EAAE,SAAS,EAAE,EAAE,GAAG,EAAE,YAAY,EAAE,EAAE;EACnE,YAAY;EACZ;;;;;;;;AASF,SAAgB,eAAe,YAAoB,QAAyB;CAC3E,MAAM,YAAY,QAAQ,WAAW;AAErC,KAAI,CAAC,WAAW,UAAU,CACzB,WAAU,WAAW,EAAE,WAAW,MAAM,CAAC;AAG1C,eAAc,YAAY,KAAK,UAAU,QAAQ,MAAM,EAAE,EAAE,QAAQ"}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
//#region src/lib/types.d.ts
|
|
2
|
+
type Editor = "Cursor" | "VS Code" | "Claude CLI";
|
|
3
|
+
type InstallStatus = "success" | "failed";
|
|
4
|
+
interface MCPConfig {
|
|
5
|
+
mcpServers?: {
|
|
6
|
+
[key: string]: {
|
|
7
|
+
type?: string;
|
|
8
|
+
url: string;
|
|
9
|
+
headers?: Record<string, string>;
|
|
10
|
+
};
|
|
11
|
+
};
|
|
12
|
+
servers?: {
|
|
13
|
+
[key: string]: {
|
|
14
|
+
type?: string;
|
|
15
|
+
url: string;
|
|
16
|
+
headers?: Record<string, string>;
|
|
17
|
+
};
|
|
18
|
+
};
|
|
19
|
+
}
|
|
20
|
+
//#endregion
|
|
21
|
+
export { Editor, InstallStatus, MCPConfig };
|
|
22
|
+
//# sourceMappingURL=types.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.d.ts","names":[],"sources":["../../src/lib/types.ts"],"sourcesContent":[],"mappings":";KAAY,MAAA;AAAA,KAEA,aAAA,GAFM,SAAA,GAAA,QAAA;AAEN,UAEK,SAAA,CAFQ;EAER,UAAA,CAAA,EAAS;IAAA,CAAA,GAAA,EAAA,MAAA,CAAA,EAAA;MAKb,IAAA,CAAA,EAAA,MAAA;MAOA,GAAA,EAAA,MAAA;MAAM,OAAA,CAAA,EAPN,MAOM,CAAA,MAAA,EAAA,MAAA,CAAA;;;;;;;gBAAN"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { };
|