neon 4.7.0 → 4.8.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 +37 -59
- package/dist/_chunks/{auth_selection-ktL6uoEe.js → auth_selection-pitMFzDe.js} +1 -1
- package/dist/_chunks/{credential_io-B1JxWELN.js → credential_io-YeAxg9Xn.js} +2 -2
- package/dist/_chunks/{paths-g3DRqJiD.js → paths-DMq0Lt7a.js} +1 -1
- package/dist/_chunks/{profiles-D95AeZ5v.js → profiles-CvnFEQyd.js} +1 -1
- package/dist/analytics.js +2 -2
- package/dist/auth_context.js +1 -1
- package/dist/commands/auth.js +12 -9
- package/dist/commands/bootstrap.js +72 -43
- package/dist/commands/claim.js +1 -1
- package/dist/commands/init.js +80 -79
- package/dist/commands/link.js +78 -63
- package/dist/commands/mcp.js +1 -1
- package/dist/commands/plugins.js +4 -4
- package/dist/commands/profile.js +4 -4
- package/dist/commands/skills.js +1 -1
- package/dist/config.js +1 -1
- package/dist/credential_io.js +1 -1
- package/dist/index.js +1 -1
- package/dist/init/child.js +36 -0
- package/dist/init/plan.js +47 -0
- package/dist/init/tooling.js +43 -0
- package/dist/init/wizard.js +42 -0
- package/dist/mcp/targets.js +1 -1
- package/dist/plugins/plan.js +1 -1
- package/dist/plugins/targets.js +5 -1
- package/dist/retire_credential.js +1 -1
- package/dist/utils/middlewares.js +1 -1
- package/dist/utils/package_manager.js +10 -5
- package/package.json +13 -14
- package/dist/init/auth.js +0 -53
- package/dist/init/build_config.js +0 -4
- package/dist/init/detect_agent.js +0 -63
- package/dist/init/editors.js +0 -19
- package/dist/init/enrich_output.js +0 -61
- package/dist/init/extension.js +0 -154
- package/dist/init/inspect.js +0 -249
- package/dist/init/interactive.js +0 -529
- package/dist/init/neonctl.js +0 -139
- package/dist/init/orchestrate.js +0 -189
- package/dist/init/phases/auth.js +0 -195
- package/dist/init/phases/cleanup.js +0 -27
- package/dist/init/phases/db.js +0 -257
- package/dist/init/phases/getting_started.js +0 -218
- package/dist/init/phases/mcp.js +0 -219
- package/dist/init/phases/migrations.js +0 -250
- package/dist/init/phases/neon_auth.js +0 -116
- package/dist/init/phases/setup.js +0 -582
- package/dist/init/phases/skills.js +0 -86
- package/dist/init/phases/status.js +0 -82
- package/dist/init/profile_cli.js +0 -56
- package/dist/init/resolve_context.js +0 -105
- package/dist/init/route_command.js +0 -93
- package/dist/init/skills.js +0 -206
- package/dist/init/types.js +0 -1
- package/dist/init/vsix.js +0 -89
|
@@ -1,82 +0,0 @@
|
|
|
1
|
-
import { neonInitAgentCmd } from "../profile_cli.js";
|
|
2
|
-
import { tryResolveAddMcpAgentId } from "../../mcp/agents.js";
|
|
3
|
-
import { supportsSkills } from "../agents.js";
|
|
4
|
-
import { isAuthenticated } from "../auth.js";
|
|
5
|
-
import { inspectProject } from "../inspect.js";
|
|
6
|
-
//#region src/init/phases/status.ts
|
|
7
|
-
async function handleStatusPhase(options) {
|
|
8
|
-
const authed = await isAuthenticated();
|
|
9
|
-
const inspection = await inspectProject([
|
|
10
|
-
{
|
|
11
|
-
id: "mcp_server",
|
|
12
|
-
description: "",
|
|
13
|
-
lookFor: []
|
|
14
|
-
},
|
|
15
|
-
{
|
|
16
|
-
id: "database_url",
|
|
17
|
-
description: "",
|
|
18
|
-
lookFor: []
|
|
19
|
-
},
|
|
20
|
-
{
|
|
21
|
-
id: "skills",
|
|
22
|
-
description: "",
|
|
23
|
-
lookFor: []
|
|
24
|
-
},
|
|
25
|
-
{
|
|
26
|
-
id: "migrations",
|
|
27
|
-
description: "",
|
|
28
|
-
lookFor: []
|
|
29
|
-
}
|
|
30
|
-
]);
|
|
31
|
-
const hasDatabaseUrl = inspection.databaseUrl === true;
|
|
32
|
-
const mcpConfigured = inspection.mcpConfigured === true;
|
|
33
|
-
const skillsInstalled = inspection.skillsInstalled === true;
|
|
34
|
-
const migrationTool = inspection.migrationTool ?? null;
|
|
35
|
-
const hasMigrations = migrationTool !== null && migrationTool !== "none" && inspection.migrationDir !== "none";
|
|
36
|
-
const recommendations = [];
|
|
37
|
-
if (!authed) recommendations.push({
|
|
38
|
-
priority: "high",
|
|
39
|
-
message: "Not authenticated with Neon",
|
|
40
|
-
command: neonInitAgentCmd({ step: "auth" })
|
|
41
|
-
});
|
|
42
|
-
if (!hasDatabaseUrl) recommendations.push({
|
|
43
|
-
priority: "high",
|
|
44
|
-
message: "No DATABASE_URL found in .env",
|
|
45
|
-
command: neonInitAgentCmd({ step: "db" })
|
|
46
|
-
});
|
|
47
|
-
const statusAgent = options.agent ? tryResolveAddMcpAgentId(options.agent) : void 0;
|
|
48
|
-
if (!skillsInstalled && (!statusAgent || supportsSkills(statusAgent))) recommendations.push({
|
|
49
|
-
priority: "medium",
|
|
50
|
-
message: "Neon agent skills not detected in this project",
|
|
51
|
-
command: neonInitAgentCmd({
|
|
52
|
-
step: "skills",
|
|
53
|
-
install: true
|
|
54
|
-
})
|
|
55
|
-
});
|
|
56
|
-
if (migrationTool && !hasMigrations) recommendations.push({
|
|
57
|
-
priority: "medium",
|
|
58
|
-
message: `${migrationTool} detected but no migrations found`,
|
|
59
|
-
command: neonInitAgentCmd({ step: "migrations" })
|
|
60
|
-
});
|
|
61
|
-
return {
|
|
62
|
-
auth: { authenticated: authed },
|
|
63
|
-
tooling: {
|
|
64
|
-
mcpServer: {
|
|
65
|
-
configured: mcpConfigured,
|
|
66
|
-
scope: inspection.mcpScope || null
|
|
67
|
-
},
|
|
68
|
-
skills: {
|
|
69
|
-
installed: skillsInstalled,
|
|
70
|
-
scope: inspection.skillsScope || null
|
|
71
|
-
}
|
|
72
|
-
},
|
|
73
|
-
project: { databaseUrl: hasDatabaseUrl },
|
|
74
|
-
migrations: {
|
|
75
|
-
tool: migrationTool,
|
|
76
|
-
hasMigrations
|
|
77
|
-
},
|
|
78
|
-
recommendations
|
|
79
|
-
};
|
|
80
|
-
}
|
|
81
|
-
//#endregion
|
|
82
|
-
export { handleStatusPhase };
|
package/dist/init/profile_cli.js
DELETED
|
@@ -1,56 +0,0 @@
|
|
|
1
|
-
import { n as configDir } from "../_chunks/paths-g3DRqJiD.js";
|
|
2
|
-
import { t as DEFAULT_PROFILE } from "../_chunks/profiles-D95AeZ5v.js";
|
|
3
|
-
import { i as selectCredential, t as credentialInputs } from "../_chunks/auth_selection-ktL6uoEe.js";
|
|
4
|
-
//#region src/init/profile_cli.ts
|
|
5
|
-
/**
|
|
6
|
-
* API-key selections fall back to DEFAULT because init authenticates from stored credentials.
|
|
7
|
-
*/
|
|
8
|
-
function selectedProfileName() {
|
|
9
|
-
const selection = selectCredential(credentialInputs());
|
|
10
|
-
return selection.source === "profile" ? selection.profile : DEFAULT_PROFILE;
|
|
11
|
-
}
|
|
12
|
-
/**
|
|
13
|
-
* An explicit `--config-dir` must override `NEON_CONFIG_DIR` and the home path.
|
|
14
|
-
*/
|
|
15
|
-
function selectedConfigDir() {
|
|
16
|
-
const recorded = credentialInputs().configDir;
|
|
17
|
-
return recorded !== "" ? recorded : configDir();
|
|
18
|
-
}
|
|
19
|
-
/**
|
|
20
|
-
* Omitting implicit DEFAULT preserves command compatibility; retaining explicit DEFAULT
|
|
21
|
-
* prevents ambient `NEON_PROFILE` from overriding it in subprocesses.
|
|
22
|
-
*/
|
|
23
|
-
function explicitProfileArgs() {
|
|
24
|
-
const selection = selectCredential(credentialInputs());
|
|
25
|
-
if (selection.source === "profile" && selection.explicit) return ["--profile", selection.profile];
|
|
26
|
-
return [];
|
|
27
|
-
}
|
|
28
|
-
function explicitProfileCli() {
|
|
29
|
-
const args = explicitProfileArgs();
|
|
30
|
-
return args.length === 0 ? "" : ` ${args.join(" ")}`;
|
|
31
|
-
}
|
|
32
|
-
function explicitConfigDirArgs() {
|
|
33
|
-
const dir = credentialInputs().configDir;
|
|
34
|
-
return dir !== "" ? ["--config-dir", dir] : [];
|
|
35
|
-
}
|
|
36
|
-
function posixSingleQuote(value) {
|
|
37
|
-
return `'${value.replace(/'/g, `'\\''`)}'`;
|
|
38
|
-
}
|
|
39
|
-
function explicitConfigDirCli() {
|
|
40
|
-
const dir = credentialInputs().configDir;
|
|
41
|
-
return dir === "" ? "" : ` --config-dir ${posixSingleQuote(dir)}`;
|
|
42
|
-
}
|
|
43
|
-
function npxNeonArgs(command) {
|
|
44
|
-
return [
|
|
45
|
-
"-y",
|
|
46
|
-
"neon",
|
|
47
|
-
...explicitProfileArgs(),
|
|
48
|
-
...explicitConfigDirArgs(),
|
|
49
|
-
...command
|
|
50
|
-
];
|
|
51
|
-
}
|
|
52
|
-
function neonInitAgentCmd(data) {
|
|
53
|
-
return `neon init --agent${explicitProfileCli()}${explicitConfigDirCli()} --data '${JSON.stringify(data)}'`;
|
|
54
|
-
}
|
|
55
|
-
//#endregion
|
|
56
|
-
export { explicitConfigDirCli, explicitProfileArgs, explicitProfileCli, neonInitAgentCmd, npxNeonArgs, selectedConfigDir, selectedProfileName };
|
|
@@ -1,105 +0,0 @@
|
|
|
1
|
-
import { npxNeonArgs } from "./profile_cli.js";
|
|
2
|
-
import { existsSync, readFileSync } from "node:fs";
|
|
3
|
-
import { resolve } from "node:path";
|
|
4
|
-
import { execa } from "execa";
|
|
5
|
-
//#region src/init/resolve_context.ts
|
|
6
|
-
/**
|
|
7
|
-
* Resolves the Neon org/project from a DATABASE_URL in .env.
|
|
8
|
-
* Parses the endpoint hostname, then queries the Neon API to find
|
|
9
|
-
* which project it belongs to.
|
|
10
|
-
*/
|
|
11
|
-
/**
|
|
12
|
-
* Extracts the Neon endpoint host from .env's DATABASE_URL or PGHOST.
|
|
13
|
-
* Returns the hostname (e.g. "ep-cool-name-12345.us-east-2.aws.neon.tech") or null.
|
|
14
|
-
*/
|
|
15
|
-
function extractNeonHost(cwd) {
|
|
16
|
-
for (const envFile of [".env", ".env.local"]) {
|
|
17
|
-
const envPath = resolve(cwd, envFile);
|
|
18
|
-
if (!existsSync(envPath)) continue;
|
|
19
|
-
try {
|
|
20
|
-
const content = readFileSync(envPath, "utf-8");
|
|
21
|
-
const dbUrlMatch = content.match(/^DATABASE_URL=.*@([^/:\s]+\.neon\.tech)/m);
|
|
22
|
-
if (dbUrlMatch) return dbUrlMatch[1];
|
|
23
|
-
const pgHostMatch = content.match(/^PGHOST=([^\s]+\.neon\.tech)/m);
|
|
24
|
-
if (pgHostMatch) return pgHostMatch[1];
|
|
25
|
-
} catch {}
|
|
26
|
-
}
|
|
27
|
-
return null;
|
|
28
|
-
}
|
|
29
|
-
/**
|
|
30
|
-
* Resolves the Neon org and project that own the endpoint in DATABASE_URL.
|
|
31
|
-
*
|
|
32
|
-
* Strategy:
|
|
33
|
-
* 1. Extract endpoint hostname from .env
|
|
34
|
-
* 2. List orgs via the Neon CLI
|
|
35
|
-
* 3. For each org, list projects
|
|
36
|
-
* 4. For each project, get connection string and compare hostnames
|
|
37
|
-
* 5. Return the matching org/project
|
|
38
|
-
*/
|
|
39
|
-
async function resolveNeonContext(cwd) {
|
|
40
|
-
const neonHost = extractNeonHost(cwd);
|
|
41
|
-
if (!neonHost) return null;
|
|
42
|
-
let orgs;
|
|
43
|
-
try {
|
|
44
|
-
const result = await execa("npx", npxNeonArgs([
|
|
45
|
-
"orgs",
|
|
46
|
-
"list",
|
|
47
|
-
"--output",
|
|
48
|
-
"json"
|
|
49
|
-
]), {
|
|
50
|
-
stdio: "pipe",
|
|
51
|
-
timeout: 3e4,
|
|
52
|
-
env: {
|
|
53
|
-
...process.env,
|
|
54
|
-
CI: void 0
|
|
55
|
-
}
|
|
56
|
-
});
|
|
57
|
-
orgs = JSON.parse(result.stdout);
|
|
58
|
-
} catch {
|
|
59
|
-
return null;
|
|
60
|
-
}
|
|
61
|
-
if (orgs.length === 0) return null;
|
|
62
|
-
for (const org of orgs) {
|
|
63
|
-
let projects;
|
|
64
|
-
try {
|
|
65
|
-
const result = await execa("npx", npxNeonArgs([
|
|
66
|
-
"projects",
|
|
67
|
-
"list",
|
|
68
|
-
"--org-id",
|
|
69
|
-
org.id,
|
|
70
|
-
"--output",
|
|
71
|
-
"json"
|
|
72
|
-
]), {
|
|
73
|
-
stdio: "pipe",
|
|
74
|
-
timeout: 3e4,
|
|
75
|
-
env: {
|
|
76
|
-
...process.env,
|
|
77
|
-
CI: void 0
|
|
78
|
-
}
|
|
79
|
-
});
|
|
80
|
-
projects = JSON.parse(result.stdout);
|
|
81
|
-
} catch {
|
|
82
|
-
continue;
|
|
83
|
-
}
|
|
84
|
-
for (const project of projects) try {
|
|
85
|
-
if ((await execa("npx", npxNeonArgs([
|
|
86
|
-
"connection-string",
|
|
87
|
-
"--project-id",
|
|
88
|
-
project.id
|
|
89
|
-
]), {
|
|
90
|
-
stdio: "pipe",
|
|
91
|
-
timeout: 15e3,
|
|
92
|
-
env: {
|
|
93
|
-
...process.env,
|
|
94
|
-
CI: void 0
|
|
95
|
-
}
|
|
96
|
-
})).stdout.trim().includes(neonHost)) return {
|
|
97
|
-
orgId: org.id,
|
|
98
|
-
projectId: project.id
|
|
99
|
-
};
|
|
100
|
-
} catch {}
|
|
101
|
-
}
|
|
102
|
-
return null;
|
|
103
|
-
}
|
|
104
|
-
//#endregion
|
|
105
|
-
export { resolveNeonContext };
|
|
@@ -1,93 +0,0 @@
|
|
|
1
|
-
//#region src/init/route_command.ts
|
|
2
|
-
/**
|
|
3
|
-
* Routes a `--data` payload to a phase handler by its `step` field, without
|
|
4
|
-
* shelling out. This is the whole agent-facing surface of `neon init`:
|
|
5
|
-
*
|
|
6
|
-
* neon init --agent --data '{"step":"auth"}'
|
|
7
|
-
* neon init --agent --data '{"step":"db","projectId":"xyz"}'
|
|
8
|
-
*/
|
|
9
|
-
async function routeDataStep(data, agent) {
|
|
10
|
-
const { step, agentId, ...rest } = data;
|
|
11
|
-
const resolvedAgent = typeof agentId === "string" ? agentId : agent;
|
|
12
|
-
if (rest.data !== void 0 && Object.keys(rest).length === 1) {
|
|
13
|
-
let nested = rest.data;
|
|
14
|
-
if (typeof nested === "string") try {
|
|
15
|
-
nested = JSON.parse(nested);
|
|
16
|
-
} catch {}
|
|
17
|
-
if (typeof nested === "object" && nested !== null) {
|
|
18
|
-
Object.assign(rest, nested);
|
|
19
|
-
delete rest.data;
|
|
20
|
-
}
|
|
21
|
-
}
|
|
22
|
-
switch (step) {
|
|
23
|
-
case "auth": {
|
|
24
|
-
const { handleAuthPhase } = await import("./phases/auth.js");
|
|
25
|
-
return handleAuthPhase({
|
|
26
|
-
agent: resolvedAgent,
|
|
27
|
-
...rest
|
|
28
|
-
});
|
|
29
|
-
}
|
|
30
|
-
case "db": {
|
|
31
|
-
const { handleDbPhase } = await import("./phases/db.js");
|
|
32
|
-
return handleDbPhase({
|
|
33
|
-
agent: resolvedAgent,
|
|
34
|
-
...rest
|
|
35
|
-
});
|
|
36
|
-
}
|
|
37
|
-
case "setup": {
|
|
38
|
-
const { handleSetupPhase } = await import("./phases/setup.js");
|
|
39
|
-
return handleSetupPhase({
|
|
40
|
-
agent: resolvedAgent,
|
|
41
|
-
...rest
|
|
42
|
-
});
|
|
43
|
-
}
|
|
44
|
-
case "getting-started": {
|
|
45
|
-
const { handleGettingStartedPhase } = await import("./phases/getting_started.js");
|
|
46
|
-
return handleGettingStartedPhase({
|
|
47
|
-
agent: resolvedAgent,
|
|
48
|
-
...rest,
|
|
49
|
-
cwd: process.cwd()
|
|
50
|
-
});
|
|
51
|
-
}
|
|
52
|
-
case "mcp": {
|
|
53
|
-
const { handleMcpPhase } = await import("./phases/mcp.js");
|
|
54
|
-
return handleMcpPhase({
|
|
55
|
-
agent: resolvedAgent,
|
|
56
|
-
...rest
|
|
57
|
-
});
|
|
58
|
-
}
|
|
59
|
-
case "skills": {
|
|
60
|
-
const { handleSkillsPhase } = await import("./phases/skills.js");
|
|
61
|
-
return handleSkillsPhase({
|
|
62
|
-
agent: resolvedAgent,
|
|
63
|
-
...rest
|
|
64
|
-
});
|
|
65
|
-
}
|
|
66
|
-
case "migrations": {
|
|
67
|
-
const { handleMigrationsPhase } = await import("./phases/migrations.js");
|
|
68
|
-
return handleMigrationsPhase({
|
|
69
|
-
agent: resolvedAgent,
|
|
70
|
-
...rest,
|
|
71
|
-
cwd: process.cwd()
|
|
72
|
-
});
|
|
73
|
-
}
|
|
74
|
-
case "neon-auth": {
|
|
75
|
-
const { handleNeonAuthPhase } = await import("./phases/neon_auth.js");
|
|
76
|
-
return handleNeonAuthPhase({
|
|
77
|
-
agent: resolvedAgent,
|
|
78
|
-
...rest
|
|
79
|
-
});
|
|
80
|
-
}
|
|
81
|
-
case "status": {
|
|
82
|
-
const { handleStatusPhase } = await import("./phases/status.js");
|
|
83
|
-
return handleStatusPhase({ agent: resolvedAgent });
|
|
84
|
-
}
|
|
85
|
-
case "finalize": {
|
|
86
|
-
const { handleCleanup } = await import("./phases/cleanup.js");
|
|
87
|
-
return handleCleanup();
|
|
88
|
-
}
|
|
89
|
-
default: throw new Error(`Unknown step: "${step}". Valid steps: auth, db, setup, getting-started, mcp, skills, migrations, neon-auth, status, finalize`);
|
|
90
|
-
}
|
|
91
|
-
}
|
|
92
|
-
//#endregion
|
|
93
|
-
export { routeDataStep };
|
package/dist/init/skills.js
DELETED
|
@@ -1,206 +0,0 @@
|
|
|
1
|
-
import { globalInstallCommand, resolveInvokingPackageManager } from "../utils/package_manager.js";
|
|
2
|
-
import { getSkillsAgentName } from "./agents.js";
|
|
3
|
-
import { existsSync, statSync } from "node:fs";
|
|
4
|
-
import { resolve } from "node:path";
|
|
5
|
-
import { log, spinner } from "@clack/prompts";
|
|
6
|
-
import { dim } from "yoctocolors";
|
|
7
|
-
import { execa } from "execa";
|
|
8
|
-
//#region src/init/skills.ts
|
|
9
|
-
/**
|
|
10
|
-
* Ensures the `skills` CLI is globally installed so npx doesn't need
|
|
11
|
-
* to download it (which can fail behind corporate proxies / sandboxes).
|
|
12
|
-
*/
|
|
13
|
-
async function ensureSkillsCli() {
|
|
14
|
-
try {
|
|
15
|
-
await execa("skills", ["--version"], {
|
|
16
|
-
stdio: "pipe",
|
|
17
|
-
timeout: 5e3
|
|
18
|
-
});
|
|
19
|
-
} catch {
|
|
20
|
-
const install = globalInstallCommand(resolveInvokingPackageManager(), "skills");
|
|
21
|
-
if (!install) return;
|
|
22
|
-
try {
|
|
23
|
-
await execa(install.command, install.args, {
|
|
24
|
-
stdio: "pipe",
|
|
25
|
-
timeout: 6e4
|
|
26
|
-
});
|
|
27
|
-
} catch {}
|
|
28
|
-
}
|
|
29
|
-
}
|
|
30
|
-
/** Base skills installed for all invocations */
|
|
31
|
-
const BASE_SKILLS = ["neon", "neon-postgres"];
|
|
32
|
-
/** Additional skills installed for preview (non-bootstrap) invocations */
|
|
33
|
-
const PREVIEW_SKILLS = [
|
|
34
|
-
"neon-object-storage",
|
|
35
|
-
"neon-functions",
|
|
36
|
-
"neon-ai-gateway"
|
|
37
|
-
];
|
|
38
|
-
/** Returns the skill list based on whether preview mode is active */
|
|
39
|
-
function getSkillList(preview) {
|
|
40
|
-
return preview ? [...BASE_SKILLS, ...PREVIEW_SKILLS] : BASE_SKILLS;
|
|
41
|
-
}
|
|
42
|
-
const SKILL_BASE_URL = "https://neon.com/docs/ai/skills/neon-postgres/references";
|
|
43
|
-
const SKILL_REFERENCE_URLS = {
|
|
44
|
-
gettingStarted: `${SKILL_BASE_URL}/getting-started.md`,
|
|
45
|
-
connectionMethods: `${SKILL_BASE_URL}/connection-methods.md`,
|
|
46
|
-
neonAuth: `${SKILL_BASE_URL}/neon-auth.md`,
|
|
47
|
-
serverlessDriver: `${SKILL_BASE_URL}/neon-serverless.md`,
|
|
48
|
-
neonCli: `${SKILL_BASE_URL}/neon-cli.md`,
|
|
49
|
-
devtools: `${SKILL_BASE_URL}/devtools.md`,
|
|
50
|
-
branching: `${SKILL_BASE_URL}/branching.md`,
|
|
51
|
-
neonJs: `${SKILL_BASE_URL}/neon-js.md`
|
|
52
|
-
};
|
|
53
|
-
/**
|
|
54
|
-
* Installs Neon agent skills using Vercel's skills CLI.
|
|
55
|
-
*/
|
|
56
|
-
async function installAgentSkills(selectedAgents, options) {
|
|
57
|
-
const quiet = options?.json === true;
|
|
58
|
-
const agentsWithSkills = selectedAgents.filter((id) => getSkillsAgentName(id) !== void 0);
|
|
59
|
-
if (agentsWithSkills.length === 0) return true;
|
|
60
|
-
const skillsSpinner = quiet ? null : spinner();
|
|
61
|
-
skillsSpinner?.start("Installing agent skills for Neon in this project...");
|
|
62
|
-
let anyFailed = false;
|
|
63
|
-
await ensureSkillsCli();
|
|
64
|
-
const skills = getSkillList(options?.preview);
|
|
65
|
-
for (const agent of agentsWithSkills) {
|
|
66
|
-
const agentName = getSkillsAgentName(agent);
|
|
67
|
-
if (!agentName) continue;
|
|
68
|
-
for (const skill of skills) try {
|
|
69
|
-
await execa("skills", [
|
|
70
|
-
"add",
|
|
71
|
-
"neondatabase/agent-skills",
|
|
72
|
-
"--skill",
|
|
73
|
-
skill,
|
|
74
|
-
"--agent",
|
|
75
|
-
agentName,
|
|
76
|
-
...options?.scope === "global" ? ["-g"] : [],
|
|
77
|
-
"-y"
|
|
78
|
-
], {
|
|
79
|
-
stdio: "pipe",
|
|
80
|
-
timeout: 12e4
|
|
81
|
-
});
|
|
82
|
-
} catch (error) {
|
|
83
|
-
if (!quiet) log.error(`Failed to install skill ${skill} for ${agent}: ${error instanceof Error ? error.message : "Unknown error"}`);
|
|
84
|
-
anyFailed = true;
|
|
85
|
-
}
|
|
86
|
-
}
|
|
87
|
-
if (anyFailed) {
|
|
88
|
-
skillsSpinner?.stop("Agent skills installation for this project completed with errors");
|
|
89
|
-
if (!quiet) log.info("You can manually install skills by running: npx skills add neondatabase/agent-skills --skill neon --skill neon-postgres");
|
|
90
|
-
return false;
|
|
91
|
-
}
|
|
92
|
-
skillsSpinner?.stop(dim("Agent skills installed ✓"));
|
|
93
|
-
return true;
|
|
94
|
-
}
|
|
95
|
-
const SKILLS_FRESHNESS_MS = 432e5;
|
|
96
|
-
/**
|
|
97
|
-
* Agent skills directory paths by agent, keyed by scope.
|
|
98
|
-
* These are the directories that `skills add` writes to.
|
|
99
|
-
*/
|
|
100
|
-
const GLOBAL_SKILLS_DIRS = (() => {
|
|
101
|
-
const home = process.env.HOME || process.env.USERPROFILE || "";
|
|
102
|
-
const agentsDir = resolve(home, ".agents", "skills");
|
|
103
|
-
return {
|
|
104
|
-
cursor: [resolve(home, ".cursor", "skills"), agentsDir],
|
|
105
|
-
"claude-code": [resolve(home, ".claude", "skills"), agentsDir],
|
|
106
|
-
"github-copilot": [resolve(home, ".vscode", "skills"), agentsDir],
|
|
107
|
-
codex: [resolve(home, ".codex", "skills"), agentsDir],
|
|
108
|
-
cline: [resolve(home, ".cline", "skills"), agentsDir],
|
|
109
|
-
grok: [resolve(home, ".grok", "skills"), agentsDir]
|
|
110
|
-
};
|
|
111
|
-
})();
|
|
112
|
-
const PROJECT_SKILLS_DIRS = {
|
|
113
|
-
cursor: [".cursor/skills", ".agents/skills"],
|
|
114
|
-
"claude-code": [".claude/skills", ".agents/skills"],
|
|
115
|
-
"github-copilot": [".vscode/skills", ".agents/skills"],
|
|
116
|
-
codex: [".codex/skills", ".agents/skills"],
|
|
117
|
-
cline: [".cline/skills", ".agents/skills"],
|
|
118
|
-
grok: [".grok/skills", ".agents/skills"]
|
|
119
|
-
};
|
|
120
|
-
function skillDirsForAgent(skillsId, scope, cwd, home) {
|
|
121
|
-
if (scope === "global") return [.../* @__PURE__ */ new Set([...GLOBAL_SKILLS_DIRS[skillsId] ?? [], resolve(home, ".agents", "skills")])];
|
|
122
|
-
return [...new Set([...PROJECT_SKILLS_DIRS[skillsId] ?? [], ".agents/skills"].map((dir) => resolve(cwd, dir)))];
|
|
123
|
-
}
|
|
124
|
-
function missingSkillsForAgent(agent, options = {}) {
|
|
125
|
-
const skillsId = getSkillsAgentName(agent);
|
|
126
|
-
if (!skillsId) return [];
|
|
127
|
-
const cwd = options.cwd ?? process.cwd();
|
|
128
|
-
const home = process.env.HOME || process.env.USERPROFILE || "";
|
|
129
|
-
const dirs = skillDirsForAgent(skillsId, options.scope ?? "project", cwd, home);
|
|
130
|
-
return getSkillList(options.preview).filter((skill) => !dirs.some((dir) => existsSync(resolve(dir, skill, "SKILL.md"))));
|
|
131
|
-
}
|
|
132
|
-
function skillsInstalledForAgent(agent, cwd = process.cwd(), preview = false) {
|
|
133
|
-
if (!getSkillsAgentName(agent)) return true;
|
|
134
|
-
return missingSkillsForAgent(agent, {
|
|
135
|
-
cwd,
|
|
136
|
-
scope: "project",
|
|
137
|
-
preview
|
|
138
|
-
}).length === 0 || missingSkillsForAgent(agent, {
|
|
139
|
-
cwd,
|
|
140
|
-
scope: "global",
|
|
141
|
-
preview
|
|
142
|
-
}).length === 0;
|
|
143
|
-
}
|
|
144
|
-
/**
|
|
145
|
-
* Checks whether skills were recently updated (within the freshness window).
|
|
146
|
-
* Checks both project-level (skills-lock.json mtime) and global (skills dir mtime).
|
|
147
|
-
*/
|
|
148
|
-
function skillsAreFresh(agent, requiredSkills) {
|
|
149
|
-
const now = Date.now();
|
|
150
|
-
const cwd = process.cwd();
|
|
151
|
-
const skillsId = getSkillsAgentName(agent);
|
|
152
|
-
const projectDirs = (skillsId ? PROJECT_SKILLS_DIRS[skillsId] : void 0)?.map((dir) => resolve(cwd, dir)) ?? [];
|
|
153
|
-
const lockPath = resolve(cwd, "skills-lock.json");
|
|
154
|
-
if (existsSync(lockPath) && projectDirs.length > 0) try {
|
|
155
|
-
if (now - statSync(lockPath).mtimeMs < SKILLS_FRESHNESS_MS) {
|
|
156
|
-
if (requiredSkills.every((skill) => projectDirs.some((dir) => existsSync(resolve(dir, skill, "SKILL.md"))))) return true;
|
|
157
|
-
}
|
|
158
|
-
} catch {}
|
|
159
|
-
const agentName = getSkillsAgentName(agent);
|
|
160
|
-
if (!agentName) return false;
|
|
161
|
-
const globalDirs = GLOBAL_SKILLS_DIRS[agentName] ?? [];
|
|
162
|
-
for (const dir of globalDirs) if (requiredSkills.every((skill) => existsSync(resolve(dir, skill, "SKILL.md")))) try {
|
|
163
|
-
if (now - statSync(resolve(dir, requiredSkills[0], "SKILL.md")).mtimeMs < SKILLS_FRESHNESS_MS) return true;
|
|
164
|
-
} catch {}
|
|
165
|
-
return false;
|
|
166
|
-
}
|
|
167
|
-
/**
|
|
168
|
-
* Ensures Neon agent skills are up to date. Runs `skills add` if the skills
|
|
169
|
-
* haven't been updated within the freshness window (12 hours).
|
|
170
|
-
*
|
|
171
|
-
* Designed to be called from any phase handler — cheap to call repeatedly
|
|
172
|
-
* since it's a no-op when skills are fresh.
|
|
173
|
-
*/
|
|
174
|
-
async function ensureSkillsUpToDate(agent, scope, preview) {
|
|
175
|
-
const resolvedAgent = agent || "cursor";
|
|
176
|
-
const agentName = getSkillsAgentName(resolvedAgent);
|
|
177
|
-
if (!agentName) return true;
|
|
178
|
-
if (skillsAreFresh(resolvedAgent, getSkillList(preview))) return true;
|
|
179
|
-
await ensureSkillsCli();
|
|
180
|
-
let allOk = true;
|
|
181
|
-
const missingSkills = missingSkillsForAgent(resolvedAgent, {
|
|
182
|
-
scope: scope ?? "project",
|
|
183
|
-
preview
|
|
184
|
-
});
|
|
185
|
-
if (missingSkills.length === 0) return true;
|
|
186
|
-
for (const skill of missingSkills) try {
|
|
187
|
-
await execa("skills", [
|
|
188
|
-
"add",
|
|
189
|
-
"neondatabase/agent-skills",
|
|
190
|
-
"--skill",
|
|
191
|
-
skill,
|
|
192
|
-
"--agent",
|
|
193
|
-
agentName,
|
|
194
|
-
...scope === "global" ? ["-g"] : [],
|
|
195
|
-
"-y"
|
|
196
|
-
], {
|
|
197
|
-
stdio: "pipe",
|
|
198
|
-
timeout: 12e4
|
|
199
|
-
});
|
|
200
|
-
} catch {
|
|
201
|
-
allOk = false;
|
|
202
|
-
}
|
|
203
|
-
return allOk;
|
|
204
|
-
}
|
|
205
|
-
//#endregion
|
|
206
|
-
export { SKILL_REFERENCE_URLS, ensureSkillsUpToDate, getSkillList, installAgentSkills, missingSkillsForAgent, skillsInstalledForAgent };
|
package/dist/init/types.js
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export {};
|
package/dist/init/vsix.js
DELETED
|
@@ -1,89 +0,0 @@
|
|
|
1
|
-
import "./build_config.js";
|
|
2
|
-
import { createWriteStream } from "node:fs";
|
|
3
|
-
import { join } from "node:path";
|
|
4
|
-
import { tmpdir } from "node:os";
|
|
5
|
-
import { pipeline } from "node:stream/promises";
|
|
6
|
-
//#region src/init/vsix.ts
|
|
7
|
-
/**
|
|
8
|
-
* Shared VSIX download utilities for extension installation.
|
|
9
|
-
* Supports corporate proxy via NEON_VSX_GALLERY_URL env var.
|
|
10
|
-
*/
|
|
11
|
-
const NEON_EXTENSION_ID = "databricks.neon-local-connect";
|
|
12
|
-
const OPEN_VSX_API = "https://open-vsx.org/api";
|
|
13
|
-
/**
|
|
14
|
-
* Downloads a .vsix file for the Neon extension.
|
|
15
|
-
*
|
|
16
|
-
* Strategy:
|
|
17
|
-
* 1. If NEON_VSX_GALLERY_URL is set, download from the corporate proxy gallery
|
|
18
|
-
* 2. Otherwise, download from the public Open VSX API
|
|
19
|
-
*
|
|
20
|
-
* Returns the path to the temp .vsix file, or null on failure.
|
|
21
|
-
*/
|
|
22
|
-
async function downloadVsix() {
|
|
23
|
-
const proxyGallery = process.env.NEON_VSX_GALLERY_URL || "";
|
|
24
|
-
if (proxyGallery) return downloadFromGallery(proxyGallery);
|
|
25
|
-
return downloadFromOpenVsx();
|
|
26
|
-
}
|
|
27
|
-
/**
|
|
28
|
-
* Downloads from a VS Code marketplace-compatible gallery API (corporate proxy).
|
|
29
|
-
* Uses the VS Code extensionquery POST API to find the VSIX download URL.
|
|
30
|
-
*/
|
|
31
|
-
async function downloadFromGallery(galleryUrl) {
|
|
32
|
-
const [publisher, name] = NEON_EXTENSION_ID.split(".");
|
|
33
|
-
const queryUrl = `${galleryUrl.replace(/\/+$/, "")}/extensionquery`;
|
|
34
|
-
try {
|
|
35
|
-
const queryRes = await fetch(queryUrl, {
|
|
36
|
-
method: "POST",
|
|
37
|
-
headers: {
|
|
38
|
-
"Content-Type": "application/json",
|
|
39
|
-
Accept: "application/json;api-version=6.1-preview.1"
|
|
40
|
-
},
|
|
41
|
-
body: JSON.stringify({
|
|
42
|
-
filters: [{ criteria: [{
|
|
43
|
-
filterType: 7,
|
|
44
|
-
value: `${publisher}.${name}`
|
|
45
|
-
}] }],
|
|
46
|
-
flags: 914
|
|
47
|
-
}),
|
|
48
|
-
signal: AbortSignal.timeout(15e3)
|
|
49
|
-
});
|
|
50
|
-
if (!queryRes.ok) return null;
|
|
51
|
-
const vsixFile = (((await queryRes.json()).results?.[0]?.extensions?.[0])?.versions?.[0])?.files?.find((f) => f.assetType === "Microsoft.VisualStudio.Services.VSIXPackage");
|
|
52
|
-
if (!vsixFile?.source) return null;
|
|
53
|
-
const vsixRes = await fetch(vsixFile.source, {
|
|
54
|
-
signal: AbortSignal.timeout(3e4),
|
|
55
|
-
redirect: "follow"
|
|
56
|
-
});
|
|
57
|
-
if (!vsixRes.ok || !vsixRes.body) return null;
|
|
58
|
-
const tmpPath = join(tmpdir(), `${NEON_EXTENSION_ID}-proxy.vsix`);
|
|
59
|
-
const fileStream = createWriteStream(tmpPath);
|
|
60
|
-
await pipeline(vsixRes.body, fileStream);
|
|
61
|
-
return tmpPath;
|
|
62
|
-
} catch {
|
|
63
|
-
return null;
|
|
64
|
-
}
|
|
65
|
-
}
|
|
66
|
-
/**
|
|
67
|
-
* Downloads from the public Open VSX API.
|
|
68
|
-
*/
|
|
69
|
-
async function downloadFromOpenVsx() {
|
|
70
|
-
const [publisher, name] = NEON_EXTENSION_ID.split(".");
|
|
71
|
-
const metaUrl = `${OPEN_VSX_API}/${publisher}/${name}/latest`;
|
|
72
|
-
try {
|
|
73
|
-
const metaRes = await fetch(metaUrl, { signal: AbortSignal.timeout(1e4) });
|
|
74
|
-
if (!metaRes.ok) return null;
|
|
75
|
-
const meta = await metaRes.json();
|
|
76
|
-
const downloadUrl = meta.files?.download;
|
|
77
|
-
if (!downloadUrl) return null;
|
|
78
|
-
const vsixRes = await fetch(downloadUrl, { signal: AbortSignal.timeout(3e4) });
|
|
79
|
-
if (!vsixRes.ok || !vsixRes.body) return null;
|
|
80
|
-
const tmpPath = join(tmpdir(), `${NEON_EXTENSION_ID}-${meta.version ?? "latest"}.vsix`);
|
|
81
|
-
const fileStream = createWriteStream(tmpPath);
|
|
82
|
-
await pipeline(vsixRes.body, fileStream);
|
|
83
|
-
return tmpPath;
|
|
84
|
-
} catch {
|
|
85
|
-
return null;
|
|
86
|
-
}
|
|
87
|
-
}
|
|
88
|
-
//#endregion
|
|
89
|
-
export { NEON_EXTENSION_ID, downloadVsix };
|