neon 4.3.1 → 4.4.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 +29 -0
- package/dist/analytics.js +4 -2
- package/dist/commands/auth.js +2 -2
- package/dist/commands/index.js +2 -0
- package/dist/commands/plugins.js +135 -0
- package/dist/context.js +3 -2
- package/dist/env.js +6 -1
- package/dist/index.js +1 -0
- package/dist/plugins/plan.js +69 -0
- package/dist/plugins/run.js +123 -0
- package/dist/plugins/targets.js +64 -0
- package/dist/plugins/wizard.js +36 -0
- package/package.json +3 -3
package/README.md
CHANGED
|
@@ -791,6 +791,34 @@ On a TTY the command asks which agents and which skills, then shows a summary to
|
|
|
791
791
|
|
|
792
792
|
`--agent` names match `neon mcp`, minus agents that cannot install skills: `antigravity`, `cline`, `cline-cli`, `claude-code`, `claude-desktop`, `codex`, `cursor`, `gemini-cli`, `goose`, `github-copilot-cli`, `grok-build`, `opencode`, `vscode`, `windsurf`, `zed`. `mcporter` is a known MCP name that is then skipped.
|
|
793
793
|
|
|
794
|
+
## Install the Neon plugin (`plugins`)
|
|
795
|
+
|
|
796
|
+
`neon plugins` installs the Neon agent plugin (`neon-postgres`) by running `npx plugins add`. It does not call the Neon API.
|
|
797
|
+
|
|
798
|
+
```bash
|
|
799
|
+
# Interactive: agents, then confirm.
|
|
800
|
+
$ neon plugins
|
|
801
|
+
|
|
802
|
+
# Skip prompts. Every detected agent.
|
|
803
|
+
$ neon plugins -y
|
|
804
|
+
|
|
805
|
+
$ neon plugins --agent cursor --agent claude-code
|
|
806
|
+
|
|
807
|
+
# User-level install.
|
|
808
|
+
$ neon plugins --global
|
|
809
|
+
$ neon plugins --global --agent vscode
|
|
810
|
+
```
|
|
811
|
+
|
|
812
|
+
On a TTY the command asks which agents, then shows a summary to confirm. Detected agents start selected from project-folder markers such as `.cursor`. There is one plugin (`neon-postgres`); there is no plugin picker and no `update` subcommand.
|
|
813
|
+
|
|
814
|
+
`-y` skips those questions and installs into every detected agent. `--agent` names specific agents and skips the agent picker. Without a TTY, pass `-y` or `--agent`. `--agent` alone is enough because the plugin is fixed.
|
|
815
|
+
|
|
816
|
+
Default scope is `project`. `--global` is `user`. On macOS and Linux, Cursor and Claude Code store the plugin cache under `~/.claude/plugins`; on Windows, Cursor installs into Cursor extensions. `project` vs `user` is the scope field the plugins CLI records, not a directory in the repo. VS Code, GitHub Copilot CLI, and Grok Build only install user-level: they are skipped at the default scope with a warning, and `--agent vscode` without `--global` fails if nothing else is selected.
|
|
817
|
+
|
|
818
|
+
`--agent` names with a plugins mapping: `claude-code`, `claude-desktop`, `codex`, `cursor`, `github-copilot-cli`, `grok-build`, `vscode`. `claude-desktop` installs as Claude Code; naming both produces one install and lists both names in the table. `mcporter` is a known MCP name that is then skipped.
|
|
819
|
+
|
|
820
|
+
The plugins CLI installs every plugin it finds in the Neon plugin package. Today that is `neon-postgres`.
|
|
821
|
+
|
|
794
822
|
## Snapshots (`snapshots`)
|
|
795
823
|
|
|
796
824
|
`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.
|
|
@@ -1171,6 +1199,7 @@ Id Name Project Created At Last Used At Last
|
|
|
1171
1199
|
| bootstrap | | Scaffold a project from a template |
|
|
1172
1200
|
| init | | Set up a project for a coding agent |
|
|
1173
1201
|
| mcp | | Install the Neon MCP server |
|
|
1202
|
+
| plugins | | Install the Neon plugin |
|
|
1174
1203
|
| skills | `update` | Install Neon agent skills |
|
|
1175
1204
|
| bucket | `create`, `list`, `delete`, `object list`, `object get`, `object put`, `object delete` (incl. `--recursive`) | Manage buckets and their objects |
|
|
1176
1205
|
| [completion](https://neon.com/docs/reference/cli-completion) | | Generate a completion script |
|
package/dist/analytics.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { a as OAUTH, t as storeFor } from "./_chunks/credential_io-D2vw1UOw.js";
|
|
2
|
-
import { getGithubEnvVars, isCi } from "./env.js";
|
|
2
|
+
import { getCliAgent, getGithubEnvVars, isCi } from "./env.js";
|
|
3
3
|
import { log } from "./log.js";
|
|
4
4
|
import pkg_default from "./pkg.js";
|
|
5
5
|
import { getApiClient, isNeonApiError } from "./api.js";
|
|
@@ -190,13 +190,15 @@ const trackEvent = (event, properties) => {
|
|
|
190
190
|
};
|
|
191
191
|
const getErrorAnalyticsEventContext = (_args) => ({
|
|
192
192
|
version: pkg_default.version,
|
|
193
|
-
ci: isCi()
|
|
193
|
+
ci: isCi(),
|
|
194
|
+
agent: getCliAgent(process.env)
|
|
194
195
|
});
|
|
195
196
|
const getAnalyticsEventProperties = (args) => ({
|
|
196
197
|
version: pkg_default.version,
|
|
197
198
|
command: args._.join(" "),
|
|
198
199
|
flags: { output: args.output },
|
|
199
200
|
ci: isCi(),
|
|
201
|
+
agent: getCliAgent(process.env),
|
|
200
202
|
githubEnvVars: getGithubEnvVars(process.env)
|
|
201
203
|
});
|
|
202
204
|
//#endregion
|
package/dist/commands/auth.js
CHANGED
|
@@ -6,7 +6,7 @@ import { log } from "../log.js";
|
|
|
6
6
|
import { getApiClient } from "../api.js";
|
|
7
7
|
import { o as isOwnedCredentialPath } from "../_chunks/paths-g3DRqJiD.js";
|
|
8
8
|
import { setAuthContext } from "../auth_context.js";
|
|
9
|
-
import { isConfigInit, isCurrentBranchProbe, isMcpCommand, isMcpOauth, isProfileCommand, isSkillsCommand } from "../context.js";
|
|
9
|
+
import { isConfigInit, isCurrentBranchProbe, isMcpCommand, isMcpOauth, isPluginsCommand, isProfileCommand, isSkillsCommand } from "../context.js";
|
|
10
10
|
import { extendTokenSet } from "../utils/auth.js";
|
|
11
11
|
import { auth, refreshToken } from "../auth.js";
|
|
12
12
|
import { i as selectCredential, n as displacedProfileWarning, t as credentialInputs } from "../_chunks/auth_selection-Bx2NAgkk.js";
|
|
@@ -217,7 +217,7 @@ const ensureAuth = async (props) => {
|
|
|
217
217
|
if (isProfileCommand(props)) return;
|
|
218
218
|
if (isMcpOauth(props)) return;
|
|
219
219
|
if (props._[0] === "open") return;
|
|
220
|
-
if (isSkillsCommand(props)) return;
|
|
220
|
+
if (isSkillsCommand(props) || isPluginsCommand(props)) return;
|
|
221
221
|
const isLocalDev = props._[0] === "dev";
|
|
222
222
|
const isBootstrap = props._[0] === "bootstrap";
|
|
223
223
|
const isInit = props._[0] === "init";
|
package/dist/commands/index.js
CHANGED
|
@@ -25,6 +25,7 @@ import { t as neon_auth_exports } from "./neon_auth.js";
|
|
|
25
25
|
import { t as open_exports } from "./open.js";
|
|
26
26
|
import { t as operations_exports } from "./operations.js";
|
|
27
27
|
import { t as orgs_exports } from "./orgs.js";
|
|
28
|
+
import { t as plugins_exports } from "./plugins.js";
|
|
28
29
|
import { t as profile_exports } from "./profile.js";
|
|
29
30
|
import { t as psql_exports } from "./psql.js";
|
|
30
31
|
import { t as roles_exports } from "./roles.js";
|
|
@@ -61,6 +62,7 @@ var commands_default = [
|
|
|
61
62
|
open_exports,
|
|
62
63
|
init_exports,
|
|
63
64
|
mcp_exports,
|
|
65
|
+
plugins_exports,
|
|
64
66
|
skills_exports,
|
|
65
67
|
data_api_exports,
|
|
66
68
|
functions_exports,
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
import { t as __exportAll } from "../_chunks/rolldown-runtime-8H4AJuhK.js";
|
|
2
|
+
import { log } from "../log.js";
|
|
3
|
+
import { writer } from "../writer.js";
|
|
4
|
+
import { noPassthrough } from "../utils/flags.js";
|
|
5
|
+
import { getAgentDisplayName } from "../mcp/agents.js";
|
|
6
|
+
import "../init/agents.js";
|
|
7
|
+
import { canPickAgentsInteractively } from "../utils/agent_picker.js";
|
|
8
|
+
import { resolvePluginsPlan } from "../plugins/plan.js";
|
|
9
|
+
import { NEON_PLUGIN_NAME, neonPluginsRetryCommand, pluginsAddArgs, runPluginsCli } from "../plugins/run.js";
|
|
10
|
+
import { confirmPluginsInstall } from "../plugins/wizard.js";
|
|
11
|
+
//#region src/commands/plugins.ts
|
|
12
|
+
var plugins_exports = /* @__PURE__ */ __exportAll({
|
|
13
|
+
builder: () => builder,
|
|
14
|
+
command: () => command,
|
|
15
|
+
describe: () => describe,
|
|
16
|
+
handler: () => handler
|
|
17
|
+
});
|
|
18
|
+
const scopeLabel = (scope) => scope === "project" ? "project" : "user";
|
|
19
|
+
const coerceAgents = (value) => {
|
|
20
|
+
if (value === void 0) return [];
|
|
21
|
+
const list = Array.isArray(value) ? value : [value];
|
|
22
|
+
if (list.length === 0) throw new Error("--agent needs a value. Pass one, or omit the flag entirely.");
|
|
23
|
+
return list.map((item) => {
|
|
24
|
+
if (typeof item !== "string" || item.trim() === "") throw new Error("--agent needs a value. Pass one, or omit the flag entirely.");
|
|
25
|
+
return item;
|
|
26
|
+
});
|
|
27
|
+
};
|
|
28
|
+
const command = "plugins";
|
|
29
|
+
const describe = "Install the Neon plugin into coding agents";
|
|
30
|
+
const builder = (argv) => argv.usage("$0 plugins [options]").options({
|
|
31
|
+
yes: {
|
|
32
|
+
alias: "y",
|
|
33
|
+
type: "boolean",
|
|
34
|
+
default: false,
|
|
35
|
+
describe: "Skip prompts"
|
|
36
|
+
},
|
|
37
|
+
global: {
|
|
38
|
+
type: "boolean",
|
|
39
|
+
default: false,
|
|
40
|
+
describe: "Install user-level. Default is project"
|
|
41
|
+
},
|
|
42
|
+
agent: {
|
|
43
|
+
alias: "a",
|
|
44
|
+
type: "array",
|
|
45
|
+
string: true,
|
|
46
|
+
describe: "Coding agent to install into (repeatable). Skips the agent picker",
|
|
47
|
+
coerce: coerceAgents
|
|
48
|
+
}
|
|
49
|
+
}).example("$0 plugins", "Interactive: agents, then confirm").example("$0 plugins -y", "Detected agents, skip prompts").example("$0 plugins --agent cursor --agent claude-code", "Install into specific agents").example("$0 plugins --global", "Install user-level").strict().check(noPassthrough("plugins"));
|
|
50
|
+
const handler = async (props) => {
|
|
51
|
+
const cwd = process.cwd();
|
|
52
|
+
const yes = props.yes === true;
|
|
53
|
+
const interactive = canPickAgentsInteractively() && !yes;
|
|
54
|
+
const plan = await resolvePluginsPlan({
|
|
55
|
+
global: props.global === true,
|
|
56
|
+
agents: props.agent ?? [],
|
|
57
|
+
yes,
|
|
58
|
+
cwd,
|
|
59
|
+
interactive
|
|
60
|
+
});
|
|
61
|
+
for (const agent of plan.skipped) log.warning("Skipping %s: no plugins mapping.", getAgentDisplayName(agent));
|
|
62
|
+
for (const agent of plan.userScopeSkipped) log.warning("Skipping %s: plugins are user-level. Pass --global.", getAgentDisplayName(agent));
|
|
63
|
+
if (interactive) {
|
|
64
|
+
if (!await confirmPluginsInstall({
|
|
65
|
+
scope: plan.scope,
|
|
66
|
+
agents: plan.agents
|
|
67
|
+
})) {
|
|
68
|
+
log.info("Aborted. Nothing was written.");
|
|
69
|
+
return;
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
const rows = [];
|
|
73
|
+
const failed = [];
|
|
74
|
+
const scope = scopeLabel(plan.scope);
|
|
75
|
+
for (const mapped of plan.targets) {
|
|
76
|
+
const args = pluginsAddArgs({
|
|
77
|
+
target: mapped.target,
|
|
78
|
+
global: plan.scope === "global"
|
|
79
|
+
});
|
|
80
|
+
const agent = mapped.agents.join(", ");
|
|
81
|
+
try {
|
|
82
|
+
await runPluginsCli({
|
|
83
|
+
args,
|
|
84
|
+
cwd
|
|
85
|
+
});
|
|
86
|
+
rows.push({
|
|
87
|
+
scope,
|
|
88
|
+
plugin: NEON_PLUGIN_NAME,
|
|
89
|
+
agent,
|
|
90
|
+
status: "installed"
|
|
91
|
+
});
|
|
92
|
+
} catch (error) {
|
|
93
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
94
|
+
rows.push({
|
|
95
|
+
scope,
|
|
96
|
+
plugin: NEON_PLUGIN_NAME,
|
|
97
|
+
agent,
|
|
98
|
+
status: "failed",
|
|
99
|
+
error: "plugins CLI failed"
|
|
100
|
+
});
|
|
101
|
+
failed.push({
|
|
102
|
+
agents: mapped.agents,
|
|
103
|
+
message
|
|
104
|
+
});
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
const out = writer(props);
|
|
108
|
+
out.write(rows, {
|
|
109
|
+
fields: [
|
|
110
|
+
"scope",
|
|
111
|
+
"plugin",
|
|
112
|
+
"agent",
|
|
113
|
+
"status",
|
|
114
|
+
"error"
|
|
115
|
+
],
|
|
116
|
+
title: "Plugins"
|
|
117
|
+
});
|
|
118
|
+
out.end();
|
|
119
|
+
if (failed.length === 0) {
|
|
120
|
+
log.info(plan.scope === "project" ? "Installed the Neon plugin (project)." : "Installed the Neon plugin (user).");
|
|
121
|
+
return;
|
|
122
|
+
}
|
|
123
|
+
const first = failed[0];
|
|
124
|
+
if (first === void 0) throw new Error("Failed to install the Neon plugin.");
|
|
125
|
+
const retry = neonPluginsRetryCommand({
|
|
126
|
+
agents: failed.flatMap((row) => row.agents),
|
|
127
|
+
global: plan.scope === "global"
|
|
128
|
+
});
|
|
129
|
+
if (first.message.includes("needs npx (Node.js)")) throw new Error(first.message);
|
|
130
|
+
const detail = failed.map((row) => row.message).join("\n");
|
|
131
|
+
if (failed.length === rows.length) throw new Error(`${detail}\nRetry with: ${retry}`);
|
|
132
|
+
throw new Error(`Failed to install the Neon plugin for: ${failed.flatMap((row) => row.agents).join(", ")}.\n${detail}\nRetry with: ${retry}`);
|
|
133
|
+
};
|
|
134
|
+
//#endregion
|
|
135
|
+
export { builder, command, describe, handler, plugins_exports as t };
|
package/dist/context.js
CHANGED
|
@@ -52,6 +52,7 @@ const isProfileCommand = (args) => args._[0] === "profile" || args._[0] === "pro
|
|
|
52
52
|
const isApiKeysCommand = (args) => args._[0] === "api-keys" || args._[0] === "api-key";
|
|
53
53
|
const isMcpCommand = (args) => args._[0] === "mcp";
|
|
54
54
|
const isSkillsCommand = (args) => args._[0] === "skills";
|
|
55
|
+
const isPluginsCommand = (args) => args._[0] === "plugins";
|
|
55
56
|
/** Raw argv is required because auth middleware runs before MCP flags are parsed. */
|
|
56
57
|
const isMcpOauth = (args) => isMcpCommand(args) && argvEnablesMcpOauth(process.argv);
|
|
57
58
|
const OAUTH_FALSE = /* @__PURE__ */ new Set([
|
|
@@ -131,7 +132,7 @@ const enrichFromContext = (args) => {
|
|
|
131
132
|
if (isApiKeysCommand(args)) return;
|
|
132
133
|
if (isProfileCommand(args)) return;
|
|
133
134
|
if (isMcpCommand(args)) return;
|
|
134
|
-
if (isSkillsCommand(args)) return;
|
|
135
|
+
if (isSkillsCommand(args) || isPluginsCommand(args)) return;
|
|
135
136
|
const context = readContextFile(args.contextFile);
|
|
136
137
|
if (!args.orgId) args.orgId = context.orgId;
|
|
137
138
|
if (!args.projectId) args.projectId = context.projectId;
|
|
@@ -237,4 +238,4 @@ const globToRegExp = (pattern) => {
|
|
|
237
238
|
return new RegExp(`^${source}$`);
|
|
238
239
|
};
|
|
239
240
|
//#endregion
|
|
240
|
-
export { applyContext, contextBranch, currentContextFile, enrichFromContext, ensureGitignored, isApiKeysCommand, isConfigInit, isCurrentBranchProbe, isMcpCommand, isMcpOauth, isProfileCommand, isSkillsCommand, readContextFile, setContext, updateContextFile, walkContextFile };
|
|
241
|
+
export { applyContext, contextBranch, currentContextFile, enrichFromContext, ensureGitignored, isApiKeysCommand, isConfigInit, isCurrentBranchProbe, isMcpCommand, isMcpOauth, isPluginsCommand, isProfileCommand, isSkillsCommand, readContextFile, setContext, updateContextFile, walkContextFile };
|
package/dist/env.js
CHANGED
|
@@ -5,6 +5,11 @@ const isCi = () => {
|
|
|
5
5
|
const isDebug = () => {
|
|
6
6
|
return Boolean(process.env.DEBUG);
|
|
7
7
|
};
|
|
8
|
+
const getCliAgent = (env) => {
|
|
9
|
+
const claudeCode = env.CLAUDE_CODE_CHILD_SESSION === "1";
|
|
10
|
+
if (claudeCode === (env.CODEX_CI === "1" && (Boolean(env.CODEX_THREAD_ID?.trim()) || Boolean(env.CODEX_SESSION_ID?.trim())))) return void 0;
|
|
11
|
+
return claudeCode ? "claude-code" : "codex";
|
|
12
|
+
};
|
|
8
13
|
const getGithubEnvVars = (env) => {
|
|
9
14
|
const vars = [
|
|
10
15
|
"GITHUB_ACTION_PATH",
|
|
@@ -27,4 +32,4 @@ const getGithubEnvVars = (env) => {
|
|
|
27
32
|
return Object.fromEntries(map);
|
|
28
33
|
};
|
|
29
34
|
//#endregion
|
|
30
|
-
export { getGithubEnvVars, isCi, isDebug };
|
|
35
|
+
export { getCliAgent, getGithubEnvVars, isCi, isDebug };
|
package/dist/index.js
CHANGED
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import { getAgentDisplayName, tryResolveAddMcpAgentId } from "../mcp/agents.js";
|
|
2
|
+
import "../init/agents.js";
|
|
3
|
+
import { agentChoicesFrom, pickAgentsInteractively, resolveAgentSelection } from "../utils/agent_picker.js";
|
|
4
|
+
import { detectPluginsAgents, getPluginsTargetName, isUserScopeOnlyPluginsTarget, mappedPluginsTargets, pluginsInstallableAgents } from "./targets.js";
|
|
5
|
+
//#region src/plugins/plan.ts
|
|
6
|
+
const assertPluginsCanRun = (options) => {
|
|
7
|
+
if (options.yes || options.interactive || options.hasAgents) return;
|
|
8
|
+
throw new Error("No interactive terminal. Pass -y to install into detected agents, or --agent <name>.");
|
|
9
|
+
};
|
|
10
|
+
async function resolvePluginsPlan(options) {
|
|
11
|
+
assertPluginsCanRun({
|
|
12
|
+
yes: options.yes,
|
|
13
|
+
interactive: options.interactive,
|
|
14
|
+
hasAgents: options.agents.length > 0
|
|
15
|
+
});
|
|
16
|
+
const prompt = options.interactive && !options.yes;
|
|
17
|
+
const scope = options.global ? "global" : "project";
|
|
18
|
+
const available = pluginsInstallableAgents(scope);
|
|
19
|
+
const detected = await detectPluginsAgents({
|
|
20
|
+
scope,
|
|
21
|
+
cwd: options.cwd
|
|
22
|
+
});
|
|
23
|
+
const selected = await resolveAgentSelection({
|
|
24
|
+
specified: options.agents,
|
|
25
|
+
choices: agentChoicesFrom(available, detected),
|
|
26
|
+
detected,
|
|
27
|
+
message: "Which coding agents should get the Neon plugin? (space to toggle, enter to confirm)",
|
|
28
|
+
nonInteractiveMessage: scope === "project" ? `No coding agents detected in this project. Pass --agent <name>. Supported agents: ${available.join(", ")}` : `No coding agents detected. Pass --agent <name>. Supported agents: ${available.join(", ")}`,
|
|
29
|
+
resolveSpecified: (raw) => {
|
|
30
|
+
if (raw === "*") throw new Error("neon plugins does not accept --agent *. Pass --agent <name> for each coding agent, or omit --agent to use detected agents.");
|
|
31
|
+
const id = tryResolveAddMcpAgentId(raw);
|
|
32
|
+
if (!id) throw new Error(`Unknown agent: "${raw}". Supported agents: ${available.join(", ")}`);
|
|
33
|
+
return id;
|
|
34
|
+
},
|
|
35
|
+
pick: prompt ? options.pickAgents ?? pickAgentsInteractively : void 0,
|
|
36
|
+
interactive: prompt
|
|
37
|
+
});
|
|
38
|
+
const agents = [];
|
|
39
|
+
const skipped = [];
|
|
40
|
+
const userScopeSkipped = [];
|
|
41
|
+
for (const id of selected) {
|
|
42
|
+
const target = getPluginsTargetName(id);
|
|
43
|
+
if (target === void 0) {
|
|
44
|
+
skipped.push(id);
|
|
45
|
+
continue;
|
|
46
|
+
}
|
|
47
|
+
if (scope === "project" && isUserScopeOnlyPluginsTarget(target)) {
|
|
48
|
+
userScopeSkipped.push(id);
|
|
49
|
+
continue;
|
|
50
|
+
}
|
|
51
|
+
agents.push(id);
|
|
52
|
+
}
|
|
53
|
+
if (agents.length === 0) {
|
|
54
|
+
if (userScopeSkipped.length > 0 && skipped.length === 0) {
|
|
55
|
+
const names = userScopeSkipped.map((id) => getAgentDisplayName(id)).join(", ");
|
|
56
|
+
throw new Error(`${names}: plugins are user-level. Pass --global. Without --global: ${pluginsInstallableAgents("project").join(", ")}`);
|
|
57
|
+
}
|
|
58
|
+
throw new Error(`None of the selected agents can install plugins. Supported agents: ${available.join(", ")}`);
|
|
59
|
+
}
|
|
60
|
+
return {
|
|
61
|
+
scope,
|
|
62
|
+
agents,
|
|
63
|
+
skipped,
|
|
64
|
+
userScopeSkipped,
|
|
65
|
+
targets: mappedPluginsTargets(agents, scope)
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
//#endregion
|
|
69
|
+
export { assertPluginsCanRun, resolvePluginsPlan };
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
import { spawnSync } from "node:child_process";
|
|
2
|
+
import { execa } from "execa";
|
|
3
|
+
//#region src/plugins/run.ts
|
|
4
|
+
const TELEMETRY_BLOCKERS = ["DISABLE_TELEMETRY", "DO_NOT_TRACK"];
|
|
5
|
+
const PLUGIN_SOURCE = "neondatabase/agent-skills";
|
|
6
|
+
const NEON_PLUGIN_NAME = "neon-postgres";
|
|
7
|
+
const PLUGINS_CLI_TIMEOUT_MS = 12e4;
|
|
8
|
+
const pluginsChildEnv = (base = process.env) => {
|
|
9
|
+
const env = { ...base };
|
|
10
|
+
for (const key of TELEMETRY_BLOCKERS) delete env[key];
|
|
11
|
+
for (const key of Object.keys(env)) if (key.toUpperCase() === "NEON_API_KEY") delete env[key];
|
|
12
|
+
return env;
|
|
13
|
+
};
|
|
14
|
+
const pluginsScopeFlag = (global) => global ? "user" : "project";
|
|
15
|
+
const pluginsAddArgs = (options) => {
|
|
16
|
+
if (options.target.length === 0) throw new Error("plugins add needs a -t target.");
|
|
17
|
+
return [
|
|
18
|
+
"-y",
|
|
19
|
+
"plugins",
|
|
20
|
+
"add",
|
|
21
|
+
PLUGIN_SOURCE,
|
|
22
|
+
"-t",
|
|
23
|
+
options.target,
|
|
24
|
+
"-s",
|
|
25
|
+
pluginsScopeFlag(options.global),
|
|
26
|
+
"-y"
|
|
27
|
+
];
|
|
28
|
+
};
|
|
29
|
+
const neonPluginsRetryCommand = (options) => {
|
|
30
|
+
const args = ["plugins"];
|
|
31
|
+
for (const agent of options.agents) args.push("--agent", agent);
|
|
32
|
+
if (options.global) args.push("--global");
|
|
33
|
+
args.push("-y");
|
|
34
|
+
return `neon ${args.map(quoteNpxArg).join(" ")}`;
|
|
35
|
+
};
|
|
36
|
+
const quoteNpxArg = (part) => /[\s"'\\*]/.test(part) ? `'${part.replace(/'/g, `'\\''`)}'` : part;
|
|
37
|
+
const runPluginsCli = async (options) => {
|
|
38
|
+
const timeoutMs = options.timeoutMs ?? 12e4;
|
|
39
|
+
const subprocess = execa("npx", options.args, {
|
|
40
|
+
cwd: options.cwd,
|
|
41
|
+
env: pluginsChildEnv(options.env ?? process.env),
|
|
42
|
+
extendEnv: false,
|
|
43
|
+
stdio: "pipe",
|
|
44
|
+
detached: true
|
|
45
|
+
});
|
|
46
|
+
let timedOut = false;
|
|
47
|
+
let stopped = false;
|
|
48
|
+
const stop = () => {
|
|
49
|
+
if (stopped) return;
|
|
50
|
+
stopped = true;
|
|
51
|
+
if (subprocess.pid !== void 0) killProcessTree(subprocess.pid);
|
|
52
|
+
};
|
|
53
|
+
const onSigint = () => {
|
|
54
|
+
stop();
|
|
55
|
+
process.exit(130);
|
|
56
|
+
};
|
|
57
|
+
const onSigterm = () => {
|
|
58
|
+
stop();
|
|
59
|
+
process.exit(143);
|
|
60
|
+
};
|
|
61
|
+
process.once("SIGINT", onSigint);
|
|
62
|
+
process.once("SIGTERM", onSigterm);
|
|
63
|
+
process.once("exit", stop);
|
|
64
|
+
const timer = setTimeout(() => {
|
|
65
|
+
timedOut = true;
|
|
66
|
+
stop();
|
|
67
|
+
}, timeoutMs);
|
|
68
|
+
try {
|
|
69
|
+
const result = await subprocess;
|
|
70
|
+
return {
|
|
71
|
+
stdout: result.stdout,
|
|
72
|
+
stderr: result.stderr
|
|
73
|
+
};
|
|
74
|
+
} catch (error) {
|
|
75
|
+
if (isCommandMissing(error)) throw new Error("neon plugins needs npx (Node.js) to run the plugins CLI. Install Node.js, then retry.");
|
|
76
|
+
if (isExecaFailure(error)) throw new Error(pluginsCliFailureMessage({
|
|
77
|
+
stderr: typeof error.stderr === "string" ? error.stderr : "",
|
|
78
|
+
stdout: typeof error.stdout === "string" ? error.stdout : "",
|
|
79
|
+
timedOut,
|
|
80
|
+
timeoutMs
|
|
81
|
+
}));
|
|
82
|
+
throw error;
|
|
83
|
+
} finally {
|
|
84
|
+
clearTimeout(timer);
|
|
85
|
+
process.removeListener("SIGINT", onSigint);
|
|
86
|
+
process.removeListener("SIGTERM", onSigterm);
|
|
87
|
+
process.removeListener("exit", stop);
|
|
88
|
+
}
|
|
89
|
+
};
|
|
90
|
+
const pluginsCliFailureMessage = (error) => {
|
|
91
|
+
const childOut = [error.stderr, error.stdout].filter((part) => typeof part === "string" && part.length > 0).join("\n");
|
|
92
|
+
if (error.timedOut === true) {
|
|
93
|
+
const seconds = Math.max(1, Math.ceil((error.timeoutMs ?? 12e4) / 1e3));
|
|
94
|
+
const headline = `plugins CLI timed out after ${seconds} ${seconds === 1 ? "second" : "seconds"}`;
|
|
95
|
+
return childOut.length > 0 ? `${headline}:\n${childOut}` : `${headline}.`;
|
|
96
|
+
}
|
|
97
|
+
if (childOut.length > 0) return `plugins CLI failed:\n${childOut}`;
|
|
98
|
+
return "plugins CLI failed.";
|
|
99
|
+
};
|
|
100
|
+
const killProcessTree = (pid) => {
|
|
101
|
+
if (process.platform === "win32") {
|
|
102
|
+
spawnSync("taskkill", [
|
|
103
|
+
"/pid",
|
|
104
|
+
String(pid),
|
|
105
|
+
"/T",
|
|
106
|
+
"/F"
|
|
107
|
+
], { stdio: "ignore" });
|
|
108
|
+
return;
|
|
109
|
+
}
|
|
110
|
+
try {
|
|
111
|
+
process.kill(-pid, "SIGKILL");
|
|
112
|
+
} catch {
|
|
113
|
+
try {
|
|
114
|
+
process.kill(pid, "SIGKILL");
|
|
115
|
+
} catch {
|
|
116
|
+
return;
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
};
|
|
120
|
+
const isCommandMissing = (error) => typeof error === "object" && error !== null && "code" in error && error.code === "ENOENT";
|
|
121
|
+
const isExecaFailure = (error) => typeof error === "object" && error !== null && "shortMessage" in error && typeof error.shortMessage === "string";
|
|
122
|
+
//#endregion
|
|
123
|
+
export { NEON_PLUGIN_NAME, PLUGINS_CLI_TIMEOUT_MS, PLUGIN_SOURCE, neonPluginsRetryCommand, pluginsAddArgs, pluginsChildEnv, pluginsCliFailureMessage, pluginsScopeFlag, quoteNpxArg, runPluginsCli };
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import { detectInstalledAgents, listMcpAgentIds, tryResolveAddMcpAgentId, uniqueAgentIds } from "../mcp/agents.js";
|
|
2
|
+
import "../init/agents.js";
|
|
3
|
+
import { detectProjectAgents } from "add-mcp";
|
|
4
|
+
//#region src/plugins/targets.ts
|
|
5
|
+
const PLUGINS_TARGET_BY_TYPE = {
|
|
6
|
+
cursor: "cursor",
|
|
7
|
+
vscode: "vscode",
|
|
8
|
+
"claude-code": "claude-code",
|
|
9
|
+
"claude-desktop": "claude-code",
|
|
10
|
+
codex: "codex",
|
|
11
|
+
"github-copilot-cli": "github-copilot",
|
|
12
|
+
"grok-build": "grok"
|
|
13
|
+
};
|
|
14
|
+
const USER_SCOPE_ONLY_TARGETS = /* @__PURE__ */ new Set([
|
|
15
|
+
"vscode",
|
|
16
|
+
"github-copilot",
|
|
17
|
+
"grok"
|
|
18
|
+
]);
|
|
19
|
+
function getPluginsTargetName(agent) {
|
|
20
|
+
if (Object.prototype.hasOwnProperty.call(PLUGINS_TARGET_BY_TYPE, agent)) return PLUGINS_TARGET_BY_TYPE[agent];
|
|
21
|
+
const id = tryResolveAddMcpAgentId(agent);
|
|
22
|
+
if (!id) return void 0;
|
|
23
|
+
return PLUGINS_TARGET_BY_TYPE[id];
|
|
24
|
+
}
|
|
25
|
+
function supportsPlugins(agent) {
|
|
26
|
+
return getPluginsTargetName(agent) !== void 0;
|
|
27
|
+
}
|
|
28
|
+
function isUserScopeOnlyPluginsTarget(target) {
|
|
29
|
+
return USER_SCOPE_ONLY_TARGETS.has(target);
|
|
30
|
+
}
|
|
31
|
+
const pluginsMappedAgents = () => listMcpAgentIds().filter((id) => supportsPlugins(id));
|
|
32
|
+
const pluginsInstallableAgents = (scope) => pluginsMappedAgents().filter((id) => {
|
|
33
|
+
if (scope === "global") return true;
|
|
34
|
+
const target = getPluginsTargetName(id);
|
|
35
|
+
return target !== void 0 && !isUserScopeOnlyPluginsTarget(target);
|
|
36
|
+
});
|
|
37
|
+
const detectPluginsAgents = async (options) => {
|
|
38
|
+
const detected = options.scope === "project" ? detectProjectAgents(options.cwd) : await detectInstalledAgents();
|
|
39
|
+
return uniqueAgentIds(detected).filter((id) => supportsPlugins(id));
|
|
40
|
+
};
|
|
41
|
+
const mappedPluginsTargets = (agents, scope) => {
|
|
42
|
+
const mapped = [];
|
|
43
|
+
const byTarget = /* @__PURE__ */ new Map();
|
|
44
|
+
for (const agent of agents) {
|
|
45
|
+
const target = getPluginsTargetName(agent);
|
|
46
|
+
if (target === void 0) continue;
|
|
47
|
+
if (scope === "project" && isUserScopeOnlyPluginsTarget(target)) continue;
|
|
48
|
+
const existing = byTarget.get(target);
|
|
49
|
+
if (existing !== void 0) {
|
|
50
|
+
if (!existing.agents.includes(agent)) existing.agents.push(agent);
|
|
51
|
+
continue;
|
|
52
|
+
}
|
|
53
|
+
const row = {
|
|
54
|
+
agents: [agent],
|
|
55
|
+
target
|
|
56
|
+
};
|
|
57
|
+
byTarget.set(target, row);
|
|
58
|
+
mapped.push(row);
|
|
59
|
+
}
|
|
60
|
+
if (mapped.length === 0) throw new Error(`None of the selected agents can install plugins. Supported agents: ${pluginsInstallableAgents(scope).join(", ")}`);
|
|
61
|
+
return mapped;
|
|
62
|
+
};
|
|
63
|
+
//#endregion
|
|
64
|
+
export { detectPluginsAgents, getPluginsTargetName, isUserScopeOnlyPluginsTarget, mappedPluginsTargets, pluginsInstallableAgents, pluginsMappedAgents, supportsPlugins };
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { getAgentDisplayName } from "../mcp/agents.js";
|
|
2
|
+
import "../init/agents.js";
|
|
3
|
+
import { canPickAgentsInteractively } from "../utils/agent_picker.js";
|
|
4
|
+
import { NEON_PLUGIN_NAME } from "./run.js";
|
|
5
|
+
import prompts from "prompts";
|
|
6
|
+
//#region src/plugins/wizard.ts
|
|
7
|
+
const restoreCursorOnAbort = (state) => {
|
|
8
|
+
if (state.aborted) {
|
|
9
|
+
process.stdout.write("\x1B[?25h");
|
|
10
|
+
process.stdout.write("\n");
|
|
11
|
+
process.exit(1);
|
|
12
|
+
}
|
|
13
|
+
};
|
|
14
|
+
const pluginsInstallSummary = (options) => {
|
|
15
|
+
const rows = [
|
|
16
|
+
["Scope", options.scope === "project" ? "project" : "user"],
|
|
17
|
+
["Agents", options.agents.map(getAgentDisplayName).join(", ")],
|
|
18
|
+
["Plugin", NEON_PLUGIN_NAME]
|
|
19
|
+
];
|
|
20
|
+
const labelWidth = Math.max(...rows.map(([label]) => label.length));
|
|
21
|
+
return rows.map(([label, value]) => `${label.padEnd(labelWidth)} ${value}`).join("\n");
|
|
22
|
+
};
|
|
23
|
+
const confirmPluginsInstall = async (options) => {
|
|
24
|
+
if (!canPickAgentsInteractively()) return true;
|
|
25
|
+
process.stdout.write(`\n${pluginsInstallSummary(options)}\n\n`);
|
|
26
|
+
const { ok } = await prompts({
|
|
27
|
+
onState: restoreCursorOnAbort,
|
|
28
|
+
type: "confirm",
|
|
29
|
+
name: "ok",
|
|
30
|
+
message: "Install the Neon plugin into these agents?",
|
|
31
|
+
initial: true
|
|
32
|
+
});
|
|
33
|
+
return ok === true;
|
|
34
|
+
};
|
|
35
|
+
//#endregion
|
|
36
|
+
export { confirmPluginsInstall, pluginsInstallSummary };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "neon",
|
|
3
|
-
"version": "4.
|
|
3
|
+
"version": "4.4.0",
|
|
4
4
|
"description": "CLI tool for Neon, the cloud backend primitives built around Lakebase Postgres",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"neon",
|
|
@@ -63,9 +63,9 @@
|
|
|
63
63
|
"yaml": "^2.9.0",
|
|
64
64
|
"yargs": "17.7.2",
|
|
65
65
|
"yoctocolors": "^2.1.2",
|
|
66
|
-
"@neon/config": "1.0.2",
|
|
66
|
+
"@neon/config-runtime": "1.0.2",
|
|
67
67
|
"@neon/sdk": "2.2.0",
|
|
68
|
-
"@neon/config
|
|
68
|
+
"@neon/config": "1.0.2"
|
|
69
69
|
},
|
|
70
70
|
"optionalDependencies": {
|
|
71
71
|
"@napi-rs/keyring": "1.3.0",
|