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

nx-content

v1.2.0

Published

Content Manager — fetch instruction, prompt, and block content from local folder or git repo

Readme

nx-content

Content Manager — resolve instruction, prompt, and block content from a local folder or a Git repo. Built for Node.js 18+, TypeScript strict mode, ESM + CJS.

Use it to:

  • Read/write by keyget(key) returns string content; set(key, content) writes to local backend
  • Resolve content keys (e.g. skills/my-skill.instructions) to file content
  • List keys with optional prefix — listKeys(prefix?) for catalog and audits
  • Deterministic namespaced keysskillTaskPromptKey(skillKey), skillInstructionsKey(skillKey)
  • Variant resolution — optional variant (e.g. prod, beta); tries variants/<variant>/<key> then <key>
  • Support local and Git backends with configurable precedence (dev = local wins, prod = git wins)
  • Inline block includes with << path >> in files
  • Export to .metadataexportToMetadata(options) to sync content to a local metadata tree (files or manifest)
  • Cache resolved content in memory (TTL configurable)
  • Drop in where a content registry is expected (e.g. ai-gateway)

Install

npm install nx-content

You can also install under the alias content-x (same package, same API):

npm install content-x@npm:nx-content

Requirements: Node.js >= 18.


Quick start

import { ContentResolver, init, getDefaultResolver } from 'nx-content';

// 1. Initialize (creates content root + readme)
const manager = await init({ localRoot: './.content' });

// 2. Resolver (recommended API)
const resolver = new ContentResolver({ localRoot: './.content' });
const { text } = await resolver.resolveInstructions('skills/my-skill.instructions');

// 3. Singleton from env — reads all options from .env automatically
const defaultResolver = getDefaultResolver();
const prompt = (await defaultResolver.resolvePrompt('prompts/default')).text;

Env-ready (works out of the box)

  • Defaults: Works with no config: local root <cwd>/.content, default mode is dev (local wins, Git is fallback), cache 1 year, no git unless you set GITHUB_REPO_URL.
  • .env: On first import, the package loads .env from the current working directory (via dotenv). All options can be set there; see .env.example. No nx-config2 (or any other config layer) is required — use it only if you already rely on it elsewhere.

Content keys

  • A key is a path relative to the content root, without file extension.
  • Resolution tries .md then .json when the key has no extension (or when the extension is not .md/.json).

Examples:

| Key | Resolved file | |-----|----------------| | skills/my-skill.instructions | skills/my-skill.instructions.md (or .json) | | prompts/summarize | prompts/summarize.md | | blocks/greeting | blocks/greeting.md | | readme.md | readme.md (exact) |

Key detection: A string is treated as a key only if, after trim, it is non-empty and contains no whitespace. Otherwise it is treated as literal text and returned as-is.

Deterministic namespaced keys: For catalog enrichment, validation, and audits, use the helpers so keys are consistent:

  • skillTaskPromptKey(skillKey)skills/tasks/<skill-key>/prompt
  • skillInstructionsKey(skillKey)skills/<skill-key>/instructions
  • normalizeKeySegment(segment) strips skills/ prefix and .instructions suffix and sanitizes for path use.

Example: skillTaskPromptKey('my-skill')skills/tasks/my-skill/prompt; store the file at .content/skills/tasks/my-skill/prompt.md.


Export to metadata

Sync resolved content to a local path for integration with metadata pipelines:

  • exportToMetadata(options?) — on ContentManager or ContentResolver.
  • Options: basePath (default '.metadata'), keys (default: all from listKeys()), format: 'files' (one file per key under basePath/content/) or 'manifest' (single content-manifest.json).
  • Returns { written: string[]; errors: Array<{ key, error }> }. Missing or invalid keys are in errors; successful keys in written.

Block includes

Inside any content file you can include another file with double angle brackets:

<< blocks/greeting >>

The manager replaces << path >> with that file’s full content. Paths use / or \; they are relative to the content root. Nested includes are supported (max depth 10; circular includes throw).


API

ContentResolver (recommended)

| Method | Description | |--------|-------------| | get(key) | Read content by key; returns raw string. Non-keys returned as-is. | | set(key, content) | Write content by key (local backend only). | | resolveInstructions(key) | Resolve instruction key → ResolvedContent. If not a key, returns { text: key }. | | resolvePrompt(key) | Same for prompt key. | | hasInstructionKey(key) | true if the key exists (no full read). | | listKeys(prefix?) | List keys, optionally filtered by prefix (e.g. skills/). | | resolveInstructionsBlock(blockName, agentId, taskTypeId?, configOverride?) | Resolve a block with fallback: task → agent → generic. If configOverride is non-empty, returns it. | | normalizeSkillId(skillId) | Normalize to canonical key (e.g. skills/<id>.instructions). | | isKey(value) | Whether the string is a content key (no whitespace, non-empty). | | getContentRoot() | Absolute content root path. | | getContentRegistry() / getContentManager() | Underlying ContentManager when enabled. | | exportToMetadata(options?) | Sync/export content to local .metadata path (files or manifest). | | isEnabled() | Whether the manager is configured. |

ContentManager

| Method | Description | |--------|-------------| | get(key) | Read content by key; returns raw string. | | set(key, content) | Write content by key (local backend only). | | resolve(key) | Resolve key to ResolvedContent (cache + block includes). | | exists(key) | Whether the key exists. | | listKeys(prefix?) | List keys, optionally filtered by prefix. | | normalizeSkillId(skillId) | Canonical key for skill. | | resolveInstructionsBlock(blockName, agentId, taskTypeId?, configOverride?) | Block resolution with fallback. | | ping() | Backend reachable (e.g. root exists). | | listAll() | All relative paths under the root. | | invalidateKey(key) / invalidateAll() | Clear cache. | | exportToMetadata(options?) | Sync/export content to local .metadata path (files or manifest). | | getContentRoot() | Absolute local root. | | isEnabled() | Whether a backend is configured. |

Functions

| Function | Description | |----------|-------------| | init(config?) | Create content root dir, write readme, return ContentManager. Idempotent. | | isKey(value) | Key detection (no whitespace, non-empty). | | isKeyLike(value) | Heuristic: content looks like a key (safety net). | | normalizeKeySegment(segment) | Normalize segment for namespaced keys (strip skills/ prefix, .instructions suffix). | | skillTaskPromptKey(skillKey) | Deterministic key skills/tasks/<skill-key>/prompt. | | skillInstructionsKey(skillKey) | Deterministic key skills/<skill-key>/instructions. | | runDiagnostics(manager) | Returns DiagnosticsResult: enabled, localRoot, backendReachable, availableKeys, error. | | getDefaultResolver() | Singleton ContentResolver from env. |

Types

  • ContentManagerConfiglocalRoot, gitRepoUrl, gitToken, gitBranch, gitClonePath, mode, variant, cacheTtlMs
  • ResolvedContent{ text: string; metadata?: ContentMetadata }
  • ContentMetadatakey, path, backend, version, variant, rawContent
  • ExportToMetadataOptionsbasePath? (default '.metadata'), keys? (default: all from listKeys()), format?: 'files' | 'manifest'
  • ExportToMetadataResult{ written: string[]; errors: Array<{ key: string; error: Error }> }
  • ContentBackend — interface: read, exists, listAll, listKeys?, ping, location
  • DiagnosticsResultenabled, localRoot, backendReachable, availableKeys, error
  • GitBackendConfigrepoUrl, token?, branch?, clonePath?

Errors (all extend ContentManagerError, have .code)

| Class | Code | When | |-------|------|------| | ContentNotFoundError | CONTENT_NOT_FOUND | Key not found in any backend. | | ContentManagerNotAvailableError | CONTENT_MANAGER_NOT_AVAILABLE | No backend configured. | | ContentBackendError | CONTENT_BACKEND_ERROR | I/O or git failure. | | ContentInvalidError | CONTENT_INVALID | Stored content is key-like or empty (never return key as content). |


Configuration

Options (ContentManagerConfig)

| Option | Env fallback | Default | Description | |--------|----------------|--------|-------------| | localRoot | CONTENT_REGISTRY_LOCAL_ROOT | <cwd>/.content | Content root (resolved to absolute). | | gitRepoUrl | GITHUB_REPO_URL (only when no config or empty config) | — | Git repo URL (SSH or HTTPS). When you pass a config object, omitting gitRepoUrl means no Git (env is not used). Set to null or '' to explicitly disable Git. | | gitToken | GITHUB_TOKEN | — | GitHub PAT for HTTPS. | | gitBranch | CONTENT_REGISTRY_GIT_BRANCH | main | Branch to use. | | gitClonePath | CONTENT_REGISTRY_GIT_LOCAL_CLONE_PATH | os.tmpdir()/nx-content-git | Local clone path. | | mode | CONTENT_REGISTRY_MODE | dev | Default is dev (local-first: local wins, Git is fallback). prod = git wins. | | variant | CONTENT_REGISTRY_VARIANT | — | Optional. When set, resolution tries variants/<variant>/<key> then <key>. | | cacheTtlMs | CONTENT_REGISTRY_CACHE_TTL_MS | 1 year | Cache TTL in ms. |

Environment variables

Same names as above; used when the corresponding option is not passed. All paths from config/env are resolved to absolute at init.


Recommended folder layout

.content/
  skills/
    my-skill.instructions.md
  prompts/
    default.md
  blocks/
    greeting.md
    disclaimer.md
  variants/          # optional: when variant is set (e.g. prod, beta)
    prod/
      prompt.md

Testing

# Unit + integration (local)
npm test

# Integration only
npm run test:integration

# Watch
npm run test:watch

Git backend integration tests run only when GITHUB_TOKEN (and optionally GITHUB_REPO_URL) are set; otherwise they are skipped.


Build

npm run build

Produces:

  • dist/esm/ — ESM
  • dist/cjs/ — CommonJS ("type": "commonjs")
  • dist/types/ — Declaration files

License

MIT