Skip to content

Add neon plugins to install the Neon agent plugin into coding agents - #479

Merged
andrelandgraf merged 5 commits into
mainfrom
feat/neon-plugins
Aug 25, 2026
Merged

Add neon plugins to install the Neon agent plugin into coding agents#479
andrelandgraf merged 5 commits into
mainfrom
feat/neon-plugins

Conversation

@andrelandgraf

@andrelandgraf andrelandgraf commented Aug 25, 2026

Copy link
Copy Markdown
Collaborator

Problem

The Neon agent plugin ships from the neondatabase/agent-skills marketplace. Installing it today means knowing that Vercel's plugins CLI exists, knowing the marketplace source string and knowing which -t target name each coding agent goes by:

npx plugins add neondatabase/agent-skills -t cursor -s project

All three of those are facts about Neon's packaging, so the CLI is the place that should hold them. It already holds the equivalents for its other agent integrations: neon mcp writes the MCP server config and neon skills installs the Neon skills. The plugin was the piece still left to the user.

The command

neon plugins installs the Neon plugin (neon-postgres) into coding agents by running npx plugins add once per mapped target. It does not call the Neon API.

$ neon plugins -y
Plugins
Scope    Plugin         Agent   Status
project  neon-postgres  cursor  installed
INFO: Installed the Neon plugin (project).

On a TTY, neon plugins asks which agents should get the plugin, then prints this summary:

Scope   project
Agents  Cursor
Plugin  neon-postgres

followed by the confirm question Install the Neon plugin into these agents?, which defaults to yes. Answering no prints Aborted. Nothing was written. and exits 0.

Detected agents start selected. At project scope detection reads project-folder markers such as .cursor; with --global it detects agents installed on the machine.

Flags

Flag Type Default Meaning
-y, --yes boolean false Skip prompts
--global boolean false Install user-level. Default is project
-a, --agent <name> array Coding agent to install into (repeatable). Skips the agent picker

There is one plugin, so there is no plugin picker and no --plugin. There is no update, discover or targets subcommand; neon plugins update is rejected with Unknown command: update.

--agent on its own is enough. It names the agents and the plugin and scope are already fixed, so nothing is left for -y to answer:

$ neon plugins --agent cursor --agent claude-code

Without a TTY and without either flag, the command refuses rather than guessing:

$ neon plugins
ERROR: No interactive terminal. Pass -y to install into detected agents, or --agent <name>.

Scope

Default scope is project. --global is user. Those two words are the scope field the plugins CLI records against the install; the files still land in the agent's own cache either way. On macOS and Linux, Cursor and Claude Code cache the plugin under ~/.claude/plugins. On Windows, Cursor installs into Cursor extensions.

Scope shows up as project or user in the confirm summary, in the table and in the success line. -s user and -s project are the plugins CLI's vocabulary and stay inside the child invocation.

Agent mapping

--agent plugins -t target Project scope
cursor cursor yes
claude-code claude-code yes
claude-desktop claude-code yes
codex codex yes
vscode vscode --global only
github-copilot-cli github-copilot --global only
grok-build grok --global only

Claude Desktop and Claude Code share a target, so naming both is one spawn. neon skills collapses the same pair. Here the row keeps both requested names in its Agent cell (claude-code, claude-desktop), so the table accounts for everything that was asked for.

The three user-level-only agents are dropped from project scope. When something else is selected they are a warning and the rest installs:

$ neon plugins --agent cursor --agent vscode
WARNING: Skipping VS Code: plugins are user-level. Pass --global.
Plugins
Scope    Plugin         Agent   Status
project  neon-postgres  cursor  installed
INFO: Installed the Neon plugin (project).

When nothing else is selected, it is an error that names the way out:

$ neon plugins --agent vscode
ERROR: VS Code: plugins are user-level. Pass --global. Without --global: claude-code, claude-desktop, codex, cursor

The supported-agent list is filtered by the scope in play, so the suggestions are always installable as invoked:

$ neon plugins -y --agent eve
ERROR: Unknown agent: "eve". Supported agents: claude-code, claude-desktop, codex, cursor

$ neon plugins -y --global --agent eve
ERROR: Unknown agent: "eve". Supported agents: claude-code, claude-desktop, codex, cursor, github-copilot-cli, grok-build, vscode

--agent * gets its own message: neon plugins does not accept --agent *. Pass --agent <name> for each coding agent, or omit --agent to use detected agents. mcporter is a known MCP name with no plugins mapping, so it is skipped with a warning.

The child process

One spawn per mapped target:

npx -y plugins add neondatabase/agent-skills -t cursor -s project -y

The marketplace source is fixed in the code. Help output and the retry line are written in terms of neon plugins, so the source reaches the user only when the child prints it in its own failure dump.

The child environment drops DISABLE_TELEMETRY and DO_NOT_TRACK, so the plugins CLI records its own install while Neon records the parent command. It also drops NEON_API_KEY in any casing, since the child talks to a marketplace and has no use for a Neon credential. Both rules match neon skills, which builds its child environment the same way.

neon plugins skips Neon authentication and .neon context enrichment, the same way neon skills does. It runs in an unlinked directory and while signed out.

Failures

The table row carries a fixed plugins CLI failed. The child's output is printed once on stderr, followed by a retry line that is a neon command:

$ neon plugins -y
Plugins
Scope    Plugin         Agent   Status  Error
project  neon-postgres  cursor  failed  plugins CLI failed
ERROR: plugins CLI failed:
npm error code ECONNREFUSED
npm error errno ECONNREFUSED
npm error FetchError: request to https://registry.npmjs.org/plugins failed, reason:
[...]
Retry with: neon plugins --agent cursor -y

On a partial failure the retry names only the agents that failed. --global is carried through:

Retry with: neon plugins --agent claude-code -y
Retry with: neon plugins --agent cursor --global -y

A missing npx gets its own message and names the fix:

ERROR: neon plugins needs npx (Node.js) to run the plugins CLI. Install Node.js, then retry.

Timeout

Each target gets 120 seconds.

npx leaves the plugins CLI running as a grandchild. execa's own timeout signals npx and then keeps waiting on the stdio pipe, which the grandchild still holds, so a hang there never settles and the bound does nothing. The child is therefore spawned detached and the timer kills the whole process group (taskkill /pid <pid> /T /F on Windows). Detached also turns off execa's parent-exit cleanup, so Ctrl-C and SIGTERM kill that same tree before the CLI exits.

A child that printed a banner before it hung leaves output behind. That output on its own would read as an ordinary failure, so the timeout wins the message and the child's text follows it:

plugins CLI timed out after 120 seconds:
<whatever the child printed>

With nothing printed, the message is plugins CLI timed out after 120 seconds.

Also in here

  • isPluginsCommand in context.ts, used by the auth middleware and by enrichFromContext to skip both for plugins.
  • plugins added to NO_SUBCOMMANDS_VERBS in index.ts, so the help-fallback middleware lets a bare neon plugins run its handler instead of printing help.
  • README section for the command, plus its row in the command table.
  • Changeset: minor for neon and neonctl.

Verification

Built with pnpm exec tsdown in packages/cli, over base d0101bf.

Live install with the built CLI and real npx, into an isolated HOME and a scratch project containing .cursor. installed_plugins.json under that HOME records neon-postgres@neon version 1.1.2 with "scope": "project". known_marketplaces.json records the neon marketplace pointing at neondatabase/agent-skills. Every terminal excerpt above is copied from runs of that build.

pnpm exec vitest run src/plugins src/commands/plugins.test.ts src/context.test.ts: 84 tests across 6 files, all passing. What they cover:

  • -y installs into agents detected from project-folder markers, with argv exactly -y plugins add neondatabase/agent-skills -t cursor -s project -y
  • the child environment has no DISABLE_TELEMETRY, no DO_NOT_TRACK and no NEON_API_KEY, in any casing
  • --agent installs without -y; a non-TTY run with neither flag spawns nothing at all
  • claude-code and claude-desktop collapse to one spawn with both names in the row
  • unknown agent names and --agent * are rejected, with a supported list that follows the scope
  • --plugin and plugins update are rejected before anything spawns
  • auth and .neon enrichment are skipped even with a .neon file present
  • --global sends -s user; --agent vscode needs it and fails without spawning when it is absent
  • a failing child keeps its dump out of the table and prints exactly one retry line and one copy of the dump
  • a partial failure names only the failed agents in the retry; a --global failure keeps --global
  • a silent child failure prints neither the npx argv nor execa's Command failed with exit code
  • a missing npx is named
  • --help shows --agent and --global, without --plugin, plugins update, the marketplace source or -s user / -s project
  • the timeout kills a fake npx that spawned a grandchild holding the pipe: with a 400 ms bound the run rejects with timed out after 1 second in under 4 seconds

Not verified:

  • Windows. The taskkill /T /F branch of the process-group kill has no test. The Cursor extensions install path is the plugins CLI's behavior rather than something this branch controls.
  • A real interactive session. The confirm summary is covered by a unit test on its text and the agent picker by an injected fake in the plan test. Neither drives a terminal.

For your attention

  • The marketplace source is hardcoded. Pointing neon plugins at a different marketplace is a code change, with no flag or env override.
  • The command shells out to npx, so the first run downloads the plugins package. On a machine without Node.js it fails with the named error and installs nothing.
  • 120 seconds per target is fixed, with no flag to raise it. A slow network on a cold npx cache spends part of that budget before the plugins CLI starts.
  • The skip-and-continue path for user-level-only agents exits 0. A user who runs neon plugins -y in a project with VS Code and Cursor gets a warning about VS Code and a success line about Cursor.
  • There is no update. Reinstalling means running the command again; the marketplace entry is written with autoUpdate: true, which the plugins CLI owns.
  • neon skills and neon plugins install overlapping content through different mechanisms. Nothing in this branch reconciles them or warns when both are installed.

Use project/user in the table and summary, list only agents that can install at the current scope, and name a silent timeout instead of leaking the child argv.
Argv assertions use the GitHub shorthand rather than the production constant, so a source change fails the suite. The README notes Windows Cursor uses extensions instead of ~/.claude/plugins.
Kill the npx process group so a hung plugins grandchild cannot outlive the 120 second bound, and say so even when the child already printed a banner. Deduped Claude targets list every requested agent in the table.
detached disables execa's parent-exit cleanup, so SIGINT and SIGTERM kill the process group before the CLI exits. The timeout test's grandchild now inherits stdio, which is the hang that used to outlive execa's timeout.
@andrelandgraf
andrelandgraf merged commit bdb0db6 into main Aug 25, 2026
18 checks passed
@andrelandgraf
andrelandgraf deleted the feat/neon-plugins branch August 25, 2026 17:56
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant