neon-init 0.8.1 → 0.9.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 CHANGED
@@ -1,6 +1,6 @@
1
1
  # neon-init
2
2
 
3
- Set up your project with Neon's MCP Server and AI assistant best practices.
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
- ### 1. Configures Neon MCP Server
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
- ### 2. Creates AGENTS.md
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
- Adds Neon-specific guidance to `AGENTS.md` (or creates it if it doesn't exist) with:
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
@@ -1,6 +1,6 @@
1
1
  //#region src/index.d.ts
2
2
  /**
3
- * Initialize Neon projects with MCP Server and AI assistant rules
3
+ * Initialize Neon projects with MCP Server
4
4
  */
5
5
  declare function init(): Promise<void>;
6
6
  //#endregion
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","names":[],"sources":["../src/index.ts"],"sourcesContent":[],"mappings":";;AAwbA;;iBAAsB,IAAA,CAAA,GAAQ"}
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 { promisify } from "node:util";
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
- const execAsync = promisify(exec);
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 { success: false };
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 { success: false };
112
+ if (isCancel(response)) return false;
214
113
  shouldReconfigure = response;
215
- if (!shouldReconfigure) log.info("Keeping existing global configuration.");
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 { success: false };
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 { success: false };
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 { success: false };
141
+ return false;
263
142
  }
264
143
  }
265
144
  /**
266
- * Initialize Neon projects with MCP Server and AI assistant rules
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
- const { success: mcpSuccess, orgId } = await installMCPServer();
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
- } else log.step("Installed Neon MCP server");
286
- if (!await createNeonMd(orgId)) log.warn("Failed to create .neon/AGENTS.md, but MCP Server is configured.");
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[0mAsk Cursor to "${bold(cyan("Get started with Neon"))}\x1b[0m" in the chat`, "What's next?");
166
+ note(`\x1b[0mAsk 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":"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"}
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[0mAsk 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.8.1",
3
+ "version": "0.9.0",
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",
@@ -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
- ---