neon 8.11.4 → 8.12.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.
Files changed (35) hide show
  1. package/README.md +13 -0
  2. package/dist/_chunks/{checkout-CXIOydqZ.js → checkout-BGvlLc43.js} +3 -3
  3. package/dist/_chunks/{claim-DqAvAvDm.js → claim-DqzLXZMN.js} +1 -1
  4. package/dist/_chunks/{config-BH_qRHiG.js → config-BnwcGpbq.js} +1 -1
  5. package/dist/_chunks/{deploy-D7B0Xfjg.js → deploy-BCVcDR1O.js} +1 -1
  6. package/dist/_chunks/{dev-CSvxAecE.js → dev-mrJKMt2R.js} +3 -3
  7. package/dist/_chunks/{git-H32TNt6M.js → git-pN4kMH0d.js} +2 -2
  8. package/dist/_chunks/{link-BgtkOBws.js → link-DOM84hYj.js} +2 -2
  9. package/dist/_chunks/{skills-DyiBXKS3.js → skills-BP6pIm7O.js} +35 -4
  10. package/dist/_chunks/{status-DV8Z2kw3.js → status-DoNjIbKf.js} +1 -1
  11. package/dist/commands/checkout.js +1 -1
  12. package/dist/commands/claim.js +1 -1
  13. package/dist/commands/config.js +1 -1
  14. package/dist/commands/deploy.js +1 -1
  15. package/dist/commands/dev.js +1 -1
  16. package/dist/commands/env.js +1 -1
  17. package/dist/commands/functions_new.js +1 -1
  18. package/dist/commands/git.js +1 -1
  19. package/dist/commands/index.js +10 -10
  20. package/dist/commands/link.js +1 -1
  21. package/dist/commands/skills.js +1 -1
  22. package/dist/commands/status.js +1 -1
  23. package/dist/commands/subscribe.js +1 -1
  24. package/dist/index.js +13 -3
  25. package/dist/init/auth.js +1 -1
  26. package/dist/init/link.js +1 -1
  27. package/dist/init/run.js +3 -3
  28. package/dist/init/tooling.js +1 -1
  29. package/dist/skills/check_worker.js +17 -0
  30. package/dist/skills/freshness.js +349 -0
  31. package/dist/skills/run.js +1 -2
  32. package/dist/test_utils/fixtures.js +1 -0
  33. package/dist/update_notifier.js +15 -1
  34. package/package.json +7 -7
  35. package/dist/_chunks/{env-5HS5qQGd.js → env-uVRY4xah.js} +1 -1
package/README.md CHANGED
@@ -58,6 +58,13 @@ of them is Homebrew, the warning recommends `brew uninstall neonctl`, which
58
58
  leaves `neon` running the other install, such as npm. While duplicates remain, the update notice is held back;
59
59
  `NO_UPDATE_NOTIFIER=1` silences both.
60
60
 
61
+ Commands also warn once a day when Neon agent skills are behind their source, in this
62
+ directory's `skills-lock.json` or the user-level skills lock (see
63
+ [`neon skills status`](#install-neon-agent-skills-skills)). This warning runs without a
64
+ terminal too, so coding agents see it. It checks GitHub once a day in the background and
65
+ follows the same CI, machine-readable output, standalone binary, and
66
+ `NO_UPDATE_NOTIFIER=1` rules.
67
+
61
68
  ## Connect
62
69
 
63
70
  Run the following command to authenticate a connection to Neon:
@@ -973,8 +980,14 @@ $ neon skills --global
973
980
  $ neon skills update
974
981
  $ neon skills update -y
975
982
  $ neon skills update --global -y
983
+
984
+ # Check whether installed Neon skills are behind their source.
985
+ $ neon skills status
986
+ $ neon skills status --global
976
987
  ```
977
988
 
989
+ `neon skills status` compares each Neon skill in this directory's `skills-lock.json` (or, with `--global`, the user-level skills lock) with the skill folder on its source repository's default branch, and prints `up to date`, `outdated`, or `not found upstream` per skill. It reads GitHub anonymously; set `GITHUB_TOKEN` or `GH_TOKEN` if the GitHub API rate limit is reached. Skills installed through an agent plugin are not in either lock and are not listed.
990
+
978
991
  On a TTY the command asks which agents and which skills, then installs. Detected agents start selected from project-folder markers such as `.cursor`. Default skills start selected. `neon-postgres-agent-platforms` is offered and starts unselected.
979
992
 
980
993
  `-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.
@@ -11,9 +11,9 @@ import { looksLikeBranchId } from "../utils/formats.js";
11
11
  import { fillSingleProject, listAllProjectBranches } from "../utils/enrichers.js";
12
12
  import { buildCheckoutEvent, readGitContext } from "../utils/git.js";
13
13
  import { buildHookBranch, loadHookConfig, resolveHookEnv, runCheckoutAfterHook, runCheckoutBeforeHook, runCreateAfterHook, runCreateBeforeHook } from "../utils/hooks.js";
14
- import { n as autoPullEnvAfterPin } from "./env-5HS5qQGd.js";
15
- import { a as applyPolicyOnCreate, f as envFlag, l as createBranchFromPolicyOnCheckout } from "./config-BH_qRHiG.js";
16
- import { i as handler$1 } from "./link-BgtkOBws.js";
14
+ import { n as autoPullEnvAfterPin } from "./env-uVRY4xah.js";
15
+ import { a as applyPolicyOnCreate, f as envFlag, l as createBranchFromPolicyOnCheckout } from "./config-BnwcGpbq.js";
16
+ import { i as handler$1 } from "./link-DOM84hYj.js";
17
17
  import chalk from "chalk";
18
18
  import prompts from "prompts";
19
19
  //#region src/commands/checkout.ts
@@ -12,7 +12,7 @@ import { projectConfigPath } from "../project.js";
12
12
  import { resolveEnvFilePath } from "../env_file.js";
13
13
  import { writer } from "../writer.js";
14
14
  import { noPassthrough } from "../utils/flags.js";
15
- import { c as pull } from "./env-5HS5qQGd.js";
15
+ import { c as pull } from "./env-uVRY4xah.js";
16
16
  import { existsSync, readFileSync, unlinkSync, writeFileSync } from "node:fs";
17
17
  import open from "open";
18
18
  import prompts from "prompts";
@@ -23,7 +23,7 @@ import { readGitContext } from "../utils/git.js";
23
23
  import { resolveHookEnv, runDeployAfterHook, runDeployBeforeHook } from "../utils/hooks.js";
24
24
  import { pickServicesInteractively } from "../utils/service_picker.js";
25
25
  import { zipBundle } from "../utils/zip.js";
26
- import { n as autoPullEnvAfterPin } from "./env-5HS5qQGd.js";
26
+ import { n as autoPullEnvAfterPin } from "./env-uVRY4xah.js";
27
27
  import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
28
28
  import { dirname, join, relative, resolve } from "node:path";
29
29
  import chalk from "chalk";
@@ -1,6 +1,6 @@
1
1
  import { t as __exportAll } from "./rolldown-runtime-8H4AJuhK.js";
2
2
  import { fillSingleProject } from "../utils/enrichers.js";
3
- import { f as envFlag, i as applyFlags, p as envPullFlag, r as applyCmd } from "./config-BH_qRHiG.js";
3
+ import { f as envFlag, i as applyFlags, p as envPullFlag, r as applyCmd } from "./config-BnwcGpbq.js";
4
4
  import { DEPLOY_COMMANDS_EPILOGUE } from "../commands/deploy_help.js";
5
5
  //#region src/commands/deploy.ts
6
6
  var deploy_exports = /* @__PURE__ */ __exportAll({
@@ -4,12 +4,12 @@ import { isClaimableEnvTarget } from "../claimable/state.js";
4
4
  import { getCliName } from "../utils/cli_name.js";
5
5
  import { defaultDevEnvFiles, readEnvFile, resolveEnvFilePath } from "../env_file.js";
6
6
  import { l as functionBaseUrlKey } from "./env-BpP3Dqpe.js";
7
- import { branchIdResolve } from "../utils/enrichers.js";
8
- import { bundleEntry } from "../utils/esbuild.js";
9
7
  import { a as resolveDevEnv } from "./env-BNm69Qh8.js";
10
- import { f as envFlag } from "./config-BH_qRHiG.js";
11
8
  import { findConfigFunctionBySource, resolveFunctionsFromConfig } from "../dev/functions.js";
12
9
  import { resolveWatchInputs } from "../dev/inputs.js";
10
+ import { branchIdResolve } from "../utils/enrichers.js";
11
+ import { bundleEntry } from "../utils/esbuild.js";
12
+ import { f as envFlag } from "./config-BnwcGpbq.js";
13
13
  import { existsSync, mkdirSync, rmSync, statSync, writeFileSync } from "node:fs";
14
14
  import { basename, dirname, join, relative, resolve } from "node:path";
15
15
  import { fileURLToPath } from "node:url";
@@ -5,8 +5,8 @@ import { CONTEXT_FILE, contextBranch, gitBranchMap, gitBranchMapping, isUnfollow
5
5
  import { writer } from "../writer.js";
6
6
  import { neonSafeBranchName } from "../utils/branch_name.js";
7
7
  import { currentGitBranch, gitPull, gitRepoRoot, hasUpstream, installPostCheckoutHook, isGitRepo, isManagedHook, localGitBranches, postCheckoutHookPath, readGitContext, removePostCheckoutHook } from "../utils/git.js";
8
- import { o as quoteFlagValue } from "./link-BgtkOBws.js";
9
- import { o as handler$1 } from "./checkout-CXIOydqZ.js";
8
+ import { o as quoteFlagValue } from "./link-DOM84hYj.js";
9
+ import { o as handler$1 } from "./checkout-BGvlLc43.js";
10
10
  import { existsSync } from "node:fs";
11
11
  import { join } from "node:path";
12
12
  import chalk from "chalk";
@@ -13,8 +13,8 @@ import { writer } from "../writer.js";
13
13
  import { createBranch, pickBranchInteractively } from "../utils/branch_picker.js";
14
14
  import { looksLikeBranchId } from "../utils/formats.js";
15
15
  import { listAllProjectBranches } from "../utils/enrichers.js";
16
- import { n as autoPullEnvAfterPin } from "./env-5HS5qQGd.js";
17
- import { h as initCmd } from "./config-BH_qRHiG.js";
16
+ import { n as autoPullEnvAfterPin } from "./env-uVRY4xah.js";
17
+ import { h as initCmd } from "./config-BnwcGpbq.js";
18
18
  import { formatLinkSummary } from "../commands/link_output.js";
19
19
  import { r as REGIONS } from "./projects-BwI2Uo0z.js";
20
20
  import prompts from "prompts";
@@ -10,9 +10,10 @@ import { noPassthrough } from "../utils/flags.js";
10
10
  import { AGENT_SKILLS_SKIP_MESSAGE } from "../init/copy.js";
11
11
  import { canPickAgentsInteractively, skippableAgentPicker } from "../utils/agent_picker.js";
12
12
  import { skillsHelpValues, skillsYesHelp } from "../skills/catalog.js";
13
+ import { lockPath, neonSkillsUpdateCommand, readLockedSkills, refreshSkillsCheckCache, scopeLabel, skillsCheckCachePath, skillsStatusRows } from "../skills/freshness.js";
13
14
  import { confirmSkillsUpdate } from "../skills/wizard.js";
14
15
  import { assertSkillsCanRun, resolveSkillsPlan } from "../skills/plan.js";
15
- import { assertSkillsNode, neonSkillsRetryCommand, neonSkillsUpdateRetryCommand, runSkillsCli, skillsAddArgs, skillsMetadata, skillsUpdateArgs, skillsUpdateDetail, skillsUpdateHadNothing } from "../skills/run.js";
16
+ import { assertSkillsNode, neonSkillsRetryCommand, runSkillsCli, skillsAddArgs, skillsMetadata, skillsUpdateArgs, skillsUpdateDetail, skillsUpdateHadNothing } from "../skills/run.js";
16
17
  //#region src/commands/skills.ts
17
18
  var skills_exports = /* @__PURE__ */ __exportAll({
18
19
  aliases: () => aliases,
@@ -24,7 +25,6 @@ var skills_exports = /* @__PURE__ */ __exportAll({
24
25
  reportSkillsInstall: () => reportSkillsInstall,
25
26
  skillsInstallError: () => skillsInstallError
26
27
  });
27
- const scopeLabel = (scope) => scope === "project" ? "this directory" : "user-level";
28
28
  const coerceAgents = (value) => {
29
29
  if (value === void 0) return [];
30
30
  const list = Array.isArray(value) ? value : [value];
@@ -60,7 +60,17 @@ const builder = (argv) => argv.usage("$0 skills [command] [options]").command("u
60
60
  const skills = args.skill;
61
61
  if (Array.isArray(skills) && skills.length > 0) throw new Error("neon skills update does not take --skill. It refreshes every installed skill in this directory (or --global).");
62
62
  return true;
63
- }), (args) => updateHandler(args)).options({
63
+ }), (args) => updateHandler(args)).command("status", "Check whether installed Neon skills match their source", (yargs) => yargs.usage("$0 skills status [options]").options({ global: {
64
+ type: "boolean",
65
+ default: false,
66
+ describe: "Check user-level skills (skills CLI -g). Default is this directory"
67
+ } }).example("$0 skills status", "Neon skills in this directory's skills-lock.json").example("$0 skills status --global", "User-level Neon skills").strict().hide("agent").hide("skill").hide("yes").check((args) => {
68
+ noPassthrough("skills status")(args);
69
+ const agents = args.agent;
70
+ const skills = args.skill;
71
+ if (Array.isArray(agents) && agents.length > 0 || Array.isArray(skills) && skills.length > 0) throw new Error("neon skills status does not take --agent or --skill. It checks every Neon skill in this directory (or --global).");
72
+ return true;
73
+ }), (args) => statusHandler(args)).options({
64
74
  yes: {
65
75
  alias: "y",
66
76
  type: "boolean",
@@ -249,7 +259,28 @@ const updateHandler = async (props) => {
249
259
  title: "Skills"
250
260
  });
251
261
  out.end();
252
- if (failure !== void 0) throw new Error(failure.includes("needs npx (Node.js)") || failure.includes("needs Node.js") ? failure : `${failure}\nRetry with: ${neonSkillsUpdateRetryCommand(scope === "global")}`);
262
+ if (failure !== void 0) throw new Error(failure.includes("needs npx (Node.js)") || failure.includes("needs Node.js") ? failure : `${failure}\nRetry with: ${neonSkillsUpdateCommand(scope)}`);
263
+ };
264
+ const statusHandler = async (props) => {
265
+ const scope = props.global === true ? "global" : "project";
266
+ const skills = readLockedSkills(lockPath(scope, process.cwd()), scope);
267
+ const folders = skills.length === 0 ? {} : await refreshSkillsCheckCache({
268
+ cachePath: skillsCheckCachePath(props.configDir),
269
+ skills
270
+ });
271
+ const rows = skillsStatusRows(skills, folders);
272
+ const out = writer(props);
273
+ out.write(rows, {
274
+ fields: [
275
+ "scope",
276
+ "skill",
277
+ "status"
278
+ ],
279
+ title: "Skills",
280
+ emptyMessage: scope === "project" ? "No Neon skills in this directory's skills-lock.json. For user-level skills, run: neon skills status --global" : "No user-level Neon skills in the skills CLI lock."
281
+ });
282
+ out.end();
283
+ if (rows.some((row) => row.status === "outdated")) log.info("Update them: %s", neonSkillsUpdateCommand(scope));
253
284
  };
254
285
  //#endregion
255
286
  export { handler as a, skillsInstallError as c, describe as i, skills_exports as l, builder as n, installSkills as o, command as r, reportSkillsInstall as s, aliases as t };
@@ -1,6 +1,6 @@
1
1
  import { t as __exportAll } from "./rolldown-runtime-8H4AJuhK.js";
2
2
  import { fillSingleProject } from "../utils/enrichers.js";
3
- import { y as status } from "./config-BH_qRHiG.js";
3
+ import { y as status } from "./config-BnwcGpbq.js";
4
4
  //#region src/commands/status.ts
5
5
  var status_exports = /* @__PURE__ */ __exportAll({
6
6
  builder: () => builder,
@@ -1 +1 @@
1
- export { t as builder, r as command, i as describe, a as formatCheckoutPolicyFailure, o as handler } from "../_chunks/checkout-CXIOydqZ.js";
1
+ export { t as builder, r as command, i as describe, a as formatCheckoutPolicyFailure, o as handler } from "../_chunks/checkout-BGvlLc43.js";
@@ -1 +1 @@
1
- export { t as CLAIM_CREATE_FIELDS, n as CLAIM_LIST_FIELDS, r as aliases, i as builder, a as claimCreateFields, s as claimableCapabilities, c as command, l as create, u as describe, d as handler } from "../_chunks/claim-DqAvAvDm.js";
1
+ export { t as CLAIM_CREATE_FIELDS, n as CLAIM_LIST_FIELDS, r as aliases, i as builder, a as claimCreateFields, s as claimableCapabilities, c as command, l as create, u as describe, d as handler } from "../_chunks/claim-DqzLXZMN.js";
@@ -1 +1 @@
1
- export { t as ConfigInstallFailed, n as addCmd, r as applyCmd, i as applyFlags, a as applyPolicyOnCreate, o as builder, s as command, l as createBranchFromPolicyOnCheckout, u as describe, d as ensureConfigPackages, f as envFlag, p as envPullFlag, m as handler, h as initCmd, g as installCommandFrom, _ as missingDependencies, v as planCmd, y as status } from "../_chunks/config-BH_qRHiG.js";
1
+ export { t as ConfigInstallFailed, n as addCmd, r as applyCmd, i as applyFlags, a as applyPolicyOnCreate, o as builder, s as command, l as createBranchFromPolicyOnCheckout, u as describe, d as ensureConfigPackages, f as envFlag, p as envPullFlag, m as handler, h as initCmd, g as installCommandFrom, _ as missingDependencies, v as planCmd, y as status } from "../_chunks/config-BnwcGpbq.js";
@@ -1 +1 @@
1
- export { t as builder, n as command, i as describe, a as handler } from "../_chunks/deploy-D7B0Xfjg.js";
1
+ export { t as builder, n as command, i as describe, a as handler } from "../_chunks/deploy-BCVcDR1O.js";
@@ -1 +1 @@
1
- export { t as builder, n as captureShellEnv, r as command, i as describe, a as devBundleDir, o as devEnvContext, c as diffUnits, l as formatEnvFiles, u as formatEnvSummary, d as handler, f as loadDevEnvFiles, p as localFunctionUrlEnv, m as planFunctionsToUnits, h as reportDevCredential, g as sourceFunctionUrlOverlay } from "../_chunks/dev-CSvxAecE.js";
1
+ export { t as builder, n as captureShellEnv, r as command, i as describe, a as devBundleDir, o as devEnvContext, c as diffUnits, l as formatEnvFiles, u as formatEnvSummary, d as handler, f as loadDevEnvFiles, p as localFunctionUrlEnv, m as planFunctionsToUnits, h as reportDevCredential, g as sourceFunctionUrlOverlay } from "../_chunks/dev-mrJKMt2R.js";
@@ -1 +1 @@
1
- export { t as ENV_PULL_SKIPPED_HINT, n as autoPullEnvAfterPin, r as builder, i as command, a as describe, s as handler, c as pull } from "../_chunks/env-5HS5qQGd.js";
1
+ export { t as ENV_PULL_SKIPPED_HINT, n as autoPullEnvAfterPin, r as builder, i as command, a as describe, s as handler, c as pull } from "../_chunks/env-uVRY4xah.js";
@@ -8,7 +8,7 @@ import { writer } from "../writer.js";
8
8
  import { formatInitDone, printInitDone } from "../init/chrome.js";
9
9
  import { FUNCTION_SLUG_PATTERN, deriveFunctionSlug } from "../utils/function_slug.js";
10
10
  import { displayPath } from "./env_output.js";
11
- import { _ as missingDependencies, d as ensureConfigPackages, g as installCommandFrom } from "../_chunks/config-BH_qRHiG.js";
11
+ import { _ as missingDependencies, d as ensureConfigPackages, g as installCommandFrom } from "../_chunks/config-BnwcGpbq.js";
12
12
  import { recommendedOperations } from "../functions/registry.js";
13
13
  import { collidingFiles, operationsFromFlags, planConfigRegistration, planEnvExample, planScaffold, writeScaffold } from "../functions/scaffold.js";
14
14
  import { describeRegistrySource, openRegistry } from "../functions/source.js";
@@ -1 +1 @@
1
- export { t as aliases, n as builder, r as cleanup, i as command, a as describe, s as handler, c as install, l as partitionBranchesToPrune, u as status, d as sync, f as uninstall } from "../_chunks/git-H32TNt6M.js";
1
+ export { t as aliases, n as builder, r as cleanup, i as command, a as describe, s as handler, c as install, l as partitionBranchesToPrune, u as status, d as sync, f as uninstall } from "../_chunks/git-pN4kMH0d.js";
@@ -2,29 +2,29 @@ import { r as auth_exports } from "../_chunks/auth-CIUtXByB.js";
2
2
  import { t as api_exports } from "../_chunks/api-GbEaBZJ5.js";
3
3
  import { n as api_keys_exports } from "../_chunks/api_keys-CNnnugbQ.js";
4
4
  import { n as ask_exports } from "../_chunks/ask-DVwy0eUL.js";
5
- import { o as env_exports } from "../_chunks/env-5HS5qQGd.js";
6
- import { c as config_exports } from "../_chunks/config-BH_qRHiG.js";
5
+ import { o as env_exports } from "../_chunks/env-uVRY4xah.js";
6
+ import { c as config_exports } from "../_chunks/config-BnwcGpbq.js";
7
7
  import { u as projects_exports } from "../_chunks/projects-BwI2Uo0z.js";
8
- import { a as link_exports } from "../_chunks/link-BgtkOBws.js";
8
+ import { a as link_exports } from "../_chunks/link-DOM84hYj.js";
9
9
  import { o as mcp_exports } from "../_chunks/mcp-Bo-7TKc7.js";
10
10
  import { c as plugins_exports } from "../_chunks/plugins-DqD7BDO1.js";
11
- import { l as skills_exports } from "../_chunks/skills-DyiBXKS3.js";
11
+ import { l as skills_exports } from "../_chunks/skills-BP6pIm7O.js";
12
12
  import { t as bootstrap_exports } from "../_chunks/bootstrap-wPZWa1sT.js";
13
13
  import { r as branches_exports } from "../_chunks/branches-BNa1xX1L.js";
14
14
  import { n as bucket_exports } from "../_chunks/bucket-BCdryTkD.js";
15
- import { n as checkout_exports } from "../_chunks/checkout-CXIOydqZ.js";
16
- import { o as claim_exports } from "../_chunks/claim-DqAvAvDm.js";
15
+ import { n as checkout_exports } from "../_chunks/checkout-BGvlLc43.js";
16
+ import { o as claim_exports } from "../_chunks/claim-DqzLXZMN.js";
17
17
  import { a as connection_string_exports } from "../_chunks/connection_string-DB4awPzZ.js";
18
18
  import { a as credentials_exports } from "../_chunks/credentials-BSjE9fRR.js";
19
19
  import { r as data_api_exports } from "../_chunks/data_api-Djxj5ZQD.js";
20
20
  import { o as databases_exports } from "../_chunks/databases-Dok4wru7.js";
21
- import { r as deploy_exports } from "../_chunks/deploy-D7B0Xfjg.js";
22
- import { s as dev_exports } from "../_chunks/dev-CSvxAecE.js";
21
+ import { r as deploy_exports } from "../_chunks/deploy-BCVcDR1O.js";
22
+ import { s as dev_exports } from "../_chunks/dev-mrJKMt2R.js";
23
23
  import { i as diff_exports } from "../_chunks/diff-DhxxbdQE.js";
24
24
  import { c as docs_exports } from "../_chunks/docs-CrVybkTP.js";
25
25
  import { o as feedback_exports } from "../_chunks/feedback-CMayyTa8.js";
26
26
  import { o as functions_exports } from "../_chunks/functions-Q0zuvtnk.js";
27
- import { o as git_exports } from "../_chunks/git-H32TNt6M.js";
27
+ import { o as git_exports } from "../_chunks/git-pN4kMH0d.js";
28
28
  import { a as init_exports } from "../_chunks/init-BkA6RilG.js";
29
29
  import { o as inspect_exports } from "../_chunks/inspect-g0wekylj.js";
30
30
  import { a as ip_allow_exports } from "../_chunks/ip_allow-5lLFvIuE.js";
@@ -39,7 +39,7 @@ import { s as realtime_exports } from "../_chunks/realtime-BRqIyGR-.js";
39
39
  import { l as roles_exports } from "../_chunks/roles-B4w3YT_7.js";
40
40
  import { a as set_context_exports } from "../_chunks/set_context-DbrwoImy.js";
41
41
  import { s as snapshots_exports } from "../_chunks/snapshots-RJBzW8GM.js";
42
- import { a as status_exports } from "../_chunks/status-DV8Z2kw3.js";
42
+ import { a as status_exports } from "../_chunks/status-DoNjIbKf.js";
43
43
  import { d as triggers_exports } from "../_chunks/triggers-BrRo1z4b.js";
44
44
  import { s as user_exports } from "../_chunks/user-dlOGXbX9.js";
45
45
  import { i as vpc_endpoints_exports } from "../_chunks/vpc_endpoints-CNb0YfMo.js";
@@ -1 +1 @@
1
- export { t as builder, n as command, r as describe, i as handler, o as quoteFlagValue, s as runLink, c as shouldOfferConfigInit } from "../_chunks/link-BgtkOBws.js";
1
+ export { t as builder, n as command, r as describe, i as handler, o as quoteFlagValue, s as runLink, c as shouldOfferConfigInit } from "../_chunks/link-DOM84hYj.js";
@@ -1 +1 @@
1
- export { t as aliases, n as builder, r as command, i as describe, a as handler, o as installSkills, s as reportSkillsInstall, c as skillsInstallError } from "../_chunks/skills-DyiBXKS3.js";
1
+ export { t as aliases, n as builder, r as command, i as describe, a as handler, o as installSkills, s as reportSkillsInstall, c as skillsInstallError } from "../_chunks/skills-BP6pIm7O.js";
@@ -1 +1 @@
1
- export { t as builder, n as command, r as describe, i as handler } from "../_chunks/status-DV8Z2kw3.js";
1
+ export { t as builder, n as command, r as describe, i as handler } from "../_chunks/status-DoNjIbKf.js";
@@ -1,6 +1,6 @@
1
1
  import { writer } from "../writer.js";
2
- import { branchIdFromProps } from "../utils/enrichers.js";
3
2
  import { o as resolveNeonEnvVars } from "../_chunks/env-BNm69Qh8.js";
3
+ import { branchIdFromProps } from "../utils/enrichers.js";
4
4
  import YAML from "yaml";
5
5
  //#region src/commands/subscribe.ts
6
6
  const builder = (argv) => argv.usage("$0 realtime subscribe \"<query>\" [options]").positional("query", {
package/dist/index.js CHANGED
@@ -15,7 +15,7 @@ import { UsageError, failOnUsageError, isBareParentCommand, showHelp, showUsageE
15
15
  import { lazyCommands, preloadCommand } from "./commands/manifest.js";
16
16
  import { rewriteUnknownAgentArg } from "./init/plan.js";
17
17
  import { recoverFromSSO } from "./sso_recovery.js";
18
- import { notifyIfUpdateAvailable } from "./update_notifier.js";
18
+ import { notifyIfUpdateAvailable, shouldRunSkillsNotifier } from "./update_notifier.js";
19
19
  import { fillInArgs, resolveApiKeyFromEnv } from "./utils/middlewares.js";
20
20
  import { collapseRepeatedFlags, declaredOptions } from "./utils/repeated_flags.js";
21
21
  import { stagingFeaturesEnabledForArgv } from "./utils/staging.js";
@@ -214,13 +214,23 @@ async function handleError(msg, err, canRetry) {
214
214
  process.exit(0);
215
215
  }
216
216
  await closeAnalytics();
217
- notifyIfUpdateAvailable({
217
+ const notifier = {
218
218
  commandPath: args._.map(String),
219
219
  configDir: typeof args.configDir === "string" ? args.configDir : defaultDir,
220
220
  currentBranch: Boolean(args.currentBranch),
221
- currentVersion: pkg_default.version,
222
221
  output: typeof args.output === "string" ? args.output : "table"
222
+ };
223
+ notifyIfUpdateAvailable({
224
+ ...notifier,
225
+ currentVersion: pkg_default.version
223
226
  });
227
+ if (shouldRunSkillsNotifier(notifier)) {
228
+ const { notifyIfSkillsOutdated } = await import("./skills/freshness.js");
229
+ notifyIfSkillsOutdated({
230
+ configDir: notifier.configDir,
231
+ cwd: process.cwd()
232
+ });
233
+ }
224
234
  break;
225
235
  } catch (err) {
226
236
  takeCommandSuccessExtras();
package/dist/init/auth.js CHANGED
@@ -4,7 +4,7 @@ import "../config.js";
4
4
  import "../auth.js";
5
5
  import { c as ensureAuth } from "../_chunks/auth-CIUtXByB.js";
6
6
  import { recoverFrom401 } from "../auth_recovery.js";
7
- import { c as pull } from "../_chunks/env-5HS5qQGd.js";
7
+ import { c as pull } from "../_chunks/env-uVRY4xah.js";
8
8
  import { c as setupNeonMcp } from "../_chunks/mcp-Bo-7TKc7.js";
9
9
  //#region src/init/auth.ts
10
10
  const isUnauthorized = (error) => isNeonApiError(error) && error.status === 401;
package/dist/init/link.js CHANGED
@@ -4,7 +4,7 @@ import "../config.js";
4
4
  import "../auth.js";
5
5
  import { c as ensureAuth } from "../_chunks/auth-CIUtXByB.js";
6
6
  import { recoverFrom401 } from "../auth_recovery.js";
7
- import { s as runLink } from "../_chunks/link-BgtkOBws.js";
7
+ import { s as runLink } from "../_chunks/link-DOM84hYj.js";
8
8
  //#region src/init/link.ts
9
9
  const isUnauthorized = (error) => isNeonApiError(error) && error.status === 401;
10
10
  const authenticationFailed = () => /* @__PURE__ */ new Error("Authentication failed.");
package/dist/init/run.js CHANGED
@@ -17,15 +17,15 @@ import { FALLBACK_SKILLS_AGENTS, assertNamedAgentTooling, funnelAgentSetup, plan
17
17
  import { inferPackageManager, resolvePackageManager } from "../utils/package_manager.js";
18
18
  import { CANCELLED_BODY, CLAIMABLE_ACCOUNT_FLAGS, CLAIMABLE_ALREADY_LINKED, CLAIMABLE_MCP_API_KEY, CLAIMABLE_NO_LINK, MCP_API_KEY_NEEDS_AUTH, MCP_SCOPED_NEEDS_PROJECT, NON_TTY_LINK_NEEDS_AUTH, NO_AGENTS_FALLBACK_STATUS, PROGRESS, PROJECT_PINNED_OFFLINE, claimableNext, claimableThenServicesNext, configInitNext, envPullFailedNext, existingConfigNext, installFailedNext, mcpAfterLinkNext, mcpConfigLocationSkipped, mcpConfigLocationUnavailable, mcpKeyFailedNext, namedAgentsUnavailable, newConfigNext, offlinePinNext, skippedLinkNext, unattendedUnauthedNext } from "./copy.js";
19
19
  import { agentsRowValue, configRowValue, formatInitDone, headingForKind, printInitBanner, printInitDone, printInitProgress, projectRowValue, shouldPrintInitBanner } from "./chrome.js";
20
- import { h as initCmd, t as ConfigInstallFailed } from "../_chunks/config-BH_qRHiG.js";
21
- import { o as quoteFlagValue, s as runLink } from "../_chunks/link-BgtkOBws.js";
20
+ import { h as initCmd, t as ConfigInstallFailed } from "../_chunks/config-BnwcGpbq.js";
21
+ import { o as quoteFlagValue, s as runLink } from "../_chunks/link-DOM84hYj.js";
22
22
  import { runAuthenticatedLink } from "./link.js";
23
23
  import { CannotMintApiKeyError } from "../mcp/mint.js";
24
24
  import { pullInitEnv } from "./auth.js";
25
25
  import { InitCancelled, raceSigint, restoreCursor } from "./cancelled.js";
26
26
  import { pickAgentSetupInteractively, pickInitAgentsInteractively, pickInitConfigInteractively, pickInitMcpAuthInteractively, pickInitMcpConfigLocationInteractively, pickInitModeInteractively, pickInitPackageManagerInteractively, pickInitProjectSetupInteractively, pickInitServicesInteractively, pickInitSkillsInteractively } from "./wizard.js";
27
27
  import { runToolingSteps } from "./tooling.js";
28
- import { l as create } from "../_chunks/claim-DqAvAvDm.js";
28
+ import { l as create } from "../_chunks/claim-DqzLXZMN.js";
29
29
  import { INIT_CONFIG_SERVICES_CONFLICT, configPlanFromResolution, initEnvPullAfterConfig, resolveInitConfigChoice } from "./choices.js";
30
30
  import { detectInitEnvironment } from "./detect.js";
31
31
  import { createInitFunnel, emitInitStart, finishInitFunnel, initStartProperties } from "./funnel.js";
@@ -7,7 +7,7 @@ import { detectAgent } from "./detect_host.js";
7
7
  import { AgentSelectionSkipped, canPickAgentsInteractively } from "../utils/agent_picker.js";
8
8
  import { a as mcpInstallError, s as reportMcpInstall } from "../_chunks/mcp-Bo-7TKc7.js";
9
9
  import { l as reportPluginsInstall, o as installPlugins, s as pluginsInstallError } from "../_chunks/plugins-DqD7BDO1.js";
10
- import { c as skillsInstallError, o as installSkills, s as reportSkillsInstall } from "../_chunks/skills-DyiBXKS3.js";
10
+ import { c as skillsInstallError, o as installSkills, s as reportSkillsInstall } from "../_chunks/skills-BP6pIm7O.js";
11
11
  import { runAuthenticatedMcp } from "./auth.js";
12
12
  import { raceSigint } from "./cancelled.js";
13
13
  import { pickAgentSetupInteractively } from "./wizard.js";
@@ -0,0 +1,17 @@
1
+ import { globalLockPath, projectLockPath, readLockedSkills, refreshSkillsCheckCache } from "./freshness.js";
2
+ import { pathToFileURL } from "node:url";
3
+ //#region src/skills/check_worker.ts
4
+ const run = async () => {
5
+ const [cachePath, cwd] = process.argv.slice(2);
6
+ if (cachePath === void 0 || cwd === void 0) return;
7
+ try {
8
+ await refreshSkillsCheckCache({
9
+ cachePath,
10
+ skills: [...readLockedSkills(globalLockPath(), "global"), ...readLockedSkills(projectLockPath(cwd), "project")]
11
+ });
12
+ } catch {}
13
+ };
14
+ const entry = process.argv[1];
15
+ if (entry !== void 0 && import.meta.url === pathToFileURL(entry).href) run();
16
+ //#endregion
17
+ export {};
@@ -0,0 +1,349 @@
1
+ import { log } from "../log.js";
2
+ import { writeJsonFile } from "../utils/json_file.js";
3
+ import { showNotice } from "../notices.js";
4
+ import { AGENT_SKILLS_SOURCE, PLATFORMS_SKILLS_SOURCE } from "./catalog.js";
5
+ import { closeSync, constants, existsSync, fstatSync, openSync, readFileSync, statSync } from "node:fs";
6
+ import { join } from "node:path";
7
+ import { createHash } from "node:crypto";
8
+ import { fileURLToPath } from "node:url";
9
+ import { homedir } from "node:os";
10
+ import { spawn } from "node:child_process";
11
+ //#region src/skills/freshness.ts
12
+ const DAY_MS = 864e5;
13
+ const CACHE_FILE = "skills-check.json";
14
+ const FETCH_TIMEOUT_MS = 1e4;
15
+ const NEON_SOURCES = /* @__PURE__ */ new Set([AGENT_SKILLS_SOURCE, PLATFORMS_SKILLS_SOURCE]);
16
+ const scopeLabel = (scope) => scope === "project" ? "this directory" : "user-level";
17
+ const neonSkillsUpdateCommand = (scope) => scope === "global" ? "neon skills update --global -y" : "neon skills update -y";
18
+ /** Where the skills CLI keeps its user-level lock. */
19
+ const globalLockPath = (env = process.env) => env.XDG_STATE_HOME ? join(env.XDG_STATE_HOME, "skills", ".skill-lock.json") : join(homedir(), ".agents", ".skill-lock.json");
20
+ const projectLockPath = (cwd) => join(cwd, "skills-lock.json");
21
+ const lockPath = (scope, cwd) => scope === "global" ? globalLockPath() : projectLockPath(cwd);
22
+ const isRecord = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
23
+ /** The Agent Skills name rule. A lock is repository content, and names reach the terminal. */
24
+ const SKILL_NAME = /^[a-z0-9][a-z0-9-]{0,63}$/;
25
+ const skillFolder = (skillPath) => skillPath.replace(/\\/g, "/").replace(/\/?SKILL\.md$/i, "");
26
+ /**
27
+ * Entries pinned to a `ref` are left out: `neon skills` never pins one, and a pinned skill
28
+ * tracks that ref rather than the repository's default branch.
29
+ *
30
+ * The global lock's `skillFolderHash` is usually a tree SHA, but the skills CLI stores a
31
+ * content SHA-256 there when it installed from a Git clone; it tells them apart by length.
32
+ */
33
+ const parseSkillsLock = (raw, scope) => {
34
+ const value = JSON.parse(raw);
35
+ if (!isRecord(value) || !isRecord(value.skills)) return [];
36
+ const field = scope === "global" ? "skillFolderHash" : "computedHash";
37
+ const skills = [];
38
+ for (const [skill, entry] of Object.entries(value.skills)) {
39
+ if (!isRecord(entry)) continue;
40
+ const { source, skillPath, ref } = entry;
41
+ const hash = entry[field];
42
+ if (!SKILL_NAME.test(skill) || typeof source !== "string" || !NEON_SOURCES.has(source) || ref !== void 0 || typeof skillPath !== "string" || typeof hash !== "string" || hash === "") continue;
43
+ const folder = skillFolder(skillPath);
44
+ if (folder === "") continue;
45
+ const hashKind = scope === "global" && /^[0-9a-f]{40}$/i.test(hash) ? "tree" : "content";
46
+ skills.push({
47
+ scope,
48
+ skill,
49
+ source,
50
+ folder,
51
+ hash,
52
+ hashKind
53
+ });
54
+ }
55
+ return skills;
56
+ };
57
+ const readLockedSkills = (path, scope) => {
58
+ if (!existsSync(path)) return [];
59
+ const raw = readLockFile(path);
60
+ try {
61
+ return parseSkillsLock(raw, scope);
62
+ } catch {
63
+ throw new Error(`${path} is not valid JSON.`);
64
+ }
65
+ };
66
+ const MAX_LOCK_BYTES = 1048576;
67
+ /**
68
+ * A project lock is repository content: a FIFO would block the open and `/dev/zero` the read,
69
+ * on every command run in that directory.
70
+ */
71
+ const readLockFile = (path) => {
72
+ const fd = openSync(path, constants.O_RDONLY | (constants.O_NONBLOCK ?? 0));
73
+ try {
74
+ const stat = fstatSync(fd);
75
+ if (!stat.isFile()) throw new Error(`${path} is not a regular file.`);
76
+ if (stat.size > MAX_LOCK_BYTES) throw new Error(`${path} is larger than ${MAX_LOCK_BYTES} bytes.`);
77
+ return readFileSync(fd, "utf8");
78
+ } finally {
79
+ closeSync(fd);
80
+ }
81
+ };
82
+ const folderKey = ({ source, folder }) => `${source}:${folder}`;
83
+ /** `undefined` when the remote side has not been fetched yet. */
84
+ const skillStatus = (skill, remote) => {
85
+ if (remote?.missing === true) return "not found upstream";
86
+ const latest = skill.hashKind === "tree" ? remote?.tree : remote?.content;
87
+ if (latest === void 0) return void 0;
88
+ return latest === skill.hash ? "up to date" : "outdated";
89
+ };
90
+ const parseRemoteFolder = (value) => {
91
+ if (!isRecord(value)) return void 0;
92
+ const { tree, content, missing, pending } = value;
93
+ if (tree !== void 0 && typeof tree !== "string" || content !== void 0 && typeof content !== "string" || missing !== void 0 && missing !== true || pending !== void 0 && pending !== true) return;
94
+ return {
95
+ ...tree === void 0 ? {} : { tree },
96
+ ...content === void 0 ? {} : { content },
97
+ ...missing === void 0 ? {} : { missing },
98
+ ...pending === void 0 ? {} : { pending }
99
+ };
100
+ };
101
+ const parseSkillsCheckCache = (raw) => {
102
+ try {
103
+ const value = JSON.parse(raw);
104
+ if (!isRecord(value) || typeof value.checkedAt !== "number" || !Number.isFinite(value.checkedAt) || !isRecord(value.folders)) return;
105
+ const folders = {};
106
+ for (const [key, entry] of Object.entries(value.folders)) {
107
+ const folder = parseRemoteFolder(entry);
108
+ if (folder !== void 0) folders[key] = folder;
109
+ }
110
+ const { attemptedAt } = value;
111
+ return {
112
+ checkedAt: value.checkedAt,
113
+ ...typeof attemptedAt === "number" && Number.isFinite(attemptedAt) ? { attemptedAt } : {},
114
+ folders
115
+ };
116
+ } catch {
117
+ return;
118
+ }
119
+ };
120
+ const readSkillsCheckCache = (cachePath) => {
121
+ try {
122
+ return parseSkillsCheckCache(readFileSync(cachePath, "utf8"));
123
+ } catch {
124
+ return;
125
+ }
126
+ };
127
+ const skillsCheckCachePath = (configDir) => join(configDir, CACHE_FILE);
128
+ /**
129
+ * The digest the skills CLI writes to `computedHash`: every file's folder-relative path and
130
+ * then its bytes, in `localeCompare` order of path.
131
+ */
132
+ const skillContentHash = (files) => {
133
+ const hash = createHash("sha256");
134
+ for (const file of [...files].sort((a, b) => a.path.localeCompare(b.path))) {
135
+ hash.update(file.path);
136
+ hash.update(file.bytes);
137
+ }
138
+ return hash.digest("hex");
139
+ };
140
+ const gitBlobSha = (bytes) => createHash("sha1").update(`blob ${bytes.byteLength}\0`).update(bytes).digest("hex");
141
+ const isTreeEntry = (value) => isRecord(value) && typeof value.path === "string" && typeof value.mode === "string" && typeof value.type === "string" && typeof value.sha === "string";
142
+ const githubHeaders = (env) => {
143
+ const token = env.GITHUB_TOKEN || env.GH_TOKEN;
144
+ return {
145
+ Accept: "application/vnd.github+json",
146
+ "User-Agent": "neon-cli",
147
+ ...token ? { Authorization: `Bearer ${token}` } : {}
148
+ };
149
+ };
150
+ const fetchTree = async (source, env) => {
151
+ const response = await fetch(`https://api.github.com/repos/${source}/git/trees/HEAD?recursive=1`, {
152
+ headers: githubHeaders(env),
153
+ signal: AbortSignal.timeout(FETCH_TIMEOUT_MS)
154
+ });
155
+ if (!response.ok) {
156
+ if ((response.status === 403 || response.status === 429) && response.headers.get("x-ratelimit-remaining") === "0") throw new Error(`GitHub API rate limit reached while checking ${source}. Set GITHUB_TOKEN and retry.`);
157
+ throw new Error(`GitHub returned ${response.status} for the file tree of ${source}.`);
158
+ }
159
+ const body = await response.json();
160
+ if (!isRecord(body) || !Array.isArray(body.tree)) throw new Error(`GitHub returned an unexpected file tree for ${source}.`);
161
+ if (body.truncated === true) throw new Error(`GitHub truncated the file tree of ${source}.`);
162
+ return body.tree.filter(isTreeEntry);
163
+ };
164
+ /** The files the skills CLI hashes: regular files, outside `.git` and `node_modules`. */
165
+ const folderFiles = (tree, folder) => tree.filter((entry) => {
166
+ if (entry.type !== "blob" || entry.mode === "120000") return false;
167
+ if (!entry.path.startsWith(`${folder}/`)) return false;
168
+ const dirs = entry.path.slice(folder.length + 1).split("/").slice(0, -1);
169
+ return !dirs.includes(".git") && !dirs.includes("node_modules");
170
+ });
171
+ const fetchContentHash = async (source, folder, tree) => {
172
+ const files = await Promise.all(folderFiles(tree, folder).map(async (entry) => {
173
+ const path = entry.path.split("/").map(encodeURIComponent).join("/");
174
+ const response = await fetch(`https://raw.githubusercontent.com/${source}/HEAD/${path}`, { signal: AbortSignal.timeout(FETCH_TIMEOUT_MS) });
175
+ if (!response.ok) throw new Error(`GitHub returned ${response.status} for ${entry.path} in ${source}.`);
176
+ const bytes = new Uint8Array(await response.arrayBuffer());
177
+ if (gitBlobSha(bytes) !== entry.sha) throw new Error(`${source} changed during the check. Retry.`);
178
+ return {
179
+ path: entry.path.slice(folder.length + 1),
180
+ bytes
181
+ };
182
+ }));
183
+ return skillContentHash(files);
184
+ };
185
+ /**
186
+ * The folders to fetch: the given skills, plus every folder already in the cache so
187
+ * results for other directories stay current.
188
+ */
189
+ const folderRequests = (skills, previous) => {
190
+ const requests = /* @__PURE__ */ new Map();
191
+ const add = (request) => {
192
+ const key = folderKey(request);
193
+ const existing = requests.get(key);
194
+ requests.set(key, {
195
+ ...request,
196
+ content: request.content || existing?.content === true
197
+ });
198
+ };
199
+ for (const [key, remote] of Object.entries(previous)) {
200
+ const split = key.lastIndexOf(":");
201
+ const source = key.slice(0, split);
202
+ if (split <= 0 || !NEON_SOURCES.has(source)) continue;
203
+ add({
204
+ source,
205
+ folder: key.slice(split + 1),
206
+ content: remote.content !== void 0
207
+ });
208
+ }
209
+ for (const skill of skills) add({
210
+ source: skill.source,
211
+ folder: skill.folder,
212
+ content: skill.hashKind === "content"
213
+ });
214
+ return [...requests.values()];
215
+ };
216
+ /**
217
+ * Fetches the default-branch state of every requested skill folder and writes it to the
218
+ * cache. File contents are downloaded only for folders whose tree changed since the cache.
219
+ */
220
+ const refreshSkillsCheckCache = async ({ cachePath, skills, env = process.env, now = Date.now() }) => {
221
+ const previous = readSkillsCheckCache(cachePath)?.folders ?? {};
222
+ const requests = folderRequests(skills, previous);
223
+ const folders = {};
224
+ for (const source of new Set(requests.map((request) => request.source))) {
225
+ const tree = await fetchTree(source, env);
226
+ for (const request of requests) {
227
+ if (request.source !== source) continue;
228
+ const key = folderKey(request);
229
+ const node = tree.find((entry) => entry.type === "tree" && entry.path === request.folder);
230
+ if (node === void 0) {
231
+ folders[key] = { missing: true };
232
+ continue;
233
+ }
234
+ const prior = previous[key];
235
+ let content = prior?.tree === node.sha ? prior.content : void 0;
236
+ if (request.content && content === void 0) content = await fetchContentHash(source, request.folder, tree);
237
+ folders[key] = {
238
+ tree: node.sha,
239
+ ...content === void 0 ? {} : { content }
240
+ };
241
+ }
242
+ }
243
+ writeJsonFile(cachePath, {
244
+ checkedAt: now,
245
+ folders
246
+ }, "skills check cache");
247
+ return folders;
248
+ };
249
+ const skillsStatusRows = (skills, folders) => skills.map((skill) => {
250
+ const status = skillStatus(skill, folders[folderKey(skill)]);
251
+ if (status === void 0) throw new Error(`No result for ${skill.skill} from ${skill.source}. Retry the check.`);
252
+ return {
253
+ scope: scopeLabel(skill.scope),
254
+ skill: skill.skill,
255
+ status
256
+ };
257
+ });
258
+ const formatSkillsNotice = (outdated) => {
259
+ if (outdated.length === 0) return void 0;
260
+ const scopes = ["global", "project"].filter((scope) => outdated.some((skill) => skill.scope === scope));
261
+ return [
262
+ "Neon agent skills have updates:",
263
+ ...scopes.map((scope) => ` ${scopeLabel(scope)}: ${outdated.filter((skill) => skill.scope === scope).map((skill) => skill.skill).join(", ")}`),
264
+ "Update them:",
265
+ ...scopes.map((scope) => ` ${neonSkillsUpdateCommand(scope)}`)
266
+ ].join("\n");
267
+ };
268
+ /**
269
+ * `pending` folders are left alone until the next daily check. A lock written after the last
270
+ * check (an install or an update) holds back the warning and checks every skill again,
271
+ * because the cache can be older than what was just installed.
272
+ */
273
+ const planSkillsCheck = ({ skills, cache, lockModifiedAt, now }) => {
274
+ const remote = (skill) => cache?.folders[folderKey(skill)];
275
+ const settled = (skill) => remote(skill)?.pending !== true;
276
+ const lockChanged = cache !== void 0 && lockModifiedAt > cache.checkedAt;
277
+ const outdated = lockChanged ? [] : skills.filter((skill) => settled(skill) && skillStatus(skill, remote(skill)) === "outdated");
278
+ const toCheck = skills.filter((skill) => settled(skill) && (lockChanged || skillStatus(skill, remote(skill)) === void 0));
279
+ return {
280
+ outdated,
281
+ toCheck,
282
+ refresh: cache === void 0 || toCheck.length > 0 || now - (cache.attemptedAt ?? cache.checkedAt) >= DAY_MS
283
+ };
284
+ };
285
+ const spawnSkillsCheckWorker = (cachePath, cwd) => {
286
+ const workerPath = fileURLToPath(new URL("./check_worker.js", import.meta.url));
287
+ if (!existsSync(workerPath)) return;
288
+ try {
289
+ const child = spawn(process.execPath, [
290
+ workerPath,
291
+ cachePath,
292
+ cwd
293
+ ], {
294
+ detached: true,
295
+ stdio: "ignore",
296
+ windowsHide: true
297
+ });
298
+ child.on("error", (error) => {
299
+ log.debug("Skills check failed to start: %s", error.message);
300
+ });
301
+ child.unref();
302
+ } catch (error) {
303
+ log.debug("Could not start the skills check: %s", error instanceof Error ? error.message : String(error));
304
+ }
305
+ };
306
+ /**
307
+ * Warns at most once a day when a Neon skill in the user-level lock or this directory's
308
+ * `skills-lock.json` is behind its source, from a cache a background worker refreshes daily.
309
+ */
310
+ const notifyIfSkillsOutdated = ({ configDir, cwd, now = Date.now() }) => {
311
+ const lockPaths = [globalLockPath(), projectLockPath(cwd)];
312
+ let skills;
313
+ try {
314
+ skills = [...readLockedSkills(lockPaths[0], "global"), ...readLockedSkills(lockPaths[1], "project")];
315
+ } catch (error) {
316
+ log.debug("Skipping the skills check: %s", error instanceof Error ? error.message : String(error));
317
+ return;
318
+ }
319
+ if (skills.length === 0) return;
320
+ const cachePath = skillsCheckCachePath(configDir);
321
+ const cache = readSkillsCheckCache(cachePath);
322
+ const plan = planSkillsCheck({
323
+ skills,
324
+ cache,
325
+ lockModifiedAt: Math.max(...lockPaths.map((path) => statSync(path, { throwIfNoEntry: false })?.mtimeMs ?? 0)),
326
+ now
327
+ });
328
+ const notice = formatSkillsNotice(plan.outdated);
329
+ if (notice !== void 0) showNotice({
330
+ configDir,
331
+ id: "skills",
332
+ intervalMs: DAY_MS,
333
+ message: notice,
334
+ now
335
+ });
336
+ if (!plan.refresh) return;
337
+ const folders = { ...cache?.folders };
338
+ for (const skill of plan.toCheck) folders[folderKey(skill)] = {
339
+ ...folders[folderKey(skill)],
340
+ pending: true
341
+ };
342
+ if (writeJsonFile(cachePath, {
343
+ checkedAt: cache?.checkedAt ?? 0,
344
+ attemptedAt: now,
345
+ folders
346
+ }, "skills check cache")) spawnSkillsCheckWorker(cachePath, cwd);
347
+ };
348
+ //#endregion
349
+ export { formatSkillsNotice, globalLockPath, lockPath, neonSkillsUpdateCommand, notifyIfSkillsOutdated, parseSkillsCheckCache, parseSkillsLock, planSkillsCheck, projectLockPath, readLockedSkills, refreshSkillsCheckCache, scopeLabel, skillContentHash, skillStatus, skillsCheckCachePath, skillsStatusRows };
@@ -59,7 +59,6 @@ const neonSkillsRetryCommand = (options) => {
59
59
  args.push("-y");
60
60
  return `neon ${args.map(quoteNpxArg).join(" ")}`;
61
61
  };
62
- const neonSkillsUpdateRetryCommand = (global) => global ? "neon skills update --global -y" : "neon skills update -y";
63
62
  const quoteNpxArg = (part) => /[\s"'\\*]/.test(part) ? `'${part.replace(/'/g, `'\\''`)}'` : part;
64
63
  const npxCommand = (args) => `npx ${args.map(quoteNpxArg).join(" ")}`;
65
64
  const ANSI = /\u001b\[[0-9;]*[A-Za-z]/g;
@@ -116,4 +115,4 @@ const runSkillsCli = async (options) => {
116
115
  const isCommandMissing = (error) => typeof error === "object" && error !== null && "code" in error && error.code === "ENOENT";
117
116
  const isExecaFailure = (error) => typeof error === "object" && error !== null && "shortMessage" in error && typeof error.shortMessage === "string";
118
117
  //#endregion
119
- export { SKILLS_MIN_NODE, assertSkillsNode, neonSkillsRetryCommand, neonSkillsUpdateRetryCommand, nodeMeetsMinimum, npxCommand, quoteNpxArg, runSkillsCli, skillsAddArgs, skillsChildEnv, skillsMetadata, skillsUpdateArgs, skillsUpdateDetail, skillsUpdateHadNothing };
118
+ export { SKILLS_MIN_NODE, assertSkillsNode, neonSkillsRetryCommand, nodeMeetsMinimum, npxCommand, quoteNpxArg, runSkillsCli, skillsAddArgs, skillsChildEnv, skillsMetadata, skillsUpdateArgs, skillsUpdateDetail, skillsUpdateHadNothing };
@@ -91,6 +91,7 @@ const test = test$1.extend({
91
91
  ...options.cwd ? { cwd: options.cwd } : {},
92
92
  env: {
93
93
  PATH: `${join(process.cwd(), "mocks/bin")}:${process.env.PATH}`,
94
+ NO_UPDATE_NOTIFIER: "1",
94
95
  ...options.env ?? {}
95
96
  }
96
97
  });
@@ -145,6 +145,20 @@ const shouldRunUpdateNotifier = ({ commandPath, currentBranch, disabled, isCi: r
145
145
  commandPath,
146
146
  currentBranch
147
147
  });
148
+ /**
149
+ * The skills notice drops the terminal checks: coding agents run the CLI without a TTY,
150
+ * and they are the ones that act on it. `neon skills` itself reports skill state.
151
+ */
152
+ const shouldRunSkillsNotifier = ({ commandPath, currentBranch, output }) => commandPath[0] !== "skills" && commandPath[0] !== "skill" && shouldRunUpdateNotifier({
153
+ commandPath,
154
+ currentBranch,
155
+ disabled: "NO_UPDATE_NOTIFIER" in process.env,
156
+ isCi: isCi(),
157
+ isPackaged: isPackagedExecutable(),
158
+ output,
159
+ stderrIsTty: true,
160
+ stdoutIsTty: true
161
+ });
148
162
  const notifyIfUpdateAvailable = ({ commandPath, configDir, currentBranch, currentVersion, output }) => {
149
163
  if (!shouldRunUpdateNotifier({
150
164
  commandPath,
@@ -204,4 +218,4 @@ const recordLatestVersion = (cachePath, source, latestVersion) => {
204
218
  });
205
219
  };
206
220
  //#endregion
207
- export { cacheForUpdateSource, findCliInstalls, formatDuplicateInstallNotice, formatUpdateNotice, notifyIfUpdateAvailable, parseUpdateCheckCache, recordLatestVersion, shouldRefreshUpdateCheck, shouldRunUpdateNotifier, spawnUpdateWorkerProcess, writeUpdateCheckCache };
221
+ export { cacheForUpdateSource, findCliInstalls, formatDuplicateInstallNotice, formatUpdateNotice, notifyIfUpdateAvailable, parseUpdateCheckCache, recordLatestVersion, shouldRefreshUpdateCheck, shouldRunSkillsNotifier, shouldRunUpdateNotifier, spawnUpdateWorkerProcess, writeUpdateCheckCache };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "neon",
3
- "version": "8.11.4",
3
+ "version": "8.12.0",
4
4
  "description": "CLI tool for Neon, the cloud backend primitives built around Lakebase Postgres",
5
5
  "keywords": [
6
6
  "neon",
@@ -72,10 +72,10 @@
72
72
  "yaml": "^2.9.0",
73
73
  "yargs": "17.7.2",
74
74
  "yoctocolors": "^2.1.2",
75
- "@neon/config-runtime": "1.10.3",
76
75
  "@neon/config": "1.9.4",
77
- "@neon/sdk": "7.5.0",
78
- "@neon/realtime": "0.1.2"
76
+ "@neon/config-runtime": "1.10.3",
77
+ "@neon/realtime": "0.1.2",
78
+ "@neon/sdk": "7.5.0"
79
79
  },
80
80
  "optionalDependencies": {
81
81
  "@napi-rs/keyring": "1.3.0",
@@ -112,10 +112,10 @@
112
112
  "typescript": "^5.9.0",
113
113
  "vitest": "^3.0.9",
114
114
  "@neon-internals/env-core": "0.0.35",
115
- "@neon/functions": "0.11.0",
116
- "@neon/env": "1.6.5",
117
115
  "@neon/e2e-harness": "0.0.0",
118
- "@neon-internals/cli-core": "0.0.0"
116
+ "@neon/env": "1.6.5",
117
+ "@neon-internals/cli-core": "0.0.0",
118
+ "@neon/functions": "0.11.0"
119
119
  },
120
120
  "publishConfig": {
121
121
  "access": "public",
@@ -7,9 +7,9 @@ import { deprecatedServiceMessage, parseServices, servicesFlagValue, servicesOpt
7
7
  import { mergeEnvFile, readEnvFile, resolveEnvFilePath } from "../env_file.js";
8
8
  import { t as NEON_ENV_VAR_KEYS, u as isFunctionBaseUrlKey } from "./env-BpP3Dqpe.js";
9
9
  import { ENV_PULL_KEY_HELP, ENV_PULL_SERVICES, envKeysForSelection, envPullFlagValue, ownedEnvServiceKeys, parseEnvPullKeys } from "../env_services.js";
10
+ import { i as dropClaimableUnsupported, o as resolveNeonEnvVars } from "./env-BNm69Qh8.js";
10
11
  import { fillSingleProject, resolveBranchRef } from "../utils/enrichers.js";
11
12
  import { warnAiGateway } from "../utils/ai_gateway_notice.js";
12
- import { i as dropClaimableUnsupported, o as resolveNeonEnvVars } from "./env-BNm69Qh8.js";
13
13
  import { formatPulledEnv, pulledBranch } from "../commands/env_output.js";
14
14
  import { existsSync } from "node:fs";
15
15
  import chalk from "chalk";