npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@neottia/config

v0.2.0

Published

Typed contribution registry and immutable configuration snapshots for Neottia

Readme

@neottia/config

@neottia/config is Neottia's domain-neutral configuration platform. Domain packages contribute their schemas, defaults, environment bindings, and secret fields. A registry resolves those contributions together, so every consumer receives one coherent immutable snapshot without @neottia/config importing a domain, MCP, or harness package.

Read the unified configuration guide to configure global and project files, profiles, secrets, migrated modules, diagnostics, and embedding. The complete strict editor schema is exported as @neottia/config/config.schema.json. Neottia hosts use @neottia/config-registry to register every official module without adding domain dependencies to this generic package.

Define and register a contribution

A contribution owns one canonical path beneath a registered root section. File and runtime patches use separate schemas so file-only rules, including secret references, can differ from trusted runtime overrides. Patch schemas must not inject defaults; the complete merged value is checked once with resolvedSchema.

import { createConfigRegistry, defineConfigContribution } from "@neottia/config";
import { z } from "zod";

const patchSchema = z
  .object({
    enabled: z.boolean().optional(),
    token: z.string().optional(),
  })
  .strict();

const resolvedSchema = z
  .object({
    enabled: z.boolean(),
    token: z.string().optional(),
  })
  .strict();

export const exampleConfig = defineConfigContribution({
  id: "example",
  path: ["modules", "example"],
  filePatchSchema: patchSchema,
  runtimePatchSchema: patchSchema,
  resolvedSchema,
  defaults: { enabled: false },
  environment: [
    {
      path: ["enabled"],
      names: ["NEOTTIA_EXAMPLE_ENABLED"],
      kind: "boolean",
    },
  ],
  secrets: [
    {
      path: ["token"],
      fallbackEnvironment: ["NEOTTIA_EXAMPLE_TOKEN"],
    },
  ],
  legacyPaths: [["skills", "example"]],
});

export const registry = createConfigRegistry([exampleConfig]);

Environment names are ordered from the preferred name to compatibility aliases. Empty values are unset. Secret and environment paths are relative to the contribution shard. legacyPaths identify deprecated source locations; snapshots and serialization always use the canonical path.

A binding may declare a synchronous parse(value) hook when a legacy environment variable does not map directly to a string, integer, or boolean leaf. This hook runs as trusted module-contribution code and replaces built-in kind coercion. Its result must satisfy the contribution's runtime patch and resolved schemas. Thrown errors and invalid results produce value-free environment diagnostics; parser errors and raw environment text are never included.

The configurable root sections are modules, sdlc, connections, capabilities, agents, harnesses, assets, and templates. version and profiles are document metadata. Registration rejects invalid metadata, duplicate IDs, exact duplicate paths, ancestor/descendant ownership, and canonical or legacy alias collisions.

Generate a composed schema

generateConfigJsonSchema(registry) returns a draft-07 schema for the registry's canonical file paths and profile fragments. The schema requires version: 1 and rejects unregistered keys. Neottia's published config.schema.json composes the official Memory, Issues, Design Docs, and Searchable contributions at repository build time, so this package keeps no runtime dependency on those modules.

Resolve configuration

resolveConfig reads every selected YAML document once and resolves all registered contributions in this order:

  1. contribution defaults;
  2. global base configuration;
  3. project base configuration;
  4. the selected global profile;
  5. the selected project profile;
  6. registered environment bindings;
  7. explicit runtime overrides.

Mappings merge recursively. Scalars and arrays replace the lower-precedence value. Every file and profile patch is independently checked before merging, so a valid higher layer cannot hide an invalid lower layer. The final merged shard is checked with its resolved schema.

import { resolveConfig } from "@neottia/config";
import { exampleConfig, registry } from "./example-config.js";

const snapshot = resolveConfig(registry, {
  cwd: process.cwd(),
  env: process.env,
  profile: "ci",
  overrides: {
    modules: {
      example: { enabled: true },
    },
  },
});

export const config = snapshot.get(exampleConfig);

Pass cwd for every invocation. Relative explicitly selected files and the default project file resolve from this directory. Pass env to isolate resolution from ambient process environment in an embedding application or test. Every present global or project document must declare the exact integer version: 1; an absent optional default file still resolves to contribution defaults.

File discovery

The default project file is <cwd>/.neottia/config.yml. The optional default global file is:

  • Linux: $XDG_CONFIG_HOME/neottia/config.yml, or ~/.config/neottia/config.yml;
  • macOS: $XDG_CONFIG_HOME/neottia/config.yml, or ~/Library/Application Support/neottia/config.yml;
  • Windows: %APPDATA%\neottia\config.yml.

NEOTTIA_GLOBAL_CONFIG_FILE and NEOTTIA_CONFIG_FILE explicitly select the global and project files. globalFile and projectFile options take precedence; use false to disable a source. Missing default files are optional. A missing path selected by an option or environment variable is an error.

The resolver accepts regular files up to MAX_CONFIG_FILE_BYTES, currently 1 MiB. It also limits YAML to MAX_CONFIG_YAML_NODES, currently 10,000 scalar and collection nodes, and MAX_CONFIG_YAML_DEPTH, currently 64 collection levels. It reports LIMIT before converting YAML that exceeds these limits.

Profiles

Profiles are root-shaped configuration fragments under profiles.<name>:

version: 1
modules:
  example:
    enabled: false
profiles:
  ci:
    modules:
      example:
        enabled: true

ResolveConfigOptions.profile takes precedence over NEOTTIA_PROFILE. One profile can be selected. A selected profile must exist in at least one loaded document. Global and project fragments with the same name compose in normal precedence order. All declared profiles are validated, including unselected profiles; they cannot introduce unknown sections, version, nested profiles, or unregistered shards.

Environment values and secrets

String, integer, and boolean environment bindings are supported. Integers use trimmed base-10 safe-integer syntax. For compatibility with existing Neottia modules, booleans accept case-insensitive, trimmed true, false, 1, or 0. Domain-specific trusted parsers may map legacy text to a different schema-valid leaf shape; aliases and environment precedence remain unchanged.

A secret in YAML must be one exact environment reference, such as ${EXAMPLE_TOKEN}. Prefixes, suffixes, and literal file secrets are rejected. Only the winning file reference is resolved, and it is resolved once: if EXAMPLE_TOKEN itself contains ${OTHER_TOKEN}, that text remains literal. Secret fallback environment bindings and explicit runtime overrides may supply trusted literal values.

Typed shard access exposes a secret to its intended consumer. The platform keeps resolved secret values out of diagnostics and provenance, and snapshot.toJSON() replaces every declared secret with [REDACTED]. Applications must still avoid logging typed shards directly.

Provenance and diagnostics

sourceOf returns value-free leaf metadata. Its path is relative to the contribution shard:

snapshot.sourceOf(exampleConfig, ["enabled"]);
// {kind: "override", label: "explicit overrides"}

Arrays have provenance as complete leaves. Metadata can identify a source kind, file, profile, environment variable name, or deprecated path, but never contains a resolved secret value.

Host-effective runtime policy

Hosts can derive an immutable effective snapshot without rereading configuration or mutating declared values. This is how non-interactive MCP and OpenCode surfaces translate stale_policy: prompt to rebuild, while Pi preserves the interactive prompt:

export const effective = snapshot.derive({ modules: { example: { enabled: true } } }, "non-interactive host policy");

Derived overrides are checked with each contribution's runtime and resolved schemas, retain lower-precedence values, and receive override provenance with the supplied label. Unknown shard paths fail. Keep the original snapshot when both declared and host-effective views are needed.

Resolution failures throw ConfigResolutionError. Its immutable diagnostics cover YAML, version, I/O, path ownership, merge collisions, schemas, profiles, environment coercion, and secrets. Diagnostics report paths and constraints without rejected values and are bounded by MAX_CONFIG_DIAGNOSTICS.

Deprecated wrapper compatibility

Domain compatibility wrappers may pass compatibility.ignoreUnregisteredPaths so a standalone loader can read its registered shard from a legacy shared document containing unrelated roots or module shards. They may also pass compatibility.resolveOverrideSecretReferences to preserve an older library API that accepted ${ENV_VAR} in explicit overrides. Normal multi-module resolution must leave both options unset: unregistered paths then fail, and trusted runtime strings remain literal. These switches do not suppress YAML, version, owned-shard schema, profile, environment, or secret errors.

Construct a trusted snapshot directly

An embedding layer with already resolved typed values can use the lower-level snapshot boundary:

import { createResolvedConfigSnapshot } from "@neottia/config";
import { exampleConfig, registry } from "./example-config.js";

const snapshot = createResolvedConfigSnapshot(registry, {
  example: { enabled: true, token: "runtime-secret" },
});

export const config = snapshot.get(exampleConfig);

Omitted shard IDs use contribution defaults. Supplied shards are validated, cloned, and recursively frozen. Snapshot access accepts only a contribution object registered in that snapshot. Direct construction is a trusted runtime boundary: it does not expand secret references, although declared secret paths are still redacted by toJSON().

License

MIT