neon 6.0.0 → 6.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +5 -1
- package/dist/commands/bootstrap.js +19 -17
- package/dist/commands/dev.js +1 -1
- package/dist/commands/index.js +3 -3
- package/dist/commands/init.js +2 -4
- package/dist/commands/mcp.js +67 -25
- package/dist/commands/plugins.js +54 -23
- package/dist/commands/skills.js +54 -23
- package/dist/commands/snapshots.js +23 -7
- package/dist/init/auth.js +84 -0
- package/dist/init/cancelled.js +26 -1
- package/dist/init/chrome.js +2 -19
- package/dist/init/detect.js +1 -1
- package/dist/init/plan.js +85 -63
- package/dist/init/run.js +30 -26
- package/dist/init/tooling.js +103 -21
- package/dist/init/wizard.js +1 -1
- package/dist/mcp/plan.js +1 -1
- package/dist/plugins/plan.js +1 -1
- package/dist/skills/plan.js +1 -1
- package/package.json +2 -2
- package/dist/init/child.js +0 -49
package/README.md
CHANGED
|
@@ -936,12 +936,15 @@ The plugins CLI installs every plugin it finds in the Neon plugin package. Today
|
|
|
936
936
|
|
|
937
937
|
`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.
|
|
938
938
|
|
|
939
|
-
Every sub-command resolves the project through the standard chain (`--project-id`, then the `.neon` context file, then a single-project auto-detect). Branch-scoped sub-commands (`create`, `schedule`) default to the branch pinned in `.neon`, falling back to the project's default branch, and accept `--branch <id|name>`. The `get`, `update`, `delete`, and `restore` sub-commands take a snapshot **id or
|
|
939
|
+
Every sub-command resolves the project through the standard chain (`--project-id`, then the `.neon` context file, then a single-project auto-detect). Branch-scoped sub-commands (`create`, `schedule`) default to the branch pinned in `.neon`, falling back to the project's default branch, and accept `--branch <id|name>`. The `get`, `update`, `delete`, and `restore` sub-commands take a snapshot **id, unique name, or slug** as their positional argument. An id wins. A unique name still wins over another snapshot's matching slug, so a name you already use keeps targeting that snapshot. Slug is used when no name matches. An ambiguous name errors and asks you to use the id.
|
|
940
940
|
|
|
941
941
|
```bash
|
|
942
942
|
# Snapshot the head of the current/default branch
|
|
943
943
|
neon snapshots create --name pre-migration
|
|
944
944
|
|
|
945
|
+
# Give the snapshot a slug (unique in the project; omit to let the API generate one)
|
|
946
|
+
neon snapshots create --branch main --name "Before migration" --slug before-migration
|
|
947
|
+
|
|
945
948
|
# Snapshot a specific branch at a point in time (RFC 3339 timestamp OR LSN — mutually exclusive)
|
|
946
949
|
neon snapshots create --branch main --timestamp 2025-01-01T00:00:00Z
|
|
947
950
|
neon snapshots create --branch main --lsn 0/1F3C8A0 --expires-at 2025-12-31T23:59:59Z
|
|
@@ -949,6 +952,7 @@ neon snapshots create --branch main --lsn 0/1F3C8A0 --expires-at 2025-12-31T23:5
|
|
|
949
952
|
# List / inspect
|
|
950
953
|
neon snapshots list
|
|
951
954
|
neon snapshots get pre-migration
|
|
955
|
+
neon snapshots get before-migration
|
|
952
956
|
|
|
953
957
|
# Rename or change expiration (omit both to error; --expires-at and --clear-expiration conflict)
|
|
954
958
|
neon snapshots update snap-1234 --name nightly
|
|
@@ -2,12 +2,10 @@ import { t as __exportAll } from "../_chunks/rolldown-runtime-8H4AJuhK.js";
|
|
|
2
2
|
import { isCi } from "../env.js";
|
|
3
3
|
import { log } from "../log.js";
|
|
4
4
|
import { recordCommandSuccessExtras, recordScaffoldedTemplate } from "../analytics.js";
|
|
5
|
-
import { t as credentialInputs } from "../_chunks/auth_selection-pitMFzDe.js";
|
|
6
5
|
import { getCliName } from "../utils/cli_name.js";
|
|
7
6
|
import { helpCsv, helpEpilogue } from "../utils/help_text.js";
|
|
8
7
|
import { writer } from "../writer.js";
|
|
9
8
|
import { FALLBACK_TEMPLATES, ensureTargetUsable, fetchTemplates, findTemplate, scaffoldTemplate, templateIds } from "../init/bootstrap.js";
|
|
10
|
-
import { spawnCliChild } from "../init/child.js";
|
|
11
9
|
import { agentSetupDoneLabel, formatInitDone, printInitBanner, printInitDone, shouldPrintInitBanner } from "../init/chrome.js";
|
|
12
10
|
import { formatInstallCommand, inferPackageManager, installArgs, installedPackageManagers, resolvePackageManager, runCommand } from "../utils/package_manager.js";
|
|
13
11
|
import { runAuthenticatedLink } from "../init/link.js";
|
|
@@ -271,7 +269,9 @@ const runDefaultSteps = async (props, targetDir, pm, neonConfig, template, named
|
|
|
271
269
|
};
|
|
272
270
|
const executePostScaffold = async (props, targetDir, choices) => {
|
|
273
271
|
const kids = {
|
|
274
|
-
|
|
272
|
+
output: "table",
|
|
273
|
+
auth: bootstrapAuth(props, targetDir),
|
|
274
|
+
operations: props.operations,
|
|
275
275
|
...props.narrate ? { narrate: props.narrate } : {}
|
|
276
276
|
};
|
|
277
277
|
let installed = false;
|
|
@@ -370,20 +370,22 @@ const hasNeonConfig = (dir) => NEON_CONFIG_FILENAMES.some((name) => existsSync(j
|
|
|
370
370
|
const logSkippedLink = (pm) => {
|
|
371
371
|
log.info(`Skipping the Neon link step: \`${getCliName()} link\` reads this project's neon.ts to pull env vars, which needs its dependencies. Run \`${formatInstallCommand(pm)}\`, then \`${getCliName()} link\`.`);
|
|
372
372
|
};
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
}
|
|
386
|
-
}
|
|
373
|
+
/**
|
|
374
|
+
* plugins/skills need no Neon auth at all; mcp resolves it itself, in-process, exactly as
|
|
375
|
+
* the standalone `neon mcp` does (see `init/auth.ts`) — this is that shared context.
|
|
376
|
+
*/
|
|
377
|
+
const bootstrapAuth = (props, targetDir) => ({
|
|
378
|
+
apiClient: props.apiClient,
|
|
379
|
+
apiKey: props.apiKey,
|
|
380
|
+
apiHost: props.apiHost,
|
|
381
|
+
contextFile: projectContextFile(targetDir, props.contextFile),
|
|
382
|
+
...props.configDir ? { configDir: props.configDir } : {},
|
|
383
|
+
...props.profile ? { profile: props.profile } : {},
|
|
384
|
+
...props.oauthHost ? { oauthHost: props.oauthHost } : {},
|
|
385
|
+
...props.clientId ? { clientId: props.clientId } : {},
|
|
386
|
+
...props.forceAuth !== void 0 ? { forceAuth: props.forceAuth } : {},
|
|
387
|
+
...props.allowUnsafeTls !== void 0 ? { allowUnsafeTls: props.allowUnsafeTls } : {}
|
|
388
|
+
});
|
|
387
389
|
/**
|
|
388
390
|
* Initialize a git repository in the scaffolded directory. Just `git init` — we
|
|
389
391
|
* deliberately don't auto-commit, both to avoid failing on a machine with no
|
package/dist/commands/dev.js
CHANGED
|
@@ -13,8 +13,8 @@ import { existsSync, mkdirSync, rmSync, statSync, writeFileSync } from "node:fs"
|
|
|
13
13
|
import { basename, dirname, join, relative, resolve } from "node:path";
|
|
14
14
|
import { fileURLToPath } from "node:url";
|
|
15
15
|
import chalk from "chalk";
|
|
16
|
-
import { spawn, spawnSync } from "node:child_process";
|
|
17
16
|
import { bundleAsIs, describeNativeFinding, findUndeclaredNativePackages, resolveEsbuildEntry } from "@neon/config-runtime";
|
|
17
|
+
import { spawn, spawnSync } from "node:child_process";
|
|
18
18
|
import { once } from "node:events";
|
|
19
19
|
import { createServer } from "node:net";
|
|
20
20
|
//#region src/commands/dev.ts
|
package/dist/commands/index.js
CHANGED
|
@@ -6,6 +6,9 @@ import { t as env_exports } from "./env.js";
|
|
|
6
6
|
import { t as config_exports } from "./config.js";
|
|
7
7
|
import { t as projects_exports } from "./projects.js";
|
|
8
8
|
import { t as link_exports } from "./link.js";
|
|
9
|
+
import { t as mcp_exports } from "./mcp.js";
|
|
10
|
+
import { t as plugins_exports } from "./plugins.js";
|
|
11
|
+
import { t as skills_exports } from "./skills.js";
|
|
9
12
|
import { t as bootstrap_exports } from "./bootstrap.js";
|
|
10
13
|
import { t as branches_exports } from "./branches.js";
|
|
11
14
|
import { t as bucket_exports } from "./bucket.js";
|
|
@@ -23,17 +26,14 @@ import { t as init_exports } from "./init.js";
|
|
|
23
26
|
import { t as inspect_exports } from "./inspect.js";
|
|
24
27
|
import { t as ip_allow_exports } from "./ip_allow.js";
|
|
25
28
|
import { t as logs_exports } from "./logs.js";
|
|
26
|
-
import { t as mcp_exports } from "./mcp.js";
|
|
27
29
|
import { t as neon_auth_exports } from "./neon_auth.js";
|
|
28
30
|
import { t as open_exports } from "./open.js";
|
|
29
31
|
import { t as operations_exports } from "./operations.js";
|
|
30
32
|
import { t as orgs_exports } from "./orgs.js";
|
|
31
|
-
import { t as plugins_exports } from "./plugins.js";
|
|
32
33
|
import { t as profile_exports } from "./profile.js";
|
|
33
34
|
import { t as psql_exports } from "./psql.js";
|
|
34
35
|
import { t as roles_exports } from "./roles.js";
|
|
35
36
|
import { t as set_context_exports } from "./set_context.js";
|
|
36
|
-
import { t as skills_exports } from "./skills.js";
|
|
37
37
|
import { t as snapshots_exports } from "./snapshots.js";
|
|
38
38
|
import { t as status_exports } from "./status.js";
|
|
39
39
|
import { t as triggers_exports } from "./triggers.js";
|
package/dist/commands/init.js
CHANGED
|
@@ -3,7 +3,6 @@ import { getCliName } from "../utils/cli_name.js";
|
|
|
3
3
|
import { helpCsv, helpEpilogue } from "../utils/help_text.js";
|
|
4
4
|
import { servicesOption } from "../neon_services.js";
|
|
5
5
|
import { CONFIG_INIT_NONE_MEANS, CONFIG_INIT_SERVICES } from "../config_template.js";
|
|
6
|
-
import { initChildEnv } from "../init/child.js";
|
|
7
6
|
import { coerceAgentFlag } from "../utils/agent_flag.js";
|
|
8
7
|
import { initPluginAgents, initSkillsMcpAgents } from "../init/plan.js";
|
|
9
8
|
import { listSkillIds } from "../skills/catalog.js";
|
|
@@ -13,8 +12,7 @@ var init_exports = /* @__PURE__ */ __exportAll({
|
|
|
13
12
|
builder: () => builder,
|
|
14
13
|
command: () => command,
|
|
15
14
|
describe: () => describe,
|
|
16
|
-
handler: () => handler
|
|
17
|
-
initChildEnv: () => initChildEnv
|
|
15
|
+
handler: () => handler
|
|
18
16
|
});
|
|
19
17
|
const command = "init";
|
|
20
18
|
const describe = "Set up coding agents and this directory for Neon. -y is Recommended. -y with Custom flags applies those answers without prompts.";
|
|
@@ -108,4 +106,4 @@ const handler = async (props) => {
|
|
|
108
106
|
await runInit(props);
|
|
109
107
|
};
|
|
110
108
|
//#endregion
|
|
111
|
-
export { builder, command, describe, handler,
|
|
109
|
+
export { builder, command, describe, handler, init_exports as t };
|
package/dist/commands/mcp.js
CHANGED
|
@@ -8,15 +8,18 @@ import { writer } from "../writer.js";
|
|
|
8
8
|
import { noPassthrough, single } from "../utils/flags.js";
|
|
9
9
|
import { NEON_MCP_CATEGORIES, NEON_MCP_URL, existingNeonApiKey, installNeonMcpServer, neonMcpUrl, parseMcpCategories, trackedProjectMcpConfig } from "../mcp/install.js";
|
|
10
10
|
import { mcpInstallableAgents, resolveInstallTargets } from "../mcp/targets.js";
|
|
11
|
-
import { canPickAgentsInteractively } from "../utils/agent_picker.js";
|
|
12
11
|
import { mintMcpApiKey, mintedKeyRevokeCommand, withdrawMintedKey } from "../mcp/mint.js";
|
|
12
|
+
import { canPickAgentsInteractively } from "../utils/agent_picker.js";
|
|
13
13
|
import { resolveMcpPlan } from "../mcp/plan.js";
|
|
14
14
|
//#region src/commands/mcp.ts
|
|
15
15
|
var mcp_exports = /* @__PURE__ */ __exportAll({
|
|
16
16
|
builder: () => builder,
|
|
17
17
|
command: () => "mcp",
|
|
18
18
|
describe: () => describe,
|
|
19
|
-
handler: () => handler
|
|
19
|
+
handler: () => handler,
|
|
20
|
+
mcpInstallError: () => mcpInstallError,
|
|
21
|
+
reportMcpInstall: () => reportMcpInstall,
|
|
22
|
+
setupNeonMcp: () => setupNeonMcp
|
|
20
23
|
});
|
|
21
24
|
const command = "mcp";
|
|
22
25
|
const describe = "Install the Neon MCP server into coding agents";
|
|
@@ -87,20 +90,29 @@ const builder = (argv) => argv.usage("$0 mcp [options]").options({
|
|
|
87
90
|
}
|
|
88
91
|
}
|
|
89
92
|
}).example("$0 mcp", "Interactive: config location, agents, then auth").example("$0 mcp -y", "Global config, installed apps else the host CLI agent, reuse or mint an API key").example("$0 mcp --oauth", "Install with OAuth; the agent signs in on first use").example("$0 mcp --agent cursor --agent claude-code", "Install into specific agents").example("$0 mcp --project", "Write project-level config").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").epilogue(helpEpilogue(`Installs ${NEON_MCP_URL}`, helpCsv("Supported agents at global scope", mcpGlobalAgents), helpCsv("--project does not support", mcpProjectDroppedAgents), helpCsv("Supported categories", NEON_MCP_CATEGORIES), "neon mcp -y:", " global config", " globally installed apps, else the host CLI agent", " reuse an existing Neon MCP API key, else mint an account-wide key", " write tools on, all categories", " no project pin (including from .neon)")).strict().check(noPassthrough("mcp"));
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
93
|
+
/**
|
|
94
|
+
* Resolves auth, mints or reuses an API key, and writes the Neon MCP server into every
|
|
95
|
+
* resolved agent. No CLI-only concerns here (no `writer` table, no
|
|
96
|
+
* `recordCommandSuccessExtras`): a caller that only wants the outcome — `neon init` /
|
|
97
|
+
* `neon bootstrap` — gets it without rendering a table.
|
|
98
|
+
*
|
|
99
|
+
* Throws directly when zero agents succeed, since that path never reaches a results table
|
|
100
|
+
* worth showing (same as the CLI handler always did).
|
|
101
|
+
*/
|
|
102
|
+
const setupNeonMcp = async (options) => {
|
|
103
|
+
const cwd = options.cwd ?? process.cwd();
|
|
104
|
+
const interactive = canPickAgentsInteractively() && options.yes !== true;
|
|
105
|
+
const linkedProjectId = readContextFile(options.contextFile).projectId;
|
|
94
106
|
const plan = await resolveMcpPlan({
|
|
95
|
-
project:
|
|
96
|
-
oauth:
|
|
97
|
-
agents:
|
|
98
|
-
yes:
|
|
107
|
+
project: options.project === true,
|
|
108
|
+
oauth: options.oauth === true,
|
|
109
|
+
agents: options.agent ?? [],
|
|
110
|
+
yes: options.yes === true,
|
|
99
111
|
cwd,
|
|
100
112
|
interactive,
|
|
101
|
-
readOnly:
|
|
102
|
-
projectId:
|
|
103
|
-
categories:
|
|
113
|
+
readOnly: options.readOnly === true,
|
|
114
|
+
projectId: options.projectId,
|
|
115
|
+
categories: options.category ?? [],
|
|
104
116
|
linkedProjectId
|
|
105
117
|
});
|
|
106
118
|
const { install, skipped } = resolveInstallTargets({
|
|
@@ -125,8 +137,8 @@ const handler = async (props) => {
|
|
|
125
137
|
cwd
|
|
126
138
|
}) : void 0;
|
|
127
139
|
if (plan.auth === "api-key" && !existing) {
|
|
128
|
-
if (!
|
|
129
|
-
if (!canPickAgentsInteractively() &&
|
|
140
|
+
if (!options.apiClient || !options.apiKey) throw new Error(`Authentication required. Run \`${getCliName()} auth\`, pass --api-key or use --oauth to install without a Neon credential.`);
|
|
141
|
+
if (!canPickAgentsInteractively() && options.yes !== true && (options.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.");
|
|
130
142
|
}
|
|
131
143
|
let auth;
|
|
132
144
|
let minted;
|
|
@@ -140,7 +152,7 @@ const handler = async (props) => {
|
|
|
140
152
|
if (plan.urlProjectId) log.warning("That key keeps its existing scope. Remove the Neon MCP entry to mint a project-scoped key, or pass --oauth to pin tools without a key.");
|
|
141
153
|
} else {
|
|
142
154
|
minted = await mintMcpApiKey({
|
|
143
|
-
apiClient:
|
|
155
|
+
apiClient: options.apiClient,
|
|
144
156
|
projectId: plan.urlProjectId
|
|
145
157
|
});
|
|
146
158
|
auth = {
|
|
@@ -183,15 +195,28 @@ const handler = async (props) => {
|
|
|
183
195
|
error: row.error
|
|
184
196
|
});
|
|
185
197
|
if (successes === 0) {
|
|
186
|
-
if (minted &&
|
|
187
|
-
const withdrawn = await withdrawMintedKey(
|
|
198
|
+
if (minted && options.apiClient) {
|
|
199
|
+
const withdrawn = await withdrawMintedKey(options.apiClient, minted);
|
|
188
200
|
throw new Error(withdrawn ? "Failed to write Neon MCP config to any agent. The minted API key has been revoked." : `Failed to write Neon MCP config to any agent. The minted API key could NOT be revoked. Remove it with \`${mintedKeyRevokeCommand(minted)}\`.`);
|
|
189
201
|
}
|
|
190
202
|
throw new Error("Failed to write Neon MCP config to any agent.");
|
|
191
203
|
}
|
|
192
|
-
|
|
204
|
+
return {
|
|
205
|
+
scope: plan.scope,
|
|
206
|
+
rows,
|
|
207
|
+
failedAgents,
|
|
208
|
+
url,
|
|
209
|
+
auth: auth.kind,
|
|
210
|
+
...minted ? { minted } : {}
|
|
211
|
+
};
|
|
212
|
+
};
|
|
213
|
+
/** The exact error `neon mcp` throws when some (not all) agents failed, or `undefined` on full success. */
|
|
214
|
+
const mcpInstallError = (outcome) => outcome.failedAgents.length > 0 ? /* @__PURE__ */ new Error(`Failed to write Neon MCP config for: ${outcome.failedAgents.join(", ")}.`) : void 0;
|
|
215
|
+
/** Writes the results table plus the URL/minted-key/OAuth notices. Shared by `neon mcp` and any in-process caller that wants the same human output. */
|
|
216
|
+
const reportMcpInstall = (props, outcome) => {
|
|
217
|
+
log.info("URL: %s", outcome.url);
|
|
193
218
|
const out = writer(props);
|
|
194
|
-
out.write(rows, {
|
|
219
|
+
out.write(outcome.rows, {
|
|
195
220
|
fields: [
|
|
196
221
|
"agent",
|
|
197
222
|
"status",
|
|
@@ -200,10 +225,27 @@ const handler = async (props) => {
|
|
|
200
225
|
title: "MCP"
|
|
201
226
|
});
|
|
202
227
|
out.end();
|
|
203
|
-
if (minted) log.info("Minted API key %s (id %d, %s). Revoke with: %s", minted.name, minted.id, minted.projectId ? "project" : "account", mintedKeyRevokeCommand(minted));
|
|
204
|
-
if (
|
|
205
|
-
|
|
206
|
-
|
|
228
|
+
if (outcome.minted) log.info("Minted API key %s (id %d, %s). Revoke with: %s", outcome.minted.name, outcome.minted.id, outcome.minted.projectId ? "project" : "account", mintedKeyRevokeCommand(outcome.minted));
|
|
229
|
+
if (outcome.auth === "oauth") log.info("The agent will prompt for Neon sign-in on first use.");
|
|
230
|
+
};
|
|
231
|
+
const handler = async (props) => {
|
|
232
|
+
const outcome = await setupNeonMcp({
|
|
233
|
+
apiClient: props.apiClient,
|
|
234
|
+
apiKey: props.apiKey,
|
|
235
|
+
contextFile: props.contextFile,
|
|
236
|
+
cwd: props.cwd,
|
|
237
|
+
yes: props.yes,
|
|
238
|
+
oauth: props.oauth,
|
|
239
|
+
project: props.project,
|
|
240
|
+
agent: props.agent,
|
|
241
|
+
readOnly: props.readOnly,
|
|
242
|
+
projectId: props.projectId,
|
|
243
|
+
category: props.category
|
|
244
|
+
});
|
|
245
|
+
reportMcpInstall(props, outcome);
|
|
246
|
+
const error = mcpInstallError(outcome);
|
|
247
|
+
if (error) throw error;
|
|
248
|
+
recordCommandSuccessExtras({ scope: outcome.scope });
|
|
207
249
|
};
|
|
208
250
|
//#endregion
|
|
209
|
-
export { builder, command, describe, handler, mcp_exports as t };
|
|
251
|
+
export { builder, command, describe, handler, mcpInstallError, reportMcpInstall, setupNeonMcp, mcp_exports as t };
|
package/dist/commands/plugins.js
CHANGED
|
@@ -17,7 +17,10 @@ var plugins_exports = /* @__PURE__ */ __exportAll({
|
|
|
17
17
|
builder: () => builder,
|
|
18
18
|
command: () => command,
|
|
19
19
|
describe: () => describe,
|
|
20
|
-
handler: () => handler
|
|
20
|
+
handler: () => handler,
|
|
21
|
+
installPlugins: () => installPlugins,
|
|
22
|
+
pluginsInstallError: () => pluginsInstallError,
|
|
23
|
+
reportPluginsInstall: () => reportPluginsInstall
|
|
21
24
|
});
|
|
22
25
|
const coerceAgents = (value) => {
|
|
23
26
|
if (value === void 0) return [];
|
|
@@ -54,13 +57,18 @@ const builder = (argv) => argv.usage("$0 plugins [options]").options({
|
|
|
54
57
|
coerce: coerceAgents
|
|
55
58
|
}
|
|
56
59
|
}).example("$0 plugins", "Interactive: pick agents, then install").example("$0 plugins -y", "Detected agents (project folders, else the host CLI agent), skip prompts").example("$0 plugins --agent cursor --agent claude-code", "Install into specific agents").example("$0 plugins --global", "Install user-level").epilogue(helpEpilogue(helpCsv("Supported agents at project scope", pluginProjectAgents), helpCsv("Also with --global", pluginGlobalOnlyAgents), `Currently one plugin: ${NEON_PLUGIN_NAME} from ${PLUGIN_SOURCE}.`, `It includes the Neon MCP server (${NEON_MCP_URL})`, helpCsv("and these skills", PLUGIN_SKILLS))).strict().check(noPassthrough("plugins"));
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
+
/**
|
|
61
|
+
* Plans and runs the plugin install for every resolved target. No CLI-only concerns here
|
|
62
|
+
* (no `writer` table, no `recordCommandSuccessExtras`): a caller that only wants the
|
|
63
|
+
* outcome — `neon init` / `neon bootstrap` — gets it without rendering a result table.
|
|
64
|
+
*/
|
|
65
|
+
const installPlugins = async (options) => {
|
|
66
|
+
const cwd = options.cwd ?? process.cwd();
|
|
67
|
+
const yes = options.yes === true;
|
|
60
68
|
const interactive = canPickAgentsInteractively() && !yes;
|
|
61
69
|
const plan = await resolvePluginsPlan({
|
|
62
|
-
global:
|
|
63
|
-
agents:
|
|
70
|
+
global: options.global === true,
|
|
71
|
+
agents: options.agents ?? [],
|
|
64
72
|
yes,
|
|
65
73
|
cwd,
|
|
66
74
|
interactive
|
|
@@ -70,12 +78,14 @@ const handler = async (props) => {
|
|
|
70
78
|
const rows = [];
|
|
71
79
|
const failed = [];
|
|
72
80
|
const scope = scopeLabel(plan.scope);
|
|
73
|
-
for (const mapped of plan.targets) {
|
|
81
|
+
for (const [index, mapped] of plan.targets.entries()) {
|
|
74
82
|
const args = pluginsAddArgs({
|
|
75
83
|
target: mapped.target,
|
|
76
84
|
global: plan.scope === "global"
|
|
77
85
|
});
|
|
78
86
|
const agent = mapped.agents.join(", ");
|
|
87
|
+
const displayNames = mapped.agents.map((id) => getAgentDisplayName(id)).join(", ");
|
|
88
|
+
log.info("Installing the Neon plugin for %s (%d/%d)...", displayNames, index + 1, plan.targets.length);
|
|
79
89
|
try {
|
|
80
90
|
await runPluginsCli({
|
|
81
91
|
args,
|
|
@@ -102,8 +112,31 @@ const handler = async (props) => {
|
|
|
102
112
|
});
|
|
103
113
|
}
|
|
104
114
|
}
|
|
115
|
+
return {
|
|
116
|
+
scope: plan.scope,
|
|
117
|
+
rows,
|
|
118
|
+
failed
|
|
119
|
+
};
|
|
120
|
+
};
|
|
121
|
+
/** The exact error `neon plugins` throws for a failed outcome, or `undefined` on success. */
|
|
122
|
+
const pluginsInstallError = (outcome) => {
|
|
123
|
+
const { failed, rows, scope } = outcome;
|
|
124
|
+
if (failed.length === 0) return;
|
|
125
|
+
const first = failed[0];
|
|
126
|
+
if (first === void 0) return /* @__PURE__ */ new Error("Failed to install the Neon plugin.");
|
|
127
|
+
const retry = neonPluginsRetryCommand({
|
|
128
|
+
agents: failed.flatMap((row) => row.agents),
|
|
129
|
+
global: scope === "global"
|
|
130
|
+
});
|
|
131
|
+
if (first.message.includes("needs npx (Node.js)")) return new Error(first.message);
|
|
132
|
+
const detail = failed.map((row) => row.message).join("\n");
|
|
133
|
+
if (failed.length === rows.length) return /* @__PURE__ */ new Error(`${detail}\nRetry with: ${retry}`);
|
|
134
|
+
return /* @__PURE__ */ new Error(`Failed to install the Neon plugin for: ${failed.flatMap((row) => row.agents).join(", ")}.\n${detail}\nRetry with: ${retry}`);
|
|
135
|
+
};
|
|
136
|
+
/** Writes the results table and, on success, the "Installed the Neon plugin" line. Shared by `neon plugins` and any in-process caller that wants the same human output. */
|
|
137
|
+
const reportPluginsInstall = (props, outcome) => {
|
|
105
138
|
const out = writer(props);
|
|
106
|
-
out.write(rows, {
|
|
139
|
+
out.write(outcome.rows, {
|
|
107
140
|
fields: [
|
|
108
141
|
"scope",
|
|
109
142
|
"plugin",
|
|
@@ -114,21 +147,19 @@ const handler = async (props) => {
|
|
|
114
147
|
title: "Plugins"
|
|
115
148
|
});
|
|
116
149
|
out.end();
|
|
117
|
-
if (failed.length === 0)
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
agents: failed.flatMap((row) => row.agents),
|
|
126
|
-
global: plan.scope === "global"
|
|
150
|
+
if (outcome.failed.length === 0) log.info(outcome.scope === "project" ? "Installed the Neon plugin (project)." : "Installed the Neon plugin (user).");
|
|
151
|
+
};
|
|
152
|
+
const handler = async (props) => {
|
|
153
|
+
const outcome = await installPlugins({
|
|
154
|
+
cwd: props.cwd,
|
|
155
|
+
yes: props.yes,
|
|
156
|
+
global: props.global,
|
|
157
|
+
agents: props.agent
|
|
127
158
|
});
|
|
128
|
-
|
|
129
|
-
const
|
|
130
|
-
if (
|
|
131
|
-
|
|
159
|
+
reportPluginsInstall(props, outcome);
|
|
160
|
+
const error = pluginsInstallError(outcome);
|
|
161
|
+
if (error) throw error;
|
|
162
|
+
recordCommandSuccessExtras({ scope: outcome.scope });
|
|
132
163
|
};
|
|
133
164
|
//#endregion
|
|
134
|
-
export { aliases, builder, command, describe, handler, plugins_exports as t };
|
|
165
|
+
export { aliases, builder, command, describe, handler, installPlugins, pluginsInstallError, reportPluginsInstall, plugins_exports as t };
|
package/dist/commands/skills.js
CHANGED
|
@@ -18,7 +18,10 @@ var skills_exports = /* @__PURE__ */ __exportAll({
|
|
|
18
18
|
builder: () => builder,
|
|
19
19
|
command: () => command,
|
|
20
20
|
describe: () => describe,
|
|
21
|
-
handler: () => handler
|
|
21
|
+
handler: () => handler,
|
|
22
|
+
installSkills: () => installSkills,
|
|
23
|
+
reportSkillsInstall: () => reportSkillsInstall,
|
|
24
|
+
skillsInstallError: () => skillsInstallError
|
|
22
25
|
});
|
|
23
26
|
const scopeLabel = (scope) => scope === "project" ? "this directory" : "user-level";
|
|
24
27
|
const coerceAgents = (value) => {
|
|
@@ -83,15 +86,20 @@ const builder = (argv) => argv.usage("$0 skills [command] [options]").command("u
|
|
|
83
86
|
coerce: coerceSkills
|
|
84
87
|
}
|
|
85
88
|
}).example("$0 skills", "Interactive: this directory, then agents, then skills").example("$0 skills -y", "This directory, detected agents (project folders, else the host CLI agent), the default skills").example("$0 skills -y -s neon -s neon-ai-gateway", "Named skills into detected agents").example("$0 skills -s neon -s neon-ai-gateway --agent cursor", "Named skills into a named agent").example("$0 skills --global", "Install user-level skills").epilogue(helpEpilogue(helpCsv("Supported agents", skillsInstallableAgents()), "Supported skills, by source repo:", skillsHelpValues(), skillsYesHelp())).strict().check(noPassthrough("skills"));
|
|
86
|
-
|
|
89
|
+
/**
|
|
90
|
+
* Plans and runs the skills install for every resolved source/invocation. No CLI-only
|
|
91
|
+
* concerns here (no `writer` table, no `recordCommandSuccessExtras`): a caller that only
|
|
92
|
+
* wants the outcome — `neon init` / `neon bootstrap` — gets it without rendering a table.
|
|
93
|
+
*/
|
|
94
|
+
const installSkills = async (options) => {
|
|
87
95
|
assertSkillsNode();
|
|
88
|
-
const cwd = process.cwd();
|
|
89
|
-
const yes =
|
|
96
|
+
const cwd = options.cwd ?? process.cwd();
|
|
97
|
+
const yes = options.yes === true;
|
|
90
98
|
const interactive = canPickAgentsInteractively() && !yes;
|
|
91
99
|
const plan = await resolveSkillsPlan({
|
|
92
|
-
global:
|
|
93
|
-
agents:
|
|
94
|
-
skills:
|
|
100
|
+
global: options.global === true,
|
|
101
|
+
agents: options.agents ?? [],
|
|
102
|
+
skills: options.skills ?? [],
|
|
95
103
|
yes,
|
|
96
104
|
cwd,
|
|
97
105
|
interactive
|
|
@@ -138,8 +146,32 @@ const handler = async (props) => {
|
|
|
138
146
|
});
|
|
139
147
|
}
|
|
140
148
|
}
|
|
149
|
+
return {
|
|
150
|
+
scope: plan.scope,
|
|
151
|
+
agents: plan.agents,
|
|
152
|
+
rows,
|
|
153
|
+
failed
|
|
154
|
+
};
|
|
155
|
+
};
|
|
156
|
+
/** The exact error `neon skills` throws for a failed outcome, or `undefined` on success. */
|
|
157
|
+
const skillsInstallError = (outcome) => {
|
|
158
|
+
const { failed, rows, scope, agents } = outcome;
|
|
159
|
+
if (failed.length === 0) return;
|
|
160
|
+
const first = failed[0];
|
|
161
|
+
if (first === void 0) return /* @__PURE__ */ new Error("Failed to install Neon agent skills.");
|
|
162
|
+
const retry = neonSkillsRetryCommand({
|
|
163
|
+
skills: failed.flatMap((row) => row.skills),
|
|
164
|
+
agents,
|
|
165
|
+
global: scope === "global"
|
|
166
|
+
});
|
|
167
|
+
if (first.message.includes("needs npx (Node.js)") || first.message.includes("needs Node.js")) return new Error(first.message);
|
|
168
|
+
if (failed.length === rows.length) return /* @__PURE__ */ new Error(`${first.message}\nRetry with: ${retry}`);
|
|
169
|
+
return /* @__PURE__ */ new Error(`Failed to install Neon agent skills for: ${failed.map((row) => row.label).join(", ")}.\n${first.message}\nRetry with: ${retry}`);
|
|
170
|
+
};
|
|
171
|
+
/** Writes the results table and, on success, the "Wrote ... skills" line. Shared by `neon skills` and any in-process caller that wants the same human output. */
|
|
172
|
+
const reportSkillsInstall = (props, outcome) => {
|
|
141
173
|
const out = writer(props);
|
|
142
|
-
out.write(rows, {
|
|
174
|
+
out.write(outcome.rows, {
|
|
143
175
|
fields: [
|
|
144
176
|
"scope",
|
|
145
177
|
"skills",
|
|
@@ -150,21 +182,20 @@ const handler = async (props) => {
|
|
|
150
182
|
title: "Skills"
|
|
151
183
|
});
|
|
152
184
|
out.end();
|
|
153
|
-
if (failed.length === 0)
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
skills:
|
|
162
|
-
agents: plan.agents,
|
|
163
|
-
global: plan.scope === "global"
|
|
185
|
+
if (outcome.failed.length === 0) log.info(outcome.scope === "project" ? "Wrote skills in this directory." : "Wrote user-level skills.");
|
|
186
|
+
};
|
|
187
|
+
const handler = async (props) => {
|
|
188
|
+
const outcome = await installSkills({
|
|
189
|
+
cwd: props.cwd,
|
|
190
|
+
yes: props.yes,
|
|
191
|
+
global: props.global,
|
|
192
|
+
agents: props.agent,
|
|
193
|
+
skills: props.skill
|
|
164
194
|
});
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
195
|
+
reportSkillsInstall(props, outcome);
|
|
196
|
+
const error = skillsInstallError(outcome);
|
|
197
|
+
if (error) throw error;
|
|
198
|
+
recordCommandSuccessExtras({ scope: outcome.scope });
|
|
168
199
|
};
|
|
169
200
|
const updateHandler = async (props) => {
|
|
170
201
|
assertSkillsNode();
|
|
@@ -219,4 +250,4 @@ const updateHandler = async (props) => {
|
|
|
219
250
|
if (failure !== void 0) throw new Error(failure.includes("needs npx (Node.js)") || failure.includes("needs Node.js") ? failure : `${failure}\nRetry with: ${neonSkillsUpdateRetryCommand(scope === "global")}`);
|
|
220
251
|
};
|
|
221
252
|
//#endregion
|
|
222
|
-
export { aliases, builder, command, describe, handler, skills_exports as t };
|
|
253
|
+
export { aliases, builder, command, describe, handler, installSkills, reportSkillsInstall, skillsInstallError, skills_exports as t };
|
|
@@ -17,6 +17,7 @@ var snapshots_exports = /* @__PURE__ */ __exportAll({
|
|
|
17
17
|
const SNAPSHOT_FIELDS = [
|
|
18
18
|
"id",
|
|
19
19
|
"name",
|
|
20
|
+
"slug",
|
|
20
21
|
"source_branch_id",
|
|
21
22
|
"expires_at",
|
|
22
23
|
"created_at"
|
|
@@ -38,6 +39,12 @@ const SNAPSHOT_FREQUENCIES = [
|
|
|
38
39
|
"weekly",
|
|
39
40
|
"monthly"
|
|
40
41
|
];
|
|
42
|
+
/** Matches createSnapshot.query.slug in the Management API spec. */
|
|
43
|
+
const SNAPSHOT_SLUG = /^[a-z]([a-z0-9-]{0,61}[a-z0-9])?$/;
|
|
44
|
+
const SNAPSHOT_REF = {
|
|
45
|
+
describe: "Snapshot id, unique name, or slug. Lookup order: id, unique name, slug.",
|
|
46
|
+
type: "string"
|
|
47
|
+
};
|
|
41
48
|
/** Narrow an arbitrary string to a supported {@link SnapshotFrequency}. */
|
|
42
49
|
const isSnapshotFrequency = (value) => SNAPSHOT_FREQUENCIES.some((frequency) => frequency === value);
|
|
43
50
|
const command = "snapshots";
|
|
@@ -46,7 +53,7 @@ const aliases = ["snapshot"];
|
|
|
46
53
|
const builder = (argv) => argv.usage("$0 snapshots <sub-command> [options]").options({ "project-id": {
|
|
47
54
|
describe: "Project ID",
|
|
48
55
|
type: "string"
|
|
49
|
-
} }).middleware(fillSingleProject).command("list", "List snapshots in the project", (yargs) => yargs, (args) => list(args)).command("get <id>", "Get a snapshot by id or
|
|
56
|
+
} }).middleware(fillSingleProject).command("list", "List snapshots in the project", (yargs) => yargs, (args) => list(args)).command("get <id>", "Get a snapshot by id, unique name, or slug", (yargs) => yargs.positional("id", SNAPSHOT_REF), (args) => get(args)).command("create", "Create a snapshot from a branch", (yargs) => yargs.options({
|
|
50
57
|
branch: {
|
|
51
58
|
alias: "b",
|
|
52
59
|
describe: "Branch id or name to snapshot. Defaults to the branch in your context, or the project's default branch.",
|
|
@@ -56,6 +63,10 @@ const builder = (argv) => argv.usage("$0 snapshots <sub-command> [options]").opt
|
|
|
56
63
|
describe: "A name for the snapshot",
|
|
57
64
|
type: "string"
|
|
58
65
|
},
|
|
66
|
+
slug: {
|
|
67
|
+
describe: "User-defined resource ID, unique in the project (1-63 characters: start with a lowercase letter, then lowercase letters, digits, or hyphens, ending with a letter or digit). Omit to let the API generate one. It cannot be changed later.",
|
|
68
|
+
type: "string"
|
|
69
|
+
},
|
|
59
70
|
timestamp: {
|
|
60
71
|
describe: "Take the snapshot at this point in time (RFC 3339, e.g. 2025-01-01T00:00:00Z). Must fall within the branch's restore window. Mutually exclusive with --lsn.",
|
|
61
72
|
type: "string"
|
|
@@ -71,9 +82,10 @@ const builder = (argv) => argv.usage("$0 snapshots <sub-command> [options]").opt
|
|
|
71
82
|
}).conflicts("timestamp", "lsn").example([
|
|
72
83
|
["$0 snapshots create", "Snapshot the head of the context/default branch"],
|
|
73
84
|
["$0 snapshots create --branch main --name pre-migration", "Snapshot the head of main with a name"],
|
|
85
|
+
["$0 snapshots create --branch main --name \"Before migration\" --slug before-migration", "Snapshot main with a display name and a slug"],
|
|
74
86
|
["$0 snapshots create --branch main --timestamp 2025-01-01T00:00:00Z", "Snapshot main at a point in time"],
|
|
75
87
|
["$0 snapshots create --branch main --lsn 0/1F3C8A0 --expires-at 2025-12-31T23:59:59Z", "Snapshot main at an LSN, auto-deleting at the given time"]
|
|
76
|
-
]), (args) => create(args)).command("update <id>", "Update a snapshot's name or expiration", (yargs) => yargs.options({
|
|
88
|
+
]), (args) => create(args)).command("update <id>", "Update a snapshot's name or expiration by id, unique name, or slug", (yargs) => yargs.positional("id", SNAPSHOT_REF).options({
|
|
77
89
|
name: {
|
|
78
90
|
describe: "Rename the snapshot",
|
|
79
91
|
type: "string"
|
|
@@ -86,7 +98,7 @@ const builder = (argv) => argv.usage("$0 snapshots <sub-command> [options]").opt
|
|
|
86
98
|
describe: "Clear the expiration so the snapshot is kept indefinitely.",
|
|
87
99
|
type: "boolean"
|
|
88
100
|
}
|
|
89
|
-
}).conflicts("expires-at", "clear-expiration"), (args) => update(args)).command("delete <id>", "Delete a snapshot by id or
|
|
101
|
+
}).conflicts("expires-at", "clear-expiration"), (args) => update(args)).command("delete <id>", "Delete a snapshot by id, unique name, or slug", (yargs) => yargs.positional("id", SNAPSHOT_REF), (args) => deleteSnapshot(args)).command("restore <id>", "Restore a snapshot (id, unique name, or slug) into a branch", (yargs) => yargs.positional("id", SNAPSHOT_REF).options({
|
|
90
102
|
name: {
|
|
91
103
|
describe: "Name for the newly restored branch. Auto-generated when omitted.",
|
|
92
104
|
type: "string"
|
|
@@ -155,9 +167,8 @@ const toIso = (value, flag) => {
|
|
|
155
167
|
return new Date(ms).toISOString();
|
|
156
168
|
};
|
|
157
169
|
/**
|
|
158
|
-
*
|
|
159
|
-
*
|
|
160
|
-
* hard error asking the user to disambiguate by id.
|
|
170
|
+
* Unique names precede slugs so existing name-based commands keep targeting
|
|
171
|
+
* the named snapshot when another snapshot's slug matches that name.
|
|
161
172
|
*/
|
|
162
173
|
const resolveSnapshot = async (props) => {
|
|
163
174
|
const { data: { snapshots } } = await props.apiClient.listSnapshots(props.projectId);
|
|
@@ -166,8 +177,11 @@ const resolveSnapshot = async (props) => {
|
|
|
166
177
|
const byName = snapshots.filter((s) => s.name === props.id);
|
|
167
178
|
if (byName.length === 1) return byName[0];
|
|
168
179
|
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")}`);
|
|
169
|
-
|
|
180
|
+
const bySlug = snapshots.find((s) => s.slug === props.id);
|
|
181
|
+
if (bySlug) return bySlug;
|
|
182
|
+
throw new Error(`Snapshot "${props.id}" not found.\nAvailable snapshots: ${snapshots.map((s) => formatSnapshotRef(s)).join(", ") || "none"}`);
|
|
170
183
|
};
|
|
184
|
+
const formatSnapshotRef = (snapshot) => snapshot.slug ? `${snapshot.name} (${snapshot.id}, slug: ${snapshot.slug})` : `${snapshot.name} (${snapshot.id})`;
|
|
171
185
|
const list = async (props) => {
|
|
172
186
|
const { data: { snapshots } } = await props.apiClient.listSnapshots(props.projectId);
|
|
173
187
|
writer(props).end(snapshots, {
|
|
@@ -187,12 +201,14 @@ const get = async (props) => {
|
|
|
187
201
|
const create = async (props) => {
|
|
188
202
|
if (props.lsn !== void 0 && !looksLikeLSN(props.lsn)) throw new Error(`Invalid --lsn value: "${props.lsn}". Expected an LSN like 0/1F3C8A0.`);
|
|
189
203
|
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.`);
|
|
204
|
+
if (props.slug !== void 0 && !SNAPSHOT_SLUG.test(props.slug)) throw new Error(`Invalid --slug value: "${props.slug}". Use 1-63 characters: start with a lowercase letter, then lowercase letters, digits, or hyphens, and end with a letter or digit.`);
|
|
190
205
|
const { branchId } = await resolveBranchRef({
|
|
191
206
|
...props,
|
|
192
207
|
branch: props.branch
|
|
193
208
|
});
|
|
194
209
|
const { data } = await retryOnLock(() => props.apiClient.createSnapshot(props.projectId, branchId, {
|
|
195
210
|
name: props.name,
|
|
211
|
+
slug: props.slug,
|
|
196
212
|
timestamp: props.timestamp,
|
|
197
213
|
lsn: props.lsn,
|
|
198
214
|
expires_at: props.expiresAt ? toIso(props.expiresAt, "--expires-at") : void 0
|