neon 8.3.0 → 8.3.2

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
@@ -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; otherwise it errors and names `neon login` or `--claimable`.
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
 
@@ -1,8 +1,8 @@
1
1
  import { t as __exportAll } from "../_chunks/rolldown-runtime-8H4AJuhK.js";
2
2
  import { log } from "../log.js";
3
- import { retryOnLock } from "../api.js";
3
+ import { isNeonApiError, retryOnLock } from "../api.js";
4
4
  import { writer } from "../writer.js";
5
- import { looksLikeLSN, looksLikeTimestamp, toIso } from "../utils/formats.js";
5
+ import { looksLikeBranchId, looksLikeLSN, looksLikeSnapshotId, looksLikeTimestamp, toIso } from "../utils/formats.js";
6
6
  import { branchIdResolve, fillSingleProject, resolveBranchRef } from "../utils/enrichers.js";
7
7
  import { BRANCH_FIELDS } from "./branches.js";
8
8
  //#region src/commands/snapshots.ts
@@ -166,6 +166,25 @@ const resolveSnapshot = async (props) => {
166
166
  if (byName.length > 1) throw new Error(`Multiple snapshots are named "${props.id}". Re-run with the snapshot id:\n${byName.map((s) => ` ${s.id}`).join("\n")}`);
167
167
  throw new Error(`Snapshot "${props.id}" not found.\nAvailable snapshots: ${snapshots.map((s) => `${s.name} (${s.id})`).join(", ") || "none"}`);
168
168
  };
169
+ /**
170
+ * Runs `call` on the snapshot `props.id` names. An id goes straight to the API without the
171
+ * listing; a 404 sends it through {@link resolveSnapshot}, since a name can look like an id.
172
+ * When the listing has that id, the 404 was about something else (a target branch), so it stands.
173
+ */
174
+ const withSnapshotId = async (props, call) => {
175
+ let notFound;
176
+ if (looksLikeSnapshotId(props.id)) try {
177
+ return await call(props.id);
178
+ } catch (err) {
179
+ if (!(isNeonApiError(err) && err.status === 404)) throw err;
180
+ notFound = err;
181
+ }
182
+ const snapshot = await resolveSnapshot(props);
183
+ if (notFound && snapshot.id === props.id) throw notFound;
184
+ return call(snapshot.id);
185
+ };
186
+ /** A `br-…` id needs no branch listing; a name or the context/default branch does. */
187
+ const snapshotBranchId = async (props) => props.branch && looksLikeBranchId(props.branch) ? props.branch : (await resolveBranchRef(props)).branchId;
169
188
  const list = async (props) => {
170
189
  const { data: { snapshots } } = await props.apiClient.listSnapshots(props.projectId);
171
190
  writer(props).end(snapshots, {
@@ -185,10 +204,7 @@ const get = async (props) => {
185
204
  const create = async (props) => {
186
205
  if (props.lsn !== void 0 && !looksLikeLSN(props.lsn)) throw new Error(`Invalid --lsn value: "${props.lsn}". Expected an LSN like 0/1F3C8A0.`);
187
206
  if (props.timestamp !== void 0 && !looksLikeTimestamp(props.timestamp)) throw new Error(`Invalid --timestamp value: "${props.timestamp}". Use an RFC 3339 timestamp, e.g. 2025-01-01T00:00:00Z.`);
188
- const { branchId } = await resolveBranchRef({
189
- ...props,
190
- branch: props.branch
191
- });
207
+ const branchId = await snapshotBranchId(props);
192
208
  const { data } = await retryOnLock(() => props.apiClient.createSnapshot(props.projectId, branchId, {
193
209
  name: props.name,
194
210
  timestamp: props.timestamp,
@@ -203,12 +219,11 @@ const create = async (props) => {
203
219
  };
204
220
  const update = async (props) => {
205
221
  if (props.name === void 0 && props.expiresAt === void 0 && !props.clearExpiration) throw new Error("Nothing to update. Pass --name, --expires-at, or --clear-expiration.");
206
- const snapshot = await resolveSnapshot(props);
207
222
  const expiresAt = props.clearExpiration ? null : props.expiresAt !== void 0 ? toIso(props.expiresAt, "--expires-at") : void 0;
208
- const { data } = await retryOnLock(() => props.apiClient.updateSnapshot(props.projectId, snapshot.id, { snapshot: {
223
+ const { data } = await withSnapshotId(props, (snapshotId) => retryOnLock(() => props.apiClient.updateSnapshot(props.projectId, snapshotId, { snapshot: {
209
224
  name: props.name,
210
225
  expires_at: expiresAt
211
- } }));
226
+ } })));
212
227
  writer(props).end(data.snapshot, {
213
228
  fields: SNAPSHOT_FIELDS,
214
229
  renderColumns: { expires_at: (s) => s.expires_at || "never" }
@@ -223,17 +238,18 @@ const deleteSnapshot = async (props) => {
223
238
  });
224
239
  };
225
240
  const restore = async (props) => {
226
- const snapshot = await resolveSnapshot(props);
227
- const targetBranchId = props.targetBranch ? await branchIdResolve({
228
- branch: props.targetBranch,
229
- projectId: props.projectId,
230
- apiClient: props.apiClient
231
- }) : void 0;
232
- const { data } = await retryOnLock(() => props.apiClient.restoreSnapshot(props.projectId, snapshot.id, {
233
- name: props.name,
234
- target_branch_id: targetBranchId,
235
- finalize_restore: props.finalize
236
- }));
241
+ const { data } = await withSnapshotId(props, async (snapshotId) => {
242
+ const targetBranchId = props.targetBranch ? await branchIdResolve({
243
+ branch: props.targetBranch,
244
+ projectId: props.projectId,
245
+ apiClient: props.apiClient
246
+ }) : void 0;
247
+ return retryOnLock(() => props.apiClient.restoreSnapshot(props.projectId, snapshotId, {
248
+ name: props.name,
249
+ target_branch_id: targetBranchId,
250
+ finalize_restore: props.finalize
251
+ }));
252
+ });
237
253
  const out = writer(props).write(data.branch, {
238
254
  fields: BRANCH_FIELDS,
239
255
  title: "restored branch"
@@ -259,10 +275,7 @@ const finalize = async (props) => {
259
275
  });
260
276
  };
261
277
  const scheduleGet = async (props) => {
262
- const { branchId } = await resolveBranchRef({
263
- ...props,
264
- branch: props.branch
265
- });
278
+ const branchId = await snapshotBranchId(props);
266
279
  const { data } = await props.apiClient.getSnapshotSchedule(props.projectId, branchId);
267
280
  writer(props).end(data.schedule, {
268
281
  fields: SCHEDULE_FIELDS,
@@ -304,10 +317,6 @@ const parseScheduleJson = (raw) => {
304
317
  });
305
318
  };
306
319
  const scheduleSet = async (props) => {
307
- const { branchId } = await resolveBranchRef({
308
- ...props,
309
- branch: props.branch
310
- });
311
320
  let schedule;
312
321
  if (props.schedule) schedule = parseScheduleJson(props.schedule);
313
322
  else if (props.frequency) {
@@ -318,6 +327,7 @@ const scheduleSet = async (props) => {
318
327
  if (props.retention !== void 0) item.retention_seconds = props.retention;
319
328
  schedule = [item];
320
329
  } else throw new Error("Provide --frequency (optionally with --hour/--day/--month/--retention) or --schedule <json>.");
330
+ const branchId = await snapshotBranchId(props);
321
331
  await retryOnLock(() => props.apiClient.setSnapshotSchedule(props.projectId, branchId, { schedule }));
322
332
  writer(props).end(schedule, {
323
333
  fields: SCHEDULE_FIELDS,