neon 3.4.0 → 3.5.1
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/dist/init/agents.js +73 -119
- package/dist/init/editors.js +3 -30
- package/dist/init/inspect.js +69 -20
- package/dist/init/install_mcp.js +35 -0
- package/dist/init/interactive.js +95 -107
- package/dist/init/neonctl.js +6 -2
- package/dist/init/orchestrate.js +20 -6
- package/dist/init/phases/mcp.js +91 -50
- package/dist/init/phases/setup.js +43 -34
- package/dist/init/phases/skills.js +10 -2
- package/dist/init/phases/status.js +4 -2
- package/dist/init/skills.js +55 -53
- package/package.json +6 -5
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
import { FALLBACK_TEMPLATES, fetchTemplates, findTemplate, scaffoldTemplate } from "../bootstrap.js";
|
|
2
2
|
import { detectIde, isCursorInstalled, isVSCodeInstalled } from "../detect_agent.js";
|
|
3
|
-
import { resolveAddMcpAgentId } from "../agents.js";
|
|
3
|
+
import { agentSupportsProjectMcp, getSkillsAgentName, listMcpAgentIds, resolveAddMcpAgentId, supportsSkills, tryResolveAddMcpAgentId } from "../agents.js";
|
|
4
4
|
import { NEON_EXTENSION_ID, downloadVsix } from "../vsix.js";
|
|
5
5
|
import { findEditorCommand } from "../extension.js";
|
|
6
6
|
import { inspectProject } from "../inspect.js";
|
|
7
|
+
import { installNeonMcpServer } from "../install_mcp.js";
|
|
7
8
|
import { ensureNeonctl } from "../neonctl.js";
|
|
8
9
|
import { ensureSkillsUpToDate } from "../skills.js";
|
|
9
10
|
import { writeFileSync } from "node:fs";
|
|
@@ -105,7 +106,7 @@ async function buildBulkInspection(options) {
|
|
|
105
106
|
"",
|
|
106
107
|
"GROUPING: Preferences that share the same `group` field should be presented together in a single message (e.g. list all customize options at once and let the user answer them together). Preferences without a `group` must be asked individually.",
|
|
107
108
|
"",
|
|
108
|
-
detectedIde ? `The CLI has detected the IDE as: ${detectedIde.toLowerCase()}. Include this as the "ide" field in your reportBack data. IMPORTANT: The IDE and the agent are different — you may be Claude Code (agent) running inside Cursor (IDE). The extension installs into the IDE, so if the IDE is Cursor
|
|
109
|
+
detectedIde ? `The CLI has detected the IDE as: ${detectedIde.toLowerCase()}. Include this as the "ide" field in your reportBack data. IMPORTANT: The IDE and the agent are different — you may be Claude Code (agent) running inside Cursor (IDE). The extension installs into the IDE, so if the IDE is Cursor or VS Code, the extension IS applicable even if you are Claude Code.` : installedEditors.length > 0 ? `No IDE detected, but the following editors are installed: ${installedEditors.join(", ")}. The "installedEditors" field in this response lists them. If the user wants the extension installed, ask which editor to install it for and include that as the "ide" field in your reportBack data. If not, set "ide" to "none".` : `No IDE or supported editors detected. Set "ide" to "none" in your reportBack data.`,
|
|
109
110
|
"",
|
|
110
111
|
"After all questions are answered, call reportBack with a single --data JSON containing: agent, ide, mcpConfigured, and all preference answers. The CLI will inspect the project and merge results automatically."
|
|
111
112
|
].join("\n"),
|
|
@@ -123,7 +124,7 @@ async function buildBulkInspection(options) {
|
|
|
123
124
|
{
|
|
124
125
|
id: "agent_type",
|
|
125
126
|
description: "Identify which coding agent is running this command",
|
|
126
|
-
lookFor: [
|
|
127
|
+
lookFor: [`Determine which agent you are: ${listMcpAgentIds().join(", ")}`, "Report your own agent identifier — this is used to configure the MCP server for the correct tool"]
|
|
127
128
|
},
|
|
128
129
|
...detectedIde ? [{
|
|
129
130
|
id: "extension_installed",
|
|
@@ -150,7 +151,7 @@ async function buildBulkInspection(options) {
|
|
|
150
151
|
...(() => {
|
|
151
152
|
const isPartialSkills = String(options.skillsScope ?? "").includes("partial");
|
|
152
153
|
const needsMcpChoice = !options.mcpConfigured;
|
|
153
|
-
const needsSkillsChoice = !options.skillsInstalled && !isPartialSkills;
|
|
154
|
+
const needsSkillsChoice = !options.skillsInstalled && !isPartialSkills && !(options.agent && !supportsSkills(options.agent));
|
|
154
155
|
if (!(needsMcpChoice || needsSkillsChoice)) return [];
|
|
155
156
|
return [{
|
|
156
157
|
id: "mode",
|
|
@@ -176,10 +177,14 @@ async function buildBulkInspection(options) {
|
|
|
176
177
|
value: "global",
|
|
177
178
|
label: "Global (available in all projects)"
|
|
178
179
|
},
|
|
179
|
-
{
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
180
|
+
...(() => {
|
|
181
|
+
const known = options.agent ? tryResolveAddMcpAgentId(options.agent) : void 0;
|
|
182
|
+
if (known && !agentSupportsProjectMcp(known)) return [];
|
|
183
|
+
return [{
|
|
184
|
+
value: "project",
|
|
185
|
+
label: "Project-level (scoped to this project only)"
|
|
186
|
+
}];
|
|
187
|
+
})(),
|
|
183
188
|
{
|
|
184
189
|
value: "none",
|
|
185
190
|
label: "Skip — do not install the MCP server"
|
|
@@ -192,7 +197,7 @@ async function buildBulkInspection(options) {
|
|
|
192
197
|
},
|
|
193
198
|
group: "customize"
|
|
194
199
|
},
|
|
195
|
-
...!options.skillsInstalled && !String(options.skillsScope ?? "").includes("partial") ? [{
|
|
200
|
+
...!options.skillsInstalled && !String(options.skillsScope ?? "").includes("partial") && !(options.agent && !supportsSkills(options.agent)) ? [{
|
|
196
201
|
id: "skillsScope",
|
|
197
202
|
question: "Where should Neon agent skills be installed?",
|
|
198
203
|
context: "Only ask if skills are not already installed.",
|
|
@@ -223,7 +228,7 @@ async function buildBulkInspection(options) {
|
|
|
223
228
|
label: "No"
|
|
224
229
|
}],
|
|
225
230
|
default: "true",
|
|
226
|
-
context: "The extension installs into the IDE, NOT the agent. If the CLI detected the IDE (see detectedIde field), use that — e.g. Claude Code running inside Cursor means the IDE is Cursor and the extension IS applicable. Only applicable for VS Code
|
|
231
|
+
context: "The extension installs into the IDE, NOT the agent. If the CLI detected the IDE (see detectedIde field), use that — e.g. Claude Code running inside Cursor means the IDE is Cursor and the extension IS applicable. Only applicable for VS Code and Cursor. SKIP this question if the user is NOT in VS Code or Cursor, or if the extension_installed check found it is already installed. Set installExtension to false in reportBack if skipped.",
|
|
227
232
|
condition: {
|
|
228
233
|
preferenceId: "mode",
|
|
229
234
|
equals: "customize"
|
|
@@ -245,7 +250,9 @@ async function buildBulkInspection(options) {
|
|
|
245
250
|
const needsSkillsChoice = !options.skillsInstalled && !hasPartial;
|
|
246
251
|
const hasModeQuestion = needsMcpChoice || needsSkillsChoice;
|
|
247
252
|
const modeField = hasModeQuestion ? ", mode: string" : "";
|
|
248
|
-
const
|
|
253
|
+
const knownAgent = options.agent ? tryResolveAddMcpAgentId(options.agent) : void 0;
|
|
254
|
+
const mcpScopeUnion = knownAgent && !agentSupportsProjectMcp(knownAgent) ? "'global'|'none'" : "'global'|'project'|'none'";
|
|
255
|
+
const mcpField = hasModeQuestion ? `, mcpScope?: ${mcpScopeUnion}` : "";
|
|
249
256
|
const skillsField = needsSkillsChoice ? ", skillsScope?: string" : "";
|
|
250
257
|
const extField = hasModeQuestion ? ", installExtension?: bool" : "";
|
|
251
258
|
return `<json: { agent: string, ide: string, mcpConfigured: bool${options.skillsInstalled || hasPartial ? `, skillsScope: "${options.skillsInstalled ? options.skillsScope || "project" : partialScope}"` : skillsField}${previewFlag}${modeField}${mcpField}${extField}${hasApp ? ", features?: string" : ", template: string"} }>`;
|
|
@@ -332,22 +339,12 @@ async function executeBatchedInstallation(options) {
|
|
|
332
339
|
status: "success"
|
|
333
340
|
});
|
|
334
341
|
else {
|
|
335
|
-
const
|
|
336
|
-
|
|
337
|
-
"
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
"Neon",
|
|
342
|
-
"-y",
|
|
343
|
-
"-a",
|
|
344
|
-
mcpAgentId
|
|
345
|
-
];
|
|
346
|
-
try {
|
|
347
|
-
await execa("npx", mcpArgs, {
|
|
348
|
-
stdio: "pipe",
|
|
349
|
-
timeout: 6e4
|
|
350
|
-
});
|
|
342
|
+
const installed = installNeonMcpServer({
|
|
343
|
+
agent: mcpAgentId,
|
|
344
|
+
scope: mcpScope === "project" ? "project" : "global",
|
|
345
|
+
cwd: process.cwd()
|
|
346
|
+
});
|
|
347
|
+
if (installed.ok) {
|
|
351
348
|
results.push({
|
|
352
349
|
id: "install_mcp",
|
|
353
350
|
description: `Installed Neon MCP server (${mcpScope} scope)`,
|
|
@@ -366,20 +363,32 @@ async function executeBatchedInstallation(options) {
|
|
|
366
363
|
status: "success",
|
|
367
364
|
manualAction: true
|
|
368
365
|
});
|
|
369
|
-
}
|
|
366
|
+
} else if (installed.unsupported) {
|
|
367
|
+
const projectUnsupported = mcpScope === "project" && !agentSupportsProjectMcp(mcpAgentId);
|
|
370
368
|
results.push({
|
|
371
369
|
id: "install_mcp",
|
|
372
|
-
description:
|
|
373
|
-
status: "failed",
|
|
374
|
-
error:
|
|
370
|
+
description: installed.error,
|
|
371
|
+
status: projectUnsupported ? "failed" : "success",
|
|
372
|
+
...projectUnsupported ? { error: installed.error } : { manualAction: true }
|
|
375
373
|
});
|
|
376
|
-
}
|
|
374
|
+
} else results.push({
|
|
375
|
+
id: "install_mcp",
|
|
376
|
+
description: "Failed to install Neon MCP server",
|
|
377
|
+
status: "failed",
|
|
378
|
+
error: installed.error
|
|
379
|
+
});
|
|
377
380
|
}
|
|
381
|
+
const skillsAgent = getSkillsAgentName(agentId);
|
|
378
382
|
if (isBootstrap) results.push({
|
|
379
383
|
id: "install_skills",
|
|
380
384
|
description: "Neon agent skills included in template",
|
|
381
385
|
status: "success"
|
|
382
386
|
});
|
|
387
|
+
else if (!skillsAgent) results.push({
|
|
388
|
+
id: "skip_skills",
|
|
389
|
+
description: "No Neon agent skills target for this agent",
|
|
390
|
+
status: "success"
|
|
391
|
+
});
|
|
383
392
|
else {
|
|
384
393
|
const skillsScope = options.skillsScope ?? "project";
|
|
385
394
|
if (await ensureSkillsUpToDate(agentId, skillsScope, options.preview)) results.push({
|
|
@@ -389,7 +398,7 @@ async function executeBatchedInstallation(options) {
|
|
|
389
398
|
});
|
|
390
399
|
else {
|
|
391
400
|
const { getSkillList } = await import("../skills.js");
|
|
392
|
-
const cmds = getSkillList(options.preview).map((s) => `skills add neondatabase/agent-skills --skill ${s} --agent ${
|
|
401
|
+
const cmds = getSkillList(options.preview).map((s) => `skills add neondatabase/agent-skills --skill ${s} --agent ${skillsAgent}${skillsScope === "global" ? " -g" : ""} -y`);
|
|
393
402
|
results.push({
|
|
394
403
|
id: "install_skills",
|
|
395
404
|
description: "Failed to install Neon agent skills automatically. Run these commands to install manually:",
|
|
@@ -478,7 +487,7 @@ async function mergeCliInspection(options) {
|
|
|
478
487
|
function isVscodeBasedIde(options) {
|
|
479
488
|
if (options.ide) {
|
|
480
489
|
const ide = options.ide.toLowerCase();
|
|
481
|
-
return ide === "cursor" || ide === "vscode" || ide === "vs-code"
|
|
490
|
+
return ide === "cursor" || ide === "vscode" || ide === "vs-code";
|
|
482
491
|
}
|
|
483
492
|
return options.isVscodeIde === true;
|
|
484
493
|
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { getSkillsAgentName } from "../agents.js";
|
|
2
|
-
import { SKILL_REFERENCE_URLS } from "../skills.js";
|
|
2
|
+
import { SKILL_REFERENCE_URLS, getSkillList } from "../skills.js";
|
|
3
3
|
//#region src/init/phases/skills.ts
|
|
4
4
|
async function handleSkillsPhase(options) {
|
|
5
5
|
const agentArgs = options.agent ? [
|
|
@@ -8,6 +8,14 @@ async function handleSkillsPhase(options) {
|
|
|
8
8
|
"--json"
|
|
9
9
|
] : ["--json"];
|
|
10
10
|
const skillsAgent = options.agent ? getSkillsAgentName(options.agent) : "claude-code";
|
|
11
|
+
if (options.agent && !skillsAgent) return {
|
|
12
|
+
phase: "tooling",
|
|
13
|
+
status: "skipped",
|
|
14
|
+
nextAction: {
|
|
15
|
+
type: "run_neon_init",
|
|
16
|
+
args: agentArgs
|
|
17
|
+
}
|
|
18
|
+
};
|
|
11
19
|
if (options.status) return {
|
|
12
20
|
phase: "tooling",
|
|
13
21
|
status: "skills_check",
|
|
@@ -34,7 +42,7 @@ async function handleSkillsPhase(options) {
|
|
|
34
42
|
status: "installing_skills",
|
|
35
43
|
nextAction: {
|
|
36
44
|
type: "run_command",
|
|
37
|
-
command: `npx -y skills add neondatabase/agent-skills --skill
|
|
45
|
+
command: getSkillList().map((skill) => `npx -y skills add neondatabase/agent-skills --skill ${skill} --agent ${skillsAgent ?? "claude-code"} -y`).join(" && "),
|
|
38
46
|
description: `Installing Neon agent skills for ${skillsAgent}.`,
|
|
39
47
|
timeout: 3e4,
|
|
40
48
|
onSuccess: {
|
|
@@ -1,7 +1,8 @@
|
|
|
1
|
+
import { supportsSkills, tryResolveAddMcpAgentId } from "../agents.js";
|
|
1
2
|
import { isAuthenticated } from "../auth.js";
|
|
2
3
|
import { inspectProject } from "../inspect.js";
|
|
3
4
|
//#region src/init/phases/status.ts
|
|
4
|
-
async function handleStatusPhase(
|
|
5
|
+
async function handleStatusPhase(options) {
|
|
5
6
|
const authed = await isAuthenticated();
|
|
6
7
|
const inspection = await inspectProject([
|
|
7
8
|
{
|
|
@@ -41,7 +42,8 @@ async function handleStatusPhase(_options) {
|
|
|
41
42
|
message: "No DATABASE_URL found in .env",
|
|
42
43
|
command: `neon init --agent --data '{"step":"db"}'`
|
|
43
44
|
});
|
|
44
|
-
|
|
45
|
+
const statusAgent = options.agent ? tryResolveAddMcpAgentId(options.agent) : void 0;
|
|
46
|
+
if (!skillsInstalled && (!statusAgent || supportsSkills(statusAgent))) recommendations.push({
|
|
45
47
|
priority: "medium",
|
|
46
48
|
message: "Neon agent skills not detected in this project",
|
|
47
49
|
command: `neon init --agent --data '{"step":"skills","install":true}'`
|
package/dist/init/skills.js
CHANGED
|
@@ -3,8 +3,8 @@ import { getSkillsAgentName } from "./agents.js";
|
|
|
3
3
|
import { existsSync, statSync } from "node:fs";
|
|
4
4
|
import { resolve } from "node:path";
|
|
5
5
|
import { log, spinner } from "@clack/prompts";
|
|
6
|
-
import { execa } from "execa";
|
|
7
6
|
import { dim } from "yoctocolors";
|
|
7
|
+
import { execa } from "execa";
|
|
8
8
|
//#region src/init/skills.ts
|
|
9
9
|
/**
|
|
10
10
|
* Ensures the `skills` CLI is globally installed so npx doesn't need
|
|
@@ -51,41 +51,20 @@ const SKILL_REFERENCE_URLS = {
|
|
|
51
51
|
neonJs: `${SKILL_BASE_URL}/neon-js.md`
|
|
52
52
|
};
|
|
53
53
|
/**
|
|
54
|
-
* Maps Editor display names to the skills CLI agent name.
|
|
55
|
-
*/
|
|
56
|
-
function editorToSkillsAgent(editor) {
|
|
57
|
-
switch (editor) {
|
|
58
|
-
case "Cursor": return "cursor";
|
|
59
|
-
case "VS Code":
|
|
60
|
-
case "GitHub Copilot CLI": return "github-copilot";
|
|
61
|
-
case "Claude CLI": return "claude-code";
|
|
62
|
-
case "Codex": return "codex";
|
|
63
|
-
case "OpenCode": return "opencode";
|
|
64
|
-
case "Antigravity": return "antigravity";
|
|
65
|
-
case "Cline":
|
|
66
|
-
case "Cline CLI": return "cline";
|
|
67
|
-
case "Gemini CLI": return "gemini-cli";
|
|
68
|
-
case "Goose": return "goose";
|
|
69
|
-
case "Claude Desktop": return "claude-code";
|
|
70
|
-
case "MCPorter": return "mcporter";
|
|
71
|
-
case "Zed": return "zed";
|
|
72
|
-
default: return "";
|
|
73
|
-
}
|
|
74
|
-
}
|
|
75
|
-
/**
|
|
76
54
|
* Installs Neon agent skills using Vercel's skills CLI.
|
|
77
55
|
*/
|
|
78
|
-
async function installAgentSkills(
|
|
56
|
+
async function installAgentSkills(selectedAgents, options) {
|
|
79
57
|
const quiet = options?.json === true;
|
|
80
|
-
const
|
|
81
|
-
if (
|
|
58
|
+
const agentsWithSkills = selectedAgents.filter((id) => getSkillsAgentName(id) !== void 0);
|
|
59
|
+
if (agentsWithSkills.length === 0) return true;
|
|
82
60
|
const skillsSpinner = quiet ? null : spinner();
|
|
83
61
|
skillsSpinner?.start("Installing agent skills for Neon in this project...");
|
|
84
62
|
let anyFailed = false;
|
|
85
63
|
await ensureSkillsCli();
|
|
86
64
|
const skills = getSkillList(options?.preview);
|
|
87
|
-
for (const
|
|
88
|
-
const agentName =
|
|
65
|
+
for (const agent of agentsWithSkills) {
|
|
66
|
+
const agentName = getSkillsAgentName(agent);
|
|
67
|
+
if (!agentName) continue;
|
|
89
68
|
for (const skill of skills) try {
|
|
90
69
|
await execa("skills", [
|
|
91
70
|
"add",
|
|
@@ -101,7 +80,7 @@ async function installAgentSkills(selectedEditors, options) {
|
|
|
101
80
|
timeout: 12e4
|
|
102
81
|
});
|
|
103
82
|
} catch (error) {
|
|
104
|
-
if (!quiet) log.error(`Failed to install skill ${skill} for ${
|
|
83
|
+
if (!quiet) log.error(`Failed to install skill ${skill} for ${agent}: ${error instanceof Error ? error.message : "Unknown error"}`);
|
|
105
84
|
anyFailed = true;
|
|
106
85
|
}
|
|
107
86
|
}
|
|
@@ -126,9 +105,42 @@ const GLOBAL_SKILLS_DIRS = (() => {
|
|
|
126
105
|
"claude-code": [resolve(home, ".claude", "skills"), agentsDir],
|
|
127
106
|
"github-copilot": [resolve(home, ".vscode", "skills"), agentsDir],
|
|
128
107
|
codex: [resolve(home, ".codex", "skills"), agentsDir],
|
|
129
|
-
cline: [resolve(home, ".cline", "skills"), agentsDir]
|
|
108
|
+
cline: [resolve(home, ".cline", "skills"), agentsDir],
|
|
109
|
+
grok: [resolve(home, ".grok", "skills"), agentsDir]
|
|
130
110
|
};
|
|
131
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
|
+
}
|
|
132
144
|
/**
|
|
133
145
|
* Checks whether skills were recently updated (within the freshness window).
|
|
134
146
|
* Checks both project-level (skills-lock.json mtime) and global (skills dir mtime).
|
|
@@ -136,18 +148,16 @@ const GLOBAL_SKILLS_DIRS = (() => {
|
|
|
136
148
|
function skillsAreFresh(agent, requiredSkills) {
|
|
137
149
|
const now = Date.now();
|
|
138
150
|
const cwd = process.cwd();
|
|
139
|
-
const
|
|
140
|
-
|
|
141
|
-
".cursor",
|
|
142
|
-
".claude"
|
|
143
|
-
];
|
|
151
|
+
const skillsId = getSkillsAgentName(agent);
|
|
152
|
+
const projectDirs = (skillsId ? PROJECT_SKILLS_DIRS[skillsId] : void 0)?.map((dir) => resolve(cwd, dir)) ?? [];
|
|
144
153
|
const lockPath = resolve(cwd, "skills-lock.json");
|
|
145
|
-
if (existsSync(lockPath)) try {
|
|
154
|
+
if (existsSync(lockPath) && projectDirs.length > 0) try {
|
|
146
155
|
if (now - statSync(lockPath).mtimeMs < SKILLS_FRESHNESS_MS) {
|
|
147
|
-
if (requiredSkills.every((skill) =>
|
|
156
|
+
if (requiredSkills.every((skill) => projectDirs.some((dir) => existsSync(resolve(dir, skill, "SKILL.md"))))) return true;
|
|
148
157
|
}
|
|
149
158
|
} catch {}
|
|
150
159
|
const agentName = getSkillsAgentName(agent);
|
|
160
|
+
if (!agentName) return false;
|
|
151
161
|
const globalDirs = GLOBAL_SKILLS_DIRS[agentName] ?? [];
|
|
152
162
|
for (const dir of globalDirs) if (requiredSkills.every((skill) => existsSync(resolve(dir, skill, "SKILL.md")))) try {
|
|
153
163
|
if (now - statSync(resolve(dir, requiredSkills[0], "SKILL.md")).mtimeMs < SKILLS_FRESHNESS_MS) return true;
|
|
@@ -163,23 +173,15 @@ function skillsAreFresh(agent, requiredSkills) {
|
|
|
163
173
|
*/
|
|
164
174
|
async function ensureSkillsUpToDate(agent, scope, preview) {
|
|
165
175
|
const resolvedAgent = agent || "cursor";
|
|
166
|
-
const skills = getSkillList(preview);
|
|
167
|
-
if (skillsAreFresh(resolvedAgent, skills)) return true;
|
|
168
|
-
await ensureSkillsCli();
|
|
169
176
|
const agentName = getSkillsAgentName(resolvedAgent);
|
|
177
|
+
if (!agentName) return true;
|
|
178
|
+
if (skillsAreFresh(resolvedAgent, getSkillList(preview))) return true;
|
|
179
|
+
await ensureSkillsCli();
|
|
170
180
|
let allOk = true;
|
|
171
|
-
const
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
resolve(home, ".claude", "skills"),
|
|
176
|
-
resolve(home, ".agents", "skills")
|
|
177
|
-
] : [
|
|
178
|
-
resolve(cwd, ".cursor", "skills"),
|
|
179
|
-
resolve(cwd, ".claude", "skills"),
|
|
180
|
-
resolve(cwd, ".agents", "skills")
|
|
181
|
-
];
|
|
182
|
-
const missingSkills = skills.filter((skill) => !checkDirs.some((dir) => existsSync(resolve(dir, skill, "SKILL.md"))));
|
|
181
|
+
const missingSkills = missingSkillsForAgent(resolvedAgent, {
|
|
182
|
+
scope: scope ?? "project",
|
|
183
|
+
preview
|
|
184
|
+
});
|
|
183
185
|
if (missingSkills.length === 0) return true;
|
|
184
186
|
for (const skill of missingSkills) try {
|
|
185
187
|
await execa("skills", [
|
|
@@ -201,4 +203,4 @@ async function ensureSkillsUpToDate(agent, scope, preview) {
|
|
|
201
203
|
return allOk;
|
|
202
204
|
}
|
|
203
205
|
//#endregion
|
|
204
|
-
export { SKILL_REFERENCE_URLS, ensureSkillsUpToDate, getSkillList, installAgentSkills };
|
|
206
|
+
export { SKILL_REFERENCE_URLS, ensureSkillsUpToDate, getSkillList, installAgentSkills, missingSkillsForAgent, skillsInstalledForAgent };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "neon",
|
|
3
|
-
"version": "3.
|
|
3
|
+
"version": "3.5.1",
|
|
4
4
|
"description": "CLI tool for Neon, the cloud backend primitives built around Lakebase Postgres",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"neon",
|
|
@@ -46,6 +46,7 @@
|
|
|
46
46
|
"@clack/prompts": "0.10.1",
|
|
47
47
|
"@hono/node-server": "2.0.4",
|
|
48
48
|
"@segment/analytics-node": "1.3.0",
|
|
49
|
+
"add-mcp": "2.0.0",
|
|
49
50
|
"chalk": "5.3.0",
|
|
50
51
|
"chokidar": "5.0.0",
|
|
51
52
|
"cli-table": "0.3.11",
|
|
@@ -63,9 +64,9 @@
|
|
|
63
64
|
"yaml": "^2.9.0",
|
|
64
65
|
"yargs": "17.7.2",
|
|
65
66
|
"yoctocolors": "^2.1.2",
|
|
66
|
-
"@neon/config": "1.0.
|
|
67
|
-
"@neon/
|
|
68
|
-
"@neon/
|
|
67
|
+
"@neon/config-runtime": "1.0.1",
|
|
68
|
+
"@neon/config": "1.0.1",
|
|
69
|
+
"@neon/sdk": "2.1.0"
|
|
69
70
|
},
|
|
70
71
|
"optionalDependencies": {
|
|
71
72
|
"@napi-rs/keyring": "1.3.0",
|
|
@@ -97,8 +98,8 @@
|
|
|
97
98
|
"tsx": "4.22.3",
|
|
98
99
|
"typescript": "^5.9.0",
|
|
99
100
|
"vitest": "^3.0.9",
|
|
101
|
+
"@neon-internals/env-core": "0.0.1",
|
|
100
102
|
"@neon-internals/cli-core": "0.0.0",
|
|
101
|
-
"@neon-internals/env-core": "0.0.0",
|
|
102
103
|
"@neon/e2e-harness": "0.0.0"
|
|
103
104
|
},
|
|
104
105
|
"publishConfig": {
|