neon 4.10.1 → 4.11.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 +33 -22
- package/dist/commands/bootstrap.js +201 -89
- package/dist/commands/checkout.js +0 -1
- package/dist/commands/init.js +45 -15
- package/dist/commands/link.js +2 -10
- package/dist/commands/mcp.js +4 -4
- package/dist/commands/plugins.js +4 -4
- package/dist/commands/skills.js +12 -9
- package/dist/errors.js +2 -1
- package/dist/index.js +17 -2
- package/dist/init/chrome.js +44 -0
- package/dist/init/detect_host.js +38 -0
- package/dist/init/plan.js +204 -5
- package/dist/init/tooling.js +48 -12
- package/dist/init/wizard.js +2 -1
- package/dist/mcp/plan.js +16 -3
- package/dist/plugins/plan.js +18 -4
- package/dist/plugins/targets.js +1 -1
- package/dist/skills/catalog.js +0 -5
- package/dist/skills/plan.js +19 -5
- package/dist/skills/targets.js +1 -1
- package/dist/skills/wizard.js +1 -1
- package/dist/utils/agent_flag.js +13 -0
- package/dist/utils/agent_picker.js +2 -2
- package/package.json +3 -3
package/README.md
CHANGED
|
@@ -683,7 +683,7 @@ When a package cannot be bundled — a native addon with no esbuild loader, or a
|
|
|
683
683
|
|
|
684
684
|
`neon bootstrap` copies a Neon starter template into a new (or current) directory — conceptually like `degit`, but it only pulls from a small set of templates we maintain in the public [`neondatabase/examples`](https://github.com/neondatabase/examples) repo. The template copy needs no Neon login: it downloads files from GitHub.
|
|
685
685
|
|
|
686
|
-
After scaffolding, an interactive terminal
|
|
686
|
+
After scaffolding, an interactive terminal asks about dependency install, git, agent tooling (the Neon plugin, or skills and MCP separately — never both), and `neon link` before running those steps. Dependency install is last, except when the template has a `neon.ts` and you chose to link — then install runs first so link can pull env. `--default` / `-y` skips the template, install, git, and agent pickers, then installs agent tooling for project folders, else the host CLI agent. If none are found, it exits: pass `--agent <name>`, run from a supported agent, or omit `--default` / `-y` in a terminal to pick. `--agent` / `-a` names coding agents, skips agent selection, and is forwarded to `plugins`, or to `skills` and `mcp`, not both. `link --yes` still asks for a project unless one is already linked. `--no-agent-setup` and `--no-link` skip those. Non-interactive without `--default` prints next steps and does not install, set up agents, or link.
|
|
687
687
|
|
|
688
688
|
Pass a target directory (or `.` for the current one). In an interactive terminal you pick the template from a list; in CI / non-interactive contexts pass `--template <id>`.
|
|
689
689
|
|
|
@@ -694,6 +694,9 @@ $ neon bootstrap my-app
|
|
|
694
694
|
# Scaffold a specific template into the current directory (skips the template picker)
|
|
695
695
|
$ neon bootstrap . --template hono
|
|
696
696
|
|
|
697
|
+
# Skip agent selection; install the plugin for those agents
|
|
698
|
+
$ neon bootstrap my-app --agent cursor --agent claude-code
|
|
699
|
+
|
|
697
700
|
# List templates
|
|
698
701
|
$ neon bootstrap --list-templates
|
|
699
702
|
|
|
@@ -707,7 +710,7 @@ The target directory must be empty unless you pass `--force` (a lone `.git` is i
|
|
|
707
710
|
|
|
708
711
|
`neon init` sets up this directory for Neon.
|
|
709
712
|
|
|
710
|
-
An empty directory (nothing except `.git`) runs `neon bootstrap .` and stops. With `-y` that is `neon bootstrap . --default`. Bootstrap handles scaffolding, agent tooling, and linking.
|
|
713
|
+
An empty directory (nothing except `.git`) runs `neon bootstrap .` and stops. With `-y` that is `neon bootstrap . --default`. Bootstrap handles scaffolding, agent tooling, and linking. Interactive bootstrap prints a NEON banner, asks every setup question, then runs the work — dependency install last, except when a `neon.ts` needs deps before `link`.
|
|
711
714
|
|
|
712
715
|
An existing app installs agent tooling, then `neon link` unless `.neon` already has a projectId, then `neon config init`. Interactive `config init` opens the services picker; `-y` uses `--services none` (starter policy).
|
|
713
716
|
|
|
@@ -716,11 +719,16 @@ In an interactive terminal it offers one of: the Neon plugin (`neon plugins`), s
|
|
|
716
719
|
```bash
|
|
717
720
|
$ neon init
|
|
718
721
|
$ neon init -y
|
|
722
|
+
$ neon init --agent cursor --agent claude-code
|
|
719
723
|
```
|
|
720
724
|
|
|
721
|
-
|
|
725
|
+
Without a TTY, pass `-y`. `--agent` skips agent selection but does not replace `-y` for link or templates.
|
|
726
|
+
|
|
727
|
+
`-y` skips the template picker and the agent-setup offer. Empty dir: `bootstrap --default`. Existing app: plugin when Cursor, Claude Code, or Codex is in project folders, else the host CLI agent; otherwise skills and MCP. If none are found, it exits: pass `--agent <name>`, run from a supported agent, or omit `-y` in a terminal to pick. VS Code, GitHub Copilot CLI, and Grok only take the plugin user-level (`neon plugins --global`), so `-y` uses skills and MCP for those.
|
|
728
|
+
|
|
729
|
+
`--agent` / `-a` (repeatable) names coding agents and skips agent selection, interactive or with `-y`. Init forwards those names to `plugins`, or to `skills` and `mcp`, not both.
|
|
722
730
|
|
|
723
|
-
`-y` forwards `-y` to `plugins` or `skills`/`mcp`, `--default` to `bootstrap`, `--yes` to `link`, and `--services none` to `config init`. `link --yes` only skips the "already linked" confirmation; it still asks for a project unless one is already linked.
|
|
731
|
+
`-y` forwards `-y` to `plugins` or `skills`/`mcp`, `--default` to `bootstrap`, `--yes` to `link`, and `--services none` to `config init`. `--agent` is forwarded with them. `mcp -y` is the global install. `link --yes` only skips the "already linked" confirmation; it still asks for a project unless one is already linked.
|
|
724
732
|
|
|
725
733
|
A failed step stops the rest. `--profile` and `--config-dir` are forwarded to each child. `--output json` and `--output yaml` are refused; the commands init runs print their own output.
|
|
726
734
|
|
|
@@ -734,17 +742,18 @@ A failed step stops the rest. `--profile` and `--config-dir` are forwarded to ea
|
|
|
734
742
|
# Interactive: global or project, then agents, then API key or OAuth, then confirm.
|
|
735
743
|
$ neon mcp
|
|
736
744
|
|
|
737
|
-
# Skip prompts. Global config,
|
|
745
|
+
# Skip prompts. Global config, installed apps else the host CLI agent, reuse or mint an API key.
|
|
738
746
|
$ neon mcp -y
|
|
739
747
|
|
|
740
748
|
# OAuth: no API key minted. The agent prompts for Neon sign-in on first use.
|
|
741
749
|
$ neon mcp --oauth
|
|
742
750
|
|
|
751
|
+
# Named agents.
|
|
752
|
+
$ neon mcp --agent cursor --agent claude-code
|
|
753
|
+
|
|
743
754
|
# Project-level config. A minted key is still account-wide unless a project is pinned.
|
|
744
755
|
$ neon mcp --project
|
|
745
756
|
|
|
746
|
-
$ neon mcp --agent cursor --agent claude-code
|
|
747
|
-
|
|
748
757
|
# Hide write tools. Does not change the minted key.
|
|
749
758
|
$ neon mcp --read-only
|
|
750
759
|
|
|
@@ -757,9 +766,9 @@ $ neon mcp --category querying --category schema
|
|
|
757
766
|
|
|
758
767
|
On a TTY the command asks for config location (global is the default), then agents, then API key vs OAuth, then a summary to confirm before it writes. Detected agents start selected: globally installed agents or project-folder markers such as `.cursor` when the install is project.
|
|
759
768
|
|
|
760
|
-
`-y` skips those questions. `neon mcp -y` writes `https://mcp.neon.tech/mcp` into global config for
|
|
769
|
+
`-y` skips those questions. `neon mcp -y` writes `https://mcp.neon.tech/mcp` into global config for globally installed apps, else the host CLI agent, reuses an existing Neon MCP API key or mints an account-wide key, leaves write tools enabled, exposes every tool category, and does not pin a project (including from `.neon`). `--agent`, `--project`, `--oauth`, `--read-only`, `--project-id` and `--category` still apply with `-y`. `--read-only` and `--category` are flags only and are never prompted. A linked project-folder install asks whether to pin MCP tools to that `.neon` project (`?projectId=`). If you pin and selected API-key auth, the minted key is limited to that project too. An unlinked project folder does not ask. Global installs never add that param unless you pass `--project-id`. Without a TTY, pass `-y` to mint into every detected agent, `--agent <name>` to name them, or `--oauth` to write the URL only. If `-y` finds no agent, it exits: pass `--agent <name>`, run from a supported agent, or omit `-y` in a terminal to pick. `neon mcp --help` lists the server URL, those `-y` defaults, the supported agent names, and the `--category` values.
|
|
761
770
|
|
|
762
|
-
|
|
771
|
+
Supported agents: `antigravity`, `cline`, `cline-cli`, `claude-code`, `codex`, `cursor`, `gemini-cli`, `goose`, `github-copilot-cli`, `grok-build`, `mcporter`, `opencode`, `vscode`, `windsurf`, `zed`. Project installs drop `antigravity`, `cline`, `cline-cli`, `goose` and `windsurf`. `claude-desktop` is a known name that is then skipped.
|
|
763
772
|
|
|
764
773
|
The default mints an account-wide API key (or reuses the Bearer already configured for Neon at `https://mcp.neon.tech/mcp`) and writes it into each selected agent's config. That key reaches everything the account can, in every organization. Revoke it with `neon api-keys revoke <id>`. `--oauth` writes the URL with no `Authorization` header; the agent signs in on first use. `--project` writes into the project config (`.cursor/mcp.json` and similar). `--read-only` adds `?readonly=true`. `--project-id` adds `?projectId=` and, when a key is minted, limits that key to the named project. Accepting the linked-project pin does the same. Revoke a project-scoped key with `neon api-keys revoke <id> --org-id <org>`. A reused Bearer keeps the scope it already has. `--category` adds `?category=` (repeatable or comma-separated: `projects`, `branches`, `schema`, `querying`, `neon_auth`, `data_api`, `observability`, `docs`). `--read-only` and `--category` restrict which MCP tools the server exposes; they do not change what the minted key can do.
|
|
765
774
|
|
|
@@ -771,12 +780,14 @@ The default mints an account-wide API key (or reuses the Bearer already configur
|
|
|
771
780
|
# Interactive: this directory, then agents, then skills, then confirm.
|
|
772
781
|
$ neon skills
|
|
773
782
|
|
|
774
|
-
# Skip prompts. This directory,
|
|
783
|
+
# Skip prompts. This directory, detected agents (project folders, else the host CLI agent), the default skills.
|
|
775
784
|
$ neon skills -y
|
|
776
785
|
|
|
777
|
-
|
|
786
|
+
# Named skills into detected agents.
|
|
787
|
+
$ neon skills -y -s neon -s neon-ai-gateway
|
|
778
788
|
|
|
779
|
-
|
|
789
|
+
# Named skills into a named agent.
|
|
790
|
+
$ neon skills -s neon -s neon-ai-gateway --agent cursor
|
|
780
791
|
|
|
781
792
|
# User-level skills.
|
|
782
793
|
$ neon skills --global
|
|
@@ -789,11 +800,11 @@ $ neon skills update --global -y
|
|
|
789
800
|
|
|
790
801
|
On a TTY the command asks which agents and which skills, then shows a summary to confirm. Detected agents start selected from project-folder markers such as `.cursor`. Default skills start selected. `neon-postgres-agent-platforms` is offered and starts unselected.
|
|
791
802
|
|
|
792
|
-
`-y` skips those questions and installs the default skills. `--skill` / `-s` names specific skills and skips the skill picker
|
|
803
|
+
`-y` skips those questions and installs the default skills into detected agents: project-folder markers such as `.cursor`, else the agent driving the CLI. `--agent` / `-a` names coding agents and skips the agent picker. `--skill` / `-s` names specific skills and skips the skill picker; it does not select agents. `--global -y` uses installed apps, else the host CLI agent. Without a TTY, pass `-y`, or `--skill <name>` (add `--agent <name>` to name agents). If `-y` finds no agent, it exits: pass `--agent <name>`, run from a supported agent, or omit `-y` in a terminal to pick.
|
|
793
804
|
|
|
794
|
-
`--skill` names by source repo: `neondatabase/agent-skills` (`
|
|
805
|
+
`--skill` names by source repo: `neondatabase/agent-skills` (`neon`, `neon-ai-gateway`, `neon-functions`, `neon-object-storage`, `neon-postgres`, `neon-postgres-branches`, `neon-postgres-egress-optimizer`); `neondatabase/neon-for-agent-platforms` (`neon-postgres-agent-platforms`).
|
|
795
806
|
|
|
796
|
-
|
|
807
|
+
Supported agents 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. `neon skills --help` lists the same skill and agent values, and that `-y` leaves out `neon-postgres-agent-platforms`.
|
|
797
808
|
|
|
798
809
|
## Install the Neon plugin (`plugins`)
|
|
799
810
|
|
|
@@ -803,27 +814,27 @@ On a TTY the command asks which agents and which skills, then shows a summary to
|
|
|
803
814
|
# Interactive: agents, then confirm.
|
|
804
815
|
$ neon plugins
|
|
805
816
|
|
|
806
|
-
# Skip prompts.
|
|
817
|
+
# Skip prompts. Detected agents (project folders, else the host CLI agent).
|
|
807
818
|
$ neon plugins -y
|
|
808
819
|
|
|
820
|
+
# Named agents.
|
|
809
821
|
$ neon plugins --agent cursor --agent claude-code
|
|
810
822
|
|
|
811
823
|
# User-level install.
|
|
812
824
|
$ neon plugins --global
|
|
813
|
-
$ neon plugins --global --agent vscode
|
|
814
825
|
```
|
|
815
826
|
|
|
816
827
|
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.
|
|
817
828
|
|
|
818
|
-
`-y` skips those questions and installs into
|
|
829
|
+
`-y` skips those questions and installs into detected agents: project-folder markers such as `.cursor`, else the agent driving the CLI. `--agent` / `-a` names coding agents and skips the agent picker. `--global -y` uses installed apps, else the host CLI agent. Without a TTY, pass `-y` or `--agent <name>`. If `-y` finds no agent, it exits: pass `--agent <name>`, run from a supported agent, or omit `-y` in a terminal to pick.
|
|
819
830
|
|
|
820
|
-
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
|
|
831
|
+
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 a VS Code-only project fails if nothing else is selected. Pass `--global` for those.
|
|
821
832
|
|
|
822
|
-
|
|
833
|
+
Supported agents with a plugins mapping: `claude-code`, `claude-desktop`, `codex`, `cursor`, `github-copilot-cli`, `grok-build`, `vscode`. `claude-desktop` installs as Claude Code; detecting both produces one install and lists both names in the table. `mcporter` is a known MCP name that is then skipped.
|
|
823
834
|
|
|
824
|
-
The plugins CLI installs every plugin it finds in the Neon plugin package. Today that is `neon-postgres` from `neondatabase/agent-skills`. It includes the Neon MCP server (`https://mcp.neon.tech/mcp`) and these skills: `neon`, `neon-ai-gateway`, `neon-functions`, `neon-object-storage`, `neon-postgres`, `neon-postgres-branches`, `neon-postgres-egress-optimizer`. It does not include `
|
|
835
|
+
The plugins CLI installs every plugin it finds in the Neon plugin package. Today that is `neon-postgres` from `neondatabase/agent-skills`. It includes the Neon MCP server (`https://mcp.neon.tech/mcp`) and these skills: `neon`, `neon-ai-gateway`, `neon-functions`, `neon-object-storage`, `neon-postgres`, `neon-postgres-branches`, `neon-postgres-egress-optimizer`. It does not include `neon-postgres-agent-platforms`.
|
|
825
836
|
|
|
826
|
-
`neon plugins --help` lists the plugin, those contents, and the supported
|
|
837
|
+
`neon plugins --help` lists the plugin, those contents, and the supported agent names.
|
|
827
838
|
|
|
828
839
|
## Snapshots (`snapshots`)
|
|
829
840
|
|
|
@@ -6,8 +6,12 @@ import { t as credentialInputs } from "../_chunks/auth_selection-pitMFzDe.js";
|
|
|
6
6
|
import { writer } from "../writer.js";
|
|
7
7
|
import { FALLBACK_TEMPLATES, ensureTargetUsable, fetchTemplates, findTemplate, scaffoldTemplate, templateIds } from "../init/bootstrap.js";
|
|
8
8
|
import { spawnCliChild } from "../init/child.js";
|
|
9
|
-
import {
|
|
10
|
-
import {
|
|
9
|
+
import { agentSetupDoneLabel, formatInitDone, printInitBanner, printInitDone, shouldPrintInitBanner } from "../init/chrome.js";
|
|
10
|
+
import { coerceAgentFlag } from "../utils/agent_flag.js";
|
|
11
|
+
import { assertNamedAgentTooling, chooseYesAgentTooling, initPluginAgents, initSkillsMcpAgents, postScaffoldActions, projectContextFile, resolveNamedAgents } from "../init/plan.js";
|
|
12
|
+
import { pickAgentSetupInteractively } from "../init/wizard.js";
|
|
13
|
+
import { runAgentTooling, runInitSteps } from "../init/tooling.js";
|
|
14
|
+
import { helpCsv, helpEpilogue } from "../utils/help_text.js";
|
|
11
15
|
import { formatInstallCommand, inferPackageManager, installArgs, installedPackageManagers, resolvePackageManager, runCommand } from "../utils/package_manager.js";
|
|
12
16
|
import { existsSync } from "node:fs";
|
|
13
17
|
import { join, relative, resolve } from "node:path";
|
|
@@ -20,7 +24,6 @@ var bootstrap_exports = /* @__PURE__ */ __exportAll({
|
|
|
20
24
|
describe: () => describe,
|
|
21
25
|
handler: () => handler
|
|
22
26
|
});
|
|
23
|
-
const removedAgent = () => `\`${getCliName()} bootstrap --agent\` was removed. List templates with \`${getCliName()} bootstrap --list-templates --output json\`. Scaffold with \`${getCliName()} bootstrap <directory> --template <id>\` or \`${getCliName()} bootstrap <directory> --default\`.`;
|
|
24
27
|
const command = "bootstrap [directory]";
|
|
25
28
|
const describe = "Scaffold a new project from a Neon starter template, then install agent tooling and link a Neon project";
|
|
26
29
|
const builder = (argv) => argv.usage("$0 bootstrap [directory] [options]").positional("directory", {
|
|
@@ -42,13 +45,9 @@ const builder = (argv) => argv.usage("$0 bootstrap [directory] [options]").posit
|
|
|
42
45
|
type: "boolean",
|
|
43
46
|
default: false
|
|
44
47
|
},
|
|
45
|
-
agent: {
|
|
46
|
-
hidden: true,
|
|
47
|
-
type: "boolean"
|
|
48
|
-
},
|
|
49
48
|
default: {
|
|
50
49
|
alias: "y",
|
|
51
|
-
describe: "Quick start: scaffold the default template (or --template), then install, git, agent tooling, and link --yes. Skips those pickers; link --yes still asks for a project unless one is already linked",
|
|
50
|
+
describe: "Quick start: scaffold the default template (or --template), then install, git, agent tooling (project folders, else the host CLI agent; if none, pass --agent or omit --default in a terminal), and link --yes. Skips those pickers; link --yes still asks for a project unless one is already linked",
|
|
52
51
|
type: "boolean",
|
|
53
52
|
default: false
|
|
54
53
|
},
|
|
@@ -63,7 +62,7 @@ const builder = (argv) => argv.usage("$0 bootstrap [directory] [options]").posit
|
|
|
63
62
|
default: true
|
|
64
63
|
},
|
|
65
64
|
link: {
|
|
66
|
-
describe: `Run \`${getCliName()} link\`
|
|
65
|
+
describe: `Run \`${getCliName()} link\` after scaffolding. Templates with neon.ts link after install so env pull works; otherwise link runs before install. In interactive mode this is offered as a prompt; use --no-link to skip without being asked.`,
|
|
67
66
|
type: "boolean",
|
|
68
67
|
default: true
|
|
69
68
|
},
|
|
@@ -71,11 +70,15 @@ const builder = (argv) => argv.usage("$0 bootstrap [directory] [options]").posit
|
|
|
71
70
|
type: "boolean",
|
|
72
71
|
default: true,
|
|
73
72
|
describe: "After scaffolding, install the Neon plugin or skills and MCP. Use --no-agent-setup to skip"
|
|
73
|
+
},
|
|
74
|
+
agent: {
|
|
75
|
+
alias: "a",
|
|
76
|
+
type: "array",
|
|
77
|
+
string: true,
|
|
78
|
+
describe: "Coding agent to install into (repeatable). Forwarded to plugins, or to skills and mcp. Skips agent selection. Values listed below",
|
|
79
|
+
coerce: coerceAgentFlag
|
|
74
80
|
}
|
|
75
|
-
}).example("$0 bootstrap my-app", "Create ./my-app from an interactively chosen template").example("$0 bootstrap . --template hono", "Scaffold the Hono template into the current directory").example("$0 bootstrap my-app --default", "Skip the pickers; link --yes still asks for a project unless one is already linked").example("$0 bootstrap --list-templates --output json", "Print the template catalog as JSON").
|
|
76
|
-
if (argv.agent === true) throw new Error(removedAgent());
|
|
77
|
-
return true;
|
|
78
|
-
}).strict();
|
|
81
|
+
}).example("$0 bootstrap my-app", "Create ./my-app from an interactively chosen template").example("$0 bootstrap . --template hono", "Scaffold the Hono template into the current directory").example("$0 bootstrap my-app --default", "Skip the pickers; link --yes still asks for a project unless one is already linked").example("$0 bootstrap --list-templates --output json", "Print the template catalog as JSON").example("$0 bootstrap my-app --agent cursor --agent claude-code", "Skip agent selection; install the plugin for those agents").epilogue(helpEpilogue("--agent / -a is forwarded to plugins, or to skills and mcp, not both. It skips agent selection, including with --default.", helpCsv("Plugin agents", initPluginAgents()), helpCsv("Skills and MCP agents", initSkillsMcpAgents()))).strict();
|
|
79
82
|
const handler = async (props) => {
|
|
80
83
|
if (props.listTemplates) {
|
|
81
84
|
const templates = await fetchTemplates();
|
|
@@ -99,6 +102,12 @@ const handler = async (props) => {
|
|
|
99
102
|
}
|
|
100
103
|
return;
|
|
101
104
|
}
|
|
105
|
+
if (shouldPrintInitBanner(props.default)) printInitBanner();
|
|
106
|
+
const named = resolveNamedAgents(props.agent ?? []);
|
|
107
|
+
if (props.agentSetup !== false) assertNamedAgentTooling(named, "bootstrap", {
|
|
108
|
+
...props.directory !== void 0 && props.directory.length > 0 ? { directory: props.directory } : {},
|
|
109
|
+
...props.default ? { yes: true } : {}
|
|
110
|
+
});
|
|
102
111
|
const templates = await resolveTemplateList(props);
|
|
103
112
|
const interactive = !props.default && Boolean(process.stdout.isTTY) && !isCi();
|
|
104
113
|
const template = await resolveSelectedTemplate(props, interactive, templates);
|
|
@@ -106,7 +115,7 @@ const handler = async (props) => {
|
|
|
106
115
|
ensureTargetUsable(targetDir, props.force);
|
|
107
116
|
await scaffold(template, targetDir);
|
|
108
117
|
printScaffolded(template, targetDir);
|
|
109
|
-
await runPostScaffoldSteps(props, targetDir, interactive);
|
|
118
|
+
await runPostScaffoldSteps(props, targetDir, interactive, template, named);
|
|
110
119
|
};
|
|
111
120
|
/**
|
|
112
121
|
* The template list to choose from. When --template is given we try the
|
|
@@ -179,83 +188,155 @@ const scaffold = async (template, targetDir) => {
|
|
|
179
188
|
log.info("Scaffolded %d files into %s.", filesWritten, targetDir);
|
|
180
189
|
return filesWritten;
|
|
181
190
|
};
|
|
182
|
-
const runPostScaffoldSteps = async (props, targetDir, interactive) => {
|
|
191
|
+
const runPostScaffoldSteps = async (props, targetDir, interactive, template, named) => {
|
|
183
192
|
const inferred = inferPackageManager(targetDir);
|
|
184
193
|
const defaultPm = resolvePackageManager(targetDir);
|
|
194
|
+
const neonConfig = hasNeonConfig(targetDir);
|
|
185
195
|
if (props.default) {
|
|
186
|
-
await runDefaultSteps(props, targetDir, defaultPm);
|
|
196
|
+
await runDefaultSteps(props, targetDir, defaultPm, neonConfig, template, named);
|
|
187
197
|
return;
|
|
188
198
|
}
|
|
189
199
|
if (!interactive) {
|
|
190
|
-
|
|
200
|
+
printDoneSummary({
|
|
201
|
+
heading: "Project scaffolded.",
|
|
202
|
+
template,
|
|
203
|
+
targetDir,
|
|
204
|
+
pm: defaultPm,
|
|
191
205
|
installed: false,
|
|
192
|
-
|
|
206
|
+
installFailed: false,
|
|
207
|
+
gitFailed: false,
|
|
208
|
+
git: false,
|
|
209
|
+
agentSetup: "skip",
|
|
210
|
+
linked: false,
|
|
211
|
+
skippedLinkForDeps: false,
|
|
212
|
+
suggestLink: true,
|
|
213
|
+
agentsRan: false
|
|
193
214
|
});
|
|
194
215
|
return;
|
|
195
216
|
}
|
|
196
217
|
let pm = defaultPm;
|
|
197
|
-
|
|
198
|
-
if (
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
if (props.
|
|
203
|
-
const
|
|
204
|
-
|
|
205
|
-
const kids = bootstrapChildren(props, targetDir);
|
|
206
|
-
await runScaffoldFollowUp({
|
|
207
|
-
cwd: targetDir,
|
|
218
|
+
const wantInstall = props.install && await confirm(installPrompt(inferred));
|
|
219
|
+
if (wantInstall) pm = inferred ?? await selectPackageManager();
|
|
220
|
+
const wantGit = props.git && !isGitRepo(targetDir) && await confirm("Initialize a git repository?");
|
|
221
|
+
const agentSetup = props.agentSetup === false ? "skip" : named.length > 0 ? chooseYesAgentTooling(named).setup : await (props.pickAgentSetup ?? pickAgentSetupInteractively)();
|
|
222
|
+
const canLink = props.link && !(neonConfig && !wantInstall);
|
|
223
|
+
if (props.link && !canLink) logSkippedLink(pm);
|
|
224
|
+
const wantLink = canLink && await confirm(`Link this project to a Neon project now? (runs ${getCliName()} link)`);
|
|
225
|
+
const outcome = await executePostScaffold(props, targetDir, {
|
|
208
226
|
yes: false,
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
227
|
+
lockAgentSetup: named.length === 0,
|
|
228
|
+
pm,
|
|
229
|
+
git: wantGit,
|
|
230
|
+
agentSetup,
|
|
231
|
+
install: wantInstall,
|
|
232
|
+
link: wantLink,
|
|
233
|
+
hasNeonConfig: neonConfig,
|
|
234
|
+
named
|
|
215
235
|
});
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
...kids
|
|
224
|
-
});
|
|
225
|
-
printNextSteps(targetDir, pm, {
|
|
226
|
-
installed,
|
|
227
|
-
suggestLink: false
|
|
228
|
-
});
|
|
229
|
-
return;
|
|
230
|
-
}
|
|
231
|
-
printNextSteps(targetDir, pm, {
|
|
232
|
-
installed,
|
|
233
|
-
suggestLink: true
|
|
236
|
+
finishPostScaffold({
|
|
237
|
+
heading: "Project scaffolded.",
|
|
238
|
+
template,
|
|
239
|
+
targetDir,
|
|
240
|
+
pm,
|
|
241
|
+
...outcome,
|
|
242
|
+
suggestLink: !outcome.linked
|
|
234
243
|
});
|
|
235
244
|
};
|
|
236
245
|
const installPrompt = (inferred) => inferred ? `Install dependencies with ${inferred}?` : "Install dependencies?";
|
|
237
246
|
/** `link --yes` still asks for a project unless one is already linked. */
|
|
238
|
-
const runDefaultSteps = async (props, targetDir, pm) => {
|
|
247
|
+
const runDefaultSteps = async (props, targetDir, pm, neonConfig, template, named) => {
|
|
239
248
|
log.info("Quick start (--default): skipping the template, install, git, and agent pickers. link --yes still asks for a project unless one is already linked.");
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
const skipLink = shouldSkipLinkForDeps(targetDir, installed);
|
|
244
|
-
if (props.link && skipLink) logSkippedLink(pm);
|
|
245
|
-
await runScaffoldFollowUp({
|
|
246
|
-
cwd: targetDir,
|
|
249
|
+
const wantGit = props.git && !isGitRepo(targetDir);
|
|
250
|
+
const agentSetup = props.agentSetup === false ? "skip" : named.length > 0 ? chooseYesAgentTooling(named).setup : "skills-mcp";
|
|
251
|
+
const outcome = await executePostScaffold(props, targetDir, {
|
|
247
252
|
yes: true,
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
253
|
+
lockAgentSetup: false,
|
|
254
|
+
pm,
|
|
255
|
+
git: wantGit,
|
|
256
|
+
agentSetup,
|
|
257
|
+
install: props.install,
|
|
258
|
+
link: props.link,
|
|
259
|
+
hasNeonConfig: neonConfig,
|
|
260
|
+
named
|
|
254
261
|
});
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
262
|
+
finishPostScaffold({
|
|
263
|
+
heading: "Project scaffolded.",
|
|
264
|
+
template,
|
|
265
|
+
targetDir,
|
|
266
|
+
pm,
|
|
267
|
+
...outcome,
|
|
268
|
+
suggestLink: !outcome.linked
|
|
269
|
+
});
|
|
270
|
+
};
|
|
271
|
+
const executePostScaffold = async (props, targetDir, choices) => {
|
|
272
|
+
const kids = bootstrapChildren(props, targetDir);
|
|
273
|
+
let installed = false;
|
|
274
|
+
let installFailed = false;
|
|
275
|
+
let gitFailed = false;
|
|
276
|
+
let git = false;
|
|
277
|
+
let linked = false;
|
|
278
|
+
let skippedLinkForDeps = false;
|
|
279
|
+
let agentSetup = choices.agentSetup;
|
|
280
|
+
let agentsRan = false;
|
|
281
|
+
const actions = postScaffoldActions({
|
|
282
|
+
git: choices.git,
|
|
283
|
+
agentSetup: choices.agentSetup,
|
|
284
|
+
install: choices.install,
|
|
285
|
+
link: choices.link,
|
|
286
|
+
hasNeonConfig: choices.hasNeonConfig
|
|
258
287
|
});
|
|
288
|
+
for (const action of actions) {
|
|
289
|
+
if (action === "git") {
|
|
290
|
+
git = await initGitRepo(targetDir);
|
|
291
|
+
if (!git) {
|
|
292
|
+
gitFailed = true;
|
|
293
|
+
break;
|
|
294
|
+
}
|
|
295
|
+
continue;
|
|
296
|
+
}
|
|
297
|
+
if (action === "agent") {
|
|
298
|
+
agentSetup = await runAgentTooling({
|
|
299
|
+
cwd: targetDir,
|
|
300
|
+
yes: choices.yes,
|
|
301
|
+
...choices.lockAgentSetup ? { agentSetup: choices.agentSetup } : {},
|
|
302
|
+
...kids,
|
|
303
|
+
...choices.named.length > 0 ? { agents: choices.named } : {},
|
|
304
|
+
...props.pickAgentSetup ? { pickAgentSetup: props.pickAgentSetup } : {},
|
|
305
|
+
...props.hasProjectPlugins ? { hasProjectPlugins: props.hasProjectPlugins } : {},
|
|
306
|
+
...props.detectProjectAgents ? { detectProjectAgents: props.detectProjectAgents } : {},
|
|
307
|
+
...props.detectAgent ? { detectAgent: props.detectAgent } : {},
|
|
308
|
+
command: "bootstrap"
|
|
309
|
+
});
|
|
310
|
+
agentsRan = true;
|
|
311
|
+
continue;
|
|
312
|
+
}
|
|
313
|
+
if (action === "install") {
|
|
314
|
+
installed = await runCommand(choices.pm, installArgs(choices.pm), targetDir);
|
|
315
|
+
if (!installed) installFailed = true;
|
|
316
|
+
continue;
|
|
317
|
+
}
|
|
318
|
+
if (action !== "link") throw new Error(`Unhandled post-scaffold action: ${action}`);
|
|
319
|
+
if (choices.hasNeonConfig && !installed) {
|
|
320
|
+
skippedLinkForDeps = true;
|
|
321
|
+
logSkippedLink(choices.pm);
|
|
322
|
+
continue;
|
|
323
|
+
}
|
|
324
|
+
await runInitSteps([choices.yes ? ["link", "--yes"] : ["link"]], {
|
|
325
|
+
cwd: targetDir,
|
|
326
|
+
...kids
|
|
327
|
+
});
|
|
328
|
+
linked = true;
|
|
329
|
+
}
|
|
330
|
+
return {
|
|
331
|
+
installed,
|
|
332
|
+
installFailed,
|
|
333
|
+
gitFailed,
|
|
334
|
+
git,
|
|
335
|
+
linked,
|
|
336
|
+
skippedLinkForDeps,
|
|
337
|
+
agentSetup,
|
|
338
|
+
agentsRan
|
|
339
|
+
};
|
|
259
340
|
};
|
|
260
341
|
const isGitRepo = (dir) => existsSync(join(dir, ".git"));
|
|
261
342
|
const NEON_CONFIG_FILENAMES = [
|
|
@@ -265,7 +346,6 @@ const NEON_CONFIG_FILENAMES = [
|
|
|
265
346
|
"neon.mjs"
|
|
266
347
|
];
|
|
267
348
|
const hasNeonConfig = (dir) => NEON_CONFIG_FILENAMES.some((name) => existsSync(join(dir, name)));
|
|
268
|
-
const shouldSkipLinkForDeps = (dir, installed) => !installed && hasNeonConfig(dir);
|
|
269
349
|
const logSkippedLink = (pm) => {
|
|
270
350
|
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\`.`);
|
|
271
351
|
};
|
|
@@ -288,9 +368,7 @@ const bootstrapChildren = (props, targetDir) => {
|
|
|
288
368
|
* deliberately don't auto-commit, both to avoid failing on a machine with no
|
|
289
369
|
* git identity configured and to leave the first commit to the user.
|
|
290
370
|
*/
|
|
291
|
-
const initGitRepo = async (dir) =>
|
|
292
|
-
await runCommand("git", ["init"], dir);
|
|
293
|
-
};
|
|
371
|
+
const initGitRepo = async (dir) => runCommand("git", ["init"], dir);
|
|
294
372
|
const confirm = async (message) => {
|
|
295
373
|
const { value } = await prompts({
|
|
296
374
|
onState: onPromptState,
|
|
@@ -324,22 +402,56 @@ const selectPackageManager = async () => {
|
|
|
324
402
|
return pm ?? "npm";
|
|
325
403
|
};
|
|
326
404
|
const printScaffolded = (template, targetDir) => {
|
|
327
|
-
log.info("");
|
|
328
|
-
log.info("Done. Scaffolded \"%s\" into %s.", template.title, isCurrentDir(targetDir) ? "the current directory" : displayDir(targetDir));
|
|
405
|
+
log.info("Scaffolded \"%s\" into %s.", template.title, isCurrentDir(targetDir) ? "the current directory" : displayDir(targetDir));
|
|
329
406
|
};
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
const
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
if (!
|
|
339
|
-
if (
|
|
340
|
-
if (
|
|
341
|
-
|
|
342
|
-
|
|
407
|
+
const printDoneSummary = (input) => {
|
|
408
|
+
const unfinished = input.installFailed || input.gitFailed;
|
|
409
|
+
const heading = unfinished ? "Setup did not finish." : input.heading;
|
|
410
|
+
const deps = input.installFailed ? "install failed" : input.installed ? `installed with ${input.pm}` : "skipped";
|
|
411
|
+
const git = input.gitFailed ? "init failed" : input.git ? "initialized" : "skipped";
|
|
412
|
+
const project = input.linked ? "linked" : input.skippedLinkForDeps ? "skipped (needs dependencies)" : "not linked";
|
|
413
|
+
const next = [];
|
|
414
|
+
if (!isCurrentDir(input.targetDir)) next.push(`cd ${displayDir(input.targetDir)}`);
|
|
415
|
+
if (!input.installed) next.push(formatInstallCommand(input.pm));
|
|
416
|
+
if (input.suggestLink) next.push(`${getCliName()} link`);
|
|
417
|
+
if (!unfinished) next.push("See the README to run it.");
|
|
418
|
+
printInitDone(formatInitDone({
|
|
419
|
+
heading,
|
|
420
|
+
rows: [
|
|
421
|
+
{
|
|
422
|
+
label: "Template",
|
|
423
|
+
value: input.template.title
|
|
424
|
+
},
|
|
425
|
+
{
|
|
426
|
+
label: "Directory",
|
|
427
|
+
value: displayDir(input.targetDir)
|
|
428
|
+
},
|
|
429
|
+
{
|
|
430
|
+
label: "Dependencies",
|
|
431
|
+
value: deps
|
|
432
|
+
},
|
|
433
|
+
{
|
|
434
|
+
label: "Git",
|
|
435
|
+
value: git
|
|
436
|
+
},
|
|
437
|
+
{
|
|
438
|
+
label: "Agents",
|
|
439
|
+
value: agentSetupDoneLabel({
|
|
440
|
+
setup: input.agentSetup,
|
|
441
|
+
ran: input.agentsRan
|
|
442
|
+
})
|
|
443
|
+
},
|
|
444
|
+
{
|
|
445
|
+
label: "Project",
|
|
446
|
+
value: project
|
|
447
|
+
}
|
|
448
|
+
],
|
|
449
|
+
next
|
|
450
|
+
}));
|
|
451
|
+
};
|
|
452
|
+
const finishPostScaffold = (input) => {
|
|
453
|
+
printDoneSummary(input);
|
|
454
|
+
if (input.installFailed || input.gitFailed) throw new Error("Setup did not finish.");
|
|
343
455
|
};
|
|
344
456
|
const isCurrentDir = (targetDir) => relative(process.cwd(), targetDir) === "";
|
|
345
457
|
/**
|
|
@@ -208,7 +208,6 @@ const resolveProjectId = async (props) => {
|
|
|
208
208
|
if (!runLink) throw new Error("Aborted: no project selected. Re-run with --project-id or link a project first.");
|
|
209
209
|
await handler$1({
|
|
210
210
|
...props,
|
|
211
|
-
agent: false,
|
|
212
211
|
yes: false,
|
|
213
212
|
clear: false,
|
|
214
213
|
checks: true
|