neon-init 0.8.1 → 0.9.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 +5 -13
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +17 -141
- package/dist/index.js.map +1 -1
- package/package.json +2 -3
- package/src/agents-template.md +0 -228
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# neon-init
|
|
2
2
|
|
|
3
|
-
Set up your project with Neon's MCP Server
|
|
3
|
+
Set up your project with Neon's MCP Server for AI-powered database operations.
|
|
4
4
|
|
|
5
5
|
## Installation
|
|
6
6
|
|
|
@@ -19,30 +19,22 @@ await init();
|
|
|
19
19
|
Then:
|
|
20
20
|
|
|
21
21
|
1. Restart your AI coding assistant (Cursor, Windsurf, etc.)
|
|
22
|
-
2. Type **"Get started with Neon"** in your AI chat
|
|
22
|
+
2. Type **"Get started with Neon using MCP Resource"** in your AI chat
|
|
23
23
|
|
|
24
24
|
## What It Does
|
|
25
25
|
|
|
26
|
-
###
|
|
26
|
+
### Configures Neon MCP Server
|
|
27
27
|
|
|
28
28
|
Creates `~/.cursor/mcp.json` (global config) to enable AI-powered database operations through the Model Context Protocol.
|
|
29
29
|
|
|
30
30
|
**Authentication:** Uses OAuth via `neonctl` and creates an API key for you - opens your browser, no manual API keys needed.
|
|
31
31
|
|
|
32
|
-
|
|
32
|
+
**Agent Guidelines:** The Neon MCP Server includes built-in agent guidelines as an MCP resource. Your AI assistant will automatically have access to:
|
|
33
33
|
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
- Interactive "Get started with Neon" onboarding flow for AI assistants
|
|
34
|
+
- Interactive "Get started with Neon" onboarding flow
|
|
37
35
|
- Security, performance, and schema management best practices
|
|
38
36
|
- Neon-specific features (branching, autoscaling, scale-to-zero)
|
|
39
37
|
|
|
40
|
-
Works with all AI tools that support AGENTS.md: Cursor, Windsurf, Claude Code, etc.
|
|
41
|
-
|
|
42
|
-
### 3. Organization Selection
|
|
43
|
-
|
|
44
|
-
If you have multiple Neon organizations, you'll choose which one to use. The org ID is saved in AGENTS.md so AI assistants know which organization context to use.
|
|
45
|
-
|
|
46
38
|
## Development
|
|
47
39
|
|
|
48
40
|
| Command | Description |
|
package/dist/index.d.ts
CHANGED
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":";;AA+OA;;iBAAsB,IAAA,CAAA,GAAQ"}
|
package/dist/index.js
CHANGED
|
@@ -1,15 +1,12 @@
|
|
|
1
|
-
import { exec } from "node:child_process";
|
|
2
1
|
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
3
2
|
import { dirname, resolve } from "node:path";
|
|
4
3
|
import { fileURLToPath } from "node:url";
|
|
5
|
-
import {
|
|
6
|
-
import { confirm, intro, isCancel, log, note, outro, select, spinner } from "@clack/prompts";
|
|
4
|
+
import { confirm, intro, isCancel, log, note, outro, spinner } from "@clack/prompts";
|
|
7
5
|
import { execa } from "execa";
|
|
8
6
|
import { bold, cyan } from "yoctocolors";
|
|
9
7
|
|
|
10
8
|
//#region src/index.ts
|
|
11
|
-
|
|
12
|
-
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
9
|
+
dirname(fileURLToPath(import.meta.url));
|
|
13
10
|
/**
|
|
14
11
|
* Ensures neonctl is authenticated by running a command that triggers auth if needed
|
|
15
12
|
* This will automatically start the OAuth flow if the user isn't already authenticated
|
|
@@ -72,19 +69,6 @@ async function createApiKeyFromNeonctl() {
|
|
|
72
69
|
return null;
|
|
73
70
|
}
|
|
74
71
|
}
|
|
75
|
-
async function fetchOrganizations() {
|
|
76
|
-
try {
|
|
77
|
-
const { stdout } = await execAsync("npx -y neonctl orgs list --output json --no-analytics", { maxBuffer: 1024 * 1024 });
|
|
78
|
-
const data = JSON.parse(stdout);
|
|
79
|
-
return Array.isArray(data) ? data.map((org) => ({
|
|
80
|
-
id: org.id,
|
|
81
|
-
name: org.name || org.id
|
|
82
|
-
})) : [];
|
|
83
|
-
} catch (error) {
|
|
84
|
-
log.warn(`Unable to fetch organizations: ${error instanceof Error ? error.message : "Unknown error"}`);
|
|
85
|
-
return [];
|
|
86
|
-
}
|
|
87
|
-
}
|
|
88
72
|
/**
|
|
89
73
|
* Gets or creates the .cursor/mcp.json configuration
|
|
90
74
|
*/
|
|
@@ -108,98 +92,13 @@ function writeMCPConfig(cursorDir, config) {
|
|
|
108
92
|
writeFileSync(mcpConfigPath, JSON.stringify(config, null, 2), "utf-8");
|
|
109
93
|
}
|
|
110
94
|
/**
|
|
111
|
-
* Gets the neon.md template with Neon-specific best practices
|
|
112
|
-
*/
|
|
113
|
-
function getNeonMdTemplate() {
|
|
114
|
-
return readFileSync(resolve(__dirname, "../src/agents-template.md"), "utf-8");
|
|
115
|
-
}
|
|
116
|
-
/**
|
|
117
|
-
* Generates the organization configuration section for neon.md
|
|
118
|
-
*/
|
|
119
|
-
function getOrgConfigSection(orgId) {
|
|
120
|
-
return `## Neon Project Configuration
|
|
121
|
-
|
|
122
|
-
> **🔴 IMPORTANT: You MUST use this organization for all Neon operations in this project.**
|
|
123
|
-
|
|
124
|
-
**Organization ID:** \`${orgId}\`
|
|
125
|
-
|
|
126
|
-
When using any Neon MCP tools or API calls, always pass this \`org_id\` parameter. The MCP server is pre-configured with this organization.
|
|
127
|
-
|
|
128
|
-
**Example:**
|
|
129
|
-
- When listing projects: Use \`mcp_Neon_list_projects\` with \`org_id: "${orgId}"\`
|
|
130
|
-
- When creating resources: Always include \`org_id: "${orgId}"\` in your tool calls
|
|
131
|
-
|
|
132
|
-
---
|
|
133
|
-
|
|
134
|
-
`;
|
|
135
|
-
}
|
|
136
|
-
/**
|
|
137
|
-
* Gets the reference text to add to AGENTS.md
|
|
138
|
-
*/
|
|
139
|
-
function getAgentsNeonReference() {
|
|
140
|
-
return `## Working with Neon Database
|
|
141
|
-
|
|
142
|
-
When the user asks to **"Get started with Neon"** or something similar, refer to the detailed guidelines in \`.neon/AGENTS.md\`.`;
|
|
143
|
-
}
|
|
144
|
-
/**
|
|
145
|
-
* Creates or updates .neon/AGENTS.md with detailed Neon guidelines
|
|
146
|
-
*/
|
|
147
|
-
async function createNeonMd(orgId) {
|
|
148
|
-
const neonDir = resolve(process.cwd(), ".neon");
|
|
149
|
-
const neonAgentsPath = resolve(neonDir, "AGENTS.md");
|
|
150
|
-
try {
|
|
151
|
-
if (!existsSync(neonDir)) mkdirSync(neonDir, { recursive: true });
|
|
152
|
-
if (existsSync(neonAgentsPath)) {
|
|
153
|
-
const response = await confirm({
|
|
154
|
-
message: "Replace existing .neon/AGENTS.md with updated guidelines? (suggested)",
|
|
155
|
-
initialValue: true
|
|
156
|
-
});
|
|
157
|
-
if (isCancel(response)) return false;
|
|
158
|
-
if (!response) return true;
|
|
159
|
-
}
|
|
160
|
-
let content = "";
|
|
161
|
-
if (orgId) content += getOrgConfigSection(orgId);
|
|
162
|
-
content += getNeonMdTemplate();
|
|
163
|
-
writeFileSync(neonAgentsPath, content, "utf-8");
|
|
164
|
-
return true;
|
|
165
|
-
} catch (error) {
|
|
166
|
-
log.error(`Failed to create .neon/AGENTS.md: ${error instanceof Error ? error.message : "Unknown error"}`);
|
|
167
|
-
return false;
|
|
168
|
-
}
|
|
169
|
-
}
|
|
170
|
-
/**
|
|
171
|
-
* Creates or updates AGENTS.md with a reference to .neon/AGENTS.md
|
|
172
|
-
*/
|
|
173
|
-
async function createAgentsMd() {
|
|
174
|
-
const agentsPath = resolve(process.cwd(), "AGENTS.md");
|
|
175
|
-
try {
|
|
176
|
-
const neonReference = getAgentsNeonReference();
|
|
177
|
-
if (existsSync(agentsPath)) {
|
|
178
|
-
const existingContent = readFileSync(agentsPath, "utf-8");
|
|
179
|
-
if (existingContent.includes("## Working with Neon Database")) return true;
|
|
180
|
-
writeFileSync(agentsPath, existingContent + "\n\n---\n\n" + neonReference, "utf-8");
|
|
181
|
-
} else writeFileSync(agentsPath, `# AGENTS.md
|
|
182
|
-
|
|
183
|
-
This file provides guidance to AI coding assistants when working with code in this project.
|
|
184
|
-
|
|
185
|
-
---
|
|
186
|
-
|
|
187
|
-
${neonReference}`, "utf-8");
|
|
188
|
-
return true;
|
|
189
|
-
} catch (error) {
|
|
190
|
-
log.error(`Failed to create/update AGENTS.md: ${error instanceof Error ? error.message : "Unknown error"}`);
|
|
191
|
-
return false;
|
|
192
|
-
}
|
|
193
|
-
}
|
|
194
|
-
/**
|
|
195
95
|
* Installs Neon's MCP Server by configuring it globally in ~/.cursor/mcp.json
|
|
196
|
-
* Returns the selected organization ID if one was chosen
|
|
197
96
|
*/
|
|
198
97
|
async function installMCPServer() {
|
|
199
98
|
const homeDir = process.env.HOME || process.env.USERPROFILE;
|
|
200
99
|
if (!homeDir) {
|
|
201
100
|
log.error("Could not determine home directory");
|
|
202
|
-
return
|
|
101
|
+
return false;
|
|
203
102
|
}
|
|
204
103
|
const cursorDir = resolve(homeDir, ".cursor");
|
|
205
104
|
const config = getMCPConfig(cursorDir);
|
|
@@ -210,42 +109,25 @@ async function installMCPServer() {
|
|
|
210
109
|
message: "Neon MCP Server is already configured. Would you like to reconfigure it? (Y/n)",
|
|
211
110
|
initialValue: true
|
|
212
111
|
});
|
|
213
|
-
if (isCancel(response)) return
|
|
112
|
+
if (isCancel(response)) return false;
|
|
214
113
|
shouldReconfigure = response;
|
|
215
|
-
if (!shouldReconfigure)
|
|
114
|
+
if (!shouldReconfigure) {
|
|
115
|
+
log.info("Keeping existing global configuration.");
|
|
116
|
+
return true;
|
|
117
|
+
}
|
|
216
118
|
}
|
|
217
119
|
const authSpinner = spinner();
|
|
218
120
|
authSpinner.start("Authenticating...");
|
|
219
121
|
if (!await ensureNeonctlAuth()) {
|
|
220
122
|
authSpinner.stop("Authentication failed");
|
|
221
|
-
return
|
|
123
|
+
return false;
|
|
222
124
|
}
|
|
223
125
|
authSpinner.stop("Authentication successful ✓");
|
|
224
|
-
let selectedOrgId;
|
|
225
|
-
const organizations = await fetchOrganizations();
|
|
226
|
-
if (organizations.length > 1) {
|
|
227
|
-
const orgChoice = await select({
|
|
228
|
-
message: "Select an organization for this project:",
|
|
229
|
-
options: organizations.map((org) => ({
|
|
230
|
-
value: org.id,
|
|
231
|
-
label: org.name
|
|
232
|
-
}))
|
|
233
|
-
});
|
|
234
|
-
if (isCancel(orgChoice)) return { success: false };
|
|
235
|
-
selectedOrgId = orgChoice.toString();
|
|
236
|
-
} else if (organizations.length === 1) {
|
|
237
|
-
selectedOrgId = organizations[0].id;
|
|
238
|
-
log.info(`Using organization: ${organizations[0].name}`);
|
|
239
|
-
} else log.info("Using personal account");
|
|
240
|
-
if (alreadyConfigured && !shouldReconfigure) return {
|
|
241
|
-
success: true,
|
|
242
|
-
orgId: selectedOrgId
|
|
243
|
-
};
|
|
244
126
|
const apiKey = await createApiKeyFromNeonctl();
|
|
245
127
|
if (!apiKey) {
|
|
246
128
|
log.error("Could not create API key after authentication.");
|
|
247
129
|
log.info("You can manually create one at: https://console.neon.tech/app/settings/api-keys");
|
|
248
|
-
return
|
|
130
|
+
return false;
|
|
249
131
|
}
|
|
250
132
|
config.mcpServers.Neon = {
|
|
251
133
|
url: "https://mcp.neon.tech/mcp",
|
|
@@ -253,17 +135,14 @@ async function installMCPServer() {
|
|
|
253
135
|
};
|
|
254
136
|
try {
|
|
255
137
|
writeMCPConfig(cursorDir, config);
|
|
256
|
-
return
|
|
257
|
-
success: true,
|
|
258
|
-
orgId: selectedOrgId
|
|
259
|
-
};
|
|
138
|
+
return true;
|
|
260
139
|
} catch (error) {
|
|
261
140
|
log.error(`Failed to write global configuration: ${error instanceof Error ? error.message : "Unknown error"}`);
|
|
262
|
-
return
|
|
141
|
+
return false;
|
|
263
142
|
}
|
|
264
143
|
}
|
|
265
144
|
/**
|
|
266
|
-
* Initialize Neon projects with MCP Server
|
|
145
|
+
* Initialize Neon projects with MCP Server
|
|
267
146
|
*/
|
|
268
147
|
async function init() {
|
|
269
148
|
intro("Adding Neon to your project");
|
|
@@ -278,16 +157,13 @@ async function init() {
|
|
|
278
157
|
outro("📣 Is this unexpected? Email us at feedback@neon.tech");
|
|
279
158
|
process.exit(1);
|
|
280
159
|
}
|
|
281
|
-
|
|
282
|
-
if (!mcpSuccess) {
|
|
160
|
+
if (!await installMCPServer()) {
|
|
283
161
|
outro("Initialization cancelled or failed. Please check the output above and try again.");
|
|
284
162
|
process.exit(1);
|
|
285
|
-
}
|
|
286
|
-
|
|
287
|
-
if (!await createAgentsMd()) log.warn("Failed to create AGENTS.md, but MCP Server is configured.");
|
|
288
|
-
else log.step("Added Neon instructions to AGENTS.md");
|
|
163
|
+
}
|
|
164
|
+
log.step("Installed Neon MCP server");
|
|
289
165
|
log.step("Success! Neon is now ready to use with Cursor.\n");
|
|
290
|
-
note(`\x1b[
|
|
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?");
|
|
291
167
|
outro("Have feedback? Email us at feedback@neon.tech");
|
|
292
168
|
}
|
|
293
169
|
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","names":["selectedOrgId: string | undefined"],"sources":["../src/index.ts"],"sourcesContent":["import { exec } from \"node:child_process\";\nimport { existsSync, mkdirSync, readFileSync, writeFileSync } from \"node:fs\";\nimport { dirname, resolve } from \"node:path\";\nimport { fileURLToPath } from \"node:url\";\nimport { promisify } from \"node:util\";\nimport {\n\tconfirm,\n\tintro,\n\tisCancel,\n\tlog,\n\tnote,\n\toutro,\n\tselect,\n\tspinner,\n} from \"@clack/prompts\";\nimport { execa } from \"execa\";\nimport { bold, cyan } from \"yoctocolors\";\n\nconst execAsync = promisify(exec);\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\ninterface NeonOrganization {\n\tid: string;\n\tname: string;\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\nasync function fetchOrganizations(): Promise<NeonOrganization[]> {\n\ttry {\n\t\tconst { stdout } = await execAsync(\n\t\t\t\"npx -y neonctl orgs list --output json --no-analytics\",\n\t\t\t{ maxBuffer: 1024 * 1024 },\n\t\t);\n\n\t\tconst data = JSON.parse(stdout);\n\n\t\t// The neon CLI returns an array of organizations\n\t\tconst organizations: NeonOrganization[] = Array.isArray(data)\n\t\t\t? data.map((org: { id: string; name?: string }) => ({\n\t\t\t\t\tid: org.id,\n\t\t\t\t\tname: org.name || org.id,\n\t\t\t\t}))\n\t\t\t: [];\n\n\t\treturn organizations;\n\t} catch (error) {\n\t\tlog.warn(\n\t\t\t`Unable to fetch organizations: ${error instanceof Error ? error.message : \"Unknown error\"}`,\n\t\t);\n\t\treturn [];\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 * Gets the neon.md template with Neon-specific best practices\n */\nfunction getNeonMdTemplate(): string {\n\tconst templatePath = resolve(__dirname, \"../src/agents-template.md\");\n\treturn readFileSync(templatePath, \"utf-8\");\n}\n\n/**\n * Generates the organization configuration section for neon.md\n */\nfunction getOrgConfigSection(orgId: string): string {\n\treturn `## Neon Project Configuration\n\n> **🔴 IMPORTANT: You MUST use this organization for all Neon operations in this project.**\n\n**Organization ID:** \\`${orgId}\\`\n\nWhen using any Neon MCP tools or API calls, always pass this \\`org_id\\` parameter. The MCP server is pre-configured with this organization.\n\n**Example:**\n- When listing projects: Use \\`mcp_Neon_list_projects\\` with \\`org_id: \"${orgId}\"\\`\n- When creating resources: Always include \\`org_id: \"${orgId}\"\\` in your tool calls\n\n---\n\n`;\n}\n\n/**\n * Gets the reference text to add to AGENTS.md\n */\nfunction getAgentsNeonReference(): string {\n\treturn `## Working with Neon Database\n\nWhen the user asks to **\"Get started with Neon\"** or something similar, refer to the detailed guidelines in \\`.neon/AGENTS.md\\`.`;\n}\n\n/**\n * Creates or updates .neon/AGENTS.md with detailed Neon guidelines\n */\nasync function createNeonMd(orgId?: string): Promise<boolean> {\n\tconst neonDir = resolve(process.cwd(), \".neon\");\n\tconst neonAgentsPath = resolve(neonDir, \"AGENTS.md\");\n\n\ttry {\n\t\t// Create .neon directory if it doesn't exist\n\t\tif (!existsSync(neonDir)) {\n\t\t\tmkdirSync(neonDir, { recursive: true });\n\t\t}\n\n\t\t// Check if .neon/AGENTS.md already exists\n\t\tif (existsSync(neonAgentsPath)) {\n\t\t\tconst response = await confirm({\n\t\t\t\tmessage:\n\t\t\t\t\t\"Replace existing .neon/AGENTS.md with updated guidelines? (suggested)\",\n\t\t\t\tinitialValue: true,\n\t\t\t});\n\n\t\t\tif (isCancel(response)) {\n\t\t\t\treturn false;\n\t\t\t}\n\n\t\t\tif (!response) {\n\t\t\t\treturn true;\n\t\t\t}\n\t\t}\n\n\t\tlet content = \"\";\n\n\t\t// Add org ID context if provided\n\t\tif (orgId) {\n\t\t\tcontent += getOrgConfigSection(orgId);\n\t\t}\n\n\t\tcontent += getNeonMdTemplate();\n\n\t\twriteFileSync(neonAgentsPath, content, \"utf-8\");\n\t\treturn true;\n\t} catch (error) {\n\t\tlog.error(\n\t\t\t`Failed to create .neon/AGENTS.md: ${error instanceof Error ? error.message : \"Unknown error\"}`,\n\t\t);\n\t\treturn false;\n\t}\n}\n\n/**\n * Creates or updates AGENTS.md with a reference to .neon/AGENTS.md\n */\nasync function createAgentsMd(): Promise<boolean> {\n\tconst agentsPath = resolve(process.cwd(), \"AGENTS.md\");\n\n\ttry {\n\t\tconst neonReference = getAgentsNeonReference();\n\n\t\t// Check if AGENTS.md already exists\n\t\tif (existsSync(agentsPath)) {\n\t\t\t// Append to existing file\n\t\t\tconst existingContent = readFileSync(agentsPath, \"utf-8\");\n\n\t\t\t// Check if Neon section already exists to avoid duplicates\n\t\t\tif (existingContent.includes(\"## Working with Neon Database\")) {\n\t\t\t\treturn true;\n\t\t\t}\n\n\t\t\tconst separator = \"\\n\\n---\\n\\n\";\n\t\t\tconst updatedContent = existingContent + separator + neonReference;\n\t\t\twriteFileSync(agentsPath, updatedContent, \"utf-8\");\n\t\t} else {\n\t\t\t// Create new file with proper header\n\t\t\tconst newContent = `# AGENTS.md\n\nThis file provides guidance to AI coding assistants when working with code in this project.\n\n---\n\n${neonReference}`;\n\t\t\twriteFileSync(agentsPath, newContent, \"utf-8\");\n\t\t}\n\t\treturn true;\n\t} catch (error) {\n\t\tlog.error(\n\t\t\t`Failed to create/update AGENTS.md: ${error instanceof Error ? error.message : \"Unknown error\"}`,\n\t\t);\n\t\treturn false;\n\t}\n}\n\n/**\n * Installs Neon's MCP Server by configuring it globally in ~/.cursor/mcp.json\n * Returns the selected organization ID if one was chosen\n */\nasync function installMCPServer(): Promise<{\n\tsuccess: boolean;\n\torgId?: string;\n}> {\n\tconst homeDir = process.env.HOME || process.env.USERPROFILE;\n\tif (!homeDir) {\n\t\tlog.error(\"Could not determine home directory\");\n\t\treturn { success: 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 { success: 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}\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 { success: false };\n\t}\n\n\tauthSpinner.stop(\"Authentication successful ✓\");\n\n\t// Fetch organizations and let user select\n\tlet selectedOrgId: string | undefined;\n\n\tconst organizations = await fetchOrganizations();\n\n\tif (organizations.length > 1) {\n\t\tconst orgChoice = await select({\n\t\t\tmessage: \"Select an organization for this project:\",\n\t\t\toptions: organizations.map((org) => ({\n\t\t\t\tvalue: org.id,\n\t\t\t\tlabel: org.name,\n\t\t\t})),\n\t\t});\n\n\t\tif (isCancel(orgChoice)) {\n\t\t\treturn { success: false };\n\t\t}\n\n\t\tselectedOrgId = orgChoice.toString();\n\t} else if (organizations.length === 1) {\n\t\t// Only one org, auto-select it\n\t\tselectedOrgId = organizations[0].id;\n\t\tlog.info(`Using organization: ${organizations[0].name}`);\n\t} else {\n\t\t// No organizations found (personal account)\n\t\tlog.info(\"Using personal account\");\n\t}\n\n\t// If user chose not to reconfigure, we're done (but we still return the org ID)\n\tif (alreadyConfigured && !shouldReconfigure) {\n\t\treturn { success: true, orgId: selectedOrgId };\n\t}\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 { success: false };\n\t}\n\n\t// Step 4: 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 { success: true, orgId: selectedOrgId };\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 { success: false };\n\t}\n}\n\n/**\n * Initialize Neon projects with MCP Server and AI assistant rules\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 { success: mcpSuccess, orgId } = 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} else {\n\t\tlog.step(\"Installed Neon MCP server\");\n\t}\n\n\tconst neonMdSuccess = await createNeonMd(orgId);\n\n\tif (!neonMdSuccess) {\n\t\tlog.warn(\n\t\t\t\"Failed to create .neon/AGENTS.md, but MCP Server is configured.\",\n\t\t);\n\t}\n\n\tconst agentsSuccess = await createAgentsMd();\n\n\tif (!agentsSuccess) {\n\t\tlog.warn(\"Failed to create AGENTS.md, but MCP Server is configured.\");\n\t} else {\n\t\tlog.step(\"Added Neon instructions to AGENTS.md\");\n\t}\n\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[0mAsk Cursor to \"${bold(cyan(\"Get started with Neon\"))}\\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":";;;;;;;;;;AAkBA,MAAM,YAAY,UAAU,KAAK;AAGjC,MAAM,YAAY,QADC,cAAc,OAAO,KAAK,IAAI,CACZ;;;;;AAoBrC,eAAe,oBAAsC;AACpD,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,eAAe,0BAAkD;AAChE,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;;;AAIT,eAAe,qBAAkD;AAChE,KAAI;EACH,MAAM,EAAE,WAAW,MAAM,UACxB,yDACA,EAAE,WAAW,OAAO,MAAM,CAC1B;EAED,MAAM,OAAO,KAAK,MAAM,OAAO;AAU/B,SAP0C,MAAM,QAAQ,KAAK,GAC1D,KAAK,KAAK,SAAwC;GAClD,IAAI,IAAI;GACR,MAAM,IAAI,QAAQ,IAAI;GACtB,EAAE,GACF,EAAE;UAGG,OAAO;AACf,MAAI,KACH,kCAAkC,iBAAiB,QAAQ,MAAM,UAAU,kBAC3E;AACD,SAAO,EAAE;;;;;;AAOX,SAAS,aAAa,WAA8B;CACnD,MAAM,gBAAgB,QAAQ,WAAW,WAAW;AAEpD,KAAI,WAAW,cAAc,CAC5B,KAAI;EACH,MAAM,UAAU,aAAa,eAAe,QAAQ;AACpD,SAAO,KAAK,MAAM,QAAQ;UAClB,QAAQ;AAChB,MAAI,KACH,mEACA;AACD,SAAO,EAAE,YAAY,EAAE,EAAE;;AAI3B,QAAO,EAAE,YAAY,EAAE,EAAE;;;;;AAM1B,SAAS,eAAe,WAAmB,QAAyB;CACnE,MAAM,gBAAgB,QAAQ,WAAW,WAAW;AAEpD,KAAI,CAAC,WAAW,UAAU,CACzB,WAAU,WAAW,EAAE,WAAW,MAAM,CAAC;AAG1C,eAAc,eAAe,KAAK,UAAU,QAAQ,MAAM,EAAE,EAAE,QAAQ;;;;;AAMvE,SAAS,oBAA4B;AAEpC,QAAO,aADc,QAAQ,WAAW,4BAA4B,EAClC,QAAQ;;;;;AAM3C,SAAS,oBAAoB,OAAuB;AACnD,QAAO;;;;yBAIiB,MAAM;;;;;0EAK2C,MAAM;uDACzB,MAAM;;;;;;;;;AAU7D,SAAS,yBAAiC;AACzC,QAAO;;;;;;;AAQR,eAAe,aAAa,OAAkC;CAC7D,MAAM,UAAU,QAAQ,QAAQ,KAAK,EAAE,QAAQ;CAC/C,MAAM,iBAAiB,QAAQ,SAAS,YAAY;AAEpD,KAAI;AAEH,MAAI,CAAC,WAAW,QAAQ,CACvB,WAAU,SAAS,EAAE,WAAW,MAAM,CAAC;AAIxC,MAAI,WAAW,eAAe,EAAE;GAC/B,MAAM,WAAW,MAAM,QAAQ;IAC9B,SACC;IACD,cAAc;IACd,CAAC;AAEF,OAAI,SAAS,SAAS,CACrB,QAAO;AAGR,OAAI,CAAC,SACJ,QAAO;;EAIT,IAAI,UAAU;AAGd,MAAI,MACH,YAAW,oBAAoB,MAAM;AAGtC,aAAW,mBAAmB;AAE9B,gBAAc,gBAAgB,SAAS,QAAQ;AAC/C,SAAO;UACC,OAAO;AACf,MAAI,MACH,qCAAqC,iBAAiB,QAAQ,MAAM,UAAU,kBAC9E;AACD,SAAO;;;;;;AAOT,eAAe,iBAAmC;CACjD,MAAM,aAAa,QAAQ,QAAQ,KAAK,EAAE,YAAY;AAEtD,KAAI;EACH,MAAM,gBAAgB,wBAAwB;AAG9C,MAAI,WAAW,WAAW,EAAE;GAE3B,MAAM,kBAAkB,aAAa,YAAY,QAAQ;AAGzD,OAAI,gBAAgB,SAAS,gCAAgC,CAC5D,QAAO;AAKR,iBAAc,YADS,kBADL,gBACmC,eACX,QAAQ;QAUlD,eAAc,YAPK;;;;;;EAMpB,iBACuC,QAAQ;AAE/C,SAAO;UACC,OAAO;AACf,MAAI,MACH,sCAAsC,iBAAiB,QAAQ,MAAM,UAAU,kBAC/E;AACD,SAAO;;;;;;;AAQT,eAAe,mBAGZ;CACF,MAAM,UAAU,QAAQ,IAAI,QAAQ,QAAQ,IAAI;AAChD,KAAI,CAAC,SAAS;AACb,MAAI,MAAM,qCAAqC;AAC/C,SAAO,EAAE,SAAS,OAAO;;CAE1B,MAAM,YAAY,QAAQ,SAAS,UAAU;CAC7C,MAAM,SAAS,aAAa,UAAU;CAGtC,MAAM,oBAAoB,QAAQ,OAAO,WAAW,KAAK;CACzD,IAAI,oBAAoB;AAExB,KAAI,mBAAmB;EACtB,MAAM,WAAW,MAAM,QAAQ;GAC9B,SACC;GACD,cAAc;GACd,CAAC;AAEF,MAAI,SAAS,SAAS,CACrB,QAAO,EAAE,SAAS,OAAO;AAG1B,sBAAoB;AAEpB,MAAI,CAAC,kBACJ,KAAI,KAAK,yCAAyC;;CAKpD,MAAM,cAAc,SAAS;AAC7B,aAAY,MAAM,oBAAoB;AAItC,KAAI,CAFgB,MAAM,mBAAmB,EAE3B;AACjB,cAAY,KAAK,wBAAwB;AACzC,SAAO,EAAE,SAAS,OAAO;;AAG1B,aAAY,KAAK,8BAA8B;CAG/C,IAAIA;CAEJ,MAAM,gBAAgB,MAAM,oBAAoB;AAEhD,KAAI,cAAc,SAAS,GAAG;EAC7B,MAAM,YAAY,MAAM,OAAO;GAC9B,SAAS;GACT,SAAS,cAAc,KAAK,SAAS;IACpC,OAAO,IAAI;IACX,OAAO,IAAI;IACX,EAAE;GACH,CAAC;AAEF,MAAI,SAAS,UAAU,CACtB,QAAO,EAAE,SAAS,OAAO;AAG1B,kBAAgB,UAAU,UAAU;YAC1B,cAAc,WAAW,GAAG;AAEtC,kBAAgB,cAAc,GAAG;AACjC,MAAI,KAAK,uBAAuB,cAAc,GAAG,OAAO;OAGxD,KAAI,KAAK,yBAAyB;AAInC,KAAI,qBAAqB,CAAC,kBACzB,QAAO;EAAE,SAAS;EAAM,OAAO;EAAe;CAI/C,MAAM,SAAS,MAAM,yBAAyB;AAE9C,KAAI,CAAC,QAAQ;AACZ,MAAI,MAAM,iDAAiD;AAC3D,MAAI,KACH,kFACA;AACD,SAAO,EAAE,SAAS,OAAO;;AAM1B,QAAO,WAAW,OAAO;EACxB,KAAK;EACL,SAAS,EACR,eAAe,UAAU,UACzB;EACD;AAGD,KAAI;AACH,iBAAe,WAAW,OAAO;AACjC,SAAO;GAAE,SAAS;GAAM,OAAO;GAAe;UACtC,OAAO;AACf,MAAI,MACH,yCAAyC,iBAAiB,QAAQ,MAAM,UAAU,kBAClF;AACD,SAAO,EAAE,SAAS,OAAO;;;;;;AAO3B,eAAsB,OAAsB;AAC3C,OAAM,8BAA8B;CAGpC,MAAM,UAAU,QAAQ,IAAI,QAAQ,QAAQ,IAAI;AAChD,KAAI,CAAC,SAAS;AACb,MAAI,MAAM,qCAAqC;AAC/C,UAAQ,KAAK,EAAE;;AAIhB,KAAI,CAAC,WADa,QAAQ,SAAS,UAAU,CACnB,EAAE;AAC3B,MAAI,KAAK,oBAAoB;AAC7B,MAAI,KACH,uFACA;AACD,QAAM,wDAAwD;AAC9D,UAAQ,KAAK,EAAE;;CAGhB,MAAM,EAAE,SAAS,YAAY,UAAU,MAAM,kBAAkB;AAE/D,KAAI,CAAC,YAAY;AAChB,QACC,mFACA;AACD,UAAQ,KAAK,EAAE;OAEf,KAAI,KAAK,4BAA4B;AAKtC,KAAI,CAFkB,MAAM,aAAa,MAAM,CAG9C,KAAI,KACH,kEACA;AAKF,KAAI,CAFkB,MAAM,gBAAgB,CAG3C,KAAI,KAAK,4DAA4D;KAErE,KAAI,KAAK,uCAAuC;AAGjD,KAAI,KAAK,mDAAmD;AAG5D,MACC,yBAAyB,KAAK,KAAK,wBAAwB,CAAC,CAAC,uBAC7D,eACA;AAED,OAAM,gDAAgD"}
|
|
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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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "neon-init",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.9.1",
|
|
4
4
|
"description": "Initialize Neon projects",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"neon",
|
|
@@ -31,8 +31,7 @@
|
|
|
31
31
|
"files": [
|
|
32
32
|
"README.md",
|
|
33
33
|
"dist/",
|
|
34
|
-
"package.json"
|
|
35
|
-
"src/agents-template.md"
|
|
34
|
+
"package.json"
|
|
36
35
|
],
|
|
37
36
|
"devDependencies": {
|
|
38
37
|
"@types/node": "^20.19.0",
|
package/src/agents-template.md
DELETED
|
@@ -1,228 +0,0 @@
|
|
|
1
|
-
## Neon Database Guidelines
|
|
2
|
-
|
|
3
|
-
> **Context:** These guidelines apply when working with Neon Postgres databases in this project.
|
|
4
|
-
>
|
|
5
|
-
> **When to use:** Code involving database connections, queries, schema management, or Neon-specific features.
|
|
6
|
-
|
|
7
|
-
This file provides guidance to AI coding assistants (Cursor, Windsurf, etc.) when working with Neon in this project.
|
|
8
|
-
|
|
9
|
-
---
|
|
10
|
-
|
|
11
|
-
## Communication Style
|
|
12
|
-
|
|
13
|
-
**Keep all responses succinct:**
|
|
14
|
-
|
|
15
|
-
- ✅ Tell the user what you did: "Created users table with 3 columns"
|
|
16
|
-
- ✅ Ask direct questions when needed: "Which database should I use?"
|
|
17
|
-
- ❌ Avoid verbose explanations of what you're about to do
|
|
18
|
-
- ❌ Don't explain every step unless the user asks
|
|
19
|
-
|
|
20
|
-
**Examples:**
|
|
21
|
-
|
|
22
|
-
- **Good**: "Added DATABASE_URL to .env. Ready to connect?"
|
|
23
|
-
- **Bad**: "I'm going to add the DATABASE_URL environment variable to your .env file so that your application can connect to the database. This will allow..."
|
|
24
|
-
|
|
25
|
-
---
|
|
26
|
-
|
|
27
|
-
## Get Started with Neon (Interactive Guide)
|
|
28
|
-
|
|
29
|
-
**TRIGGER PHRASE:** When the user says "Get started with Neon" or similar phrases, provide an interactive onboarding experience by following these steps:
|
|
30
|
-
|
|
31
|
-
**Before starting:** Let the user know they can pause and resume anytime by saying "Continue with Neon setup" if they need to come back later.
|
|
32
|
-
|
|
33
|
-
**RESUME TRIGGER:** If the user says "Continue with Neon setup" or similar, check what's already configured (MCP server, .env, dependencies, schema) and resume from where they left off. Ask them which step they'd like to continue from or analyze their setup to determine automatically.
|
|
34
|
-
|
|
35
|
-
### Step 1: Check Existing Neon Projects
|
|
36
|
-
|
|
37
|
-
Use the Neon MCP Server to check if the user has existing projects (remember to use the configured `org_id` if specified above). Then guide them based on what you find:
|
|
38
|
-
|
|
39
|
-
**If they have NO projects:**
|
|
40
|
-
|
|
41
|
-
- Ask if they want to create a new project
|
|
42
|
-
- Guide them to create one at console.neon.tech or help them do it via the MCP server
|
|
43
|
-
|
|
44
|
-
**If they have 1 project:**
|
|
45
|
-
|
|
46
|
-
- Show them the project name and ask: "Would you like to use '{project_name}' or create a new one?"
|
|
47
|
-
- If they choose existing project, proceed to Step 3
|
|
48
|
-
- If they want to create new, guide them accordingly
|
|
49
|
-
|
|
50
|
-
**If they have multiple projects (less than 20):**
|
|
51
|
-
|
|
52
|
-
- List all their projects with each name
|
|
53
|
-
- Ask which one they want to work on, OR
|
|
54
|
-
- Offer the option to create a new project
|
|
55
|
-
- Confirm their selection before proceeding
|
|
56
|
-
|
|
57
|
-
**If they have many projects (20+):**
|
|
58
|
-
|
|
59
|
-
- Tell them how many projects they have
|
|
60
|
-
- Ask them to specify which project they want to use by name or ID
|
|
61
|
-
- Or offer to help them create a new one
|
|
62
|
-
|
|
63
|
-
### Step 2: Database Setup
|
|
64
|
-
|
|
65
|
-
**Get the connection string:**
|
|
66
|
-
|
|
67
|
-
- Use the MCP server to get the connection string for the selected project
|
|
68
|
-
|
|
69
|
-
**Configure it for their environment:**
|
|
70
|
-
|
|
71
|
-
- Most projects use a `.env` file with `DATABASE_URL`
|
|
72
|
-
- For other setups (deployed platforms, containers, cloud configs), check their project structure and ask where they store credentials
|
|
73
|
-
|
|
74
|
-
**Before modifying .env:**
|
|
75
|
-
|
|
76
|
-
1. **Always try to read the .env file first** to check if `DATABASE_URL` already exists
|
|
77
|
-
2. If the file exists and is readable:
|
|
78
|
-
- Use `search_replace` to update existing `DATABASE_URL`, or
|
|
79
|
-
- Append new `DATABASE_URL` if it doesn't exist
|
|
80
|
-
3. If the file is unreadable (in .cursorignore/.gitignore) or you lack write permissions:
|
|
81
|
-
- **DO NOT use the write tool** (it would overwrite the entire file)
|
|
82
|
-
- Instead, run the append command: `echo "DATABASE_URL=postgresql://..." >> .env`
|
|
83
|
-
- Or show them the exact line to add manually:
|
|
84
|
-
|
|
85
|
-
```
|
|
86
|
-
DATABASE_URL=postgresql://user:password@host/database
|
|
87
|
-
```
|
|
88
|
-
|
|
89
|
-
### Step 3: Install Dependencies
|
|
90
|
-
|
|
91
|
-
Check if the user already has a common driver installed. If not, based on their framework, environment and use case, recommend the appropriate driver and install it for the user. Keep the conversation focused.
|
|
92
|
-
|
|
93
|
-
**For Serverless/Edge (Vercel, Cloudflare Workers, etc.):**
|
|
94
|
-
|
|
95
|
-
```bash
|
|
96
|
-
npm install @neondatabase/serverless
|
|
97
|
-
```
|
|
98
|
-
|
|
99
|
-
**For Traditional Node.js:**
|
|
100
|
-
|
|
101
|
-
```bash
|
|
102
|
-
npm install pg
|
|
103
|
-
```
|
|
104
|
-
|
|
105
|
-
### Step 4: Understand the Project
|
|
106
|
-
|
|
107
|
-
**First, check if this is an empty/new project:**
|
|
108
|
-
|
|
109
|
-
- Look for existing source code, routes, components, or substantial application logic
|
|
110
|
-
- Check if it's just a bare package.json or minimal boilerplate
|
|
111
|
-
|
|
112
|
-
**If it's an empty or near-empty project:**
|
|
113
|
-
|
|
114
|
-
Ask the user briefly (1-2 questions):
|
|
115
|
-
|
|
116
|
-
- What are they building? (e.g., "a blog", "an API for a mobile app", "a SaaS dashboard")
|
|
117
|
-
- Any specific technologies they want to use? (e.g., "Next.js", "tRPC", "Express")
|
|
118
|
-
|
|
119
|
-
**If it's an established project:**
|
|
120
|
-
|
|
121
|
-
Skip the questions - you can infer what they're building from the existing codebase. Update any relevant code to use the driver you just installed to connect to their Neon database.
|
|
122
|
-
|
|
123
|
-
**Remember the context** (whether from questions or code analysis) for all subsequent MCP Server interactions and recommendations. However, stay focused on Neon setup - don't get sidetracked into other architectural discussions until setup is complete.
|
|
124
|
-
|
|
125
|
-
### Step 5: ORM Setup
|
|
126
|
-
|
|
127
|
-
**Check if they have an ORM:**
|
|
128
|
-
|
|
129
|
-
Look for ORM configuration files or imports (Prisma, Drizzle, TypeORM, etc.)
|
|
130
|
-
|
|
131
|
-
**If no ORM found:**
|
|
132
|
-
|
|
133
|
-
Ask: "Want to set up an ORM for type-safe database queries?"
|
|
134
|
-
|
|
135
|
-
**If yes, suggest based on their project:**
|
|
136
|
-
|
|
137
|
-
If they decline, proceed with raw SQL queries.
|
|
138
|
-
|
|
139
|
-
### Step 6: Schema Setup
|
|
140
|
-
|
|
141
|
-
**First, check for existing schema:**
|
|
142
|
-
|
|
143
|
-
Search the codebase for:
|
|
144
|
-
|
|
145
|
-
- SQL migration files (`.sql`, `migrations/` folder)
|
|
146
|
-
- ORM schemas (Prisma `schema.prisma`, Drizzle schema files, TypeORM entities)
|
|
147
|
-
- Database initialization scripts
|
|
148
|
-
|
|
149
|
-
**If they have existing schema:**
|
|
150
|
-
|
|
151
|
-
- Show them what you found
|
|
152
|
-
- Ask: "Found existing schema definitions. Want to migrate these to your Neon database?"
|
|
153
|
-
- If yes, help execute the migrations using the MCP server or guide them through their ORM's migration process
|
|
154
|
-
|
|
155
|
-
**If no existing schema found:**
|
|
156
|
-
|
|
157
|
-
Ask if they want to:
|
|
158
|
-
|
|
159
|
-
1. Create a simple example schema (users table)
|
|
160
|
-
2. Design a custom schema together
|
|
161
|
-
3. Skip schema setup for now
|
|
162
|
-
|
|
163
|
-
**If they choose (1):** Create a basic example users table using the MCP server:
|
|
164
|
-
|
|
165
|
-
```sql
|
|
166
|
-
CREATE TABLE users (
|
|
167
|
-
id SERIAL PRIMARY KEY,
|
|
168
|
-
email VARCHAR(255) UNIQUE NOT NULL,
|
|
169
|
-
name VARCHAR(255),
|
|
170
|
-
created_at TIMESTAMP DEFAULT NOW()
|
|
171
|
-
);
|
|
172
|
-
```
|
|
173
|
-
|
|
174
|
-
**If they choose (2):** Ask them about their app's needs and help design tables. Then create the schema using the MCP server or guide them to create it via their ORM.
|
|
175
|
-
|
|
176
|
-
**If they choose (3):** Move on to Step 7. They can always come back to add schema later.
|
|
177
|
-
|
|
178
|
-
### Step 7: What's Next
|
|
179
|
-
|
|
180
|
-
Let them know you're ready to help with more:
|
|
181
|
-
|
|
182
|
-
"You're all set! Here are some things I can help with - feel free to ask about any of these (or anything else):
|
|
183
|
-
|
|
184
|
-
- Neon-specific features (branching, autoscaling, scale-to-zero)
|
|
185
|
-
- Connection pooling for production
|
|
186
|
-
- Writing queries or building API endpoints
|
|
187
|
-
- Database migrations and schema changes
|
|
188
|
-
- Performance optimization"
|
|
189
|
-
|
|
190
|
-
### Important Notes:
|
|
191
|
-
|
|
192
|
-
- Be succinct yet conversational and guide them step-by-step
|
|
193
|
-
- Know the context of the user's codebase before each step
|
|
194
|
-
- Provide working, tested code examples
|
|
195
|
-
- Check for errors in their existing setup before proceeding
|
|
196
|
-
- Don't give up - always at least give the user a way to complete the setup manually.
|
|
197
|
-
|
|
198
|
-
---
|
|
199
|
-
|
|
200
|
-
## Neon Database Best Practices
|
|
201
|
-
|
|
202
|
-
### Security
|
|
203
|
-
|
|
204
|
-
**Follow these security practices:**
|
|
205
|
-
|
|
206
|
-
1. Never commit connection strings to version control
|
|
207
|
-
2. Use environment variables for all database credentials
|
|
208
|
-
3. Prefer SSL connections (default in Neon)
|
|
209
|
-
4. Use least-privilege database roles for applications
|
|
210
|
-
5. Rotate API keys and passwords regularly
|
|
211
|
-
|
|
212
|
-
### Neon-Specific Features
|
|
213
|
-
|
|
214
|
-
**Leverage Neon's unique features:**
|
|
215
|
-
|
|
216
|
-
1. **Branching**: Create database branches for development/staging
|
|
217
|
-
2. **Autoscaling**: Neon automatically scales compute based on load
|
|
218
|
-
3. **Scale to Zero**: Databases automatically suspend after inactivity
|
|
219
|
-
4. **Point-in-Time Recovery**: Restore databases to any point in time
|
|
220
|
-
|
|
221
|
-
## Additional Resources
|
|
222
|
-
|
|
223
|
-
- [Neon Documentation](https://neon.com/docs)
|
|
224
|
-
- [Neon Serverless Driver](https://neon.com/docs/serverless/serverless-driver)
|
|
225
|
-
- [Neon API Reference](https://neon.com/docs/reference/api-reference)
|
|
226
|
-
- [Postgres Documentation](https://www.postgresql.org/docs)
|
|
227
|
-
|
|
228
|
-
---
|