Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/init-install-neon-not-neonctl.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"neon": patch
---

`neon init` now detects and installs the `neon` CLI instead of the retired `neonctl` alias. Version probing checks `neon` first (falling back to `neonctl` so an existing global install still counts as installed), the update check reads `npm view neon`, and auth/context lookups shell out to `neon`.
68 changes: 34 additions & 34 deletions packages/cli/src/init/__snapshots__/agent_snapshot.test.ts.snap
Original file line number Diff line number Diff line change
Expand Up @@ -946,8 +946,8 @@ exports[`neon init --agent: every step, against every project shape > connected
--- stderr ---

--- subprocesses ---
<stub>/neonctl --version
<stub>/npm view neonctl version
<stub>/neon --version
<stub>/npm view neon version
<stub>/npx -y add-mcp https://mcp.neon.tech/mcp -g -n Neon -y -a cursor
<stub>/skills --version
<stub>/skills add neondatabase/agent-skills --skill neon --agent cursor -y
Expand Down Expand Up @@ -985,8 +985,8 @@ exports[`neon init --agent: every step, against every project shape > connected
--- stderr ---

--- subprocesses ---
<stub>/neonctl --version
<stub>/npm view neonctl version
<stub>/neon --version
<stub>/npm view neon version
<stub>/npx -y add-mcp https://mcp.neon.tech/mcp -g -n Neon -y -a cursor
<stub>/skills --version
<stub>/skills add neondatabase/agent-skills --skill neon --agent cursor -y
Expand Down Expand Up @@ -1974,8 +1974,8 @@ exports[`neon init --agent: every step, against every project shape > drizzle >
--- stderr ---

--- subprocesses ---
<stub>/neonctl --version
<stub>/npm view neonctl version
<stub>/neon --version
<stub>/npm view neon version
<stub>/npx -y add-mcp https://mcp.neon.tech/mcp -g -n Neon -y -a cursor
<stub>/skills --version
<stub>/skills add neondatabase/agent-skills --skill neon --agent cursor -y
Expand Down Expand Up @@ -2013,8 +2013,8 @@ exports[`neon init --agent: every step, against every project shape > drizzle >
--- stderr ---

--- subprocesses ---
<stub>/neonctl --version
<stub>/npm view neonctl version
<stub>/neon --version
<stub>/npm view neon version
<stub>/npx -y add-mcp https://mcp.neon.tech/mcp -g -n Neon -y -a cursor
<stub>/skills --version
<stub>/skills add neondatabase/agent-skills --skill neon --agent cursor -y
Expand Down Expand Up @@ -2986,8 +2986,8 @@ exports[`neon init --agent: every step, against every project shape > fully-conf
--- stderr ---

--- subprocesses ---
<stub>/neonctl --version
<stub>/npm view neonctl version
<stub>/neon --version
<stub>/npm view neon version
<stub>/npx -y add-mcp https://mcp.neon.tech/mcp -g -n Neon -y -a cursor
<stub>/skills --version"
`;
Expand Down Expand Up @@ -3023,8 +3023,8 @@ exports[`neon init --agent: every step, against every project shape > fully-conf
--- stderr ---

--- subprocesses ---
<stub>/neonctl --version
<stub>/npm view neonctl version
<stub>/neon --version
<stub>/npm view neon version
<stub>/npx -y add-mcp https://mcp.neon.tech/mcp -g -n Neon -y -a cursor
<stub>/skills --version"
`;
Expand Down Expand Up @@ -3999,8 +3999,8 @@ exports[`neon init --agent: every step, against every project shape > greenfield
--- stderr ---

--- subprocesses ---
<stub>/neonctl --version
<stub>/npm view neonctl version
<stub>/neon --version
<stub>/npm view neon version
<stub>/npx -y add-mcp https://mcp.neon.tech/mcp -g -n Neon -y -a cursor
<stub>/skills --version
<stub>/skills add neondatabase/agent-skills --skill neon --agent cursor -y
Expand Down Expand Up @@ -4038,8 +4038,8 @@ exports[`neon init --agent: every step, against every project shape > greenfield
--- stderr ---

--- subprocesses ---
<stub>/neonctl --version
<stub>/npm view neonctl version
<stub>/neon --version
<stub>/npm view neon version
<stub>/npx -y add-mcp https://mcp.neon.tech/mcp -g -n Neon -y -a cursor
<stub>/skills --version
<stub>/skills add neondatabase/agent-skills --skill neon --agent cursor -y
Expand Down Expand Up @@ -5032,8 +5032,8 @@ exports[`neon init --agent: every step, against every project shape > next-prism
--- stderr ---

--- subprocesses ---
<stub>/neonctl --version
<stub>/npm view neonctl version
<stub>/neon --version
<stub>/npm view neon version
<stub>/npx -y add-mcp https://mcp.neon.tech/mcp -g -n Neon -y -a cursor
<stub>/skills --version
<stub>/skills add neondatabase/agent-skills --skill neon --agent cursor -y
Expand Down Expand Up @@ -5071,8 +5071,8 @@ exports[`neon init --agent: every step, against every project shape > next-prism
--- stderr ---

--- subprocesses ---
<stub>/neonctl --version
<stub>/npm view neonctl version
<stub>/neon --version
<stub>/npm view neon version
<stub>/npx -y add-mcp https://mcp.neon.tech/mcp -g -n Neon -y -a cursor
<stub>/skills --version
<stub>/skills add neondatabase/agent-skills --skill neon --agent cursor -y
Expand Down Expand Up @@ -6065,8 +6065,8 @@ exports[`neon init --agent: every step, against every project shape > next-prism
--- stderr ---

--- subprocesses ---
<stub>/neonctl --version
<stub>/npm view neonctl version
<stub>/neon --version
<stub>/npm view neon version
<stub>/npx -y add-mcp https://mcp.neon.tech/mcp -g -n Neon -y -a cursor
<stub>/skills --version
<stub>/skills add neondatabase/agent-skills --skill neon --agent cursor -y
Expand Down Expand Up @@ -6104,8 +6104,8 @@ exports[`neon init --agent: every step, against every project shape > next-prism
--- stderr ---

--- subprocesses ---
<stub>/neonctl --version
<stub>/npm view neonctl version
<stub>/neon --version
<stub>/npm view neon version
<stub>/npx -y add-mcp https://mcp.neon.tech/mcp -g -n Neon -y -a cursor
<stub>/skills --version
<stub>/skills add neondatabase/agent-skills --skill neon --agent cursor -y
Expand Down Expand Up @@ -7093,8 +7093,8 @@ exports[`neon init --agent: every step, against every project shape > node-app >
--- stderr ---

--- subprocesses ---
<stub>/neonctl --version
<stub>/npm view neonctl version
<stub>/neon --version
<stub>/npm view neon version
<stub>/npx -y add-mcp https://mcp.neon.tech/mcp -g -n Neon -y -a cursor
<stub>/skills --version
<stub>/skills add neondatabase/agent-skills --skill neon --agent cursor -y
Expand Down Expand Up @@ -7132,8 +7132,8 @@ exports[`neon init --agent: every step, against every project shape > node-app >
--- stderr ---

--- subprocesses ---
<stub>/neonctl --version
<stub>/npm view neonctl version
<stub>/neon --version
<stub>/npm view neon version
<stub>/npx -y add-mcp https://mcp.neon.tech/mcp -g -n Neon -y -a cursor
<stub>/skills --version
<stub>/skills add neondatabase/agent-skills --skill neon --agent cursor -y
Expand Down Expand Up @@ -8126,8 +8126,8 @@ exports[`neon init --agent: every step, against every project shape > other-data
--- stderr ---

--- subprocesses ---
<stub>/neonctl --version
<stub>/npm view neonctl version
<stub>/neon --version
<stub>/npm view neon version
<stub>/npx -y add-mcp https://mcp.neon.tech/mcp -g -n Neon -y -a cursor
<stub>/skills --version
<stub>/skills add neondatabase/agent-skills --skill neon --agent cursor -y
Expand Down Expand Up @@ -8165,8 +8165,8 @@ exports[`neon init --agent: every step, against every project shape > other-data
--- stderr ---

--- subprocesses ---
<stub>/neonctl --version
<stub>/npm view neonctl version
<stub>/neon --version
<stub>/npm view neon version
<stub>/npx -y add-mcp https://mcp.neon.tech/mcp -g -n Neon -y -a cursor
<stub>/skills --version
<stub>/skills add neondatabase/agent-skills --skill neon --agent cursor -y
Expand Down Expand Up @@ -9570,7 +9570,7 @@ exports[`neon init --agent: the orchestrator picks the next phase > fully-config
--- stderr ---

--- subprocesses ---
<stub>/npx -y neonctl orgs list --output json"
<stub>/npx -y neon orgs list --output json"
`;

exports[`neon init --agent: the orchestrator picks the next phase > fully-configured — no --data 1`] = `
Expand Down Expand Up @@ -9602,7 +9602,7 @@ exports[`neon init --agent: the orchestrator picks the next phase > fully-config
--- stderr ---

--- subprocesses ---
<stub>/npx -y neonctl orgs list --output json
<stub>/npx -y neon orgs list --output json
<stub>/skills --version"
`;

Expand Down
9 changes: 7 additions & 2 deletions packages/cli/src/init/agent_snapshot.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -62,16 +62,21 @@ const CLI =

/** Every executable the init flow shells out to, with a fixed answer. */
const STUBS: Record<string, string> = {
// `neonctl --version` and `npm view neonctl version` agreeing is what makes
// `neon --version` and `npm view neon version` agreeing is what makes
// `ensureNeonctl` report `already_current` instead of trying to install.
neon: `case "$1" in
--version) echo "2.45.0" ;;
*) exit 0 ;;
esac`,
// Legacy alias, still probed as a fallback by getNeonCliVersion; versioned
// so the fallback path also resolves cleanly if it's ever taken.
neonctl: `case "$1" in
--version) echo "2.45.0" ;;
*) exit 0 ;;
esac`,
npm: `if [ "$1" = "view" ]; then echo "2.45.0"; fi
exit 0`,
npx: "exit 0",
neon: "exit 0",
skills: `if [ "$1" = "--version" ]; then echo "1.0.0"; fi
exit 0`,
claude: `if [ "$1" = "--version" ]; then echo "1.0.0 (Claude Code)"; fi
Expand Down
10 changes: 5 additions & 5 deletions packages/cli/src/init/auth.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ export type AuthOptions = {
};

/**
* Ensures neonctl is authenticated by running a command that triggers auth if needed
* Ensures the Neon CLI is authenticated by running a command that triggers auth if needed
* This will automatically start the OAuth flow if the user isn't already authenticated
*/
export async function ensureNeonctlAuth(
Expand All @@ -25,11 +25,11 @@ export async function ensureNeonctlAuth(
if (existingToken) return true;

try {
// Use execa to authenticate with neonctl
await execa("npx", ["-y", "neonctl", "me"], {
// Run `neon me`, which triggers the OAuth flow when not signed in.
await execa("npx", ["-y", "neon", "me"], {
// Shows OAuth URL and prompts to the user
stdio: "inherit",
// Unset CI so neonctl doesn't refuse to open the browser (e.g. when run from agent chat)
// Unset CI so the CLI doesn't refuse to open the browser (e.g. when run from agent chat)
env: { ...process.env, CI: undefined },
});
return true;
Expand All @@ -49,7 +49,7 @@ export async function ensureNeonctlAuth(
}

/**
* Checks whether neonctl has stored OAuth credentials.
* Checks whether the Neon CLI has stored OAuth credentials.
*/
export async function isAuthenticated(): Promise<boolean> {
const token = await getNeonctlAccessToken();
Expand Down
80 changes: 47 additions & 33 deletions packages/cli/src/init/neonctl.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,14 @@ import {
} from "../utils/package_manager.js";

/**
* Returns the Neon CLI command prefix: "CI= npx -y neon".
* Returns the Neon CLI command prefix emitted to users and agents:
* `CI= npx -y neon`.
*
* The CLI reads NEON_API_HOST and NEON_OAUTH_HOST from the environment
* directly, so no extra flags are needed. The `neon` package ships both the
* `neon` and `neonctl` binaries; we surface the cleaner `neon` command in the
* examples emitted to users and agents.
* directly, so no extra flags are needed. The published package is `neon`
* (its binary is `neon`; the older `neonctl` alias is no longer shipped). `npx`
* runs it with zero global-install assumptions, and the leading `CI=` keeps the
* CLI non-interactive.
*
* Usage: `${neonctlCmd()} orgs list --output json`
*/
Expand All @@ -27,29 +29,38 @@ type NeonctlStatus = {
};

/**
* Gets the currently available neonctl version.
* Tries the global binary first, then falls back to npx.
* The global binaries that indicate the Neon CLI is installed. The current
* package ships `neon`; `neonctl` is the legacy alias, kept in the probe so an
* older global install still counts as "installed" (and isn't reinstalled).
*/
async function getNeonctlVersion(): Promise<string | null> {
// Try global binary first (fast path)
try {
const result = await execa("neonctl", ["--version"], {
stdio: "pipe",
timeout: 5000,
});
const match = result.stdout.trim().match(/(\d+\.\d+\.\d+)/);
if (match) return match[1];
} catch {
// Not globally installed — that's fine
const NEON_CLI_BINARIES = ["neon", "neonctl"] as const;

/**
* Gets the currently installed Neon CLI version, probing the `neon` binary
* first and falling back to the legacy `neonctl` alias. Returns null when the
* CLI isn't on PATH.
*/
async function getNeonCliVersion(): Promise<string | null> {
for (const bin of NEON_CLI_BINARIES) {
try {
const result = await execa(bin, ["--version"], {
stdio: "pipe",
timeout: 5000,
});
const match = result.stdout.trim().match(/(\d+\.\d+\.\d+)/);
if (match) return match[1];
} catch {
// This binary isn't installed — try the next one.
}
}
return null;
}

/**
* Checks whether the neonctl CLI is globally installed and whether it's up to date.
* Checks whether the Neon CLI is globally installed and whether it's up to date.
*/
export async function checkNeonctl(): Promise<NeonctlStatus> {
const currentVersion = await getNeonctlVersion();
const currentVersion = await getNeonCliVersion();

if (!currentVersion) {
return {
Expand All @@ -63,7 +74,7 @@ export async function checkNeonctl(): Promise<NeonctlStatus> {
// Check latest version from npm registry
let latestVersion: string | null = null;
try {
const result = await execa("npm", ["view", "neonctl", "version"], {
const result = await execa("npm", ["view", "neon", "version"], {
stdio: "pipe",
timeout: 10000,
});
Expand Down Expand Up @@ -111,17 +122,20 @@ function isLocalDevSymlink(): boolean {
try {
const home = process.env.HOME || process.env.USERPROFILE || "";
const nvmDir = process.env.NVM_DIR || `${home}/.nvm`;
// Check common global module locations for a symlink
const candidates = [
`${nvmDir}/versions/node/${process.version}/lib/node_modules/neonctl`,
`${home}/.nvm/versions/node/${process.version}/lib/node_modules/neonctl`,
// Check common global module locations for a symlink, under both the
// current `neon` package name and the legacy `neonctl` one.
const roots = [
`${nvmDir}/versions/node/${process.version}/lib/node_modules`,
`${home}/.nvm/versions/node/${process.version}/lib/node_modules`,
];
for (const candidate of candidates) {
try {
const stat = lstatSync(candidate);
if (stat.isSymbolicLink()) return true;
} catch {
// path doesn't exist
for (const root of roots) {
for (const pkg of NEON_CLI_BINARIES) {
try {
const stat = lstatSync(`${root}/${pkg}`);
if (stat.isSymbolicLink()) return true;
} catch {
// path doesn't exist
}
}
}
return false;
Expand All @@ -137,7 +151,7 @@ function isLocalDevSymlink(): boolean {
export async function ensureNeonctl(): Promise<EnsureNeonctlResult> {
// Skip install for local dev symlinks to avoid permission errors
if (isLocalDevSymlink()) {
const version = await getNeonctlVersion();
const version = await getNeonCliVersion();
return {
status: "already_current",
version: version ?? "dev",
Expand All @@ -154,7 +168,7 @@ export async function ensureNeonctl(): Promise<EnsureNeonctlResult> {
}

const pm = resolveInvokingPackageManager();
const install = globalInstallCommand(pm, "neonctl");
const install = globalInstallCommand(pm, "neon");
if (!install) {
// The next step is installing a package manager, not falling back to
// npx: npx ships with npm, so it is missing in exactly this case.
Expand All @@ -172,7 +186,7 @@ export async function ensureNeonctl(): Promise<EnsureNeonctlResult> {
await execa(command, args, { stdio: "pipe", timeout: 60000 });

// Verify installation
const version = await getNeonctlVersion();
const version = await getNeonCliVersion();
return {
status: check.installed ? "updated" : "installed",
version: version ?? undefined,
Expand Down
Loading
Loading