neon 4.1.0 → 4.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +38 -0
- package/dist/commands/auth.js +7 -1
- package/dist/commands/branches.js +2 -2
- package/dist/commands/index.js +2 -0
- package/dist/commands/mcp.js +212 -0
- package/dist/commands/projects.js +11 -6
- package/dist/commands/snapshots.js +2 -2
- package/dist/context.js +23 -1
- package/dist/human_table.js +18 -51
- package/dist/index.js +1 -0
- package/dist/init/agents.js +2 -51
- package/dist/init/editors.js +2 -1
- package/dist/init/interactive.js +3 -2
- package/dist/init/orchestrate.js +2 -1
- package/dist/init/phases/mcp.js +3 -2
- package/dist/init/phases/setup.js +3 -2
- package/dist/init/phases/status.js +2 -1
- package/dist/mcp/agents.js +64 -0
- package/dist/mcp/install.js +216 -0
- package/dist/mcp/mint.js +46 -0
- package/dist/mcp/plan.js +43 -0
- package/dist/mcp/targets.js +34 -0
- package/dist/mcp/wizard.js +91 -0
- package/dist/test_utils/fixtures.js +7 -4
- package/dist/utils/agent_picker.js +70 -0
- package/package.json +3 -3
- package/dist/init/install_mcp.js +0 -35
package/README.md
CHANGED
|
@@ -722,6 +722,43 @@ That message reports where parsing stopped and nothing more. `--data` carries wh
|
|
|
722
722
|
| `--preview` | Enable preview features (scaffolding a project from a template) |
|
|
723
723
|
| `--profile <name>` | Run as that stored account. See [Which credential an invocation uses](#which-credential-an-invocation-uses). |
|
|
724
724
|
|
|
725
|
+
## Install the Neon MCP server (`mcp`)
|
|
726
|
+
|
|
727
|
+
`neon mcp` writes the hosted Neon MCP server (`https://mcp.neon.tech/mcp`) into coding-agent config files.
|
|
728
|
+
|
|
729
|
+
```bash
|
|
730
|
+
# Interactive: global or project, then agents, then API key or OAuth, then confirm.
|
|
731
|
+
$ neon mcp
|
|
732
|
+
|
|
733
|
+
# Skip prompts. Global config, every globally detected agent, minted API key.
|
|
734
|
+
$ neon mcp -y
|
|
735
|
+
|
|
736
|
+
# OAuth: no API key minted. The agent prompts for Neon sign-in on first use.
|
|
737
|
+
$ neon mcp --oauth
|
|
738
|
+
|
|
739
|
+
# Project-level config. A minted key is still account-wide.
|
|
740
|
+
$ neon mcp --project
|
|
741
|
+
|
|
742
|
+
$ neon mcp --agent cursor --agent claude-code
|
|
743
|
+
|
|
744
|
+
# Hide write tools. Does not change the minted key.
|
|
745
|
+
$ neon mcp --read-only
|
|
746
|
+
|
|
747
|
+
# Pin MCP tools to one project. Does not change the minted key.
|
|
748
|
+
$ neon mcp --project-id <project-id>
|
|
749
|
+
|
|
750
|
+
# Limit which tool categories are visible.
|
|
751
|
+
$ neon mcp --category querying --category schema
|
|
752
|
+
```
|
|
753
|
+
|
|
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
|
+
|
|
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.
|
|
757
|
+
|
|
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
|
+
|
|
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.
|
|
761
|
+
|
|
725
762
|
## Snapshots (`snapshots`)
|
|
726
763
|
|
|
727
764
|
`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.
|
|
@@ -1101,6 +1138,7 @@ Id Name Project Created At Last Used At Last
|
|
|
1101
1138
|
| deploy | | Alias for `config apply` |
|
|
1102
1139
|
| bootstrap | | Scaffold a project from a template |
|
|
1103
1140
|
| init | | Set up a project for a coding agent |
|
|
1141
|
+
| mcp | | Install the Neon MCP server |
|
|
1104
1142
|
| bucket | `create`, `list`, `delete`, `object list`, `object get`, `object put`, `object delete` (incl. `--recursive`) | Manage buckets and their objects |
|
|
1105
1143
|
| [completion](https://neon.com/docs/reference/cli-completion) | | Generate a completion script |
|
|
1106
1144
|
|
package/dist/commands/auth.js
CHANGED
|
@@ -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, isProfileCommand } from "../context.js";
|
|
9
|
+
import { isConfigInit, isCurrentBranchProbe, isMcpCommand, isMcpOauth, isProfileCommand } 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";
|
|
@@ -215,10 +215,12 @@ const ensureAuth = async (props) => {
|
|
|
215
215
|
if (isCurrentBranchProbe(props)) return;
|
|
216
216
|
if (isConfigInit(props)) return;
|
|
217
217
|
if (isProfileCommand(props)) return;
|
|
218
|
+
if (isMcpOauth(props)) return;
|
|
218
219
|
if (props._[0] === "open") return;
|
|
219
220
|
const isLocalDev = props._[0] === "dev";
|
|
220
221
|
const isBootstrap = props._[0] === "bootstrap";
|
|
221
222
|
const isInit = props._[0] === "init";
|
|
223
|
+
const isMcp = isMcpCommand(props);
|
|
222
224
|
if (props._[0] === "auth") {
|
|
223
225
|
props.apiClient = getApiClient({
|
|
224
226
|
apiKey: props.apiKey,
|
|
@@ -298,6 +300,10 @@ const ensureAuth = async (props) => {
|
|
|
298
300
|
log.debug("init: skipping global auth; init manages its own auth flow");
|
|
299
301
|
return;
|
|
300
302
|
}
|
|
303
|
+
if (isMcp) {
|
|
304
|
+
log.debug("mcp: no usable credentials; minting requires auth or --oauth");
|
|
305
|
+
return;
|
|
306
|
+
}
|
|
301
307
|
const apiKey = await authFlow({
|
|
302
308
|
...props,
|
|
303
309
|
profile: selection.profile
|
package/dist/commands/index.js
CHANGED
|
@@ -20,6 +20,7 @@ import { t as init_exports } from "./init.js";
|
|
|
20
20
|
import { t as inspect_exports } from "./inspect.js";
|
|
21
21
|
import { t as ip_allow_exports } from "./ip_allow.js";
|
|
22
22
|
import { t as logs_exports } from "./logs.js";
|
|
23
|
+
import { t as mcp_exports } from "./mcp.js";
|
|
23
24
|
import { t as neon_auth_exports } from "./neon_auth.js";
|
|
24
25
|
import { t as open_exports } from "./open.js";
|
|
25
26
|
import { t as operations_exports } from "./operations.js";
|
|
@@ -58,6 +59,7 @@ var commands_default = [
|
|
|
58
59
|
link_exports,
|
|
59
60
|
open_exports,
|
|
60
61
|
init_exports,
|
|
62
|
+
mcp_exports,
|
|
61
63
|
data_api_exports,
|
|
62
64
|
functions_exports,
|
|
63
65
|
dev_exports,
|
|
@@ -0,0 +1,212 @@
|
|
|
1
|
+
import { t as __exportAll } from "../_chunks/rolldown-runtime-8H4AJuhK.js";
|
|
2
|
+
import { log } from "../log.js";
|
|
3
|
+
import { readContextFile } from "../context.js";
|
|
4
|
+
import { getCliName } from "../utils/cli_name.js";
|
|
5
|
+
import { writer } from "../writer.js";
|
|
6
|
+
import { noPassthrough, single } from "../utils/flags.js";
|
|
7
|
+
import { existingNeonApiKey, installNeonMcpServer, neonMcpUrl, parseMcpCategories, trackedProjectMcpConfig } from "../mcp/install.js";
|
|
8
|
+
import { mintMcpApiKey, withdrawMintedKey } from "../mcp/mint.js";
|
|
9
|
+
import { canPickAgentsInteractively } from "../utils/agent_picker.js";
|
|
10
|
+
import { resolveInstallTargets } from "../mcp/targets.js";
|
|
11
|
+
import { confirmMcpInstall } from "../mcp/wizard.js";
|
|
12
|
+
import { resolveMcpPlan } from "../mcp/plan.js";
|
|
13
|
+
//#region src/commands/mcp.ts
|
|
14
|
+
var mcp_exports = /* @__PURE__ */ __exportAll({
|
|
15
|
+
builder: () => builder,
|
|
16
|
+
command: () => "mcp",
|
|
17
|
+
describe: () => describe,
|
|
18
|
+
handler: () => handler
|
|
19
|
+
});
|
|
20
|
+
const command = "mcp";
|
|
21
|
+
const describe = "Install the Neon MCP server into coding agents";
|
|
22
|
+
const builder = (argv) => argv.usage("$0 mcp [options]").options({
|
|
23
|
+
oauth: {
|
|
24
|
+
type: "boolean",
|
|
25
|
+
default: false,
|
|
26
|
+
describe: "Write the server URL only. The agent prompts for Neon sign-in on first use. No CLI login, no API key minted. Skips the auth question"
|
|
27
|
+
},
|
|
28
|
+
project: {
|
|
29
|
+
type: "boolean",
|
|
30
|
+
default: false,
|
|
31
|
+
describe: "Write project-level MCP config. Skips the config-location question. Does not change the minted key"
|
|
32
|
+
},
|
|
33
|
+
yes: {
|
|
34
|
+
alias: "y",
|
|
35
|
+
type: "boolean",
|
|
36
|
+
default: false,
|
|
37
|
+
describe: "Skip prompts. Defaults to global config, every detected agent and a minted account-wide API key. --project, --oauth, --agent, --read-only, --project-id and --category still apply"
|
|
38
|
+
},
|
|
39
|
+
agent: {
|
|
40
|
+
alias: "a",
|
|
41
|
+
type: "array",
|
|
42
|
+
string: true,
|
|
43
|
+
describe: "Coding agent to install into (repeatable). Skips the agent picker",
|
|
44
|
+
coerce: (value) => {
|
|
45
|
+
if (value === void 0) return [];
|
|
46
|
+
const list = Array.isArray(value) ? value : [value];
|
|
47
|
+
if (list.length === 0) throw new Error("--agent needs a value. Pass one, or omit the flag entirely.");
|
|
48
|
+
return list.map((item) => {
|
|
49
|
+
if (typeof item !== "string" || item.trim() === "") throw new Error("--agent needs a value. Pass one, or omit the flag entirely.");
|
|
50
|
+
return item;
|
|
51
|
+
});
|
|
52
|
+
}
|
|
53
|
+
},
|
|
54
|
+
"read-only": {
|
|
55
|
+
alias: "readonly",
|
|
56
|
+
type: "boolean",
|
|
57
|
+
default: false,
|
|
58
|
+
describe: "Restrict MCP tools to read-only (?readonly=true). Not prompted. Does not change the minted key"
|
|
59
|
+
},
|
|
60
|
+
"project-id": {
|
|
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",
|
|
63
|
+
coerce: single("project-id")
|
|
64
|
+
},
|
|
65
|
+
category: {
|
|
66
|
+
type: "array",
|
|
67
|
+
string: true,
|
|
68
|
+
describe: "MCP tool category (repeatable or comma-separated). Default: all",
|
|
69
|
+
coerce: (value) => {
|
|
70
|
+
if (value === void 0) return [];
|
|
71
|
+
const list = Array.isArray(value) ? value : [value];
|
|
72
|
+
if (list.length === 0) throw new Error("--category needs a value. Pass one, or omit the flag entirely.");
|
|
73
|
+
const parts = [];
|
|
74
|
+
for (const item of list) {
|
|
75
|
+
if (typeof item !== "string" || item.trim() === "") throw new Error("--category needs a value. Pass one, or omit the flag entirely.");
|
|
76
|
+
for (const part of item.split(",")) {
|
|
77
|
+
const trimmed = part.trim();
|
|
78
|
+
if (trimmed === "") throw new Error("--category needs a value. Pass one, or omit the flag entirely.");
|
|
79
|
+
parts.push(trimmed);
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
return parseMcpCategories(parts);
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
}).example("$0 mcp", "Interactive: config location, agents, auth, then confirm").example("$0 mcp -y", "Global config, detected agents, minted API key").example("$0 mcp --oauth", "Install with OAuth; the agent signs in on first use").example("$0 mcp --project", "Write project-level config").example("$0 mcp --agent cursor --agent claude-code", "Install into specific agents").example("$0 mcp --read-only", "Hide write tools via ?readonly=true").example("$0 mcp --project-id <id>", "Pin tools to one project via ?projectId=").example("$0 mcp --category querying --category schema", "Limit tools to those categories").strict().check(noPassthrough("mcp"));
|
|
86
|
+
const handler = async (props) => {
|
|
87
|
+
const cwd = process.cwd();
|
|
88
|
+
const interactive = canPickAgentsInteractively() && props.yes !== true;
|
|
89
|
+
const linkedProjectId = readContextFile(props.contextFile).projectId;
|
|
90
|
+
const plan = await resolveMcpPlan({
|
|
91
|
+
project: props.project === true,
|
|
92
|
+
oauth: props.oauth === true,
|
|
93
|
+
agents: props.agent ?? [],
|
|
94
|
+
yes: props.yes === true,
|
|
95
|
+
cwd,
|
|
96
|
+
interactive,
|
|
97
|
+
readOnly: props.readOnly === true,
|
|
98
|
+
projectId: props.projectId,
|
|
99
|
+
categories: props.category ?? [],
|
|
100
|
+
linkedProjectId
|
|
101
|
+
});
|
|
102
|
+
const { install, skipped } = resolveInstallTargets({
|
|
103
|
+
agents: plan.agents,
|
|
104
|
+
scope: plan.scope
|
|
105
|
+
});
|
|
106
|
+
const url = neonMcpUrl({
|
|
107
|
+
readOnly: plan.readOnly,
|
|
108
|
+
projectId: plan.urlProjectId,
|
|
109
|
+
categories: plan.categories
|
|
110
|
+
});
|
|
111
|
+
if (plan.scope === "project" && plan.auth === "api-key") {
|
|
112
|
+
const tracked = trackedProjectMcpConfig({
|
|
113
|
+
agents: install,
|
|
114
|
+
cwd
|
|
115
|
+
});
|
|
116
|
+
if (tracked) throw new Error(`${tracked} is tracked by git. Untrack it before writing an API key, or pass --oauth.`);
|
|
117
|
+
}
|
|
118
|
+
const existing = plan.auth === "api-key" ? existingNeonApiKey({
|
|
119
|
+
agents: install,
|
|
120
|
+
scope: plan.scope,
|
|
121
|
+
cwd
|
|
122
|
+
}) : void 0;
|
|
123
|
+
if (plan.auth === "api-key" && !existing) {
|
|
124
|
+
if (!props.apiClient || !props.apiKey) throw new Error(`Authentication required. Run \`${getCliName()} auth\`, pass --api-key or use --oauth to install without a Neon credential.`);
|
|
125
|
+
if (!canPickAgentsInteractively() && props.yes !== true && (props.agent ?? []).length === 0) throw new Error("No interactive terminal. Pass -y to mint into every detected agent, --agent <name> to name them or --oauth to install without minting.");
|
|
126
|
+
}
|
|
127
|
+
if (interactive) {
|
|
128
|
+
if (!await confirmMcpInstall({
|
|
129
|
+
scope: plan.scope,
|
|
130
|
+
install,
|
|
131
|
+
skipped,
|
|
132
|
+
auth: plan.auth,
|
|
133
|
+
reuse: existing !== void 0,
|
|
134
|
+
url
|
|
135
|
+
})) {
|
|
136
|
+
log.info("Aborted. Nothing was written.");
|
|
137
|
+
return;
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
let auth;
|
|
141
|
+
let minted;
|
|
142
|
+
if (plan.auth === "oauth") auth = { kind: "oauth" };
|
|
143
|
+
else if (existing) {
|
|
144
|
+
auth = {
|
|
145
|
+
kind: "api-key",
|
|
146
|
+
apiKey: existing
|
|
147
|
+
};
|
|
148
|
+
log.info("Reusing the API key already configured for the Neon MCP server.");
|
|
149
|
+
} else {
|
|
150
|
+
minted = await mintMcpApiKey({ apiClient: props.apiClient });
|
|
151
|
+
auth = {
|
|
152
|
+
kind: "api-key",
|
|
153
|
+
apiKey: minted.key
|
|
154
|
+
};
|
|
155
|
+
log.warning("This key reaches everything your account can, in every organization.");
|
|
156
|
+
}
|
|
157
|
+
const rows = [];
|
|
158
|
+
let successes = 0;
|
|
159
|
+
const failedAgents = [];
|
|
160
|
+
for (const agent of install) {
|
|
161
|
+
const result = installNeonMcpServer({
|
|
162
|
+
agent,
|
|
163
|
+
scope: plan.scope,
|
|
164
|
+
cwd,
|
|
165
|
+
auth,
|
|
166
|
+
url
|
|
167
|
+
});
|
|
168
|
+
if (result.ok) {
|
|
169
|
+
successes += 1;
|
|
170
|
+
rows.push({
|
|
171
|
+
agent,
|
|
172
|
+
status: "installed"
|
|
173
|
+
});
|
|
174
|
+
log.info("Wrote %s", result.path);
|
|
175
|
+
} else {
|
|
176
|
+
rows.push({
|
|
177
|
+
agent,
|
|
178
|
+
status: result.unsupported ? "skipped" : "failed",
|
|
179
|
+
error: result.error
|
|
180
|
+
});
|
|
181
|
+
if (!result.unsupported) failedAgents.push(agent);
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
for (const row of skipped) rows.push({
|
|
185
|
+
agent: row.agent,
|
|
186
|
+
status: "skipped",
|
|
187
|
+
error: row.error
|
|
188
|
+
});
|
|
189
|
+
if (successes === 0) {
|
|
190
|
+
if (minted && props.apiClient) {
|
|
191
|
+
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}\`.`);
|
|
193
|
+
}
|
|
194
|
+
throw new Error("Failed to write Neon MCP config to any agent.");
|
|
195
|
+
}
|
|
196
|
+
log.info("URL: %s", url);
|
|
197
|
+
const out = writer(props);
|
|
198
|
+
out.write(rows, {
|
|
199
|
+
fields: [
|
|
200
|
+
"agent",
|
|
201
|
+
"status",
|
|
202
|
+
"error"
|
|
203
|
+
],
|
|
204
|
+
title: "MCP"
|
|
205
|
+
});
|
|
206
|
+
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}`);
|
|
208
|
+
if (plan.auth === "oauth") log.info("The agent will prompt for Neon sign-in on first use.");
|
|
209
|
+
if (failedAgents.length > 0) throw new Error(`Failed to write Neon MCP config for: ${failedAgents.join(", ")}.`);
|
|
210
|
+
};
|
|
211
|
+
//#endregion
|
|
212
|
+
export { builder, command, describe, handler, mcp_exports as t };
|
|
@@ -12,6 +12,7 @@ import prompts from "prompts";
|
|
|
12
12
|
//#region src/commands/projects.ts
|
|
13
13
|
var projects_exports = /* @__PURE__ */ __exportAll({
|
|
14
14
|
PROJECT_FIELDS: () => PROJECT_FIELDS,
|
|
15
|
+
RECOVERABLE_PROJECT_FIELDS: () => RECOVERABLE_PROJECT_FIELDS,
|
|
15
16
|
REGIONS: () => REGIONS,
|
|
16
17
|
aliases: () => aliases,
|
|
17
18
|
builder: () => builder,
|
|
@@ -25,6 +26,14 @@ const PROJECT_FIELDS = [
|
|
|
25
26
|
"region_id",
|
|
26
27
|
"created_at"
|
|
27
28
|
];
|
|
29
|
+
const RECOVERABLE_PROJECT_FIELDS = [
|
|
30
|
+
"id",
|
|
31
|
+
"name",
|
|
32
|
+
"recoverable_until",
|
|
33
|
+
"deleted_at",
|
|
34
|
+
"region_id",
|
|
35
|
+
"created_at"
|
|
36
|
+
];
|
|
28
37
|
const REGIONS = [
|
|
29
38
|
"aws-us-west-2",
|
|
30
39
|
"aws-ap-southeast-1",
|
|
@@ -185,11 +194,7 @@ const list = async (props) => {
|
|
|
185
194
|
const sharedProjects = props.orgId || props.recoverableOnly ? [] : await getList(props.apiClient.listSharedProjects);
|
|
186
195
|
const out = writer(props);
|
|
187
196
|
out.write(ownedProjects, {
|
|
188
|
-
fields: props.recoverableOnly ?
|
|
189
|
-
...PROJECT_FIELDS,
|
|
190
|
-
"deleted_at",
|
|
191
|
-
"recoverable_until"
|
|
192
|
-
] : PROJECT_FIELDS,
|
|
197
|
+
fields: props.recoverableOnly ? RECOVERABLE_PROJECT_FIELDS : PROJECT_FIELDS,
|
|
193
198
|
title: "Projects",
|
|
194
199
|
emptyMessage: props.recoverableOnly ? "You don't have any recoverable projects." : "You don't have any projects yet. See how to create a new project:\n> neon projects create --help"
|
|
195
200
|
});
|
|
@@ -351,4 +356,4 @@ const onPromptState = (state) => {
|
|
|
351
356
|
}
|
|
352
357
|
};
|
|
353
358
|
//#endregion
|
|
354
|
-
export { PROJECT_FIELDS, REGIONS, aliases, builder, command, describe, handler, projects_exports as t };
|
|
359
|
+
export { PROJECT_FIELDS, RECOVERABLE_PROJECT_FIELDS, REGIONS, aliases, builder, command, describe, handler, projects_exports as t };
|
package/dist/context.js
CHANGED
|
@@ -50,6 +50,27 @@ const isProfileCommand = (args) => args._[0] === "profile" || args._[0] === "pro
|
|
|
50
50
|
* far a credential reaches must come from an explicit flag, never from `.neon`.
|
|
51
51
|
*/
|
|
52
52
|
const isApiKeysCommand = (args) => args._[0] === "api-keys" || args._[0] === "api-key";
|
|
53
|
+
const isMcpCommand = (args) => args._[0] === "mcp";
|
|
54
|
+
/** Raw argv is required because auth middleware runs before MCP flags are parsed. */
|
|
55
|
+
const isMcpOauth = (args) => isMcpCommand(args) && argvEnablesMcpOauth(process.argv);
|
|
56
|
+
const OAUTH_FALSE = /* @__PURE__ */ new Set([
|
|
57
|
+
"false",
|
|
58
|
+
"0",
|
|
59
|
+
"no"
|
|
60
|
+
]);
|
|
61
|
+
function argvEnablesMcpOauth(argv) {
|
|
62
|
+
for (let i = 0; i < argv.length; i++) {
|
|
63
|
+
const arg = argv[i];
|
|
64
|
+
if (arg === "--oauth=true" || arg === "--oauth=1") return true;
|
|
65
|
+
if (arg === "--oauth=false" || arg === "--oauth=0") continue;
|
|
66
|
+
if (arg === "--oauth") {
|
|
67
|
+
const next = argv[i + 1];
|
|
68
|
+
if (next !== void 0 && OAUTH_FALSE.has(next.toLowerCase())) continue;
|
|
69
|
+
return true;
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
return false;
|
|
73
|
+
}
|
|
53
74
|
const CONTEXT_FILE = ".neon";
|
|
54
75
|
const GITIGNORE_FILE = ".gitignore";
|
|
55
76
|
const canAccessFile = (file) => {
|
|
@@ -108,6 +129,7 @@ const enrichFromContext = (args) => {
|
|
|
108
129
|
if (args._[0] === "link" || args._[0] === "set-context") return;
|
|
109
130
|
if (isApiKeysCommand(args)) return;
|
|
110
131
|
if (isProfileCommand(args)) return;
|
|
132
|
+
if (isMcpCommand(args)) return;
|
|
111
133
|
const context = readContextFile(args.contextFile);
|
|
112
134
|
if (!args.orgId) args.orgId = context.orgId;
|
|
113
135
|
if (!args.projectId) args.projectId = context.projectId;
|
|
@@ -213,4 +235,4 @@ const globToRegExp = (pattern) => {
|
|
|
213
235
|
return new RegExp(`^${source}$`);
|
|
214
236
|
};
|
|
215
237
|
//#endregion
|
|
216
|
-
export { applyContext, contextBranch, currentContextFile, enrichFromContext, ensureGitignored, isApiKeysCommand, isConfigInit, isCurrentBranchProbe, isProfileCommand, readContextFile, setContext, updateContextFile, walkContextFile };
|
|
238
|
+
export { applyContext, contextBranch, currentContextFile, enrichFromContext, ensureGitignored, isApiKeysCommand, isConfigInit, isCurrentBranchProbe, isMcpCommand, isMcpOauth, isProfileCommand, readContextFile, setContext, updateContextFile, walkContextFile };
|
package/dist/human_table.js
CHANGED
|
@@ -32,6 +32,12 @@ function displayWidth(s) {
|
|
|
32
32
|
}
|
|
33
33
|
return width;
|
|
34
34
|
}
|
|
35
|
+
function presentFields(data, fields) {
|
|
36
|
+
return fields.filter((field) => data.some((item) => {
|
|
37
|
+
const value = fieldValue(item, field);
|
|
38
|
+
return value !== void 0 && value !== "";
|
|
39
|
+
}));
|
|
40
|
+
}
|
|
35
41
|
function formatHumanChunk(chunk) {
|
|
36
42
|
const arrayData = Array.isArray(chunk.data) ? chunk.data : [chunk.data];
|
|
37
43
|
const isList = Array.isArray(chunk.data);
|
|
@@ -42,48 +48,28 @@ function formatHumanChunk(chunk) {
|
|
|
42
48
|
out += formatTitle(chunk.title, chunk.colorTitle) + "\n";
|
|
43
49
|
}
|
|
44
50
|
if (!arrayData.length) return out;
|
|
45
|
-
|
|
46
|
-
const
|
|
47
|
-
|
|
48
|
-
|
|
51
|
+
if (isList) {
|
|
52
|
+
const fields = presentFields(arrayData, chunk.fields);
|
|
53
|
+
if (fields.length) {
|
|
54
|
+
const headers = fields.map(titleCaseField);
|
|
55
|
+
const rows = arrayData.map((item) => fields.map((field) => cellText(item, field, chunk.renderColumns)));
|
|
56
|
+
out += formatColumns(headers, rows, naturalWidths(headers, rows)) + "\n";
|
|
57
|
+
}
|
|
58
|
+
return out;
|
|
59
|
+
}
|
|
60
|
+
const fields = presentFields(arrayData, chunk.fields);
|
|
49
61
|
if (!fields.length) return out;
|
|
50
|
-
const
|
|
51
|
-
const rows = arrayData.map((item) => fields.map((field) => cellText(item, field, chunk.renderColumns)));
|
|
52
|
-
const body = isList ? formatList(headers, rows, chunk.width) : formatStacked(headers, rows, chunk.width);
|
|
62
|
+
const body = formatStacked(fields.map(titleCaseField), arrayData.map((item) => fields.map((field) => cellText(item, field, chunk.renderColumns))), chunk.width);
|
|
53
63
|
if (body) out += body + "\n";
|
|
54
64
|
return out;
|
|
55
65
|
}
|
|
56
66
|
function formatTitle(title, colorTitle) {
|
|
57
67
|
return colorTitle ? chalk.bold(title) : title;
|
|
58
68
|
}
|
|
59
|
-
function formatList(headers, rows, width) {
|
|
60
|
-
if (headers.length === 1) return formatOneColumn(headers[0] ?? "", rows.map((row) => row[0] ?? ""));
|
|
61
|
-
if (width === void 0) return formatColumns(headers, rows, naturalWidths(headers, rows));
|
|
62
|
-
for (let n = headers.length; n >= 2; n--) {
|
|
63
|
-
const subsetHeaders = headers.slice(0, n);
|
|
64
|
-
const subsetRows = rows.map((row) => row.slice(0, n));
|
|
65
|
-
const natural = naturalWidths(subsetHeaders, subsetRows);
|
|
66
|
-
if (rowWidth(natural) <= width) return formatColumns(subsetHeaders, subsetRows, natural);
|
|
67
|
-
const fitted = tryFit(subsetHeaders, subsetRows, width);
|
|
68
|
-
if (fitted === void 0) continue;
|
|
69
|
-
if (!natural.slice(0, n - 1).every((col, i) => fitted[i] === col)) continue;
|
|
70
|
-
return formatColumns(subsetHeaders.map((cell, i) => truncateTo(cell, fitted[i] ?? 0)), subsetRows.map((row) => row.map((cell, i) => truncateTo(cell, fitted[i] ?? 0))), fitted);
|
|
71
|
-
}
|
|
72
|
-
return formatStacked(headers, rows, width);
|
|
73
|
-
}
|
|
74
|
-
function rowWidth(widths) {
|
|
75
|
-
if (widths.length === 0) return 0;
|
|
76
|
-
return widths.reduce((sum, col) => sum + col, 0) + (widths.length - 1) * GUTTER;
|
|
77
|
-
}
|
|
78
|
-
function formatOneColumn(header, values) {
|
|
79
|
-
const lines = [chalk.green(header)];
|
|
80
|
-
for (const value of values) lines.push(value);
|
|
81
|
-
return lines.join("\n");
|
|
82
|
-
}
|
|
83
69
|
function formatColumns(headers, rows, widths) {
|
|
84
70
|
const joinRow = (cells) => cells.map((cell, i) => {
|
|
85
71
|
const width = widths[i] ?? 0;
|
|
86
|
-
return i === cells.length - 1 ?
|
|
72
|
+
return i === cells.length - 1 ? cell : padEndWidth(cell, width);
|
|
87
73
|
}).join(" ".repeat(GUTTER)).trimEnd();
|
|
88
74
|
const lines = [chalk.green(joinRow(headers))];
|
|
89
75
|
for (const row of rows) lines.push(joinRow(row));
|
|
@@ -101,25 +87,6 @@ function formatStacked(headers, rows, width) {
|
|
|
101
87
|
function stackedLine(header, value, labelWidth) {
|
|
102
88
|
return `${padEndWidth(header, labelWidth)}${" ".repeat(GUTTER)}${value}`;
|
|
103
89
|
}
|
|
104
|
-
function tryFit(headers, rows, width) {
|
|
105
|
-
const n = headers.length;
|
|
106
|
-
if (n === 0) return [];
|
|
107
|
-
const gutters = (n - 1) * GUTTER;
|
|
108
|
-
if (width <= gutters) return;
|
|
109
|
-
const budget = width - gutters;
|
|
110
|
-
if (budget < n * MIN_COL) return;
|
|
111
|
-
const widths = naturalWidths(headers, rows);
|
|
112
|
-
let overflow = widths.reduce((sum, col) => sum + col, 0) - budget;
|
|
113
|
-
if (overflow <= 0) return widths;
|
|
114
|
-
for (let i = n - 1; i >= 0 && overflow > 0; i--) {
|
|
115
|
-
const current = widths[i] ?? 0;
|
|
116
|
-
const take = Math.min(Math.max(current - MIN_COL, 0), overflow);
|
|
117
|
-
widths[i] = current - take;
|
|
118
|
-
overflow -= take;
|
|
119
|
-
}
|
|
120
|
-
if (overflow > 0) return;
|
|
121
|
-
return widths;
|
|
122
|
-
}
|
|
123
90
|
function naturalWidths(headers, rows) {
|
|
124
91
|
return headers.map((header, i) => Math.max(displayWidth(header), ...rows.map((row) => displayWidth(row[i] ?? ""))));
|
|
125
92
|
}
|
package/dist/index.js
CHANGED
package/dist/init/agents.js
CHANGED
|
@@ -1,32 +1,6 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { agentSupportsHttpMcp, agentSupportsProjectMcp, detectInstalledAgents, getAgentDisplayName, listMcpAgentIds, resolveAddMcpAgentId, tryResolveAddMcpAgentId } from "../mcp/agents.js";
|
|
2
|
+
import { agents } from "add-mcp";
|
|
2
3
|
//#region src/init/agents.ts
|
|
3
|
-
const AGENT_ALIASES = {
|
|
4
|
-
cursor: "cursor",
|
|
5
|
-
copilot: "vscode",
|
|
6
|
-
"github-copilot": "vscode",
|
|
7
|
-
"vs-code": "vscode",
|
|
8
|
-
vscode: "vscode",
|
|
9
|
-
claude: "claude-code",
|
|
10
|
-
"claude-code": "claude-code",
|
|
11
|
-
"claude-desktop": "claude-desktop",
|
|
12
|
-
codex: "codex",
|
|
13
|
-
opencode: "opencode",
|
|
14
|
-
antigravity: "antigravity",
|
|
15
|
-
cline: "cline",
|
|
16
|
-
"cline-cli": "cline-cli",
|
|
17
|
-
"cline-vscode": "cline",
|
|
18
|
-
"gemini-cli": "gemini-cli",
|
|
19
|
-
gemini: "gemini-cli",
|
|
20
|
-
goose: "goose",
|
|
21
|
-
windsurf: "windsurf",
|
|
22
|
-
codeium: "windsurf",
|
|
23
|
-
cascade: "windsurf",
|
|
24
|
-
"github-copilot-cli": "github-copilot-cli",
|
|
25
|
-
mcporter: "mcporter",
|
|
26
|
-
zed: "zed",
|
|
27
|
-
grok: "grok-build",
|
|
28
|
-
"grok-build": "grok-build"
|
|
29
|
-
};
|
|
30
4
|
const SKILLS_AGENT_BY_TYPE = {
|
|
31
5
|
cursor: "cursor",
|
|
32
6
|
vscode: "github-copilot",
|
|
@@ -44,26 +18,6 @@ const SKILLS_AGENT_BY_TYPE = {
|
|
|
44
18
|
zed: "zed",
|
|
45
19
|
"grok-build": "grok"
|
|
46
20
|
};
|
|
47
|
-
function listMcpAgentIds() {
|
|
48
|
-
return getAgentTypes();
|
|
49
|
-
}
|
|
50
|
-
function getAgentDisplayName(id) {
|
|
51
|
-
return agents[id].displayName;
|
|
52
|
-
}
|
|
53
|
-
function agentSupportsProjectMcp(id) {
|
|
54
|
-
return Boolean(agents[id].localConfigPath);
|
|
55
|
-
}
|
|
56
|
-
function agentSupportsHttpMcp(id) {
|
|
57
|
-
return agents[id].supportedTransports.includes("http");
|
|
58
|
-
}
|
|
59
|
-
function tryResolveAddMcpAgentId(rawAgent) {
|
|
60
|
-
return AGENT_ALIASES[rawAgent.toLowerCase()];
|
|
61
|
-
}
|
|
62
|
-
function resolveAddMcpAgentId(rawAgent) {
|
|
63
|
-
const resolved = tryResolveAddMcpAgentId(rawAgent);
|
|
64
|
-
if (!resolved) throw new Error(`Unknown agent: "${rawAgent}". Supported agents: ${listMcpAgentIds().join(", ")}`);
|
|
65
|
-
return resolved;
|
|
66
|
-
}
|
|
67
21
|
function getSkillsAgentName(agent) {
|
|
68
22
|
if (Object.prototype.hasOwnProperty.call(SKILLS_AGENT_BY_TYPE, agent)) return SKILLS_AGENT_BY_TYPE[agent];
|
|
69
23
|
const id = tryResolveAddMcpAgentId(agent);
|
|
@@ -79,9 +33,6 @@ function agentPickerHint(id) {
|
|
|
79
33
|
if (supportsSkills(id)) return "MCP server, skills";
|
|
80
34
|
return "MCP server";
|
|
81
35
|
}
|
|
82
|
-
async function detectInstalledAgents() {
|
|
83
|
-
return detectGlobalAgents();
|
|
84
|
-
}
|
|
85
36
|
function mcpPickerOptions() {
|
|
86
37
|
return listMcpAgentIds().map((id) => ({
|
|
87
38
|
value: id,
|
package/dist/init/editors.js
CHANGED
package/dist/init/interactive.js
CHANGED
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
import { FALLBACK_TEMPLATES, fetchTemplates, scaffoldTemplate } from "./bootstrap.js";
|
|
2
2
|
import { detectAgent, detectIde } from "./detect_agent.js";
|
|
3
3
|
import { neonInitAgentCmd } from "./profile_cli.js";
|
|
4
|
-
import {
|
|
4
|
+
import { installNeonMcpServer } from "../mcp/install.js";
|
|
5
|
+
import { agentSupportsProjectMcp, detectInstalledAgents, getAgentDisplayName, tryResolveAddMcpAgentId } from "../mcp/agents.js";
|
|
6
|
+
import { getSkillsAgentName, mcpPickerOptions } from "./agents.js";
|
|
5
7
|
import { ensureNeonctlAuth, isAuthenticated } from "./auth.js";
|
|
6
8
|
import { installExtension, isExtensionInstalled } from "./extension.js";
|
|
7
9
|
import { inspectProject } from "./inspect.js";
|
|
8
|
-
import { installNeonMcpServer } from "./install_mcp.js";
|
|
9
10
|
import { ensureNeonctl } from "./neonctl.js";
|
|
10
11
|
import { ensureSkillsUpToDate, installAgentSkills, skillsInstalledForAgent } from "./skills.js";
|
|
11
12
|
import { existsSync, readFileSync, writeFileSync } from "node:fs";
|
package/dist/init/orchestrate.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { tryResolveAddMcpAgentId } from "../mcp/agents.js";
|
|
2
|
+
import { supportsSkills } from "./agents.js";
|
|
2
3
|
import { isAuthenticated } from "./auth.js";
|
|
3
4
|
import { inspectProject } from "./inspect.js";
|
|
4
5
|
import { getSkillList, missingSkillsForAgent, skillsInstalledForAgent } from "./skills.js";
|
package/dist/init/phases/mcp.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { installNeonMcpServer } from "../../mcp/install.js";
|
|
2
|
+
import { agentSupportsHttpMcp, agentSupportsProjectMcp, getAgentDisplayName, resolveAddMcpAgentId, tryResolveAddMcpAgentId } from "../../mcp/agents.js";
|
|
3
|
+
import { getSkillsAgentName } from "../agents.js";
|
|
2
4
|
import { isAuthenticated } from "../auth.js";
|
|
3
|
-
import { installNeonMcpServer } from "../install_mcp.js";
|
|
4
5
|
//#region src/init/phases/mcp.ts
|
|
5
6
|
function skillsFollowUp(agent) {
|
|
6
7
|
if (agent && !getSkillsAgentName(agent)) return agent ? [
|
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
import { FALLBACK_TEMPLATES, fetchTemplates, findTemplate, scaffoldTemplate } from "../bootstrap.js";
|
|
2
2
|
import { detectIde, isCursorInstalled, isVSCodeInstalled } from "../detect_agent.js";
|
|
3
|
-
import {
|
|
3
|
+
import { installNeonMcpServer } from "../../mcp/install.js";
|
|
4
|
+
import { agentSupportsProjectMcp, listMcpAgentIds, resolveAddMcpAgentId, tryResolveAddMcpAgentId } from "../../mcp/agents.js";
|
|
5
|
+
import { getSkillsAgentName, supportsSkills } from "../agents.js";
|
|
4
6
|
import { NEON_EXTENSION_ID, downloadVsix } from "../vsix.js";
|
|
5
7
|
import { findEditorCommand } from "../extension.js";
|
|
6
8
|
import { inspectProject } from "../inspect.js";
|
|
7
|
-
import { installNeonMcpServer } from "../install_mcp.js";
|
|
8
9
|
import { ensureNeonctl } from "../neonctl.js";
|
|
9
10
|
import { ensureSkillsUpToDate } from "../skills.js";
|
|
10
11
|
import { writeFileSync } from "node:fs";
|