@voicenter-team/nuxt-llms-generator

Nuxt 3/4 module that renders AI-optimized documentation files (llms.txt, llms-full.txt, and individual .md files) from Umbraco CMS data, with a CLI that generates one template per page type using Claude Code or the Anthropic API.
6,444
Install
npm install @voicenter-team/nuxt-llms-generator
Latest Version:0.2.1
Vue:3
License:MIT
Last Updated:Oct 14, 2026
Links: npm
Maintainer: tzachish

🤖 LLMS Documentation Generator for Nuxt 3 and Nuxt 4

Turn Umbraco CMS content into AI-optimized documentation following the llms.txt standard

TypeScript Nuxt 3 & 4 Claude AI MIT License

The generator writes llms.txt, llms-full.txt and one markdown file per page from your UmbracoData.json. Each page is rendered with a Mustache template written by Claude, one template per page type (TemplateAlias).

The work is split between two entry points:

Entry point When it runs What it does Calls AI
Nuxt module nuxt build Renders the llms files from the templates committed to git Never
CLI npx nuxt-llms-generator templates Locally, by hand or from a commit hook Generates missing or outdated page-type templates Yes

The build is therefore fast, deterministic, needs no API key and never fails because of this module. A new page type gets an AI template the next time a developer runs the CLI and commits the result; until then it is rendered with a built-in fallback template.


🔄 How It Works

flowchart LR
    subgraph DEV["Developer machine (CLI)"]
        DATA1[UmbracoData.json] --> CHECK{Page type has a<br/>template covering<br/>its fields?}
        CHECK -->|yes| SKIP[Nothing to do]
        CHECK -->|no| AI[Claude Code / Anthropic API<br/>writes the type template]
        AI --> VALIDATE[Validate against every<br/>page of the type]
        VALIDATE --> STORE[".llms/{site}/{Alias}.mustache<br/>+ templates.json"]
    end

    STORE -->|git commit| BUILD

    subgraph CI["nuxt build (module)"]
        BUILD[Committed templates] --> RENDER[Render every page]
        DATA2[UmbracoData.json] --> RENDER
        RENDER --> OUT[llms.txt<br/>llms-full.txt<br/>llms/*.md]
    end

One template per page type

All pages that share a TemplateAlias share one template:

  • The CLI merges every page of the type into a union schema of the fields that are filled on at least one page, including array item fields and content block types (up to 3 levels deep).
  • Claude receives a synthetic sample built from that union, the list of fields that are filled on every page, and up to three example URLs. Optional fields must be wrapped in Mustache sections, so pages that leave them empty render cleanly.
  • A new template is accepted only if it renders every page of the type without errors and without producing an empty document.
  • Before rendering, each page is normalized against its type: fields the page lacks become ''. Without this, Mustache would look a missing field up in the parent context and repeat content from a neighbouring block.

Grow-only change detection

Each template is stored with the list of field paths it covers (templates.json). The CLI regenerates a template only when:

  • the page type has no template yet;
  • the stored template is broken (it no longer parses);
  • a page of the type has a new filled field or a new content block type that the template does not cover.

Removing a field, emptying it, or changing content values never triggers regeneration. Running the CLI when nothing changed takes about a second and makes no AI calls.


🚀 Quick Start

Installation

npm install @voicenter-team/nuxt-llms-generator

The module supports Nuxt ^3.0.0 and ^4.0.0. Production builds have been verified with Nuxt 3.19.2 and 4.4.8.

1. Create the shared config file

Both entry points read llms.config.ts (or .mts, .js, .mjs, .cjs, .json) from the project root:

// llms.config.ts
import { defineLLMSConfig } from '@voicenter-team/nuxt-llms-generator/config'

export default defineLLMSConfig({
    umbracoDataPath: 'public/UmbracoData.json',
    templatesDir: '.llms',
    finalOutputDir: 'public',
    baseSiteUrlUmbracoDataKey: 'domain'
})

2. Register the module

// nuxt.config.ts
export default defineNuxtConfig({
    modules: [ '@voicenter-team/nuxt-llms-generator' ]
})

Options set under llmsGenerator in nuxt.config.ts override the config file.

3. Generate the templates and commit them

npx nuxt-llms-generator templates
git add .llms

4. Build

npm run build

The build log reports how each page was rendered:

Rendered 422 pages: 406 with page-type templates, 0 with 0.1.x per-page templates, 16 with the built-in fallback; 0 skipped
No template for 2 page type(s), rendered with the fallback template: SupportPage (14 pages), SupportPages (1 page). Run `npx nuxt-llms-generator templates` and commit the result.

🏢 Multiple Sites

One config file can describe several sites. Shared options sit at the top level, per-site options sit in sites, and {site} in umbracoDataPath, templatesDir and finalOutputDir is replaced with the selected site name:

// llms.config.ts
import { defineLLMSConfig } from '@voicenter-team/nuxt-llms-generator/config'

export default defineLLMSConfig({
    templatesDir: '.llms/{site}',
    finalOutputDir: 'public',
    umbracoDataPath: 'public/UmbracoData.json',
    baseSiteUrlUmbracoDataKey: 'domain',
    sites: {
        'voice-he-prod': {},
        'voice-en-prod': {},
        'voice-landings-he-prod': {
            missingTemplate: 'skip'
        }
    }
})

The site is selected with --site in the CLI and with the site option in Nuxt:

// nuxt.config.ts
export default defineNuxtConfig({
    modules: [ '@voicenter-team/nuxt-llms-generator' ],
    llmsGenerator: {
        site: process.env.SITE_ENV
    }
})
npx nuxt-llms-generator templates --site voice-he-prod --data /tmp/voice-he-prod.json

Options are merged in this order, later sources win: built-in defaults, top-level options of the config file, sites[site], then nuxt.config.ts options or CLI flags. A path containing {site} without a selected site is a configuration error.


🖥 CLI

npx nuxt-llms-generator <command> [options]
Command Description
templates (default) Generate missing or outdated page-type templates with AI
render Render llms.txt, llms-full.txt and the page .md files from the stored templates, without Nuxt
Option Description
--config <file> Config file (default: llms.config.* in the current directory)
--site <name> Site section of the config; replaces {site} in paths
--data <file> Umbraco data JSON (overrides umbracoDataPath)
--templates-dir <dir> Templates directory (overrides templatesDir)
--out <dir> Output directory for render (overrides finalOutputDir)
--type <alias> Only this page type; repeatable
--force Regenerate the --type templates even when they are up to date
--provider <name> AI provider to try (claude-code, anthropic-api); repeatable, in order
--dry-run Show what would be generated or removed without calling AI or writing files
-h, --help Show the help

Behaviour that matters for scripts and commit hooks:

  • stdout contains only the paths of written and removed files, relative to the current directory, one per line. Everything else goes to stderr.
  • The exit code is 0 when templates are up to date, when they were generated, and also when no AI provider is available. It is 1 only for usage errors, invalid configuration, unreadable data, or unexpected failures.
  • The CLI loads .env from the config file's directory, so CLAUDE_CODE_OAUTH_TOKEN and ANTHROPIC_API_KEY can live there.
  • It does not need Nuxt and does not start a Nuxt build.

Examples:

# See what is missing without calling AI
npx nuxt-llms-generator templates --dry-run

# Regenerate one page type
npx nuxt-llms-generator templates --type BlogArticlePage --force

# Use only the Anthropic API
npx nuxt-llms-generator templates --provider anthropic-api

Commit hook

Because stdout lists only changed files and the exit code is 0 when AI is unavailable, the CLI can run in a pre-commit hook without ever blocking a commit:

#!/bin/sh
# .husky/pre-commit
npx nuxt-llms-generator templates --site voice-he-prod | xargs -r git add

When nothing changed, the hook finishes in about a second without calling AI.


🧠 AI Providers

The CLI tries the providers in aiProviders order (default ['claude-code', 'anthropic-api']). A provider that fails at the account or connection level is skipped for the rest of the run; a template that fails validation is retried with the next provider.

If no provider is available, the CLI prints a warning, keeps the existing templates, and exits with 0. The affected page types keep rendering with their previous or fallback templates.

claude-code (default)

Uses the locally installed Claude Code CLI in headless mode with a Claude subscription instead of API credits. It is meant for developer machines, not CI.

The call has no tools, no MCP servers and no session persistence, runs in an empty temporary directory, and is a single turn:

claude -p --model claude-opus-5-5 --output-format json --tools "" --strict-mcp-config \
    --system-prompt-file <prompt> --no-session-persistence --max-turns 1

ANTHROPIC_API_KEY and ANTHROPIC_AUTH_TOKEN are removed from the child process environment, so Claude Code always bills the subscription and never the API key.

Token setup
  1. Install Claude Code: npm install -g @anthropic-ai/claude-code or brew install --cask claude-code. The default model claude-opus-5-5 needs version 2.1.280 or newer; update with claude update (or brew upgrade claude-code).

  2. Create a long-lived token: claude setup-token, and sign in with the team account.

  3. Put the token in the project's local .env:

    # .env (never commit this file)
    CLAUDE_CODE_OAUTH_TOKEN=sk-ant-oat01-...
    
  4. Check it: claude auth status must exit with 0.

⚠️ CLAUDE_CODE_OAUTH_TOKEN is a personal subscription credential. Keep it in the local .env only: never commit it, and never add it to Cloudflare or any other CI/CD environment.

Token renewal

Tokens from claude setup-token are valid for one year and are not refreshed automatically.

Token Account Expires
Voicenter shared token serhii.ko@voicenter.com 2027-10-14 (created 2026-10-14)

When it expires, the CLI warns that Claude Code is not signed in and exits with 0. To renew, run claude setup-token again, replace CLAUDE_CODE_OAUTH_TOKEN in .env on every machine that uses it, and update the date above.

anthropic-api

Uses the Anthropic Messages API with anthropicApiKey. It is used only when a key is configured; without one, the provider is skipped with a warning.

// llms.config.ts
export default defineLLMSConfig({
    anthropicApiKey: process.env.ANTHROPIC_API_KEY,
    anthropicModel: 'claude-sonnet-5-5'
})

The key is never needed for nuxt build and is stripped from the Nuxt runtime config.


⚙️ Configuration Options

Core

Option Type Default Description
umbracoDataPath string required Path to UmbracoData.json
templatesDir string .llms-templates Directory with the page-type templates and templates.json; commit it
finalOutputDir string public Output directory for llms.txt, llms-full.txt and llms/*.md
baseSiteUrl string - Base URL for links, e.g. https://example.com; this or baseSiteUrlUmbracoDataKey is required
baseSiteUrlUmbracoDataKey string - SiteData key that holds the base URL
missingTemplate 'fallback' | 'skip' 'fallback' What the build does with a page whose type has no template: render it with the built-in fallback template, or leave it out
enableIndividualMd boolean true Write one .md file per page
enableLLMSFullTxt boolean true Write llms-full.txt
enableHtmlToMarkdown boolean true Convert HTML content to markdown with node-html-markdown

Nuxt module only

Option Type Default Description
enabled boolean true Turn the module off
configFile string | false llms.config.* Config file relative to the Nuxt root; false ignores config files
site string - Selects sites[site] and replaces {site} in paths

Template generation (CLI)

Option Type Default Description
aiProviders ('claude-code' | 'anthropic-api')[] ['claude-code', 'anthropic-api'] Providers to try, in order
claudeCodeModel string claude-opus-5-5 Model for claude-code
claudeCodeCommand string claude Claude Code executable
claudeCodeTimeoutMs number 600000 Time limit for one Claude Code call (minimum 10000)
anthropicApiKey string - Key for anthropic-api
anthropicModel string claude-sonnet-5-5 Model for anthropic-api
anthropicTemperature number auto Applied only on models that still accept it; omitted on newer models, where the API rejects it
anthropicThinking 'auto' | 'disabled' | 'adaptive' 'auto' 'auto' disables thinking on models where it is on by default, so it does not consume the output budget
anthropicMaxOutputTokens number 16000 max_tokens of the generation call; responses that hit it are rejected
maxTokens number 65000 Limit for the page-type sample sent to the model; larger samples are truncated
maxConcurrent number 3 Page types generated in parallel (1-10)
enableAutoCleanup boolean true Remove stale template files after generation
cleanupOrphaned / cleanupHidden boolean true Remove templates of page types that no longer have visible pages

📁 Template Store

.llms/voice-he-prod/
├── templates.json            # manifest: covered fields, page count, provider and model per type
├── BlogArticlePage.mustache  # one template per page type
├── HomePage.mustache
└── ...

Commit the whole directory. The build reads it and never writes to it.

When rendering a page, the module uses the first of these that works:

  1. the page-type template {TemplateAlias}.mustache;
  2. a 0.1.x per-page template {TemplateAlias}_{nodeID}.mustache (see Migration);
  3. the built-in fallback template, unless missingTemplate is 'skip'.

A stored template that fails to render a page falls back to the next option instead of failing the build.


🔁 Migrating from 0.1.x

0.2.0 stops calling AI during nuxt build. To migrate:

  1. Update the package to ^0.2.0.
  2. Move the options from nuxt.config.ts to llms.config.ts (optional, but the CLI needs either the file or flags).
  3. Remove anthropicApiKey and any enabled: Boolean(process.env.NUXT_ANTHROPIC_API_KEY) from nuxt.config.ts, and delete the API key from the CI/CD environment. The build no longer needs it.
  4. Run npx nuxt-llms-generator templates for every site and commit the template directory.

Existing 0.1.x per-page templates ({Alias}_{nodeID}.mustache) keep rendering until their page type gets a type template. The CLI then deletes them, and deletes the old cache.json once no per-page templates remain. A full regeneration of all page types is a one-time manual step; it is never done by the build.

Removed in 0.2.0: template generation during the build, the per-page structure-hash cache (cache.json), and stale-template failover. Generation options such as anthropicModel and maxConcurrent still exist but only affect the CLI.


📊 Output

llms.txt

# Business Communication Solutions | Voicenter

> Thousands of organizations manage their business communications through our cloud platform

## Services

- [Call Center Solutions](https://github.com/VoicenterTeam/nuxt-llms-generator/blob/HEAD/llms/call-center-solutions.md): Complete call center management tools
- [Smart PBX for Business](https://github.com/VoicenterTeam/nuxt-llms-generator/blob/HEAD/llms/smart-pbx-business.md): Advanced business telephony

llms-full.txt

All page documents combined in one file.

llms/*.md

One clean markdown document per visible page. Pages with hidePage: "1" are skipped.


🧪 Development

npm install
npm test           # run the test suite
npm run lint
npm run prepack    # build dist/

🐛 Troubleshooting

The build says a page type has no template. Run npx nuxt-llms-generator templates (with --site for multi-site projects) and commit the template directory.

The CLI says no AI provider is available. Check claude auth status. If it fails, the token is missing or expired; see Token renewal. Alternatively set anthropicApiKey.

"templatesDir" contains {site} but no site is selected. Pass --site to the CLI or set site in nuxt.config.ts or the config file.

A template keeps being regenerated. Run with --dry-run to see the reason. Each regeneration must be caused by a new filled field or block type; if it is not, report it with the printed reason.


📄 License

MIT License - see LICENSE file for details.

Related Packages

@voicenter-team/eslint-config-vue

Shared vue eslint config

12,953
@voicenter-team/umbraco-headless-nuxt

Nuxt module that helps you to easily load your Umbraco data directly into your g...

6,834
@voicenter-team/stream-vue-3

A Vue 3 component for cloudflare streaming

4,600
@voicenter-team/umbraco-headless

Nuxt module that helps you to easily load your Umbraco data directly into your g...

925
@voicenter-team/voicenter-ui-plus

This is Vue 3.x + Typescript component library made for Voicenter systems.

37,359

Version History

Version Released License
0.2.1 MIT
0.2.0 MIT
0.1.18 MIT
0.1.17 MIT
0.1.16 MIT