neon-init 0.8.0 → 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":";;AA2bA;;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,14 +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, outro, select, spinner } from "@clack/prompts";
4
+ import { confirm, intro, isCancel, log, note, outro, spinner } from "@clack/prompts";
7
5
  import { execa } from "execa";
6
+ import { bold, cyan } from "yoctocolors";
8
7
 
9
8
  //#region src/index.ts
10
- const execAsync = promisify(exec);
11
- const __dirname = dirname(fileURLToPath(import.meta.url));
9
+ dirname(fileURLToPath(import.meta.url));
12
10
  /**
13
11
  * Ensures neonctl is authenticated by running a command that triggers auth if needed
14
12
  * This will automatically start the OAuth flow if the user isn't already authenticated
@@ -19,8 +17,6 @@ async function ensureNeonctlAuth() {
19
17
  "-y",
20
18
  "neonctl",
21
19
  "me",
22
- "--output",
23
- "json",
24
20
  "--no-analytics"
25
21
  ], { stdio: "inherit" });
26
22
  return true;
@@ -73,19 +69,6 @@ async function createApiKeyFromNeonctl() {
73
69
  return null;
74
70
  }
75
71
  }
76
- async function fetchOrganizations() {
77
- try {
78
- const { stdout } = await execAsync("npx -y neonctl orgs list --output json --no-analytics", { maxBuffer: 1024 * 1024 });
79
- const data = JSON.parse(stdout);
80
- return Array.isArray(data) ? data.map((org) => ({
81
- id: org.id,
82
- name: org.name || org.id
83
- })) : [];
84
- } catch (error) {
85
- log.warn(`Unable to fetch organizations: ${error instanceof Error ? error.message : "Unknown error"}`);
86
- return [];
87
- }
88
- }
89
72
  /**
90
73
  * Gets or creates the .cursor/mcp.json configuration
91
74
  */
@@ -109,101 +92,13 @@ function writeMCPConfig(cursorDir, config) {
109
92
  writeFileSync(mcpConfigPath, JSON.stringify(config, null, 2), "utf-8");
110
93
  }
111
94
  /**
112
- * Gets the neon.md template with Neon-specific best practices
113
- */
114
- function getNeonMdTemplate() {
115
- return readFileSync(resolve(__dirname, "../src/agents-template.md"), "utf-8");
116
- }
117
- /**
118
- * Generates the organization configuration section for neon.md
119
- */
120
- function getOrgConfigSection(orgId) {
121
- return `## Neon Project Configuration
122
-
123
- > **🔴 IMPORTANT: You MUST use this organization for all Neon operations in this project.**
124
-
125
- **Organization ID:** \`${orgId}\`
126
-
127
- When using any Neon MCP tools or API calls, always pass this \`org_id\` parameter. The MCP server is pre-configured with this organization.
128
-
129
- **Example:**
130
- - When listing projects: Use \`mcp_Neon_list_projects\` with \`org_id: "${orgId}"\`
131
- - When creating resources: Always include \`org_id: "${orgId}"\` in your tool calls
132
-
133
- ---
134
-
135
- `;
136
- }
137
- /**
138
- * Gets the reference text to add to AGENTS.md
139
- */
140
- function getAgentsNeonReference() {
141
- return `## Working with Neon Database
142
-
143
- When the user asks to **"Get started with Neon"** or something similar, refer to the detailed guidelines in \`.neon/AGENTS.md\`.`;
144
- }
145
- /**
146
- * Creates or updates .neon/AGENTS.md with detailed Neon guidelines
147
- */
148
- async function createNeonMd(orgId) {
149
- const neonDir = resolve(process.cwd(), ".neon");
150
- const neonAgentsPath = resolve(neonDir, "AGENTS.md");
151
- try {
152
- if (!existsSync(neonDir)) mkdirSync(neonDir, { recursive: true });
153
- if (existsSync(neonAgentsPath)) {
154
- const response = await confirm({
155
- message: "Replace existing .neon/AGENTS.md with updated guidelines? (suggested)",
156
- initialValue: true
157
- });
158
- if (isCancel(response)) return false;
159
- if (!response) return true;
160
- }
161
- let content = "";
162
- if (orgId) content += getOrgConfigSection(orgId);
163
- content += getNeonMdTemplate();
164
- writeFileSync(neonAgentsPath, content, "utf-8");
165
- return true;
166
- } catch (error) {
167
- log.error(`Failed to create .neon/AGENTS.md: ${error instanceof Error ? error.message : "Unknown error"}`);
168
- return false;
169
- }
170
- }
171
- /**
172
- * Creates or updates AGENTS.md with a reference to .neon/AGENTS.md
173
- */
174
- async function createAgentsMd() {
175
- const agentsPath = resolve(process.cwd(), "AGENTS.md");
176
- try {
177
- const neonReference = getAgentsNeonReference();
178
- if (existsSync(agentsPath)) {
179
- const existingContent = readFileSync(agentsPath, "utf-8");
180
- if (existingContent.includes("## Working with Neon Database")) {
181
- log.info("Neon reference already exists in AGENTS.md");
182
- return true;
183
- }
184
- writeFileSync(agentsPath, existingContent + "\n\n---\n\n" + neonReference, "utf-8");
185
- } else writeFileSync(agentsPath, `# AGENTS.md
186
-
187
- This file provides guidance to AI coding assistants when working with code in this project.
188
-
189
- ---
190
-
191
- ${neonReference}`, "utf-8");
192
- return true;
193
- } catch (error) {
194
- log.error(`Failed to create/update AGENTS.md: ${error instanceof Error ? error.message : "Unknown error"}`);
195
- return false;
196
- }
197
- }
198
- /**
199
95
  * Installs Neon's MCP Server by configuring it globally in ~/.cursor/mcp.json
200
- * Returns the selected organization ID if one was chosen
201
96
  */
202
97
  async function installMCPServer() {
203
98
  const homeDir = process.env.HOME || process.env.USERPROFILE;
204
99
  if (!homeDir) {
205
100
  log.error("Could not determine home directory");
206
- return { success: false };
101
+ return false;
207
102
  }
208
103
  const cursorDir = resolve(homeDir, ".cursor");
209
104
  const config = getMCPConfig(cursorDir);
@@ -214,42 +109,25 @@ async function installMCPServer() {
214
109
  message: "Neon MCP Server is already configured. Would you like to reconfigure it? (Y/n)",
215
110
  initialValue: true
216
111
  });
217
- if (isCancel(response)) return { success: false };
112
+ if (isCancel(response)) return false;
218
113
  shouldReconfigure = response;
219
- if (!shouldReconfigure) log.info("Keeping existing global configuration.");
114
+ if (!shouldReconfigure) {
115
+ log.info("Keeping existing global configuration.");
116
+ return true;
117
+ }
220
118
  }
221
119
  const authSpinner = spinner();
222
120
  authSpinner.start("Authenticating...");
223
121
  if (!await ensureNeonctlAuth()) {
224
122
  authSpinner.stop("Authentication failed");
225
- return { success: false };
123
+ return false;
226
124
  }
227
125
  authSpinner.stop("Authentication successful ✓");
228
- let selectedOrgId;
229
- const organizations = await fetchOrganizations();
230
- if (organizations.length > 1) {
231
- const orgChoice = await select({
232
- message: "Select an organization for this project:",
233
- options: organizations.map((org) => ({
234
- value: org.id,
235
- label: org.name
236
- }))
237
- });
238
- if (isCancel(orgChoice)) return { success: false };
239
- selectedOrgId = orgChoice.toString();
240
- } else if (organizations.length === 1) {
241
- selectedOrgId = organizations[0].id;
242
- log.info(`Using organization: ${organizations[0].name}`);
243
- } else log.info("Using personal account");
244
- if (alreadyConfigured && !shouldReconfigure) return {
245
- success: true,
246
- orgId: selectedOrgId
247
- };
248
126
  const apiKey = await createApiKeyFromNeonctl();
249
127
  if (!apiKey) {
250
128
  log.error("Could not create API key after authentication.");
251
129
  log.info("You can manually create one at: https://console.neon.tech/app/settings/api-keys");
252
- return { success: false };
130
+ return false;
253
131
  }
254
132
  config.mcpServers.Neon = {
255
133
  url: "https://mcp.neon.tech/mcp",
@@ -257,17 +135,14 @@ async function installMCPServer() {
257
135
  };
258
136
  try {
259
137
  writeMCPConfig(cursorDir, config);
260
- return {
261
- success: true,
262
- orgId: selectedOrgId
263
- };
138
+ return true;
264
139
  } catch (error) {
265
140
  log.error(`Failed to write global configuration: ${error instanceof Error ? error.message : "Unknown error"}`);
266
- return { success: false };
141
+ return false;
267
142
  }
268
143
  }
269
144
  /**
270
- * Initialize Neon projects with MCP Server and AI assistant rules
145
+ * Initialize Neon projects with MCP Server
271
146
  */
272
147
  async function init() {
273
148
  intro("Adding Neon to your project");
@@ -279,21 +154,17 @@ async function init() {
279
154
  if (!existsSync(resolve(homeDir, ".cursor"))) {
280
155
  log.warn("Cursor not found.");
281
156
  log.warn("Error: Cursor is required to continue. Support for additional agents is coming soon.");
282
- log.info("");
283
157
  outro("📣 Is this unexpected? Email us at feedback@neon.tech");
284
158
  process.exit(1);
285
159
  }
286
- const { success: mcpSuccess, orgId } = await installMCPServer();
287
- if (!mcpSuccess) {
160
+ if (!await installMCPServer()) {
288
161
  outro("Initialization cancelled or failed. Please check the output above and try again.");
289
162
  process.exit(1);
290
- } else log.info("Installed Neon MCP server");
291
- if (!await createNeonMd(orgId)) log.warn("Failed to create .neon/AGENTS.md, but MCP Server is configured.");
292
- if (!await createAgentsMd()) log.warn("Failed to create AGENTS.md, but MCP Server is configured.");
293
- else log.step("Added Neon instructions to AGENTS.md");
294
- outro("Success! Neon is now ready to use with Cursor. \n");
295
- log.info(" 📣 Have feedback? Email us at feedback@neon.tech \n \n");
296
- log.info("Next Steps: Ask Cursor to \"Get started with Neon\" in the chat");
163
+ }
164
+ log.step("Installed Neon MCP server");
165
+ log.step("Success! Neon is now ready to use with Cursor.\n");
166
+ note(`\x1b[0mAsk Cursor to "${bold(cyan("Get started with Neon using MCP Resource"))}\x1b[0m" in the chat`, "What's next?");
167
+ outro("Have feedback? Email us at feedback@neon.tech");
297
168
  }
298
169
 
299
170
  //#endregion
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\toutro,\n\tselect,\n\tspinner,\n} from \"@clack/prompts\";\nimport { execa } from \"execa\";\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(\n\t\t\t\"npx\",\n\t\t\t[\"-y\", \"neonctl\", \"me\", \"--output\", \"json\", \"--no-analytics\"],\n\t\t\t{\n\t\t\t\tstdio: \"inherit\", // Shows OAuth URL and prompts to the user\n\t\t\t},\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\tlog.info(\"Neon reference already exists in AGENTS.md\");\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\tlog.info(\"\");\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.info(\"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\toutro(\"Success! Neon is now ready to use with Cursor. \\n\");\n\tlog.info(\" 📣 Have feedback? Email us at feedback@neon.tech \\n \\n\");\n\tlog.info('Next Steps: Ask Cursor to \"Get started with Neon\" in the chat');\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.0",
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",
@@ -44,7 +43,8 @@
44
43
  },
45
44
  "dependencies": {
46
45
  "@clack/prompts": "0.10.1",
47
- "execa": "^9.5.2"
46
+ "execa": "^9.5.2",
47
+ "yoctocolors": "^2.1.2"
48
48
  },
49
49
  "engines": {
50
50
  "node": ">=20.19.0"
@@ -1,225 +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
- **Important:** If you cannot write to the file (permissions, file location, etc.), run the command to allow the user to add it themselves:
75
-
76
- ```bash
77
- echo "DATABASE_URL=postgresql://..." >> .env
78
- ```
79
-
80
- Or show them the exact line to add:
81
-
82
- ```
83
- DATABASE_URL=postgresql://user:password@host/database
84
- ```
85
-
86
- ### Step 3: Install Dependencies
87
-
88
- 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.
89
-
90
- **For Serverless/Edge (Vercel, Cloudflare Workers, etc.):**
91
-
92
- ```bash
93
- npm install @neondatabase/serverless
94
- ```
95
-
96
- **For Traditional Node.js:**
97
-
98
- ```bash
99
- npm install pg
100
- ```
101
-
102
- ### Step 4: Understand the Project
103
-
104
- **First, check if this is an empty/new project:**
105
-
106
- - Look for existing source code, routes, components, or substantial application logic
107
- - Check if it's just a bare package.json or minimal boilerplate
108
-
109
- **If it's an empty or near-empty project:**
110
-
111
- Ask the user briefly (1-2 questions):
112
-
113
- - What are they building? (e.g., "a blog", "an API for a mobile app", "a SaaS dashboard")
114
- - Any specific technologies they want to use? (e.g., "Next.js", "tRPC", "Express")
115
-
116
- **If it's an established project:**
117
-
118
- Skip the questions - you can infer what they're building from the existing codebase.
119
-
120
- **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.
121
-
122
- ### Step 5: ORM Setup
123
-
124
- **Check if they have an ORM:**
125
-
126
- Look for ORM configuration files or imports (Prisma, Drizzle, TypeORM, etc.)
127
-
128
- **If no ORM found:**
129
-
130
- Ask: "Want to set up an ORM for type-safe database queries?"
131
-
132
- **If yes, suggest based on their project:**
133
-
134
- If they decline, proceed with raw SQL queries.
135
-
136
- ### Step 6: Schema Setup
137
-
138
- **First, check for existing schema:**
139
-
140
- Search the codebase for:
141
-
142
- - SQL migration files (`.sql`, `migrations/` folder)
143
- - ORM schemas (Prisma `schema.prisma`, Drizzle schema files, TypeORM entities)
144
- - Database initialization scripts
145
-
146
- **If they have existing schema:**
147
-
148
- - Show them what you found
149
- - Ask: "Found existing schema definitions. Want to migrate these to your Neon database?"
150
- - If yes, help execute the migrations using the MCP server or guide them through their ORM's migration process
151
-
152
- **If no existing schema found:**
153
-
154
- Ask if they want to:
155
-
156
- 1. Create a simple example schema (users table)
157
- 2. Design a custom schema together
158
- 3. Skip schema setup for now
159
-
160
- **If they choose (1):** Create a basic example users table using the MCP server:
161
-
162
- ```sql
163
- CREATE TABLE users (
164
- id SERIAL PRIMARY KEY,
165
- email VARCHAR(255) UNIQUE NOT NULL,
166
- name VARCHAR(255),
167
- created_at TIMESTAMP DEFAULT NOW()
168
- );
169
- ```
170
-
171
- **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.
172
-
173
- **If they choose (3):** Move on to Step 7. They can always come back to add schema later.
174
-
175
- ### Step 7: What's Next
176
-
177
- Let them know you're ready to help with more:
178
-
179
- "You're all set! Here are some things I can help with - feel free to ask about any of these (or anything else):
180
-
181
- - Neon-specific features (branching, autoscaling, scale-to-zero)
182
- - Connection pooling for production
183
- - Writing queries or building API endpoints
184
- - Database migrations and schema changes
185
- - Performance optimization"
186
-
187
- ### Important Notes:
188
-
189
- - Be succinct yet conversational and guide them step-by-step
190
- - Know the context of the user's codebase before each step
191
- - Provide working, tested code examples
192
- - Check for errors in their existing setup before proceeding
193
- - Don't give up - always at least give the user a way to complete the setup manually.
194
-
195
- ---
196
-
197
- ## Neon Database Best Practices
198
-
199
- ### Security
200
-
201
- **Follow these security practices:**
202
-
203
- 1. Never commit connection strings to version control
204
- 2. Use environment variables for all database credentials
205
- 3. Prefer SSL connections (default in Neon)
206
- 4. Use least-privilege database roles for applications
207
- 5. Rotate API keys and passwords regularly
208
-
209
- ### Neon-Specific Features
210
-
211
- **Leverage Neon's unique features:**
212
-
213
- 1. **Branching**: Create database branches for development/staging
214
- 2. **Autoscaling**: Neon automatically scales compute based on load
215
- 3. **Scale to Zero**: Databases automatically suspend after inactivity
216
- 4. **Point-in-Time Recovery**: Restore databases to any point in time
217
-
218
- ## Additional Resources
219
-
220
- - [Neon Documentation](https://neon.com/docs)
221
- - [Neon Serverless Driver](https://neon.com/docs/serverless/serverless-driver)
222
- - [Neon API Reference](https://neon.com/docs/reference/api-reference)
223
- - [Postgres Documentation](https://www.postgresql.org/docs)
224
-
225
- ---