neon-init 0.4.0 → 0.4.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/dist/cli.d.ts ADDED
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ export {};
package/dist/cli.js ADDED
@@ -0,0 +1,9 @@
1
+ #!/usr/bin/env node
2
+ import { init } from "./index.js";
3
+
4
+ //#region src/cli.ts
5
+ await init();
6
+
7
+ //#endregion
8
+ export { };
9
+ //# sourceMappingURL=cli.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cli.js","names":[],"sources":["../src/cli.ts"],"sourcesContent":["#!/usr/bin/env node\n\nimport { init } from \"./index.js\";\n\nawait init();\n"],"mappings":";;;;AAIA,MAAM,MAAM"}
package/dist/index.d.ts CHANGED
@@ -1,5 +1,8 @@
1
1
  //#region src/index.d.ts
2
- declare function init(): void;
2
+ /**
3
+ * Initialize Neon projects with MCP Server and AI assistant rules
4
+ */
5
+ declare function init(): Promise<void>;
3
6
  //#endregion
4
7
  export { init };
5
8
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","names":[],"sources":["../src/index.ts"],"sourcesContent":[],"mappings":";iBAAgB,IAAA,CAAA"}
1
+ {"version":3,"file":"index.d.ts","names":[],"sources":["../src/index.ts"],"sourcesContent":[],"mappings":";;AA6bA;;iBAAsB,IAAA,CAAA,GAAQ"}
package/dist/index.js CHANGED
@@ -1,6 +1,307 @@
1
+ import { exec } from "node:child_process";
2
+ import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
3
+ import { dirname, resolve } from "node:path";
4
+ import { fileURLToPath } from "node:url";
5
+ import { promisify } from "node:util";
6
+ import { confirm, intro, isCancel, log, outro, select, spinner } from "@clack/prompts";
7
+
1
8
  //#region src/index.ts
2
- function init() {
3
- console.log("Hello world from `neon-init`!");
9
+ const execAsync = promisify(exec);
10
+ const __dirname = dirname(fileURLToPath(import.meta.url));
11
+ /**
12
+ * Ensures neonctl is authenticated by running a command that triggers auth if needed
13
+ * This will automatically start the OAuth flow if the user isn't already authenticated
14
+ */
15
+ async function ensureNeonctlAuth() {
16
+ try {
17
+ const { spawn } = await import("node:child_process");
18
+ await new Promise((resolve$1, reject) => {
19
+ const meProcess = spawn("npx", [
20
+ "-y",
21
+ "neon",
22
+ "me",
23
+ "--output",
24
+ "json",
25
+ "--no-analytics"
26
+ ], { stdio: "inherit" });
27
+ meProcess.on("close", (code) => {
28
+ if (code === 0) resolve$1();
29
+ else reject(/* @__PURE__ */ new Error(`Authentication failed with exit code ${code}`));
30
+ });
31
+ meProcess.on("error", reject);
32
+ });
33
+ return true;
34
+ } catch (error) {
35
+ log.error(`Authentication failed: ${error instanceof Error ? error.message : "Unknown error"}`);
36
+ return false;
37
+ }
38
+ }
39
+ /**
40
+ * Gets the OAuth access token from neonctl's stored credentials
41
+ */
42
+ async function getNeonctlAccessToken() {
43
+ try {
44
+ const homeDir = process.env.HOME || process.env.USERPROFILE;
45
+ if (!homeDir) return null;
46
+ const credentialsPath = resolve(homeDir, ".config", "neonctl", "credentials.json");
47
+ if (!existsSync(credentialsPath)) return null;
48
+ return JSON.parse(readFileSync(credentialsPath, "utf-8")).access_token || null;
49
+ } catch {
50
+ return null;
51
+ }
52
+ }
53
+ /**
54
+ * Creates an API key using the Neon API with the OAuth token from neonctl
55
+ */
56
+ async function createApiKeyFromNeonctl() {
57
+ try {
58
+ const accessToken = await getNeonctlAccessToken();
59
+ if (!accessToken) {
60
+ log.error("Could not find OAuth token from neonctl");
61
+ return null;
62
+ }
63
+ const keyName = `neonctl-init-${(/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-").slice(0, -5)}`;
64
+ const response = await fetch("https://console.neon.tech/api/v2/api_keys", {
65
+ method: "POST",
66
+ headers: {
67
+ Authorization: `Bearer ${accessToken}`,
68
+ "Content-Type": "application/json"
69
+ },
70
+ body: JSON.stringify({ key_name: keyName })
71
+ });
72
+ if (!response.ok) {
73
+ const errorText = await response.text();
74
+ log.error(`Failed to create API key: ${response.status} ${errorText}`);
75
+ return null;
76
+ }
77
+ return (await response.json()).key || null;
78
+ } catch (error) {
79
+ log.error(`Failed to create API key: ${error instanceof Error ? error.message : "Unknown error"}`);
80
+ return null;
81
+ }
82
+ }
83
+ async function fetchOrganizations() {
84
+ try {
85
+ const { stdout } = await execAsync("npx -y neon orgs list --output json --no-analytics", { maxBuffer: 1024 * 1024 });
86
+ const data = JSON.parse(stdout);
87
+ return Array.isArray(data) ? data.map((org) => ({
88
+ id: org.id,
89
+ name: org.name || org.id
90
+ })) : [];
91
+ } catch (error) {
92
+ log.warn(`Unable to fetch organizations: ${error instanceof Error ? error.message : "Unknown error"}`);
93
+ return [];
94
+ }
95
+ }
96
+ /**
97
+ * Gets or creates the .cursor/mcp.json configuration
98
+ */
99
+ function getMCPConfig(cursorDir) {
100
+ const mcpConfigPath = resolve(cursorDir, "mcp.json");
101
+ if (existsSync(mcpConfigPath)) try {
102
+ const content = readFileSync(mcpConfigPath, "utf-8");
103
+ return JSON.parse(content);
104
+ } catch (_error) {
105
+ log.warn("Failed to parse existing mcp.json. Creating a new configuration.");
106
+ return { mcpServers: {} };
107
+ }
108
+ return { mcpServers: {} };
109
+ }
110
+ /**
111
+ * Writes the MCP configuration to .cursor/mcp.json
112
+ */
113
+ function writeMCPConfig(cursorDir, config) {
114
+ const mcpConfigPath = resolve(cursorDir, "mcp.json");
115
+ if (!existsSync(cursorDir)) mkdirSync(cursorDir, { recursive: true });
116
+ writeFileSync(mcpConfigPath, JSON.stringify(config, null, 2), "utf-8");
117
+ }
118
+ /**
119
+ * Gets the AGENTS.md template with Neon-specific best practices
120
+ */
121
+ function getAgentsMdTemplate() {
122
+ return readFileSync(resolve(__dirname, "../src/agents-template.md"), "utf-8");
123
+ }
124
+ /**
125
+ * Generates the organization configuration section for AGENTS.md
126
+ */
127
+ function getOrgConfigSection(orgId) {
128
+ return `## Neon Project Configuration
129
+
130
+ > **🔴 IMPORTANT: You MUST use this organization for all Neon operations in this project.**
131
+
132
+ **Organization ID:** \`${orgId}\`
133
+
134
+ When using any Neon MCP tools or API calls, always pass this \`org_id\` parameter. The MCP server is pre-configured with this organization.
135
+
136
+ **Example:**
137
+ - When listing projects: Use \`mcp_Neon_list_projects\` with \`org_id: "${orgId}"\`
138
+ - When creating resources: Always include \`org_id: "${orgId}"\` in your tool calls
139
+
140
+ ---
141
+
142
+ `;
143
+ }
144
+ /**
145
+ * Creates or updates AGENTS.md with Neon-specific best practices
146
+ */
147
+ async function createAgentsMd(orgId) {
148
+ const agentsPath = resolve(process.cwd(), "AGENTS.md");
149
+ try {
150
+ if (existsSync(agentsPath)) {
151
+ const existingContent = readFileSync(agentsPath, "utf-8");
152
+ if (existingContent.includes("## Neon Database Guidelines")) {
153
+ log.info("Neon guidelines already exist in AGENTS.md");
154
+ return true;
155
+ }
156
+ const separator = "\n\n---\n\n";
157
+ let content = "";
158
+ if (orgId) content += getOrgConfigSection(orgId);
159
+ content += getAgentsMdTemplate();
160
+ writeFileSync(agentsPath, existingContent + separator + content, "utf-8");
161
+ log.success(`Appended Neon best practices to existing AGENTS.md at ${agentsPath}`);
162
+ } else {
163
+ let content = "";
164
+ if (orgId) content += getOrgConfigSection(orgId);
165
+ content += getAgentsMdTemplate();
166
+ writeFileSync(agentsPath, `# AGENTS.md
167
+
168
+ This file provides guidance to AI coding assistants when working with code in this project.
169
+
170
+ ---
171
+
172
+ ${content}`, "utf-8");
173
+ log.success(`Created AGENTS.md with Neon best practices at ${agentsPath}`);
174
+ }
175
+ return true;
176
+ } catch (error) {
177
+ log.error(`Failed to create/update AGENTS.md: ${error instanceof Error ? error.message : "Unknown error"}`);
178
+ return false;
179
+ }
180
+ }
181
+ /**
182
+ * Installs Neon's MCP Server by configuring it globally in ~/.cursor/mcp.json
183
+ * Returns the selected organization ID if one was chosen
184
+ */
185
+ async function installMCPServer() {
186
+ const homeDir = process.env.HOME || process.env.USERPROFILE;
187
+ if (!homeDir) {
188
+ log.error("Could not determine home directory");
189
+ return { success: false };
190
+ }
191
+ const cursorDir = resolve(homeDir, ".cursor");
192
+ const config = getMCPConfig(cursorDir);
193
+ const alreadyConfigured = Boolean(config.mcpServers.Neon);
194
+ let shouldReconfigure = false;
195
+ if (alreadyConfigured) {
196
+ log.info("Neon MCP Server is already configured globally");
197
+ const response = await confirm({
198
+ message: "Would you like to reconfigure it?",
199
+ initialValue: false
200
+ });
201
+ if (isCancel(response)) return { success: false };
202
+ shouldReconfigure = response;
203
+ if (!shouldReconfigure) log.info("Keeping existing global configuration.");
204
+ }
205
+ log.step("Authenticating with Neon...");
206
+ log.info("The authentication URL will be displayed below if needed.");
207
+ log.info("");
208
+ const authSpinner = spinner();
209
+ authSpinner.start("Waiting for authentication...");
210
+ if (!await ensureNeonctlAuth()) {
211
+ authSpinner.stop("Authentication failed");
212
+ return { success: false };
213
+ }
214
+ authSpinner.stop("Authentication successful ✓");
215
+ let selectedOrgId;
216
+ const orgSpinner = spinner();
217
+ orgSpinner.start("Fetching your organizations...");
218
+ const organizations = await fetchOrganizations();
219
+ orgSpinner.stop(`Found ${organizations.length} organization${organizations.length !== 1 ? "s" : ""}`);
220
+ if (organizations.length > 1) {
221
+ const orgChoice = await select({
222
+ message: "Select an organization for this project:",
223
+ options: organizations.map((org) => ({
224
+ value: org.id,
225
+ label: org.name
226
+ }))
227
+ });
228
+ if (isCancel(orgChoice)) return { success: false };
229
+ selectedOrgId = orgChoice.toString();
230
+ const selectedOrg = organizations.find((org) => org.id === selectedOrgId);
231
+ log.success(`Selected organization: ${selectedOrg?.name}`);
232
+ } else if (organizations.length === 1) {
233
+ selectedOrgId = organizations[0].id;
234
+ log.info(`Using organization: ${organizations[0].name}`);
235
+ } else log.info("Using personal account");
236
+ if (alreadyConfigured && !shouldReconfigure) return {
237
+ success: true,
238
+ orgId: selectedOrgId
239
+ };
240
+ const s = spinner();
241
+ s.start("Creating API key...");
242
+ const apiKey = await createApiKeyFromNeonctl();
243
+ s.stop(apiKey ? "API key created successfully ✓" : "Failed to create API key");
244
+ if (!apiKey) {
245
+ log.error("Could not create API key after authentication.");
246
+ log.info("You can manually create one at: https://console.neon.tech/app/settings/api-keys");
247
+ return { success: false };
248
+ }
249
+ const args = [
250
+ "-y",
251
+ "@neondatabase/mcp-server-neon",
252
+ "start",
253
+ apiKey
254
+ ];
255
+ config.mcpServers.Neon = {
256
+ command: "npx",
257
+ args
258
+ };
259
+ try {
260
+ writeMCPConfig(cursorDir, config);
261
+ log.success(`Neon MCP Server configured globally at ${resolve(cursorDir, "mcp.json")}`);
262
+ log.info("This configuration will be available in all your projects.");
263
+ return {
264
+ success: true,
265
+ orgId: selectedOrgId
266
+ };
267
+ } catch (error) {
268
+ log.error(`Failed to write global configuration: ${error instanceof Error ? error.message : "Unknown error"}`);
269
+ return { success: false };
270
+ }
271
+ }
272
+ /**
273
+ * Initialize Neon projects with MCP Server and AI assistant rules
274
+ */
275
+ async function init() {
276
+ intro("🚀 Neon Project Initialization");
277
+ log.info("This will set up your project with Neon's MCP Server and AI coding best practices.");
278
+ log.step("Step 1/2: Configuring Neon MCP Server");
279
+ const { success: mcpSuccess, orgId } = await installMCPServer();
280
+ if (!mcpSuccess) {
281
+ outro("❌ Initialization cancelled or failed.");
282
+ process.exit(1);
283
+ }
284
+ log.step("Step 2/2: Creating AGENTS.md with Neon best practices");
285
+ if (!await createAgentsMd(orgId)) log.warn("Failed to create AGENTS.md, but MCP Server is configured.");
286
+ outro("Success! Neon project initialized.");
287
+ console.log("");
288
+ console.log("━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━");
289
+ console.log("");
290
+ console.log("Next steps:");
291
+ console.log("");
292
+ console.log(" 1. Restart your AI coding assistant (Cursor, Windsurf, etc.)");
293
+ console.log("");
294
+ console.log(" 2. Type this in your AI chat to begin:");
295
+ console.log("");
296
+ console.log(" ┌──────────────────────────────────────┐");
297
+ console.log(" │ │");
298
+ console.log(" │ Get started with Neon │");
299
+ console.log(" │ │");
300
+ console.log(" └──────────────────────────────────────┘");
301
+ console.log("");
302
+ console.log("Your AI assistant now has access to Neon best practices via AGENTS.md");
303
+ console.log("");
304
+ console.log("━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━");
4
305
  }
5
306
 
6
307
  //#endregion
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","names":[],"sources":["../src/index.ts"],"sourcesContent":["export function init() {\n\tconsole.log(\"Hello world from `neon-init`!\");\n}\n"],"mappings":";AAAA,SAAgB,OAAO;AACtB,SAAQ,IAAI,gCAAgC"}
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\";\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\tcommand: string;\n\t\t\targs: string[];\n\t\t\tenv?: 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 spawn to show OAuth URL if authentication is needed\n\t\tconst { spawn } = await import(\"node:child_process\");\n\n\t\tawait new Promise<void>((resolve, reject) => {\n\t\t\tconst meProcess = spawn(\n\t\t\t\t\"npx\",\n\t\t\t\t[\"-y\", \"neon\", \"me\", \"--output\", \"json\", \"--no-analytics\"],\n\t\t\t\t{\n\t\t\t\t\tstdio: \"inherit\", // Shows OAuth URL and prompts to the user\n\t\t\t\t},\n\t\t\t);\n\n\t\t\tmeProcess.on(\"close\", (code) => {\n\t\t\t\tif (code === 0) {\n\t\t\t\t\tresolve();\n\t\t\t\t} else {\n\t\t\t\t\treject(\n\t\t\t\t\t\tnew Error(\n\t\t\t\t\t\t\t`Authentication failed with exit code ${code}`,\n\t\t\t\t\t\t),\n\t\t\t\t\t);\n\t\t\t\t}\n\t\t\t});\n\n\t\t\tmeProcess.on(\"error\", reject);\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 neon 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 AGENTS.md template with Neon-specific best practices\n */\nfunction getAgentsMdTemplate(): 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 AGENTS.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 * Creates or updates AGENTS.md with Neon-specific best practices\n */\nasync function createAgentsMd(orgId?: string): Promise<boolean> {\n\tconst agentsPath = resolve(process.cwd(), \"AGENTS.md\");\n\n\ttry {\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(\"## Neon Database Guidelines\")) {\n\t\t\t\tlog.info(\"Neon guidelines already exist 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\tlet content = \"\";\n\n\t\t\t// Add org ID context if provided\n\t\t\tif (orgId) {\n\t\t\t\tcontent += getOrgConfigSection(orgId);\n\t\t\t}\n\n\t\t\tcontent += getAgentsMdTemplate();\n\n\t\t\tconst updatedContent = existingContent + separator + content;\n\t\t\twriteFileSync(agentsPath, updatedContent, \"utf-8\");\n\t\t\tlog.success(\n\t\t\t\t`Appended Neon best practices to existing AGENTS.md at ${agentsPath}`,\n\t\t\t);\n\t\t} else {\n\t\t\t// Create new file with proper header\n\t\t\tlet content = \"\";\n\n\t\t\t// Add org ID context if provided\n\t\t\tif (orgId) {\n\t\t\t\tcontent += getOrgConfigSection(orgId);\n\t\t\t}\n\n\t\t\tcontent += getAgentsMdTemplate();\n\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${content}`;\n\t\t\twriteFileSync(agentsPath, newContent, \"utf-8\");\n\t\t\tlog.success(\n\t\t\t\t`Created AGENTS.md with Neon best practices at ${agentsPath}`,\n\t\t\t);\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\tlog.info(\"Neon MCP Server is already configured globally\");\n\t\tconst response = await confirm({\n\t\t\tmessage: \"Would you like to reconfigure it?\",\n\t\t\tinitialValue: false,\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// Step 1: Ensure authentication (will trigger OAuth if needed)\n\tlog.step(\"Authenticating with Neon...\");\n\tlog.info(\"The authentication URL will be displayed below if needed.\");\n\tlog.info(\"\");\n\n\tconst authSpinner = spinner();\n\tauthSpinner.start(\"Waiting for authentication...\");\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// Step 2: Fetch organizations and let user select\n\tlet selectedOrgId: string | undefined;\n\n\tconst orgSpinner = spinner();\n\torgSpinner.start(\"Fetching your organizations...\");\n\tconst organizations = await fetchOrganizations();\n\torgSpinner.stop(\n\t\t`Found ${organizations.length} organization${organizations.length !== 1 ? \"s\" : \"\"}`,\n\t);\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\tconst selectedOrg = organizations.find(\n\t\t\t(org) => org.id === selectedOrgId,\n\t\t);\n\t\tlog.success(`Selected organization: ${selectedOrg?.name}`);\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// Step 3: Create API key using the OAuth token\n\tconst s = spinner();\n\ts.start(\"Creating API key...\");\n\tconst apiKey = await createApiKeyFromNeonctl();\n\ts.stop(\n\t\tapiKey ? \"API key created successfully ✓\" : \"Failed to create API key\",\n\t);\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\tconst args = [\"-y\", \"@neondatabase/mcp-server-neon\", \"start\", apiKey];\n\n\tconfig.mcpServers.Neon = {\n\t\tcommand: \"npx\",\n\t\targs,\n\t};\n\n\t// Write configuration\n\ttry {\n\t\twriteMCPConfig(cursorDir, config);\n\t\tlog.success(\n\t\t\t`Neon MCP Server configured globally at ${resolve(cursorDir, \"mcp.json\")}`,\n\t\t);\n\t\tlog.info(\"This configuration will be available in all your projects.\");\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(\"🚀 Neon Project Initialization\");\n\n\tlog.info(\n\t\t\"This will set up your project with Neon's MCP Server and AI coding best practices.\",\n\t);\n\n\tlog.step(\"Step 1/2: Configuring Neon MCP Server\");\n\tconst { success: mcpSuccess, orgId } = await installMCPServer();\n\n\tif (!mcpSuccess) {\n\t\toutro(\"❌ Initialization cancelled or failed.\");\n\t\tprocess.exit(1);\n\t}\n\n\tlog.step(\"Step 2/2: Creating AGENTS.md with Neon best practices\");\n\tconst agentsSuccess = await createAgentsMd(orgId);\n\n\tif (!agentsSuccess) {\n\t\tlog.warn(\"Failed to create AGENTS.md, but MCP Server is configured.\");\n\t}\n\n\toutro(\"Success! Neon project initialized.\");\n\tconsole.log(\"\");\n\tconsole.log(\n\t\t\"━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\",\n\t);\n\tconsole.log(\"\");\n\tconsole.log(\"Next steps:\");\n\tconsole.log(\"\");\n\tconsole.log(\n\t\t\" 1. Restart your AI coding assistant (Cursor, Windsurf, etc.)\",\n\t);\n\tconsole.log(\"\");\n\tconsole.log(\" 2. Type this in your AI chat to begin:\");\n\tconsole.log(\"\");\n\tconsole.log(\" ┌──────────────────────────────────────┐\");\n\tconsole.log(\" │ │\");\n\tconsole.log(\" │ Get started with Neon │\");\n\tconsole.log(\" │ │\");\n\tconsole.log(\" └──────────────────────────────────────┘\");\n\tconsole.log(\"\");\n\tconsole.log(\n\t\t\"Your AI assistant now has access to Neon best practices via AGENTS.md\",\n\t);\n\tconsole.log(\"\");\n\tconsole.log(\n\t\t\"━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\",\n\t);\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.4.0",
3
+ "version": "0.4.1",
4
4
  "description": "Initialize Neon projects",
5
5
  "keywords": [
6
6
  "neon",