neon 8.3.1 → 8.3.3
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 +3 -1
- package/dist/human_table.js +22 -2
- package/dist/init/copy.js +25 -3
- package/dist/init/run.js +137 -67
- package/dist/init/tooling.js +13 -3
- package/dist/mcp/mint.js +8 -3
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -839,7 +839,9 @@ The target directory must be empty unless you pass `--force` (a lone `.git` is i
|
|
|
839
839
|
|
|
840
840
|
Recommended installs the Neon plugin for every detected agent (global config, project folders, and the host CLI). Plugin-capable agents get the plugin; the rest get skills and MCP. If none are detected, it installs the default Neon skills for Cursor and Codex in this directory (`./.agents/skills`) and does not configure MCP. It then links a project when the CLI is authenticated, and writes a default `neon.ts` (Postgres only) using the package manager already in the directory.
|
|
841
841
|
|
|
842
|
-
Unauthenticated `-y` skips linking and prints the next step: sign up at https://neon.com/signup, then `neon login`, `neon link`, or `neon claim create` (no account, expires in 72 hours unless claimed). `-y` never opens a browser. `-y` with `--project-id` or other account flags links when the CLI is already signed in;
|
|
842
|
+
Unauthenticated `-y` skips linking and prints the next step: sign up at https://neon.com/signup, then `neon login`, `neon link`, or `neon claim create` (no account, expires in 72 hours unless claimed). `-y` never opens a browser. `-y` with `--project-id` or other account flags links when the CLI is already signed in. Without credentials, `--org-id`, `--project-id`, and `--branch` together write `.neon` offline (as `neon link --no-checks` does), and init prints `neon login` as the next step; Neon checks the IDs on the first command that reaches it. With only some of those flags, init still installs agent tooling and writes `neon.ts`, then prints `neon login` and the `neon link` command with those flags.
|
|
843
|
+
|
|
844
|
+
When init picks MCP auth (no `--mcp-auth`) and the CLI credential is an organization or project-scoped API key, which cannot mint the MCP server's key, init configures MCP with OAuth instead and continues. With `--mcp-auth api-key`, init skips MCP, finishes linking and `neon.ts`, then reports `Neon setup failed.` with the `neon mcp` commands to retry and exits 1.
|
|
843
845
|
|
|
844
846
|
Custom asks how to add Neon to coding agents (plugin, skills and MCP separately, or skip), then how to get a project (sign in and link, or a claimable project when you are not signed in), then which services `neon.ts` should declare. Skills and MCP use separate agent pickers, and either can be skipped. Postgres is listed first as always included. The package manager is inferred from the directory; Custom asks only when none is detected. `--skill` selects skills (not the plugin) and skips that picker. MCP flags (`--mcp-config-location`, `--mcp-auth`, `--mcp-project-scoped`) select skills and MCP. `--mcp-config-location` is where the config is written: `global` maps to `neon mcp`, and `project` maps to `neon mcp --project`. With `-y`, the location defaults to `global`; interactive Custom asks. If some selected agents cannot write that location, init warns and skips MCP for those agents while continuing with the supported agents. It fails when none of the selected agents support the location. `--mcp-project-scoped` limits MCP tools to the linked Neon project by passing its ID to `neon mcp --project-id`. Omitted is false, including in Custom; there is no prompt. `--no-agent-setup` skips agent setup.
|
|
845
847
|
|
package/dist/human_table.js
CHANGED
|
@@ -132,8 +132,28 @@ function flattenCell(value) {
|
|
|
132
132
|
if (isObject(value)) return JSON.stringify(value);
|
|
133
133
|
return String(value).replace(/\s+/g, " ").trim();
|
|
134
134
|
}
|
|
135
|
+
const LABEL_WORDS = {
|
|
136
|
+
api: "API",
|
|
137
|
+
cname: "CNAME",
|
|
138
|
+
cors: "CORS",
|
|
139
|
+
db: "DB",
|
|
140
|
+
id: "ID",
|
|
141
|
+
jwks: "JWKS",
|
|
142
|
+
jwt: "JWT",
|
|
143
|
+
lfc: "LFC",
|
|
144
|
+
lsn: "LSN",
|
|
145
|
+
mib: "MiB",
|
|
146
|
+
openapi: "OpenAPI",
|
|
147
|
+
pid: "PID",
|
|
148
|
+
pids: "PIDs",
|
|
149
|
+
s3: "S3",
|
|
150
|
+
ttl: "TTL",
|
|
151
|
+
uri: "URI",
|
|
152
|
+
url: "URL",
|
|
153
|
+
vpc: "VPC"
|
|
154
|
+
};
|
|
135
155
|
function titleCaseField(field) {
|
|
136
|
-
return field.split("_").map((word) => word ? word[0]?.toUpperCase() + word.slice(1) : word).join(" ");
|
|
156
|
+
return field.split("_").map((word) => LABEL_WORDS[word] ?? (word ? word[0]?.toUpperCase() + word.slice(1) : word)).join(" ");
|
|
137
157
|
}
|
|
138
158
|
const WIDE_RANGES = [
|
|
139
159
|
[4352, 4447],
|
|
@@ -206,4 +226,4 @@ function charWidth(code) {
|
|
|
206
226
|
return 1;
|
|
207
227
|
}
|
|
208
228
|
//#endregion
|
|
209
|
-
export { displayWidth, formatHumanChunk, parseColumns, resolveOutputWidth, stripAnsi };
|
|
229
|
+
export { displayWidth, formatHumanChunk, parseColumns, resolveOutputWidth, stripAnsi, titleCaseField };
|
package/dist/init/copy.js
CHANGED
|
@@ -51,7 +51,7 @@ const NO_AGENTS_FALLBACK_STATUS = "No coding agents detected. Installing the def
|
|
|
51
51
|
const NO_AGENT_SETUP_CONFLICT = "--no-agent-setup cannot be combined with --agent, --skill, or MCP flags.";
|
|
52
52
|
const INIT_NEEDS_YES = "No interactive terminal. Re-run with -y to set up this directory with Recommended defaults: `neon init -y`. To pick each step instead, run `neon init` in a terminal, or see `neon init --help` for the flag that sets each step.";
|
|
53
53
|
const NON_TTY_LINK_NEEDS_AUTH = "No interactive terminal. Sign in with `neon login`, then re-run, or pass --claimable.";
|
|
54
|
-
const
|
|
54
|
+
const MCP_OAUTH_FALLBACK = "This CLI credential cannot mint an API key for the Neon MCP server. Configuring it with OAuth instead.";
|
|
55
55
|
const MCP_SCOPED_NEEDS_PROJECT = "--mcp-project-scoped requires a linked project. Link a project first, or omit --mcp-project-scoped.";
|
|
56
56
|
const namedAgentsUnavailable = (ids) => `--agent ${ids.join(", ")} cannot install this tooling. Pass agents that support it, or omit --agent.`;
|
|
57
57
|
const mcpConfigLocationUnavailable = (ids, location) => location === "project" ? `--agent ${ids.join(", ")} cannot install project-level MCP config. Use --mcp-config-location global, or choose an agent that supports project config.` : `--agent ${ids.join(", ")} cannot install global MCP config. Choose an agent that supports global config.`;
|
|
@@ -78,7 +78,8 @@ const PROGRESS = {
|
|
|
78
78
|
install: (pm) => `Installing Neon dependencies with ${pm}...`,
|
|
79
79
|
env: "Pulling Neon environment variables..."
|
|
80
80
|
};
|
|
81
|
-
|
|
81
|
+
/** `deferred` carries the account and credential flags init was given. */
|
|
82
|
+
const unattendedUnauthedNext = (deferred) => deferred === void 0 ? [
|
|
82
83
|
"Link a project with a Neon account and an authenticated CLI.",
|
|
83
84
|
"Sign up: https://neon.com/signup",
|
|
84
85
|
`${getCliName()} login`,
|
|
@@ -86,6 +87,16 @@ const unattendedUnauthedNext = () => [
|
|
|
86
87
|
"",
|
|
87
88
|
"Or create a claimable project without an account. It expires in 72 hours unless claimed.",
|
|
88
89
|
`${getCliName()} claim create`
|
|
90
|
+
] : [
|
|
91
|
+
"Link the project once the CLI is authenticated.",
|
|
92
|
+
deferred.login,
|
|
93
|
+
deferred.link
|
|
94
|
+
];
|
|
95
|
+
const PROJECT_PINNED_OFFLINE = "linked, not verified";
|
|
96
|
+
const offlinePinNext = (input) => [
|
|
97
|
+
"The link was written without contacting Neon. Sign in to use it:",
|
|
98
|
+
input.login,
|
|
99
|
+
...input.envPull !== void 0 ? ["Then pull the branch's environment variables:", input.envPull] : []
|
|
89
100
|
];
|
|
90
101
|
const skippedLinkNext = () => ["To connect this directory to a Neon project:", `${getCliName()} link`];
|
|
91
102
|
const extraServicesNext = () => [
|
|
@@ -122,6 +133,17 @@ const installFailedNext = (command) => ({
|
|
|
122
133
|
body: "Could not install the Neon dependencies.",
|
|
123
134
|
next: ["Install the dependencies, then run neon init again:", command]
|
|
124
135
|
});
|
|
136
|
+
const MCP_API_KEY_NEEDS_AUTH = "API-key MCP setup needs a Neon credential to mint the key. Sign in with `neon login` or pass a personal API key.";
|
|
137
|
+
/** `mcp` is the `neon mcp` invocation without an auth flag. */
|
|
138
|
+
const mcpKeyFailedNext = (reason, mcp) => [
|
|
139
|
+
reason === "cannot-mint" ? "The Neon MCP server was not configured: this CLI credential cannot mint its API key." : "The Neon MCP server was not configured: minting its API key needs a signed-in CLI.",
|
|
140
|
+
"Configure it with a personal API key:",
|
|
141
|
+
`${mcp} --api-key <personal-api-key>`,
|
|
142
|
+
"",
|
|
143
|
+
"Or with OAuth:",
|
|
144
|
+
`${mcp} --oauth`
|
|
145
|
+
];
|
|
146
|
+
const mcpAfterLinkNext = (mcp) => ["Then configure the Neon MCP server for that project:", mcp];
|
|
125
147
|
const envPullFailedNext = () => ({
|
|
126
148
|
heading: HEADING_FAILED,
|
|
127
149
|
body: "The project is linked, but its environment variables could not be pulled.",
|
|
@@ -137,4 +159,4 @@ const headingForOutcome = (outcome) => {
|
|
|
137
159
|
}
|
|
138
160
|
};
|
|
139
161
|
//#endregion
|
|
140
|
-
export { AGENT_DETECTED_DESCRIPTION, AGENT_MCP_PICKER_MESSAGE, AGENT_MCP_SKIP_MESSAGE, AGENT_OTHER_DESCRIPTION, AGENT_PICKER_MESSAGE, AGENT_PLUGIN_SKIP_MESSAGE, AGENT_SETUP_MESSAGE, AGENT_SETUP_PLUGIN_DESCRIPTION, AGENT_SETUP_PLUGIN_TITLE, AGENT_SETUP_SKILLS_DESCRIPTION, AGENT_SETUP_SKILLS_TITLE, AGENT_SETUP_SKIP_DESCRIPTION, AGENT_SETUP_SKIP_TITLE, AGENT_SKILLS_PICKER_MESSAGE, AGENT_SKILLS_SKIP_MESSAGE, CANCELLED_BODY, CLAIMABLE_ACCOUNT_FLAGS, CLAIMABLE_ALREADY_LINKED, CLAIMABLE_MCP_API_KEY, CLAIMABLE_NO_LINK, CONFIG_CONFIRM_HINT, CONFIG_CONFIRM_MESSAGE, HEADING_CANCELLED, HEADING_COMPLETE, HEADING_FAILED, HEADING_PENDING, INIT_NEEDS_YES, INIT_SUBTITLE, MCP_AUTH_API_KEY_DESCRIPTION, MCP_AUTH_API_KEY_TITLE, MCP_AUTH_MESSAGE, MCP_AUTH_OAUTH_DESCRIPTION, MCP_AUTH_OAUTH_TITLE, MCP_SCOPED_NEEDS_PROJECT, MCP_SCOPE_GLOBAL_DESCRIPTION, MCP_SCOPE_GLOBAL_TITLE, MCP_SCOPE_MESSAGE, MCP_SCOPE_PROJECT_DESCRIPTION, MCP_SCOPE_PROJECT_TITLE, MODE_CUSTOM_DESCRIPTION, MODE_CUSTOM_TITLE, MODE_MESSAGE, MODE_RECOMMENDED_DESCRIPTION, MODE_RECOMMENDED_TITLE, NON_TTY_AGENT_SETUP, NON_TTY_LINK_NEEDS_AUTH, NO_AGENTS_FALLBACK_STATUS, NO_AGENT_SETUP_CONFLICT, PACKAGE_MANAGER_MESSAGE, PROGRESS, PROJECT_SETUP_CLAIMABLE_DESCRIPTION, PROJECT_SETUP_CLAIMABLE_TITLE, PROJECT_SETUP_LINK_DESCRIPTION, PROJECT_SETUP_LINK_TITLE, PROJECT_SETUP_MESSAGE, SERVICES_HINT, SERVICES_MESSAGE, SKILLS_PICKER_MESSAGE,
|
|
162
|
+
export { AGENT_DETECTED_DESCRIPTION, AGENT_MCP_PICKER_MESSAGE, AGENT_MCP_SKIP_MESSAGE, AGENT_OTHER_DESCRIPTION, AGENT_PICKER_MESSAGE, AGENT_PLUGIN_SKIP_MESSAGE, AGENT_SETUP_MESSAGE, AGENT_SETUP_PLUGIN_DESCRIPTION, AGENT_SETUP_PLUGIN_TITLE, AGENT_SETUP_SKILLS_DESCRIPTION, AGENT_SETUP_SKILLS_TITLE, AGENT_SETUP_SKIP_DESCRIPTION, AGENT_SETUP_SKIP_TITLE, AGENT_SKILLS_PICKER_MESSAGE, AGENT_SKILLS_SKIP_MESSAGE, CANCELLED_BODY, CLAIMABLE_ACCOUNT_FLAGS, CLAIMABLE_ALREADY_LINKED, CLAIMABLE_MCP_API_KEY, CLAIMABLE_NO_LINK, CONFIG_CONFIRM_HINT, CONFIG_CONFIRM_MESSAGE, HEADING_CANCELLED, HEADING_COMPLETE, HEADING_FAILED, HEADING_PENDING, INIT_NEEDS_YES, INIT_SUBTITLE, MCP_API_KEY_NEEDS_AUTH, MCP_AUTH_API_KEY_DESCRIPTION, MCP_AUTH_API_KEY_TITLE, MCP_AUTH_MESSAGE, MCP_AUTH_OAUTH_DESCRIPTION, MCP_AUTH_OAUTH_TITLE, MCP_OAUTH_FALLBACK, MCP_SCOPED_NEEDS_PROJECT, MCP_SCOPE_GLOBAL_DESCRIPTION, MCP_SCOPE_GLOBAL_TITLE, MCP_SCOPE_MESSAGE, MCP_SCOPE_PROJECT_DESCRIPTION, MCP_SCOPE_PROJECT_TITLE, MODE_CUSTOM_DESCRIPTION, MODE_CUSTOM_TITLE, MODE_MESSAGE, MODE_RECOMMENDED_DESCRIPTION, MODE_RECOMMENDED_TITLE, NON_TTY_AGENT_SETUP, NON_TTY_LINK_NEEDS_AUTH, NO_AGENTS_FALLBACK_STATUS, NO_AGENT_SETUP_CONFLICT, PACKAGE_MANAGER_MESSAGE, PROGRESS, PROJECT_PINNED_OFFLINE, PROJECT_SETUP_CLAIMABLE_DESCRIPTION, PROJECT_SETUP_CLAIMABLE_TITLE, PROJECT_SETUP_LINK_DESCRIPTION, PROJECT_SETUP_LINK_TITLE, PROJECT_SETUP_MESSAGE, SERVICES_HINT, SERVICES_MESSAGE, SKILLS_PICKER_MESSAGE, agentMissingCommandDescription, claimableNext, claimableThenServicesNext, envPullFailedNext, existingConfigNext, extraServicesNext, headingForOutcome, installFailedNext, mcpAfterLinkNext, mcpConfigLocationSkipped, mcpConfigLocationUnavailable, mcpKeyFailedNext, namedAgentsUnavailable, offlinePinNext, skippedLinkNext, unattendedUnauthedNext };
|
package/dist/init/run.js
CHANGED
|
@@ -15,10 +15,12 @@ import { missingPluginsCommands, pluginsInstallableAgents } from "../plugins/tar
|
|
|
15
15
|
import { skillsInstallableAgents } from "../skills/targets.js";
|
|
16
16
|
import { FALLBACK_SKILLS_AGENTS, assertNamedAgentTooling, funnelAgentSetup, planInitToolingSteps, recommendedTooling, resolveNamedAgents, splitInitTooling } from "./plan.js";
|
|
17
17
|
import { inferPackageManager, resolvePackageManager } from "../utils/package_manager.js";
|
|
18
|
-
import { CANCELLED_BODY, CLAIMABLE_ACCOUNT_FLAGS, CLAIMABLE_ALREADY_LINKED, CLAIMABLE_MCP_API_KEY, CLAIMABLE_NO_LINK, MCP_SCOPED_NEEDS_PROJECT, NON_TTY_LINK_NEEDS_AUTH, NO_AGENTS_FALLBACK_STATUS, PROGRESS,
|
|
18
|
+
import { CANCELLED_BODY, CLAIMABLE_ACCOUNT_FLAGS, CLAIMABLE_ALREADY_LINKED, CLAIMABLE_MCP_API_KEY, CLAIMABLE_NO_LINK, MCP_API_KEY_NEEDS_AUTH, MCP_SCOPED_NEEDS_PROJECT, NON_TTY_LINK_NEEDS_AUTH, NO_AGENTS_FALLBACK_STATUS, PROGRESS, PROJECT_PINNED_OFFLINE, claimableNext, claimableThenServicesNext, envPullFailedNext, existingConfigNext, extraServicesNext, installFailedNext, mcpAfterLinkNext, mcpConfigLocationSkipped, mcpConfigLocationUnavailable, mcpKeyFailedNext, namedAgentsUnavailable, offlinePinNext, skippedLinkNext, unattendedUnauthedNext } from "./copy.js";
|
|
19
19
|
import { agentsRowValue, configRowValue, formatInitDone, headingForKind, printInitBanner, printInitDone, printInitProgress, projectRowValue, shouldPrintInitBanner } from "./chrome.js";
|
|
20
20
|
import { ConfigInstallFailed, initCmd } from "../commands/config.js";
|
|
21
|
+
import { quoteFlagValue, runLink } from "../commands/link.js";
|
|
21
22
|
import { runAuthenticatedLink } from "./link.js";
|
|
23
|
+
import { CannotMintApiKeyError } from "../mcp/mint.js";
|
|
22
24
|
import { pullInitEnv } from "./auth.js";
|
|
23
25
|
import { InitCancelled, raceSigint, restoreCursor } from "./cancelled.js";
|
|
24
26
|
import { pickAgentSetupInteractively, pickInitAgentsInteractively, pickInitConfigInteractively, pickInitMcpAuthInteractively, pickInitMcpConfigLocationInteractively, pickInitModeInteractively, pickInitPackageManagerInteractively, pickInitProjectSetupInteractively, pickInitServicesInteractively, pickInitSkillsInteractively } from "./wizard.js";
|
|
@@ -34,6 +36,24 @@ const isLinked = (contextFile) => {
|
|
|
34
36
|
const projectId = readContextFile(contextFile).projectId;
|
|
35
37
|
return typeof projectId === "string" && projectId.length > 0;
|
|
36
38
|
};
|
|
39
|
+
const commandLine = (command, pairs) => [`${getCliName()} ${command}`, ...pairs.flatMap(([flag, value]) => value === void 0 ? [] : [`${flag} ${quoteFlagValue(value)}`])].join(" ");
|
|
40
|
+
const deferredLinkCommands = (props, inputs) => {
|
|
41
|
+
const session = [["--profile", props.profile], ["--config-dir", props.configDir === defaultDir ? void 0 : props.configDir]];
|
|
42
|
+
const context = [["--context-file", props.contextFile === ".neon" ? void 0 : props.contextFile]];
|
|
43
|
+
return {
|
|
44
|
+
login: commandLine("login", session),
|
|
45
|
+
link: commandLine("link", [
|
|
46
|
+
["--org-id", inputs.orgId],
|
|
47
|
+
["--project-id", inputs.projectId],
|
|
48
|
+
["--project-name", inputs.projectName],
|
|
49
|
+
["--region-id", inputs.regionId],
|
|
50
|
+
["--branch", inputs.branch],
|
|
51
|
+
...context,
|
|
52
|
+
...session
|
|
53
|
+
]),
|
|
54
|
+
envPull: commandLine("env pull", [...context, ...session])
|
|
55
|
+
};
|
|
56
|
+
};
|
|
37
57
|
const expandTelemetryServices = (services) => {
|
|
38
58
|
if (services.includes("data-api") && !services.includes("auth")) return CONFIG_INIT_SERVICES.filter((service) => service === "auth" || services.includes(service));
|
|
39
59
|
return [...services];
|
|
@@ -231,6 +251,7 @@ const runInit = async (props) => {
|
|
|
231
251
|
const pluginReady = (agents) => agents.filter((id) => !unavailablePluginAgents.includes(id));
|
|
232
252
|
let tooling = { setup: "skip" };
|
|
233
253
|
let mcpAuth = props.mcpAuth;
|
|
254
|
+
let mcpAuthChosen = props.mcpAuth !== void 0;
|
|
234
255
|
let mcpConfigLocation = props.mcpConfigLocation ?? "global";
|
|
235
256
|
let selectedSkills = props.skill;
|
|
236
257
|
let delayMcp = false;
|
|
@@ -293,7 +314,9 @@ const runInit = async (props) => {
|
|
|
293
314
|
mcpConfigLocation = props.mcpConfigLocation ?? (yes ? "global" : props.pickMcpConfigLocation !== void 0 ? await props.pickMcpConfigLocation() : detection.interactive ? await pickInitMcpConfigLocationInteractively() : "global");
|
|
294
315
|
if (mcpSelection.length > 0) validateMcpConfigLocationSupport(mcpSelection, mcpConfigLocation);
|
|
295
316
|
const pickAuth = props.pickMcpAuth ?? (detection.interactive ? pickInitMcpAuthInteractively : void 0);
|
|
296
|
-
|
|
317
|
+
const chosenAuth = props.mcpAuth ?? (yes ? void 0 : await pickAuth?.({ authenticated: detection.authenticated }));
|
|
318
|
+
mcpAuthChosen = chosenAuth !== void 0;
|
|
319
|
+
mcpAuth = chosenAuth ?? (detection.authenticated && props.claimable !== true ? "api-key" : "oauth");
|
|
297
320
|
}
|
|
298
321
|
tooling = skillsMcpTooling(skillsSelection, mcpSelection, mcpConfigLocation, namedFallback);
|
|
299
322
|
}
|
|
@@ -305,16 +328,36 @@ const runInit = async (props) => {
|
|
|
305
328
|
if (mcpAuth === void 0 && usesMcp && (!detection.authenticated || props.claimable === true)) mcpAuth = "oauth";
|
|
306
329
|
delayMcp = usesMcp && (mcpAuth === "api-key" || props.mcpProjectScoped === true);
|
|
307
330
|
const mcpOauth = mcpAuth === "oauth";
|
|
308
|
-
const earlyTooling = delayMcp ? tooling
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
331
|
+
const earlyTooling = delayMcp ? withoutMcp(tooling) : tooling;
|
|
332
|
+
const mcpState = {};
|
|
333
|
+
const reportedTooling = () => mcpState.error === void 0 && mcpState.deferredPin === void 0 ? tooling : withoutMcp(tooling);
|
|
334
|
+
const mcpCommand = (projectIdArg) => [
|
|
335
|
+
`${getCliName()} mcp -y`,
|
|
336
|
+
...mcpConfigLocation === "project" ? ["--project"] : [],
|
|
337
|
+
...projectIdArg !== void 0 ? [`--project-id ${projectIdArg}`] : [],
|
|
338
|
+
...("mcpAgents" in tooling ? tooling.mcpAgents : []).map((agent) => `--agent ${agent}`)
|
|
339
|
+
].join(" ");
|
|
340
|
+
const mcpFailedNext = () => mcpState.error === void 0 ? [] : mcpKeyFailedNext(mcpState.reason ?? "cannot-mint", mcpCommand(mcpState.pinId !== void 0 ? quoteFlagValue(mcpState.pinId) : void 0));
|
|
341
|
+
const mcpDeferredNext = () => mcpState.deferredPin === void 0 ? [] : mcpAfterLinkNext(`${mcpCommand(mcpState.deferredPin)}${mcpOauth ? " --oauth" : ""}`);
|
|
342
|
+
const runTooling = async (steps, plan) => {
|
|
343
|
+
let ran = plan;
|
|
344
|
+
try {
|
|
345
|
+
await runToolingSteps(steps, {
|
|
346
|
+
cwd,
|
|
347
|
+
output: props.output,
|
|
348
|
+
auth,
|
|
349
|
+
operations: props.operations,
|
|
350
|
+
narrate: "human",
|
|
351
|
+
mcpOauthFallback: !mcpAuthChosen
|
|
352
|
+
});
|
|
353
|
+
} catch (error) {
|
|
354
|
+
if (!(error instanceof CannotMintApiKeyError)) throw error;
|
|
355
|
+
mcpState.error = error;
|
|
356
|
+
mcpState.reason = "cannot-mint";
|
|
357
|
+
ran = withoutMcp(plan);
|
|
358
|
+
}
|
|
359
|
+
funnel.agentsInstalled = uniqueAgents([...funnel.agentsInstalled, ...agentsFromTooling(ran)]);
|
|
360
|
+
};
|
|
318
361
|
const toolingSteps = planInitToolingSteps({
|
|
319
362
|
tooling: earlyTooling,
|
|
320
363
|
yes: true,
|
|
@@ -324,20 +367,12 @@ const runInit = async (props) => {
|
|
|
324
367
|
mcpProject: mcpConfigLocation === "project",
|
|
325
368
|
...selectedSkills !== void 0 ? { skills: selectedSkills } : {}
|
|
326
369
|
});
|
|
327
|
-
if (toolingSteps.length > 0)
|
|
328
|
-
await runToolingSteps(toolingSteps, {
|
|
329
|
-
cwd,
|
|
330
|
-
output: props.output,
|
|
331
|
-
auth,
|
|
332
|
-
operations: props.operations,
|
|
333
|
-
narrate: "human"
|
|
334
|
-
});
|
|
335
|
-
funnel.agentsInstalled = agentsFromTooling(earlyTooling);
|
|
336
|
-
}
|
|
370
|
+
if (toolingSteps.length > 0) await runTooling(toolingSteps, earlyTooling);
|
|
337
371
|
let projectSetup = "skip";
|
|
338
372
|
let linkedNow = false;
|
|
339
373
|
let claimExpiresAt;
|
|
340
374
|
const noLink = props.link === false;
|
|
375
|
+
const linkByDefault = detection.authenticated || hasExplicitLinkInputs && !yes;
|
|
341
376
|
if (noLink) {
|
|
342
377
|
funnel.link = alreadyLinked ? "already_linked" : "skipped";
|
|
343
378
|
projectSetup = "skip";
|
|
@@ -346,19 +381,39 @@ const runInit = async (props) => {
|
|
|
346
381
|
projectSetup = "already";
|
|
347
382
|
if (claimableExisting) claimExpiresAt = readLinkedClaimableCredentials(configDir, readContextFile(contextFile))?.expiresAt;
|
|
348
383
|
} else if (recommended) {
|
|
349
|
-
if (
|
|
384
|
+
if (linkByDefault) projectSetup = "link";
|
|
350
385
|
else if (detection.interactive && !yes) projectSetup = "link";
|
|
351
386
|
else {
|
|
352
387
|
projectSetup = "skip";
|
|
353
388
|
funnel.link = "skipped";
|
|
354
389
|
}
|
|
355
390
|
} else if (props.claimable === true) projectSetup = "claimable";
|
|
356
|
-
else if (
|
|
391
|
+
else if (linkByDefault) projectSetup = "link";
|
|
357
392
|
else if (yes) {
|
|
358
393
|
projectSetup = "skip";
|
|
359
394
|
funnel.link = "skipped";
|
|
360
395
|
} else if (props.pickProjectSetup !== void 0 || detection.interactive) projectSetup = await (props.pickProjectSetup ?? pickInitProjectSetupInteractively)();
|
|
361
396
|
else throw new Error(NON_TTY_LINK_NEEDS_AUTH);
|
|
397
|
+
const linkProps = {
|
|
398
|
+
apiClient: props.apiClient,
|
|
399
|
+
apiKey: props.apiKey,
|
|
400
|
+
apiHost: props.apiHost,
|
|
401
|
+
output: props.output,
|
|
402
|
+
contextFile,
|
|
403
|
+
yes,
|
|
404
|
+
clear: false,
|
|
405
|
+
checks: true,
|
|
406
|
+
envPull: true,
|
|
407
|
+
config: false,
|
|
408
|
+
cwd,
|
|
409
|
+
...linkInputs,
|
|
410
|
+
...props.configDir ? { configDir: props.configDir } : {},
|
|
411
|
+
...props.profile ? { profile: props.profile } : {},
|
|
412
|
+
...props.oauthHost ? { oauthHost: props.oauthHost } : {},
|
|
413
|
+
...props.clientId ? { clientId: props.clientId } : {},
|
|
414
|
+
...props.forceAuth !== void 0 ? { forceAuth: props.forceAuth } : {},
|
|
415
|
+
...props.allowUnsafeTls !== void 0 ? { allowUnsafeTls: props.allowUnsafeTls } : {}
|
|
416
|
+
};
|
|
362
417
|
if (usesMcp && mcpAuth === "api-key" && (projectSetup === "claimable" || props.claimable === true)) throw new Error(CLAIMABLE_MCP_API_KEY);
|
|
363
418
|
if (projectSetup === "claimable") {
|
|
364
419
|
printInitProgress(PROGRESS.claim);
|
|
@@ -374,32 +429,28 @@ const runInit = async (props) => {
|
|
|
374
429
|
funnel.link = "claimable";
|
|
375
430
|
claimExpiresAt = readLinkedClaimableCredentials(configDir, readContextFile(contextFile))?.expiresAt;
|
|
376
431
|
} else if (projectSetup === "link") {
|
|
377
|
-
if (yes && !detection.authenticated) throw new Error(YES_LINK_NEEDS_AUTH);
|
|
378
432
|
printInitProgress(detection.authenticated ? PROGRESS.link : PROGRESS.auth);
|
|
379
|
-
await (props.linkProject ?? runAuthenticatedLink)(
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
checks:
|
|
388
|
-
envPull:
|
|
389
|
-
config: false,
|
|
390
|
-
cwd,
|
|
391
|
-
...linkInputs,
|
|
392
|
-
...props.configDir ? { configDir: props.configDir } : {},
|
|
393
|
-
...props.profile ? { profile: props.profile } : {},
|
|
394
|
-
...props.oauthHost ? { oauthHost: props.oauthHost } : {},
|
|
395
|
-
...props.clientId ? { clientId: props.clientId } : {},
|
|
396
|
-
...props.forceAuth !== void 0 ? { forceAuth: props.forceAuth } : {},
|
|
397
|
-
...props.allowUnsafeTls !== void 0 ? { allowUnsafeTls: props.allowUnsafeTls } : {}
|
|
433
|
+
await (props.linkProject ?? runAuthenticatedLink)(linkProps);
|
|
434
|
+
linkedNow = true;
|
|
435
|
+
funnel.link = "linked";
|
|
436
|
+
}
|
|
437
|
+
const pinnedOffline = funnel.link === "skipped" && !noLink && linkInputs.orgId !== void 0 && linkInputs.projectId !== void 0 && linkInputs.branch !== void 0 && linkInputs.projectName === void 0 && linkInputs.regionId === void 0;
|
|
438
|
+
if (pinnedOffline) {
|
|
439
|
+
await runLink({
|
|
440
|
+
...linkProps,
|
|
441
|
+
checks: false,
|
|
442
|
+
envPull: false
|
|
398
443
|
});
|
|
399
444
|
linkedNow = true;
|
|
400
445
|
funnel.link = "linked";
|
|
401
446
|
}
|
|
402
|
-
if (delayMcp && tooling.setup !== "skip")
|
|
447
|
+
if (delayMcp && tooling.setup !== "skip" && props.mcpProjectScoped === true && funnel.link === "skipped" && !noLink) mcpState.deferredPin = linkInputs.projectId !== void 0 ? quoteFlagValue(linkInputs.projectId) : "<project-id>";
|
|
448
|
+
else if (delayMcp && tooling.setup !== "skip" && mcpAuth === "api-key" && yes && !detection.authenticated) {
|
|
449
|
+
mcpState.error = new Error(MCP_API_KEY_NEEDS_AUTH);
|
|
450
|
+
mcpState.reason = "signed-out";
|
|
451
|
+
const linkedId = readContextFile(contextFile).projectId;
|
|
452
|
+
if (props.mcpProjectScoped === true && typeof linkedId === "string") mcpState.pinId = linkedId;
|
|
453
|
+
} else if (delayMcp && tooling.setup !== "skip") {
|
|
403
454
|
const linkedId = readContextFile(contextFile).projectId;
|
|
404
455
|
let pinId;
|
|
405
456
|
if (props.mcpProjectScoped === true) {
|
|
@@ -424,16 +475,8 @@ const runInit = async (props) => {
|
|
|
424
475
|
mcpProject: mcpConfigLocation === "project",
|
|
425
476
|
...pinId !== void 0 ? { mcpProjectId: pinId } : {}
|
|
426
477
|
});
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
cwd,
|
|
430
|
-
output: props.output,
|
|
431
|
-
auth,
|
|
432
|
-
operations: props.operations,
|
|
433
|
-
narrate: "human"
|
|
434
|
-
});
|
|
435
|
-
funnel.agentsInstalled = uniqueAgents([...funnel.agentsInstalled, ...agentsFromTooling(mcpOnly)]);
|
|
436
|
-
}
|
|
478
|
+
mcpState.pinId = pinId;
|
|
479
|
+
if (mcpSteps.length > 0) await runTooling(mcpSteps, mcpOnly);
|
|
437
480
|
}
|
|
438
481
|
const canAskConfig = props.pickConfig !== void 0 || detection.interactive;
|
|
439
482
|
const configResolution = resolveInitConfigChoice({
|
|
@@ -490,7 +533,7 @@ const runInit = async (props) => {
|
|
|
490
533
|
heading: failed.heading,
|
|
491
534
|
body: failed.body,
|
|
492
535
|
rows: [],
|
|
493
|
-
next: failed.next
|
|
536
|
+
next: joinNext(failed.next, mcpFailedNext())
|
|
494
537
|
}));
|
|
495
538
|
printed = true;
|
|
496
539
|
outcome = "error";
|
|
@@ -503,8 +546,8 @@ const runInit = async (props) => {
|
|
|
503
546
|
const context = readContextFile(contextFile);
|
|
504
547
|
const branch = contextBranch(context);
|
|
505
548
|
const linked = isLinked(contextFile);
|
|
506
|
-
const alreadyPulled = linkedNow && funnel.link === "linked";
|
|
507
|
-
if (shouldPullEnvAfterInitConfig({
|
|
549
|
+
const alreadyPulled = linkedNow && funnel.link === "linked" && !pinnedOffline;
|
|
550
|
+
if (funnel.link !== "skipped" && !pinnedOffline && shouldPullEnvAfterInitConfig({
|
|
508
551
|
wroteNewFile,
|
|
509
552
|
extraServices,
|
|
510
553
|
alreadyPulled,
|
|
@@ -530,7 +573,7 @@ const runInit = async (props) => {
|
|
|
530
573
|
{
|
|
531
574
|
label: "Agents",
|
|
532
575
|
value: agentsRowValue({
|
|
533
|
-
tooling,
|
|
576
|
+
tooling: reportedTooling(),
|
|
534
577
|
installed: funnel.agentsInstalled
|
|
535
578
|
})
|
|
536
579
|
},
|
|
@@ -543,20 +586,29 @@ const runInit = async (props) => {
|
|
|
543
586
|
value: configRowValue(funnel.config, existingFilename)
|
|
544
587
|
}
|
|
545
588
|
],
|
|
546
|
-
next: failed.next
|
|
589
|
+
next: joinNext(failed.next, mcpFailedNext())
|
|
547
590
|
}));
|
|
548
591
|
printed = true;
|
|
549
592
|
outcome = "error";
|
|
550
593
|
throw error;
|
|
551
594
|
}
|
|
552
|
-
const pendingUnauthed = funnel.link === "skipped" && !noLink
|
|
595
|
+
const pendingUnauthed = funnel.link === "skipped" && !noLink;
|
|
553
596
|
const pendingServices = extraServices && linked;
|
|
554
597
|
const pendingClaimable = funnel.link === "claimable" && claimExpiresAt !== void 0;
|
|
555
598
|
const pendingExisting = funnel.config === "existing" && linked && !wroteNewFile;
|
|
556
|
-
const pending = pendingUnauthed || pendingServices || pendingClaimable && extraServices || pendingExisting;
|
|
599
|
+
const pending = pinnedOffline || pendingUnauthed || pendingServices || pendingClaimable && extraServices || pendingExisting;
|
|
557
600
|
const next = [];
|
|
558
|
-
if (
|
|
559
|
-
|
|
601
|
+
if (pinnedOffline) {
|
|
602
|
+
const commands = deferredLinkCommands(props, linkInputs);
|
|
603
|
+
next.push(...offlinePinNext({
|
|
604
|
+
login: commands.login,
|
|
605
|
+
...extraServices || existingConfig ? {} : { envPull: commands.envPull }
|
|
606
|
+
}));
|
|
607
|
+
} else if (pendingUnauthed) {
|
|
608
|
+
next.push(...unattendedUnauthedNext(hasExplicitLinkInputs ? deferredLinkCommands(props, linkInputs) : void 0));
|
|
609
|
+
const deferredMcp = mcpDeferredNext();
|
|
610
|
+
if (deferredMcp.length > 0) next.push("", ...deferredMcp);
|
|
611
|
+
} else if (noLink && !alreadyLinked) next.push(...skippedLinkNext());
|
|
560
612
|
if (pendingClaimable && extraServices && claimExpiresAt !== void 0) {
|
|
561
613
|
if (next.length > 0) next.push("");
|
|
562
614
|
next.push(...claimableThenServicesNext(claimExpiresAt));
|
|
@@ -573,28 +625,30 @@ const runInit = async (props) => {
|
|
|
573
625
|
outcome = pending && !pendingUnauthed && noLink ? "success" : pending ? "pending" : "success";
|
|
574
626
|
if (pendingUnauthed) outcome = "pending";
|
|
575
627
|
if (noLink && !pendingUnauthed && !pendingServices && !pendingExisting) outcome = "success";
|
|
628
|
+
if (mcpState.error !== void 0) outcome = "error";
|
|
576
629
|
printInitDone(formatInitDone({
|
|
577
630
|
heading: headingForKind(outcome),
|
|
578
631
|
rows: [
|
|
579
632
|
{
|
|
580
633
|
label: "Agents",
|
|
581
634
|
value: agentsRowValue({
|
|
582
|
-
tooling,
|
|
635
|
+
tooling: reportedTooling(),
|
|
583
636
|
installed: funnel.agentsInstalled
|
|
584
637
|
})
|
|
585
638
|
},
|
|
586
639
|
{
|
|
587
640
|
label: "Project",
|
|
588
|
-
value: projectRowValue(funnel.link ?? (linked ? "already_linked" : null))
|
|
641
|
+
value: pinnedOffline ? PROJECT_PINNED_OFFLINE : projectRowValue(funnel.link ?? (linked ? "already_linked" : null))
|
|
589
642
|
},
|
|
590
643
|
{
|
|
591
644
|
label: "Config",
|
|
592
645
|
value: configRowValue(funnel.config, existingFilename)
|
|
593
646
|
}
|
|
594
647
|
],
|
|
595
|
-
next
|
|
648
|
+
next: joinNext(mcpFailedNext(), next)
|
|
596
649
|
}));
|
|
597
650
|
printed = true;
|
|
651
|
+
if (mcpState.error !== void 0) throw mcpState.error;
|
|
598
652
|
takeCommandSuccessExtras();
|
|
599
653
|
recordCommandSuccessExtras({
|
|
600
654
|
init_kind: detection.emptyDirectory ? "empty-skip" : "existing",
|
|
@@ -628,6 +682,22 @@ const uniqueAgents = (ids) => {
|
|
|
628
682
|
}
|
|
629
683
|
return out;
|
|
630
684
|
};
|
|
685
|
+
const joinNext = (first, second) => first.length === 0 || second.length === 0 ? [...first, ...second] : [
|
|
686
|
+
...first,
|
|
687
|
+
"",
|
|
688
|
+
...second
|
|
689
|
+
];
|
|
690
|
+
const withoutMcp = (tooling) => {
|
|
691
|
+
if (tooling.setup === "mixed") return {
|
|
692
|
+
...tooling,
|
|
693
|
+
mcpAgents: []
|
|
694
|
+
};
|
|
695
|
+
if (tooling.setup === "skills-mcp") return tooling.skillsAgents.length === 0 ? { setup: "skip" } : {
|
|
696
|
+
...tooling,
|
|
697
|
+
mcpAgents: []
|
|
698
|
+
};
|
|
699
|
+
return tooling;
|
|
700
|
+
};
|
|
631
701
|
const agentsFromTooling = (tooling) => {
|
|
632
702
|
switch (tooling.setup) {
|
|
633
703
|
case "skip": return [];
|
package/dist/init/tooling.js
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import { log } from "../log.js";
|
|
2
2
|
import { getCliName } from "../utils/cli_name.js";
|
|
3
3
|
import { assertNamedAgentTooling, chooseYesAgentTooling, collectYesAgents, initYesSupportedAgents, noDetectedAgentsMessage, planAgentSteps, planToolingSteps, planYesAgentSteps, resolveInitAgentSetup } from "./plan.js";
|
|
4
|
-
import { PROGRESS } from "./copy.js";
|
|
4
|
+
import { MCP_OAUTH_FALLBACK, PROGRESS } from "./copy.js";
|
|
5
|
+
import { CannotMintApiKeyError } from "../mcp/mint.js";
|
|
5
6
|
import { detectAgent } from "./detect_host.js";
|
|
6
7
|
import { AgentSelectionSkipped, canPickAgentsInteractively } from "../utils/agent_picker.js";
|
|
7
8
|
import { mcpInstallError, reportMcpInstall } from "../commands/mcp.js";
|
|
@@ -99,10 +100,19 @@ const runToolingSteps = async (steps, options) => {
|
|
|
99
100
|
break;
|
|
100
101
|
}
|
|
101
102
|
case "mcp": {
|
|
102
|
-
const
|
|
103
|
+
const install = (oauth) => raceSigint(ops.installMcp({
|
|
103
104
|
...step.options,
|
|
104
|
-
...sharedOptions
|
|
105
|
+
...sharedOptions,
|
|
106
|
+
oauth
|
|
105
107
|
}));
|
|
108
|
+
let outcome;
|
|
109
|
+
try {
|
|
110
|
+
outcome = await install(step.options.oauth);
|
|
111
|
+
} catch (error) {
|
|
112
|
+
if (!(error instanceof CannotMintApiKeyError) || options.mcpOauthFallback !== true || step.options.oauth === true) throw error;
|
|
113
|
+
log.warning(MCP_OAUTH_FALLBACK);
|
|
114
|
+
outcome = await install(true);
|
|
115
|
+
}
|
|
106
116
|
reportMcpInstall(reportProps, outcome);
|
|
107
117
|
const error = mcpInstallError(outcome);
|
|
108
118
|
if (error) throw error;
|
package/dist/mcp/mint.js
CHANGED
|
@@ -4,7 +4,12 @@ import { getCliName } from "../utils/cli_name.js";
|
|
|
4
4
|
import { mintedKeyName } from "../profile_keys.js";
|
|
5
5
|
import { orgIdForProject } from "../commands/api_keys.js";
|
|
6
6
|
//#region src/mcp/mint.ts
|
|
7
|
-
|
|
7
|
+
var CannotMintApiKeyError = class extends Error {
|
|
8
|
+
constructor() {
|
|
9
|
+
super("This CLI credential cannot mint API keys. Organization and project-scoped keys cannot create other keys. Sign in with `neon login` or pass a personal API key.");
|
|
10
|
+
this.name = "CannotMintApiKeyError";
|
|
11
|
+
}
|
|
12
|
+
};
|
|
8
13
|
function mintedKeyRevokeCommand(key) {
|
|
9
14
|
return key.orgId ? `${getCliName()} api-keys revoke ${key.id} --org-id ${key.orgId}` : `${getCliName()} api-keys revoke ${key.id}`;
|
|
10
15
|
}
|
|
@@ -44,7 +49,7 @@ const createAndAssert = async (client, create, name, scope) => {
|
|
|
44
49
|
const { data } = await create();
|
|
45
50
|
return assertUsable(client, data, name, scope);
|
|
46
51
|
} catch (err) {
|
|
47
|
-
if (isNeonApiError(err) && (err.status === 403 || err.status === 404)) throw new
|
|
52
|
+
if (isNeonApiError(err) && (err.status === 403 || err.status === 404)) throw new CannotMintApiKeyError();
|
|
48
53
|
throw err;
|
|
49
54
|
}
|
|
50
55
|
};
|
|
@@ -67,4 +72,4 @@ async function withdrawMintedKey(apiClient, key) {
|
|
|
67
72
|
return withdraw(apiClient, key.id, key.orgId);
|
|
68
73
|
}
|
|
69
74
|
//#endregion
|
|
70
|
-
export { mintMcpApiKey, mintedKeyRevokeCommand, withdrawMintedKey };
|
|
75
|
+
export { CannotMintApiKeyError, mintMcpApiKey, mintedKeyRevokeCommand, withdrawMintedKey };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "neon",
|
|
3
|
-
"version": "8.3.
|
|
3
|
+
"version": "8.3.3",
|
|
4
4
|
"description": "CLI tool for Neon, the cloud backend primitives built around Lakebase Postgres",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"neon",
|
|
@@ -64,8 +64,8 @@
|
|
|
64
64
|
"yaml": "^2.9.0",
|
|
65
65
|
"yargs": "17.7.2",
|
|
66
66
|
"yoctocolors": "^2.1.2",
|
|
67
|
-
"@neon/config-runtime": "1.8.0",
|
|
68
67
|
"@neon/config": "1.8.6",
|
|
68
|
+
"@neon/config-runtime": "1.8.0",
|
|
69
69
|
"@neon/sdk": "7.0.1"
|
|
70
70
|
},
|
|
71
71
|
"optionalDependencies": {
|