neon 4.3.0 → 4.4.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
@@ -736,7 +736,7 @@ $ neon mcp -y
736
736
  # OAuth: no API key minted. The agent prompts for Neon sign-in on first use.
737
737
  $ neon mcp --oauth
738
738
 
739
- # Project-level config. A minted key is still account-wide.
739
+ # Project-level config. A minted key is still account-wide unless a project is pinned.
740
740
  $ neon mcp --project
741
741
 
742
742
  $ neon mcp --agent cursor --agent claude-code
@@ -744,7 +744,7 @@ $ neon mcp --agent cursor --agent claude-code
744
744
  # Hide write tools. Does not change the minted key.
745
745
  $ neon mcp --read-only
746
746
 
747
- # Pin MCP tools to one project. Does not change the minted key.
747
+ # Pin MCP tools to one project. A newly minted API key is limited to that project.
748
748
  $ neon mcp --project-id <project-id>
749
749
 
750
750
  # Limit which tool categories are visible.
@@ -753,11 +753,11 @@ $ neon mcp --category querying --category schema
753
753
 
754
754
  On a TTY the command asks for config location (global is the default), then agents, then API key vs OAuth, then a summary to confirm before it writes. Detected agents start selected: globally installed agents or project-folder markers such as `.cursor` when the install is project.
755
755
 
756
- `-y` skips those questions. `--project`, `--oauth` and `--agent` skip the question they answer and still apply with `-y`. `--read-only` and `--category` are flags only and are never prompted. A linked project-folder install asks whether to pin MCP tools to that `.neon` project (`?projectId=`). An unlinked project folder does not ask. Global installs never add that param unless you pass `--project-id`. Without a TTY, pass `-y` to mint into every detected agent, `--agent` to name the agents or `--oauth` to write the URL only.
756
+ `-y` skips those questions. `--project`, `--oauth` and `--agent` skip the question they answer and still apply with `-y`. `--read-only` and `--category` are flags only and are never prompted. A linked project-folder install asks whether to pin MCP tools to that `.neon` project (`?projectId=`). If you pin and selected API-key auth, the minted key is limited to that project too. An unlinked project folder does not ask. Global installs never add that param unless you pass `--project-id`. `-y` does not infer a project from `.neon`. Without a TTY, pass `-y` to mint into every detected agent, `--agent` to name the agents or `--oauth` to write the URL only.
757
757
 
758
758
  `--agent` names: `antigravity`, `cline`, `cline-cli`, `claude-code`, `codex`, `cursor`, `gemini-cli`, `goose`, `github-copilot-cli`, `grok-build`, `mcporter`, `opencode`, `vscode`, `windsurf`, `zed`. Project installs drop `antigravity`, `cline`, `cline-cli`, `goose` and `windsurf`. `claude-desktop` is a known name that is then skipped.
759
759
 
760
- The default mints an account-wide API key (or reuses the Bearer already configured for Neon at `https://mcp.neon.tech/mcp`) and writes it into each selected agent's config. That key reaches everything the account can, in every organization. Revoke it with `neon api-keys revoke <id>`. `--oauth` writes the URL with no `Authorization` header; the agent signs in on first use. `--project` writes into the project config (`.cursor/mcp.json` and similar). `--read-only` adds `?readonly=true`. `--project-id` adds `?projectId=`. `--category` adds `?category=` (repeatable or comma-separated: `projects`, `branches`, `schema`, `querying`, `neon_auth`, `data_api`, `observability`, `docs`). Those query params restrict which MCP tools the server exposes; they do not change what the minted key can do.
760
+ The default mints an account-wide API key (or reuses the Bearer already configured for Neon at `https://mcp.neon.tech/mcp`) and writes it into each selected agent's config. That key reaches everything the account can, in every organization. Revoke it with `neon api-keys revoke <id>`. `--oauth` writes the URL with no `Authorization` header; the agent signs in on first use. `--project` writes into the project config (`.cursor/mcp.json` and similar). `--read-only` adds `?readonly=true`. `--project-id` adds `?projectId=` and, when a key is minted, limits that key to the named project. Accepting the linked-project pin does the same. Revoke a project-scoped key with `neon api-keys revoke <id> --org-id <org>`. A reused Bearer keeps the scope it already has. `--category` adds `?category=` (repeatable or comma-separated: `projects`, `branches`, `schema`, `querying`, `neon_auth`, `data_api`, `observability`, `docs`). `--read-only` and `--category` restrict which MCP tools the server exposes; they do not change what the minted key can do.
761
761
 
762
762
  ## Install Neon agent skills (`skills`)
763
763
 
@@ -791,6 +791,34 @@ On a TTY the command asks which agents and which skills, then shows a summary to
791
791
 
792
792
  `--agent` names match `neon mcp`, minus agents that cannot install skills: `antigravity`, `cline`, `cline-cli`, `claude-code`, `claude-desktop`, `codex`, `cursor`, `gemini-cli`, `goose`, `github-copilot-cli`, `grok-build`, `opencode`, `vscode`, `windsurf`, `zed`. `mcporter` is a known MCP name that is then skipped.
793
793
 
794
+ ## Install the Neon plugin (`plugins`)
795
+
796
+ `neon plugins` installs the Neon agent plugin (`neon-postgres`) by running `npx plugins add`. It does not call the Neon API.
797
+
798
+ ```bash
799
+ # Interactive: agents, then confirm.
800
+ $ neon plugins
801
+
802
+ # Skip prompts. Every detected agent.
803
+ $ neon plugins -y
804
+
805
+ $ neon plugins --agent cursor --agent claude-code
806
+
807
+ # User-level install.
808
+ $ neon plugins --global
809
+ $ neon plugins --global --agent vscode
810
+ ```
811
+
812
+ On a TTY the command asks which agents, then shows a summary to confirm. Detected agents start selected from project-folder markers such as `.cursor`. There is one plugin (`neon-postgres`); there is no plugin picker and no `update` subcommand.
813
+
814
+ `-y` skips those questions and installs into every detected agent. `--agent` names specific agents and skips the agent picker. Without a TTY, pass `-y` or `--agent`. `--agent` alone is enough because the plugin is fixed.
815
+
816
+ Default scope is `project`. `--global` is `user`. On macOS and Linux, Cursor and Claude Code store the plugin cache under `~/.claude/plugins`; on Windows, Cursor installs into Cursor extensions. `project` vs `user` is the scope field the plugins CLI records, not a directory in the repo. VS Code, GitHub Copilot CLI, and Grok Build only install user-level: they are skipped at the default scope with a warning, and `--agent vscode` without `--global` fails if nothing else is selected.
817
+
818
+ `--agent` names with a plugins mapping: `claude-code`, `claude-desktop`, `codex`, `cursor`, `github-copilot-cli`, `grok-build`, `vscode`. `claude-desktop` installs as Claude Code; naming both produces one install and lists both names in the table. `mcporter` is a known MCP name that is then skipped.
819
+
820
+ The plugins CLI installs every plugin it finds in the Neon plugin package. Today that is `neon-postgres`.
821
+
794
822
  ## Snapshots (`snapshots`)
795
823
 
796
824
  `neon snapshots` (alias `neon snapshot`) manages **snapshots** — point-in-time backups of a branch that you can list, rename, expire, restore into a branch, or schedule automatically. Snapshots are a Beta Neon feature and were previously only available in the Console and REST API; this command group brings them to the CLI.
@@ -1171,6 +1199,7 @@ Id Name Project Created At Last Used At Last
1171
1199
  | bootstrap | | Scaffold a project from a template |
1172
1200
  | init | | Set up a project for a coding agent |
1173
1201
  | mcp | | Install the Neon MCP server |
1202
+ | plugins | | Install the Neon plugin |
1174
1203
  | skills | `update` | Install Neon agent skills |
1175
1204
  | bucket | `create`, `list`, `delete`, `object list`, `object get`, `object put`, `object delete` (incl. `--recursive`) | Manage buckets and their objects |
1176
1205
  | [completion](https://neon.com/docs/reference/cli-completion) | | Generate a completion script |
package/dist/analytics.js CHANGED
@@ -1,5 +1,5 @@
1
1
  import { a as OAUTH, t as storeFor } from "./_chunks/credential_io-D2vw1UOw.js";
2
- import { getGithubEnvVars, isCi } from "./env.js";
2
+ import { getCliAgent, getGithubEnvVars, isCi } from "./env.js";
3
3
  import { log } from "./log.js";
4
4
  import pkg_default from "./pkg.js";
5
5
  import { getApiClient, isNeonApiError } from "./api.js";
@@ -190,13 +190,15 @@ const trackEvent = (event, properties) => {
190
190
  };
191
191
  const getErrorAnalyticsEventContext = (_args) => ({
192
192
  version: pkg_default.version,
193
- ci: isCi()
193
+ ci: isCi(),
194
+ agent: getCliAgent(process.env)
194
195
  });
195
196
  const getAnalyticsEventProperties = (args) => ({
196
197
  version: pkg_default.version,
197
198
  command: args._.join(" "),
198
199
  flags: { output: args.output },
199
200
  ci: isCi(),
201
+ agent: getCliAgent(process.env),
200
202
  githubEnvVars: getGithubEnvVars(process.env)
201
203
  });
202
204
  //#endregion
@@ -265,16 +265,16 @@ const revoke = async (props) => {
265
265
  * scoped key — the endpoint that accepts `project_id` is org-only — so that case fails here
266
266
  * with the reason, rather than as a 404 from a URL the user never typed.
267
267
  */
268
- const orgIdForProject = async (client, projectId) => {
268
+ const orgIdForProject = async (client, projectId, usage = "api-keys") => {
269
269
  let orgId;
270
270
  try {
271
271
  const { data: { project } } = await client.getProject(projectId);
272
272
  orgId = project.org_id;
273
273
  } catch (err) {
274
- if (isNeonApiError(err) && err.status === 404) throw new Error(projectId.startsWith("org-") ? `Project ${projectId} not found. That looks like an organization id. Pass it as --org-id instead.` : `Project ${projectId} not found. Check the id with \`neon projects list\`.`);
274
+ if (isNeonApiError(err) && err.status === 404) throw new Error(projectId.startsWith("org-") ? usage === "mcp" ? `Project ${projectId} not found. That looks like an organization id. neon mcp takes a project id on --project-id.` : `Project ${projectId} not found. That looks like an organization id. Pass it as --org-id instead.` : `Project ${projectId} not found. Check the id with \`neon projects list\`.`);
275
275
  throw err;
276
276
  }
277
- if (!orgId) throw new Error(`Project ${projectId} does not belong to an organization, so it cannot have a project-scoped API key. Omit --project-id to create an account key.`);
277
+ if (!orgId) throw new Error(usage === "mcp" ? `Project ${projectId} does not belong to an organization, so it cannot have a project-scoped API key. Pass --oauth to pin tools without minting, or omit --project-id to mint an account-wide key.` : `Project ${projectId} does not belong to an organization, so it cannot have a project-scoped API key. Omit --project-id to create an account key.`);
278
278
  return orgId;
279
279
  };
280
280
  //#endregion
@@ -6,7 +6,7 @@ import { log } from "../log.js";
6
6
  import { getApiClient } from "../api.js";
7
7
  import { o as isOwnedCredentialPath } from "../_chunks/paths-g3DRqJiD.js";
8
8
  import { setAuthContext } from "../auth_context.js";
9
- import { isConfigInit, isCurrentBranchProbe, isMcpCommand, isMcpOauth, isProfileCommand, isSkillsCommand } from "../context.js";
9
+ import { isConfigInit, isCurrentBranchProbe, isMcpCommand, isMcpOauth, isPluginsCommand, isProfileCommand, isSkillsCommand } from "../context.js";
10
10
  import { extendTokenSet } from "../utils/auth.js";
11
11
  import { auth, refreshToken } from "../auth.js";
12
12
  import { i as selectCredential, n as displacedProfileWarning, t as credentialInputs } from "../_chunks/auth_selection-Bx2NAgkk.js";
@@ -217,7 +217,7 @@ const ensureAuth = async (props) => {
217
217
  if (isProfileCommand(props)) return;
218
218
  if (isMcpOauth(props)) return;
219
219
  if (props._[0] === "open") return;
220
- if (isSkillsCommand(props)) return;
220
+ if (isSkillsCommand(props) || isPluginsCommand(props)) return;
221
221
  const isLocalDev = props._[0] === "dev";
222
222
  const isBootstrap = props._[0] === "bootstrap";
223
223
  const isInit = props._[0] === "init";
@@ -25,6 +25,7 @@ import { t as neon_auth_exports } from "./neon_auth.js";
25
25
  import { t as open_exports } from "./open.js";
26
26
  import { t as operations_exports } from "./operations.js";
27
27
  import { t as orgs_exports } from "./orgs.js";
28
+ import { t as plugins_exports } from "./plugins.js";
28
29
  import { t as profile_exports } from "./profile.js";
29
30
  import { t as psql_exports } from "./psql.js";
30
31
  import { t as roles_exports } from "./roles.js";
@@ -61,6 +62,7 @@ var commands_default = [
61
62
  open_exports,
62
63
  init_exports,
63
64
  mcp_exports,
65
+ plugins_exports,
64
66
  skills_exports,
65
67
  data_api_exports,
66
68
  functions_exports,
@@ -5,7 +5,7 @@ import { getCliName } from "../utils/cli_name.js";
5
5
  import { writer } from "../writer.js";
6
6
  import { noPassthrough, single } from "../utils/flags.js";
7
7
  import { existingNeonApiKey, installNeonMcpServer, neonMcpUrl, parseMcpCategories, trackedProjectMcpConfig } from "../mcp/install.js";
8
- import { mintMcpApiKey, withdrawMintedKey } from "../mcp/mint.js";
8
+ import { mintMcpApiKey, mintedKeyRevokeCommand, withdrawMintedKey } from "../mcp/mint.js";
9
9
  import { canPickAgentsInteractively } from "../utils/agent_picker.js";
10
10
  import { resolveInstallTargets } from "../mcp/targets.js";
11
11
  import { confirmMcpInstall } from "../mcp/wizard.js";
@@ -28,7 +28,7 @@ const builder = (argv) => argv.usage("$0 mcp [options]").options({
28
28
  project: {
29
29
  type: "boolean",
30
30
  default: false,
31
- describe: "Write project-level MCP config. Skips the config-location question. Does not change the minted key"
31
+ describe: "Write project-level MCP config. Skips the config-location question. A linked project-folder install may still pin a project and scope a newly minted key"
32
32
  },
33
33
  yes: {
34
34
  alias: "y",
@@ -59,7 +59,7 @@ const builder = (argv) => argv.usage("$0 mcp [options]").options({
59
59
  },
60
60
  "project-id": {
61
61
  type: "string",
62
- describe: "Pin MCP tools to one Neon project (?projectId=). Does not change the minted key. Interactive asks only for a linked project-folder install",
62
+ describe: "Pin MCP tools to one Neon project (?projectId=). A newly minted API key is limited to that project. A linked project-folder install asks the same when you pick API-key auth",
63
63
  coerce: single("project-id")
64
64
  },
65
65
  category: {
@@ -131,7 +131,8 @@ const handler = async (props) => {
131
131
  skipped,
132
132
  auth: plan.auth,
133
133
  reuse: existing !== void 0,
134
- url
134
+ url,
135
+ mintProjectId: plan.urlProjectId
135
136
  })) {
136
137
  log.info("Aborted. Nothing was written.");
137
138
  return;
@@ -146,13 +147,18 @@ const handler = async (props) => {
146
147
  apiKey: existing
147
148
  };
148
149
  log.info("Reusing the API key already configured for the Neon MCP server.");
150
+ if (plan.urlProjectId) log.warning("That key keeps its existing scope. Remove the Neon MCP entry to mint a project-scoped key, or pass --oauth to pin tools without a key.");
149
151
  } else {
150
- minted = await mintMcpApiKey({ apiClient: props.apiClient });
152
+ minted = await mintMcpApiKey({
153
+ apiClient: props.apiClient,
154
+ projectId: plan.urlProjectId
155
+ });
151
156
  auth = {
152
157
  kind: "api-key",
153
158
  apiKey: minted.key
154
159
  };
155
- log.warning("This key reaches everything your account can, in every organization.");
160
+ if (minted.projectId) log.info("Limited to %s: it cannot create projects, mint API keys, or read any other project. It can still change and delete everything inside that project.", minted.projectId);
161
+ else log.warning("This key reaches everything your account can, in every organization.");
156
162
  }
157
163
  const rows = [];
158
164
  let successes = 0;
@@ -189,7 +195,7 @@ const handler = async (props) => {
189
195
  if (successes === 0) {
190
196
  if (minted && props.apiClient) {
191
197
  const withdrawn = await withdrawMintedKey(props.apiClient, minted);
192
- throw new Error(withdrawn ? "Failed to write Neon MCP config to any agent. The minted API key has been revoked." : `Failed to write Neon MCP config to any agent. The minted API key could NOT be revoked. Remove it with \`${getCliName()} api-keys revoke ${minted.id}\`.`);
198
+ throw new Error(withdrawn ? "Failed to write Neon MCP config to any agent. The minted API key has been revoked." : `Failed to write Neon MCP config to any agent. The minted API key could NOT be revoked. Remove it with \`${mintedKeyRevokeCommand(minted)}\`.`);
193
199
  }
194
200
  throw new Error("Failed to write Neon MCP config to any agent.");
195
201
  }
@@ -204,7 +210,7 @@ const handler = async (props) => {
204
210
  title: "MCP"
205
211
  });
206
212
  out.end();
207
- if (minted) log.info("Minted API key %s (id %d, account). Revoke with: %s", minted.name, minted.id, `${getCliName()} api-keys revoke ${minted.id}`);
213
+ if (minted) log.info("Minted API key %s (id %d, %s). Revoke with: %s", minted.name, minted.id, minted.projectId ? "project" : "account", mintedKeyRevokeCommand(minted));
208
214
  if (plan.auth === "oauth") log.info("The agent will prompt for Neon sign-in on first use.");
209
215
  if (failedAgents.length > 0) throw new Error(`Failed to write Neon MCP config for: ${failedAgents.join(", ")}.`);
210
216
  };
@@ -0,0 +1,135 @@
1
+ import { t as __exportAll } from "../_chunks/rolldown-runtime-8H4AJuhK.js";
2
+ import { log } from "../log.js";
3
+ import { writer } from "../writer.js";
4
+ import { noPassthrough } from "../utils/flags.js";
5
+ import { getAgentDisplayName } from "../mcp/agents.js";
6
+ import "../init/agents.js";
7
+ import { canPickAgentsInteractively } from "../utils/agent_picker.js";
8
+ import { resolvePluginsPlan } from "../plugins/plan.js";
9
+ import { NEON_PLUGIN_NAME, neonPluginsRetryCommand, pluginsAddArgs, runPluginsCli } from "../plugins/run.js";
10
+ import { confirmPluginsInstall } from "../plugins/wizard.js";
11
+ //#region src/commands/plugins.ts
12
+ var plugins_exports = /* @__PURE__ */ __exportAll({
13
+ builder: () => builder,
14
+ command: () => command,
15
+ describe: () => describe,
16
+ handler: () => handler
17
+ });
18
+ const scopeLabel = (scope) => scope === "project" ? "project" : "user";
19
+ const coerceAgents = (value) => {
20
+ if (value === void 0) return [];
21
+ const list = Array.isArray(value) ? value : [value];
22
+ if (list.length === 0) throw new Error("--agent needs a value. Pass one, or omit the flag entirely.");
23
+ return list.map((item) => {
24
+ if (typeof item !== "string" || item.trim() === "") throw new Error("--agent needs a value. Pass one, or omit the flag entirely.");
25
+ return item;
26
+ });
27
+ };
28
+ const command = "plugins";
29
+ const describe = "Install the Neon plugin into coding agents";
30
+ const builder = (argv) => argv.usage("$0 plugins [options]").options({
31
+ yes: {
32
+ alias: "y",
33
+ type: "boolean",
34
+ default: false,
35
+ describe: "Skip prompts"
36
+ },
37
+ global: {
38
+ type: "boolean",
39
+ default: false,
40
+ describe: "Install user-level. Default is project"
41
+ },
42
+ agent: {
43
+ alias: "a",
44
+ type: "array",
45
+ string: true,
46
+ describe: "Coding agent to install into (repeatable). Skips the agent picker",
47
+ coerce: coerceAgents
48
+ }
49
+ }).example("$0 plugins", "Interactive: agents, then confirm").example("$0 plugins -y", "Detected agents, skip prompts").example("$0 plugins --agent cursor --agent claude-code", "Install into specific agents").example("$0 plugins --global", "Install user-level").strict().check(noPassthrough("plugins"));
50
+ const handler = async (props) => {
51
+ const cwd = process.cwd();
52
+ const yes = props.yes === true;
53
+ const interactive = canPickAgentsInteractively() && !yes;
54
+ const plan = await resolvePluginsPlan({
55
+ global: props.global === true,
56
+ agents: props.agent ?? [],
57
+ yes,
58
+ cwd,
59
+ interactive
60
+ });
61
+ for (const agent of plan.skipped) log.warning("Skipping %s: no plugins mapping.", getAgentDisplayName(agent));
62
+ for (const agent of plan.userScopeSkipped) log.warning("Skipping %s: plugins are user-level. Pass --global.", getAgentDisplayName(agent));
63
+ if (interactive) {
64
+ if (!await confirmPluginsInstall({
65
+ scope: plan.scope,
66
+ agents: plan.agents
67
+ })) {
68
+ log.info("Aborted. Nothing was written.");
69
+ return;
70
+ }
71
+ }
72
+ const rows = [];
73
+ const failed = [];
74
+ const scope = scopeLabel(plan.scope);
75
+ for (const mapped of plan.targets) {
76
+ const args = pluginsAddArgs({
77
+ target: mapped.target,
78
+ global: plan.scope === "global"
79
+ });
80
+ const agent = mapped.agents.join(", ");
81
+ try {
82
+ await runPluginsCli({
83
+ args,
84
+ cwd
85
+ });
86
+ rows.push({
87
+ scope,
88
+ plugin: NEON_PLUGIN_NAME,
89
+ agent,
90
+ status: "installed"
91
+ });
92
+ } catch (error) {
93
+ const message = error instanceof Error ? error.message : String(error);
94
+ rows.push({
95
+ scope,
96
+ plugin: NEON_PLUGIN_NAME,
97
+ agent,
98
+ status: "failed",
99
+ error: "plugins CLI failed"
100
+ });
101
+ failed.push({
102
+ agents: mapped.agents,
103
+ message
104
+ });
105
+ }
106
+ }
107
+ const out = writer(props);
108
+ out.write(rows, {
109
+ fields: [
110
+ "scope",
111
+ "plugin",
112
+ "agent",
113
+ "status",
114
+ "error"
115
+ ],
116
+ title: "Plugins"
117
+ });
118
+ out.end();
119
+ if (failed.length === 0) {
120
+ log.info(plan.scope === "project" ? "Installed the Neon plugin (project)." : "Installed the Neon plugin (user).");
121
+ return;
122
+ }
123
+ const first = failed[0];
124
+ if (first === void 0) throw new Error("Failed to install the Neon plugin.");
125
+ const retry = neonPluginsRetryCommand({
126
+ agents: failed.flatMap((row) => row.agents),
127
+ global: plan.scope === "global"
128
+ });
129
+ if (first.message.includes("needs npx (Node.js)")) throw new Error(first.message);
130
+ const detail = failed.map((row) => row.message).join("\n");
131
+ if (failed.length === rows.length) throw new Error(`${detail}\nRetry with: ${retry}`);
132
+ throw new Error(`Failed to install the Neon plugin for: ${failed.flatMap((row) => row.agents).join(", ")}.\n${detail}\nRetry with: ${retry}`);
133
+ };
134
+ //#endregion
135
+ export { builder, command, describe, handler, plugins_exports as t };
package/dist/context.js CHANGED
@@ -52,6 +52,7 @@ const isProfileCommand = (args) => args._[0] === "profile" || args._[0] === "pro
52
52
  const isApiKeysCommand = (args) => args._[0] === "api-keys" || args._[0] === "api-key";
53
53
  const isMcpCommand = (args) => args._[0] === "mcp";
54
54
  const isSkillsCommand = (args) => args._[0] === "skills";
55
+ const isPluginsCommand = (args) => args._[0] === "plugins";
55
56
  /** Raw argv is required because auth middleware runs before MCP flags are parsed. */
56
57
  const isMcpOauth = (args) => isMcpCommand(args) && argvEnablesMcpOauth(process.argv);
57
58
  const OAUTH_FALSE = /* @__PURE__ */ new Set([
@@ -131,7 +132,7 @@ const enrichFromContext = (args) => {
131
132
  if (isApiKeysCommand(args)) return;
132
133
  if (isProfileCommand(args)) return;
133
134
  if (isMcpCommand(args)) return;
134
- if (isSkillsCommand(args)) return;
135
+ if (isSkillsCommand(args) || isPluginsCommand(args)) return;
135
136
  const context = readContextFile(args.contextFile);
136
137
  if (!args.orgId) args.orgId = context.orgId;
137
138
  if (!args.projectId) args.projectId = context.projectId;
@@ -237,4 +238,4 @@ const globToRegExp = (pattern) => {
237
238
  return new RegExp(`^${source}$`);
238
239
  };
239
240
  //#endregion
240
- export { applyContext, contextBranch, currentContextFile, enrichFromContext, ensureGitignored, isApiKeysCommand, isConfigInit, isCurrentBranchProbe, isMcpCommand, isMcpOauth, isProfileCommand, isSkillsCommand, readContextFile, setContext, updateContextFile, walkContextFile };
241
+ export { applyContext, contextBranch, currentContextFile, enrichFromContext, ensureGitignored, isApiKeysCommand, isConfigInit, isCurrentBranchProbe, isMcpCommand, isMcpOauth, isPluginsCommand, isProfileCommand, isSkillsCommand, readContextFile, setContext, updateContextFile, walkContextFile };
package/dist/env.js CHANGED
@@ -5,6 +5,11 @@ const isCi = () => {
5
5
  const isDebug = () => {
6
6
  return Boolean(process.env.DEBUG);
7
7
  };
8
+ const getCliAgent = (env) => {
9
+ const claudeCode = env.CLAUDE_CODE_CHILD_SESSION === "1";
10
+ if (claudeCode === (env.CODEX_CI === "1" && (Boolean(env.CODEX_THREAD_ID?.trim()) || Boolean(env.CODEX_SESSION_ID?.trim())))) return void 0;
11
+ return claudeCode ? "claude-code" : "codex";
12
+ };
8
13
  const getGithubEnvVars = (env) => {
9
14
  const vars = [
10
15
  "GITHUB_ACTION_PATH",
@@ -27,4 +32,4 @@ const getGithubEnvVars = (env) => {
27
32
  return Object.fromEntries(map);
28
33
  };
29
34
  //#endregion
30
- export { getGithubEnvVars, isCi, isDebug };
35
+ export { getCliAgent, getGithubEnvVars, isCi, isDebug };
package/dist/index.js CHANGED
@@ -30,6 +30,7 @@ const NO_SUBCOMMANDS_VERBS = [
30
30
  "open",
31
31
  "init",
32
32
  "mcp",
33
+ "plugins",
33
34
  "skills",
34
35
  "dev",
35
36
  "deploy",
package/dist/mcp/mint.js CHANGED
@@ -1,24 +1,32 @@
1
1
  import { log } from "../log.js";
2
2
  import { isNeonApiError } from "../api.js";
3
3
  import { getCliName } from "../utils/cli_name.js";
4
+ import { orgIdForProject } from "../commands/api_keys.js";
4
5
  import { mintedKeyName } from "../profile_keys.js";
5
6
  //#region src/mcp/mint.ts
6
7
  const cannotMintMessage = "This CLI credential cannot mint API keys. Organization and project-scoped keys cannot create other keys. Sign in with `neon auth` or pass a personal API key.";
7
- const withdraw = async (client, keyId) => {
8
+ function mintedKeyRevokeCommand(key) {
9
+ return key.orgId ? `${getCliName()} api-keys revoke ${key.id} --org-id ${key.orgId}` : `${getCliName()} api-keys revoke ${key.id}`;
10
+ }
11
+ const withdraw = async (client, keyId, orgId) => {
8
12
  if (!Number.isSafeInteger(keyId) || keyId === void 0 || keyId <= 0) return false;
9
13
  try {
10
- const { data } = await client.revokeApiKey(keyId);
14
+ const { data } = orgId ? await client.revokeOrgApiKey(orgId, keyId) : await client.revokeApiKey(keyId);
11
15
  return data.revoked === true && data.id === keyId;
12
16
  } catch (err) {
13
17
  log.error("Failed to revoke API key %d: %s", keyId, err instanceof Error ? err.message : String(err));
14
18
  return false;
15
19
  }
16
20
  };
17
- const assertUsable = async (client, data, fallbackName) => {
18
- const problem = typeof data.key !== "string" || data.key.trim() === "" ? "Neon returned no key." : data.project_id !== void 0 ? `Neon returned a key scoped to ${data.project_id} rather than the whole account.` : !Number.isSafeInteger(data.id) ? "Neon returned no key id." : null;
21
+ const assertUsable = async (client, data, fallbackName, scope) => {
22
+ const wanted = scope.projectId;
23
+ const problem = typeof data.key !== "string" || data.key.trim() === "" ? "Neon returned no key." : data.project_id !== wanted ? wanted === void 0 ? `Neon returned a key scoped to ${data.project_id} rather than the whole account.` : `Neon returned a key scoped to ${data.project_id ?? "nothing"} rather than ${wanted}.` : !Number.isSafeInteger(data.id) ? "Neon returned no key id." : null;
19
24
  if (problem) {
20
- const withdrawn = await withdraw(client, data.id);
21
- throw new Error(`${problem} ${withdrawn ? "The key has been revoked; nothing was issued." : `The key could NOT be revoked and may still be live${data.id === void 0 ? "" : `. Remove it with \`${getCliName()} api-keys revoke ${data.id}\``}.`}`);
25
+ const withdrawn = await withdraw(client, data.id, scope.orgId);
26
+ throw new Error(`${problem} ${withdrawn ? "The key has been revoked; nothing was issued." : `The key could NOT be revoked and may still be live${data.id === void 0 ? "" : `. Remove it with \`${mintedKeyRevokeCommand({
27
+ id: data.id,
28
+ orgId: scope.orgId
29
+ })}\``}.`}`);
22
30
  }
23
31
  const id = data.id;
24
32
  const key = data.key;
@@ -26,21 +34,37 @@ const assertUsable = async (client, data, fallbackName) => {
26
34
  return {
27
35
  id,
28
36
  name: data.name ?? fallbackName,
29
- key
37
+ key,
38
+ orgId: scope.orgId,
39
+ projectId: scope.projectId
30
40
  };
31
41
  };
32
- async function mintMcpApiKey(options) {
33
- const name = mintedKeyName("mcp");
42
+ const createAndAssert = async (client, create, name, scope) => {
34
43
  try {
35
- const { data } = await options.apiClient.createApiKey({ key_name: name });
36
- return assertUsable(options.apiClient, data, name);
44
+ const { data } = await create();
45
+ return assertUsable(client, data, name, scope);
37
46
  } catch (err) {
38
47
  if (isNeonApiError(err) && (err.status === 403 || err.status === 404)) throw new Error(cannotMintMessage);
39
48
  throw err;
40
49
  }
50
+ };
51
+ async function mintMcpApiKey(options) {
52
+ const name = mintedKeyName("mcp");
53
+ const projectId = options.projectId;
54
+ if (projectId !== void 0) {
55
+ const orgId = await orgIdForProject(options.apiClient, projectId, "mcp");
56
+ return createAndAssert(options.apiClient, () => options.apiClient.createOrgApiKey(orgId, {
57
+ key_name: name,
58
+ project_id: projectId
59
+ }), name, {
60
+ orgId,
61
+ projectId
62
+ });
63
+ }
64
+ return createAndAssert(options.apiClient, () => options.apiClient.createApiKey({ key_name: name }), name, {});
41
65
  }
42
66
  async function withdrawMintedKey(apiClient, key) {
43
- return withdraw(apiClient, key.id);
67
+ return withdraw(apiClient, key.id, key.orgId);
44
68
  }
45
69
  //#endregion
46
- export { mintMcpApiKey, withdrawMintedKey };
70
+ export { mintMcpApiKey, mintedKeyRevokeCommand, withdrawMintedKey };
package/dist/mcp/plan.js CHANGED
@@ -28,7 +28,7 @@ async function resolveMcpPlan(options) {
28
28
  const auth = options.oauth ? "oauth" : prompt ? await (options.pickAuth ?? pickMcpAuth)() : "api-key";
29
29
  let urlProjectId = options.projectId;
30
30
  if (urlProjectId === void 0 && prompt && scope === "project" && options.linkedProjectId) {
31
- if (await (options.pickProjectPin ?? pickMcpProjectPin)(options.linkedProjectId)) urlProjectId = options.linkedProjectId;
31
+ if (await (options.pickProjectPin ?? pickMcpProjectPin)(options.linkedProjectId, auth === "api-key")) urlProjectId = options.linkedProjectId;
32
32
  }
33
33
  return {
34
34
  scope,
@@ -51,20 +51,20 @@ const pickMcpAuth = async () => {
51
51
  if (auth !== "api-key" && auth !== "oauth") throw new Error("Aborted.");
52
52
  return auth;
53
53
  };
54
- const pickMcpProjectPin = async (linkedProjectId) => {
54
+ const pickMcpProjectPin = async (linkedProjectId, willMintKey = false) => {
55
55
  if (!canPickAgentsInteractively()) return false;
56
56
  const { pin } = await prompts({
57
57
  onState: restoreCursorOnAbort,
58
58
  type: "confirm",
59
59
  name: "pin",
60
- message: `Pin MCP tools to the linked project ${linkedProjectId}?`,
60
+ message: willMintKey ? `Pin MCP tools to the linked project ${linkedProjectId}? A newly minted API key would be limited to that project.` : `Pin MCP tools to the linked project ${linkedProjectId}?`,
61
61
  initial: true
62
62
  });
63
63
  return pin === true;
64
64
  };
65
65
  const mcpInstallSummary = (options) => {
66
66
  const agents = options.install.map(getAgentDisplayName).join(", ");
67
- const auth = options.auth === "oauth" ? "OAuth, agent signs in on first use" : options.reuse ? "reuse the API key already in agent config" : "mint an account-wide API key that reaches every organization";
67
+ const auth = options.auth === "oauth" ? "OAuth, agent signs in on first use" : options.reuse ? "reuse the API key already in agent config" : options.mintProjectId ? `mint an API key limited to ${options.mintProjectId}` : "mint an account-wide API key that reaches every organization";
68
68
  const rows = [
69
69
  ["Config", options.scope === "project" ? "this directory" : "user-level"],
70
70
  ["Agents", agents],
@@ -0,0 +1,69 @@
1
+ import { getAgentDisplayName, tryResolveAddMcpAgentId } from "../mcp/agents.js";
2
+ import "../init/agents.js";
3
+ import { agentChoicesFrom, pickAgentsInteractively, resolveAgentSelection } from "../utils/agent_picker.js";
4
+ import { detectPluginsAgents, getPluginsTargetName, isUserScopeOnlyPluginsTarget, mappedPluginsTargets, pluginsInstallableAgents } from "./targets.js";
5
+ //#region src/plugins/plan.ts
6
+ const assertPluginsCanRun = (options) => {
7
+ if (options.yes || options.interactive || options.hasAgents) return;
8
+ throw new Error("No interactive terminal. Pass -y to install into detected agents, or --agent <name>.");
9
+ };
10
+ async function resolvePluginsPlan(options) {
11
+ assertPluginsCanRun({
12
+ yes: options.yes,
13
+ interactive: options.interactive,
14
+ hasAgents: options.agents.length > 0
15
+ });
16
+ const prompt = options.interactive && !options.yes;
17
+ const scope = options.global ? "global" : "project";
18
+ const available = pluginsInstallableAgents(scope);
19
+ const detected = await detectPluginsAgents({
20
+ scope,
21
+ cwd: options.cwd
22
+ });
23
+ const selected = await resolveAgentSelection({
24
+ specified: options.agents,
25
+ choices: agentChoicesFrom(available, detected),
26
+ detected,
27
+ message: "Which coding agents should get the Neon plugin? (space to toggle, enter to confirm)",
28
+ nonInteractiveMessage: scope === "project" ? `No coding agents detected in this project. Pass --agent <name>. Supported agents: ${available.join(", ")}` : `No coding agents detected. Pass --agent <name>. Supported agents: ${available.join(", ")}`,
29
+ resolveSpecified: (raw) => {
30
+ if (raw === "*") throw new Error("neon plugins does not accept --agent *. Pass --agent <name> for each coding agent, or omit --agent to use detected agents.");
31
+ const id = tryResolveAddMcpAgentId(raw);
32
+ if (!id) throw new Error(`Unknown agent: "${raw}". Supported agents: ${available.join(", ")}`);
33
+ return id;
34
+ },
35
+ pick: prompt ? options.pickAgents ?? pickAgentsInteractively : void 0,
36
+ interactive: prompt
37
+ });
38
+ const agents = [];
39
+ const skipped = [];
40
+ const userScopeSkipped = [];
41
+ for (const id of selected) {
42
+ const target = getPluginsTargetName(id);
43
+ if (target === void 0) {
44
+ skipped.push(id);
45
+ continue;
46
+ }
47
+ if (scope === "project" && isUserScopeOnlyPluginsTarget(target)) {
48
+ userScopeSkipped.push(id);
49
+ continue;
50
+ }
51
+ agents.push(id);
52
+ }
53
+ if (agents.length === 0) {
54
+ if (userScopeSkipped.length > 0 && skipped.length === 0) {
55
+ const names = userScopeSkipped.map((id) => getAgentDisplayName(id)).join(", ");
56
+ throw new Error(`${names}: plugins are user-level. Pass --global. Without --global: ${pluginsInstallableAgents("project").join(", ")}`);
57
+ }
58
+ throw new Error(`None of the selected agents can install plugins. Supported agents: ${available.join(", ")}`);
59
+ }
60
+ return {
61
+ scope,
62
+ agents,
63
+ skipped,
64
+ userScopeSkipped,
65
+ targets: mappedPluginsTargets(agents, scope)
66
+ };
67
+ }
68
+ //#endregion
69
+ export { assertPluginsCanRun, resolvePluginsPlan };
@@ -0,0 +1,123 @@
1
+ import { spawnSync } from "node:child_process";
2
+ import { execa } from "execa";
3
+ //#region src/plugins/run.ts
4
+ const TELEMETRY_BLOCKERS = ["DISABLE_TELEMETRY", "DO_NOT_TRACK"];
5
+ const PLUGIN_SOURCE = "neondatabase/agent-skills";
6
+ const NEON_PLUGIN_NAME = "neon-postgres";
7
+ const PLUGINS_CLI_TIMEOUT_MS = 12e4;
8
+ const pluginsChildEnv = (base = process.env) => {
9
+ const env = { ...base };
10
+ for (const key of TELEMETRY_BLOCKERS) delete env[key];
11
+ for (const key of Object.keys(env)) if (key.toUpperCase() === "NEON_API_KEY") delete env[key];
12
+ return env;
13
+ };
14
+ const pluginsScopeFlag = (global) => global ? "user" : "project";
15
+ const pluginsAddArgs = (options) => {
16
+ if (options.target.length === 0) throw new Error("plugins add needs a -t target.");
17
+ return [
18
+ "-y",
19
+ "plugins",
20
+ "add",
21
+ PLUGIN_SOURCE,
22
+ "-t",
23
+ options.target,
24
+ "-s",
25
+ pluginsScopeFlag(options.global),
26
+ "-y"
27
+ ];
28
+ };
29
+ const neonPluginsRetryCommand = (options) => {
30
+ const args = ["plugins"];
31
+ for (const agent of options.agents) args.push("--agent", agent);
32
+ if (options.global) args.push("--global");
33
+ args.push("-y");
34
+ return `neon ${args.map(quoteNpxArg).join(" ")}`;
35
+ };
36
+ const quoteNpxArg = (part) => /[\s"'\\*]/.test(part) ? `'${part.replace(/'/g, `'\\''`)}'` : part;
37
+ const runPluginsCli = async (options) => {
38
+ const timeoutMs = options.timeoutMs ?? 12e4;
39
+ const subprocess = execa("npx", options.args, {
40
+ cwd: options.cwd,
41
+ env: pluginsChildEnv(options.env ?? process.env),
42
+ extendEnv: false,
43
+ stdio: "pipe",
44
+ detached: true
45
+ });
46
+ let timedOut = false;
47
+ let stopped = false;
48
+ const stop = () => {
49
+ if (stopped) return;
50
+ stopped = true;
51
+ if (subprocess.pid !== void 0) killProcessTree(subprocess.pid);
52
+ };
53
+ const onSigint = () => {
54
+ stop();
55
+ process.exit(130);
56
+ };
57
+ const onSigterm = () => {
58
+ stop();
59
+ process.exit(143);
60
+ };
61
+ process.once("SIGINT", onSigint);
62
+ process.once("SIGTERM", onSigterm);
63
+ process.once("exit", stop);
64
+ const timer = setTimeout(() => {
65
+ timedOut = true;
66
+ stop();
67
+ }, timeoutMs);
68
+ try {
69
+ const result = await subprocess;
70
+ return {
71
+ stdout: result.stdout,
72
+ stderr: result.stderr
73
+ };
74
+ } catch (error) {
75
+ if (isCommandMissing(error)) throw new Error("neon plugins needs npx (Node.js) to run the plugins CLI. Install Node.js, then retry.");
76
+ if (isExecaFailure(error)) throw new Error(pluginsCliFailureMessage({
77
+ stderr: typeof error.stderr === "string" ? error.stderr : "",
78
+ stdout: typeof error.stdout === "string" ? error.stdout : "",
79
+ timedOut,
80
+ timeoutMs
81
+ }));
82
+ throw error;
83
+ } finally {
84
+ clearTimeout(timer);
85
+ process.removeListener("SIGINT", onSigint);
86
+ process.removeListener("SIGTERM", onSigterm);
87
+ process.removeListener("exit", stop);
88
+ }
89
+ };
90
+ const pluginsCliFailureMessage = (error) => {
91
+ const childOut = [error.stderr, error.stdout].filter((part) => typeof part === "string" && part.length > 0).join("\n");
92
+ if (error.timedOut === true) {
93
+ const seconds = Math.max(1, Math.ceil((error.timeoutMs ?? 12e4) / 1e3));
94
+ const headline = `plugins CLI timed out after ${seconds} ${seconds === 1 ? "second" : "seconds"}`;
95
+ return childOut.length > 0 ? `${headline}:\n${childOut}` : `${headline}.`;
96
+ }
97
+ if (childOut.length > 0) return `plugins CLI failed:\n${childOut}`;
98
+ return "plugins CLI failed.";
99
+ };
100
+ const killProcessTree = (pid) => {
101
+ if (process.platform === "win32") {
102
+ spawnSync("taskkill", [
103
+ "/pid",
104
+ String(pid),
105
+ "/T",
106
+ "/F"
107
+ ], { stdio: "ignore" });
108
+ return;
109
+ }
110
+ try {
111
+ process.kill(-pid, "SIGKILL");
112
+ } catch {
113
+ try {
114
+ process.kill(pid, "SIGKILL");
115
+ } catch {
116
+ return;
117
+ }
118
+ }
119
+ };
120
+ const isCommandMissing = (error) => typeof error === "object" && error !== null && "code" in error && error.code === "ENOENT";
121
+ const isExecaFailure = (error) => typeof error === "object" && error !== null && "shortMessage" in error && typeof error.shortMessage === "string";
122
+ //#endregion
123
+ export { NEON_PLUGIN_NAME, PLUGINS_CLI_TIMEOUT_MS, PLUGIN_SOURCE, neonPluginsRetryCommand, pluginsAddArgs, pluginsChildEnv, pluginsCliFailureMessage, pluginsScopeFlag, quoteNpxArg, runPluginsCli };
@@ -0,0 +1,64 @@
1
+ import { detectInstalledAgents, listMcpAgentIds, tryResolveAddMcpAgentId, uniqueAgentIds } from "../mcp/agents.js";
2
+ import "../init/agents.js";
3
+ import { detectProjectAgents } from "add-mcp";
4
+ //#region src/plugins/targets.ts
5
+ const PLUGINS_TARGET_BY_TYPE = {
6
+ cursor: "cursor",
7
+ vscode: "vscode",
8
+ "claude-code": "claude-code",
9
+ "claude-desktop": "claude-code",
10
+ codex: "codex",
11
+ "github-copilot-cli": "github-copilot",
12
+ "grok-build": "grok"
13
+ };
14
+ const USER_SCOPE_ONLY_TARGETS = /* @__PURE__ */ new Set([
15
+ "vscode",
16
+ "github-copilot",
17
+ "grok"
18
+ ]);
19
+ function getPluginsTargetName(agent) {
20
+ if (Object.prototype.hasOwnProperty.call(PLUGINS_TARGET_BY_TYPE, agent)) return PLUGINS_TARGET_BY_TYPE[agent];
21
+ const id = tryResolveAddMcpAgentId(agent);
22
+ if (!id) return void 0;
23
+ return PLUGINS_TARGET_BY_TYPE[id];
24
+ }
25
+ function supportsPlugins(agent) {
26
+ return getPluginsTargetName(agent) !== void 0;
27
+ }
28
+ function isUserScopeOnlyPluginsTarget(target) {
29
+ return USER_SCOPE_ONLY_TARGETS.has(target);
30
+ }
31
+ const pluginsMappedAgents = () => listMcpAgentIds().filter((id) => supportsPlugins(id));
32
+ const pluginsInstallableAgents = (scope) => pluginsMappedAgents().filter((id) => {
33
+ if (scope === "global") return true;
34
+ const target = getPluginsTargetName(id);
35
+ return target !== void 0 && !isUserScopeOnlyPluginsTarget(target);
36
+ });
37
+ const detectPluginsAgents = async (options) => {
38
+ const detected = options.scope === "project" ? detectProjectAgents(options.cwd) : await detectInstalledAgents();
39
+ return uniqueAgentIds(detected).filter((id) => supportsPlugins(id));
40
+ };
41
+ const mappedPluginsTargets = (agents, scope) => {
42
+ const mapped = [];
43
+ const byTarget = /* @__PURE__ */ new Map();
44
+ for (const agent of agents) {
45
+ const target = getPluginsTargetName(agent);
46
+ if (target === void 0) continue;
47
+ if (scope === "project" && isUserScopeOnlyPluginsTarget(target)) continue;
48
+ const existing = byTarget.get(target);
49
+ if (existing !== void 0) {
50
+ if (!existing.agents.includes(agent)) existing.agents.push(agent);
51
+ continue;
52
+ }
53
+ const row = {
54
+ agents: [agent],
55
+ target
56
+ };
57
+ byTarget.set(target, row);
58
+ mapped.push(row);
59
+ }
60
+ if (mapped.length === 0) throw new Error(`None of the selected agents can install plugins. Supported agents: ${pluginsInstallableAgents(scope).join(", ")}`);
61
+ return mapped;
62
+ };
63
+ //#endregion
64
+ export { detectPluginsAgents, getPluginsTargetName, isUserScopeOnlyPluginsTarget, mappedPluginsTargets, pluginsInstallableAgents, pluginsMappedAgents, supportsPlugins };
@@ -0,0 +1,36 @@
1
+ import { getAgentDisplayName } from "../mcp/agents.js";
2
+ import "../init/agents.js";
3
+ import { canPickAgentsInteractively } from "../utils/agent_picker.js";
4
+ import { NEON_PLUGIN_NAME } from "./run.js";
5
+ import prompts from "prompts";
6
+ //#region src/plugins/wizard.ts
7
+ const restoreCursorOnAbort = (state) => {
8
+ if (state.aborted) {
9
+ process.stdout.write("\x1B[?25h");
10
+ process.stdout.write("\n");
11
+ process.exit(1);
12
+ }
13
+ };
14
+ const pluginsInstallSummary = (options) => {
15
+ const rows = [
16
+ ["Scope", options.scope === "project" ? "project" : "user"],
17
+ ["Agents", options.agents.map(getAgentDisplayName).join(", ")],
18
+ ["Plugin", NEON_PLUGIN_NAME]
19
+ ];
20
+ const labelWidth = Math.max(...rows.map(([label]) => label.length));
21
+ return rows.map(([label, value]) => `${label.padEnd(labelWidth)} ${value}`).join("\n");
22
+ };
23
+ const confirmPluginsInstall = async (options) => {
24
+ if (!canPickAgentsInteractively()) return true;
25
+ process.stdout.write(`\n${pluginsInstallSummary(options)}\n\n`);
26
+ const { ok } = await prompts({
27
+ onState: restoreCursorOnAbort,
28
+ type: "confirm",
29
+ name: "ok",
30
+ message: "Install the Neon plugin into these agents?",
31
+ initial: true
32
+ });
33
+ return ok === true;
34
+ };
35
+ //#endregion
36
+ export { confirmPluginsInstall, pluginsInstallSummary };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "neon",
3
- "version": "4.3.0",
3
+ "version": "4.4.0",
4
4
  "description": "CLI tool for Neon, the cloud backend primitives built around Lakebase Postgres",
5
5
  "keywords": [
6
6
  "neon",
@@ -63,9 +63,9 @@
63
63
  "yaml": "^2.9.0",
64
64
  "yargs": "17.7.2",
65
65
  "yoctocolors": "^2.1.2",
66
- "@neon/config": "1.0.2",
67
66
  "@neon/config-runtime": "1.0.2",
68
- "@neon/sdk": "2.2.0"
67
+ "@neon/sdk": "2.2.0",
68
+ "@neon/config": "1.0.2"
69
69
  },
70
70
  "optionalDependencies": {
71
71
  "@napi-rs/keyring": "1.3.0",