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.3.1

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)
  • Push to GitpushToRemote(options?) to commit and push local content to the remote (content root must be a git repo)
  • 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 an exact file match for keys that do not end with .md/.json, then falls back to ${key}.md, then ${key}.json.

Examples:

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

Key–path contract: Key K corresponds to path P under the content root: P is the key with normalized separators (forward slashes). Use keyToPath(key) (sync, no I/O) for the canonical path (K if it ends with .md/.json, otherwise K + '.md'), or getPathForKey(key) (async) for the actual path when the file exists locally (or the canonical path otherwise). This lets you run git commands (e.g. git log -- <path>) or implement version history yourself.

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.

Push to Git

Commit and push content written with set() to a Git remote:

  • pushToRemote(options?) — on ContentManager or ContentResolver. Requires localRoot to be a git repository (clone the repo and use it as content root, or run git init there).
  • Options: message (default "Update content (nx-content)"), remote (default "origin"), branch (default from config or "main").
  • When gitRepoUrl and gitToken are configured, the remote URL is set with auth before push so HTTPS push works.
  • Returns { pushed: boolean; commitHash?: string; noChanges?: boolean }. noChanges: true when there was nothing to commit.

Version history (git)

When the content root is a git repository, you can list versions, read at a ref, and restore a file to a previous version:

  • getPathForKey(key) — Returns the relative path under the content root for the key (key–path contract). Use for git commands or custom tooling.
  • getVersions(key) — Returns VersionEntry[] (sha, message, date, author) for the file backing the key. Empty if not a git repo.
  • getAtRef(key, ref) — Returns raw content of the file at git ref ref (commit sha, tag, or branch). Does not change the working tree. Throws if the file did not exist at that ref.
  • setActiveVersion(key, ref, options?) — Checkouts the file at ref so the working tree matches that version; optionally pass { commit: true, message?: string } to commit. Does not push (call pushToRemote() when you want to publish).
await resolver.set('prompts/new', 'Content here.');
const result = await resolver.pushToRemote({ message: 'Add new prompt' });
// result.pushed === true, result.commitHash set

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). | | keyToPath(key) | Canonical relative path for key (sync, no I/O). See key–path contract. | | getContentRoot() | Absolute content root path. | | getContentRegistry() / getContentManager() | Underlying ContentManager when enabled. | | getPathForKey(key) | Relative path under content root for the key (key–path contract). null if no local backend. | | getVersions(key) | Git version history for the file backing the key. Empty if not a git repo. | | getAtRef(key, ref) | Raw content of the file at git ref (sha, tag, or branch). | | setActiveVersion(key, ref, options?) | Checkout file at ref; optional { commit?, message? }. | | exportToMetadata(options?) | Sync/export content to local .metadata path (files or manifest). | | pushToRemote(options?) | Commit and push local content to Git (content root must be a repo). | | 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. | | getPathForKey(key) | Relative path under content root for the key. null if no local backend. | | getVersions(key) | Git version history for the file (VersionEntry[]). Empty if not a git repo. | | getAtRef(key, ref) | Raw content at git ref. | | setActiveVersion(key, ref, options?) | Checkout file at ref; optional commit. | | invalidateKey(key) / invalidateAll() | Clear cache. | | exportToMetadata(options?) | Sync/export content to local .metadata path (files or manifest). | | pushToRemote(options?) | Commit and push local content to Git (content root must be a repo). | | 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); values containing { are treated as content. | | keyToPath(key) | Canonical relative path for key (key–path contract, no I/O). | | 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 }> }
  • PushToRemoteOptionsmessage?, remote?, branch?
  • PushToRemoteResult{ pushed: boolean; commitHash?; noChanges? }
  • VersionEntry{ sha: string; message: string; date: string; author?: string }
  • SetActiveVersionOptions{ commit?: boolean; message?: string }
  • SetActiveVersionResult{ updated: boolean }
  • 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). Shorthand: owner/repo is normalized to https://github.com/owner/repo. 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 (read): Integration tests that clone a public repo run without a token. Tests that need a private repo or token run only when GITHUB_TOKEN (and optionally GITHUB_REPO_URL) are set; otherwise skipped.
  • Git (push): The push-to-remote integration test runs only when both GITHUB_TOKEN and GITHUB_PUSH_REPO_URL are set (use a repo you have write access to); otherwise skipped.

Build

npm run build

Produces:

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

License

MIT