leed.config.json, Files and Environment

This is the reference page for two questions: where did that come from and where did that go. It covers the project configuration file, the complete set of files the leed CLI writes on your machine, and every environment variable it reads. Nothing here is something you routinely edit — the value of knowing it is that when a command behaves unexpectedly, one of these three things explains it.

leed.config.json

leed.config.json is your site’s project configuration. The CMS generates it, the installer downloads it, and leed site init finalizes it. It identifies which company and which environment this checkout belongs to, and it records the versions of the files the CLI manages inside your repository.

{
  "environment": "production",
  "domain": "example.com",
  "companyId": "cmp_4a71",
  "location": "/home/you/leed",
  "userId": "usr_1c8e",
  "initialized": true,
  "huskyVersion": "4.106.0",
  "vscodeVersion": "4.106.0",
  "aiConfiguration": {
    "claude": {
      "skillsVersion": "4.106.0",
      "installedSkills": ["site-builder", "site-preview", "site-publish"]
    }
  }
}
FieldTypeDefaultSet byWhat reads it
environmentproduction, staging or devproductionthe CMS, when it generates the fileEvery command: it selects the credentials block and the CMS base URL
domainstringrequiredthe CMS, from your company’s public domainslugify(domain) is the customer slug used for your folder name, your GitLab paths and --base-url’s default
companyIdstring""the CMSKeys the on-disk caches, so one machine can hold state for more than one company
locationstringoptionalcomputed at load time, not stored meaningfullyThe CLI, to find raw-content/ relative to the config
userIdstringrequiredthe CMS, from the account that downloaded the fileEmbedded in every commit message so the CMS can attribute the change
initializedbooleanfalseleed site init, which flips it to trueEvery leed site command: a false here is what produces “Site configuration not initialized”
huskyVersionstring""leed site init, and every later resyncThe drift check that reinstalls the git hooks after a CLI upgrade
vscodeVersionstring""leed site init, and every later resyncThe drift check that reinstalls the VS Code workspace and tasks
aiConfiguration.claude.skillsVersionstring""leed site init, and every later resyncThe drift check that reinstalls the Claude Code skills
aiConfiguration.claude.installedSkillsstring[][]the skill installerLets a resync remove a skill that a newer CLI no longer ships

Where the CLI looks for it

Exactly two places, in this order:

leed/                        <- run from here: ./leed.config.json is found
└── example.com/
    ├── leed.config.json
    ├── example.com.code-workspace
    └── raw-content/         <- run from here: ../leed.config.json is found

Nowhere else. From a third location — your home directory, a subfolder of raw-content/src/, anywhere outside the tree — every leed site command stops with:

Could not locate leed.config.json. Run this application from the directory where your repo exists.

There is one asymmetry worth knowing about. The leed auth commands do not load the config file the way leed site does; when they need a default environment they walk three levels — the current directory, its parent and its grandparent — so leed auth status can pick up your site’s environment from one level deeper than a leed site command would.

The fields the CLI maintains

Three of the fields above are drift markers, and they are the reason files reappear in your repository after you upgrade. When you run any leed site command other than init, the CLI compares huskyVersion, vscodeVersion and aiConfiguration.claude.skillsVersion against the version of the CLI that is running. Where they differ it reinstalls the husky hooks, the VS Code workspace and tasks, and the Claude Code skills, then rewrites leed.config.json with the new versions. Nothing asks first, and nothing is destroyed that the CLI did not put there. What gets installed is listed on leed site init.

location is the odd one out: it is filled in every time the file is loaded, from where you actually ran the command, and the value on disk is not consulted. Editing it does nothing.

How the environment is decided

Every command resolves one of production, staging or dev before it does anything else, and the answer decides which credentials block is read and which CMS it talks to. There are two resolvers, and they do not agree — which is deliberate, because the consequences of guessing wrong are not symmetrical.

flowchart TD
  A["leed &lt;command&gt;"] --> B{"leed auth,<br/>or leed site?"}
  B -- "leed auth login / logout" --> C{"--env passed?"}
  C -- "yes" --> R1["Use it"]
  C -- "no" --> D{"ENV_NAME set<br/>and valid?"}
  D -- "yes" --> R2["Use it"]
  D -- "no" --> E{"leed.config.json found<br/>in cwd, parent or<br/>grandparent?"}
  E -- "yes" --> R3["Use its environment"]
  E -- "no" --> R4["production"]
  B -- "leed site data commands" --> F{"leed.config.json<br/>environment present?"}
  F -- "yes" --> R5["Use it"]
  F -- "no" --> G{"ENV_NAME set<br/>and valid?"}
  G -- "yes" --> R6["Use it"]
  G -- "no" --> R7["Refuse:<br/>Could not determine the<br/>target environment."]

For leed auth login and leed auth logout the chain is --env → ENV_NAME → the config file → production. The -e, --env flag exists only on those two commands and is hidden from --help; CLI authentication covers when you need it.

For the leed site commands that talk to the CMS — push, commit, notify-cms, upload, import, create-page-type and list — the strict resolver runs instead. It reads the config file’s environment first, falls back to ENV_NAME, and then refuses rather than assuming anything:

Could not determine the target environment. Run from inside a leed-managed site
directory (leed.config.json) or set ENV_NAME=dev|staging|production.

The inversion is on purpose. A wrong-environment push is hard to detect after the fact, so the checkout you are standing in wins over an environment variable that might be left over from something else. The practical consequence: inside a site folder, setting ENV_NAME does not redirect a push or an import. Change environment in leed.config.json, or work from a checkout for the environment you mean.

Which CMS each environment is

EnvironmentCMS base URLOverride
productionhttps://app.leed.aiLEED_CMS_URL
staginghttps://staging.app.leed.aiLEED_CMS_URL
devhttp://localhost:8787LEED_CMS_URL

LEED_CMS_URL beats all three and applies to every request the CLI makes. An unrecognized environment falls back to the production URL.

Everything the CLI writes to disk

Three groups: your home directory, your project, and your cache.

~
├── .config/leed/credentials.json     0600, in a 0700 directory
├── .bunfig.toml                      the @leed registry scope
└── .cache/leed/<env>/<companyId>/
    ├── leed-validation.json          what you validated, and your last build
    └── imports/<pageTypeId>.json     one resumable OpenAPI import manifest

leed/example.com/
├── leed.config.json
├── example.com.code-workspace
└── raw-content/                      the git repository
    ├── .git/config                   origin URL, containing a token
    ├── .husky/                       pre-commit, pre-push, commit-msg
    ├── .vscode/tasks.json            12 tasks
    ├── .claude/skills/               site-builder, site-preview, site-publish
    ├── src/                          your content and templates
    └── .build/                       build output; .build/site is the rendered site
PathWritten byContentsPermissionsIn git?
${XDG_CONFIG_HOME:-~/.config}/leed/credentials.jsonthe installer, leed auth login, every token refreshone session block per environment0600 in a 0700 directoryoutside the repo
~/.bunfig.tomlthe installer, leed auth rotate, leed auth resetthe [install.scopes] "@leed" entry with its registry URL and deploy tokendefaultoutside the repo
<CLI install dir>/.lastcheck.jsonthe automatic update checkthe 24-hour throttle timestampdefaultoutside the repo
<project>/<customer>/leed.config.jsonthe installer, then leed site initproject configuration, no secretsdefaultoutside the repo
<project>/<customer>/<customer>.code-workspaceleed site init and every later resynca VS Code workspace opening raw-content/defaultoutside the repo
raw-content/.git/configleed site init, leed auth rotate, leed auth resetthe origin URL, which embeds your GitLab tokendefaultnot committed
raw-content/.husky/leed site init and every later resyncpre-commit, pre-push, commit-msg and their helpersdefaultgitignored
raw-content/.vscode/tasks.jsonleed site init and every later resync12 tasks covering build, validate, commit, push and authdefaultgitignored
raw-content/.claude/skills/leed site init and every later resyncsite-builder, site-preview, site-publishdefaultgitignored
raw-content/.build/leed site buildintermediate output, with the rendered site at .build/sitedefaultgitignored
${XDG_CACHE_HOME:-~/.cache/leed}/<env>/<companyId>/leed-validation.jsonleed site validate, commit and buildwhich files you validated, your last successful build timestamp, the pending commit set0700 directoryoutside the repo
${XDG_CACHE_HOME:-~/.cache/leed}/<env>/<companyId>/imports/<pageTypeId>.jsonleed site generate and importone resumable import manifest per page type0700 directoryoutside the repo

The cache paths are keyed by environment and company because one person can belong to more than one company on one machine, and a validation record from a staging checkout must never satisfy a production commit. What is inside the repository itself, folder by folder, is mapped in your site repository.

Relocating with XDG

XDG_CONFIG_HOME moves the credentials directory; XDG_CACHE_HOME moves the caches. Both are read at the moment a path is needed, so setting either for a single command works.

Environment variables

Split into three groups, because most of these are not for you.

Ones you might set

VariableUsed byEffectDefault
ENV_NAMEleed auth, the strict environment resolver, wrangler config generationNames the target environment. Loses to leed.config.json on leed site data commands; beats it on leed authunset
LEED_CMS_URLevery CMS requestOverrides the base URL for the resolved environment entirelyunset — the table above applies
LEED_JSONevery command1, true, yes or on turns on JSON mode, exactly as --json does. Opt-in only: LEED_JSON=0 does not turn it off for a run that passed --jsonunset
FILE_VALIDATION_MAX_SIZEleed site validateMaximum file size in kilobytes. Note the unit — FILE_VALIDATION_MAX_SIZE=2000 means 2 MB, not 2 KB500
XDG_CONFIG_HOMEcredential storageRelocates the credentials directory, keeping the leed/ segment~/.config
XDG_CACHE_HOMEvalidation tracking, import manifestsRelocates the caches, dropping the leed/ segment~/.cache/leed
DEBUGthe loggerMerged with whatever -v or -n enabled, never replaced. DEBUG=Eleventy* leed site build is the documented way to see Eleventy’s own internalsunset
NO_COLORthe paletteAny non-empty value disables color. Beats FORCE_COLOR. Set to the empty string it does nothingunset
FORCE_COLORthe paletteAny non-empty value other than 0 or false forces color on when stdout is not a terminal — which is how a piped or CI run gets color at all. Loses to NO_COLOR, and --json overrides bothunset
CIthe update check, the build recorder, the leed site resolverCI=true suppresses the automatic self-update and puts the CLI into direct mode. It is the lever for anyone scripting leed who does not want the binary replaced mid-rununset

Ones CI sets for you

These are set on the Cloudflare Builds trigger during provisioning, or injected by the build platform. You do not set them by hand, and setting them locally will make a command behave as though it were running in CI.

VariableUsed byEffectDefault
LEED_API_TOKENleed site uploadThe CMS API token a CI build authenticates with. Required in CI — the command refuses immediately with LEED_API_TOKEN not set. Required for CI builds.unset
LEED_ENTITLEMENTSthe build’s entitlement resolutionA JSON { tier, mcp } object. In CI it is the only trusted source and the build fails closed without it, so repository contents can never claim a tier they were not sold. Outside CI the build falls back to src/src.11tydata.jsonunset
BUILD_CUSTOMERthe hidden -c, --customer optionThe customer slug a CI build is buildingunset
BUILD_SITE_LOCATIONthe hidden -l, --location optionThe directory holding the content repositoriesunset
WORKERS_CI_BRANCHleed site build, leed site uploadThe branch, used instead of asking git — and the branch is what decides preview versus public outputunset
WORKERS_CI_COMMIT_SHAleed site uploadThe commit reported to the CMS with the build resultunset
WORKERS_CI_BUILD_UUIDleed site uploadCorrelates the upload with the Cloudflare build that produced itunset
Internal variables, for completeness

You will not set these, and nothing in the documented workflow depends on them. They are listed because a developer grepping their environment for an unexplained LEED_- or DNS_-prefixed name deserves an answer.

VariableUsed byEffect
BUNDLEDthe CLI entry pointSet inside the shipped bundle. Switches asset paths from the development layout to the packaged one, and is what makes the “Running in direct mode (dev)” banner disappear
SHELLleed completion installDetects bash, zsh or fish. With an unrecognized or unset value the command refuses rather than guessing
CUSTOMER_FUNCTIONS_PORTwrangler configuration generationPoints a locally generated wrangler config at a customer-functions dev server on a non-default port
DNS_ZONE_NAMEwrangler configuration generationNames the DNS zone written into a generated wrangler config

The hidden leed site options

leed site carries three options that are hidden from --help because they exist for Cloudflare Builds rather than for you: -l, --location <path> (bound to BUILD_SITE_LOCATION), -c, --customer <string> (bound to BUILD_CUSTOMER), and --ci (bound to CI). They select between three ways of working out where your files are. In direct mode — any CI signal, or a bare raw-content checkout with an identity.json and no reachable leed.config.json — the current directory is the content repository and the customer is derived from the folder above it. In local mode, the ordinary case, leed.config.json is loaded and the repository is <location>/<customer>/raw-content. Passing both --customer and --location states it explicitly. Everything else on the command tree is indexed at CLI command reference.

Passing --customer or --location also suppresses the automatic update check, which is a side effect worth knowing about rather than a feature to rely on.

Cleaning up

Most of what the CLI leaves on your machine is disposable. What each deletion costs:

Delete thisWhat happens next
~/.cache/leed/ (or one company’s subdirectory)You lose your validation state and your last-build timestamp. The next leed site commit will tell you to validate and then to build. Nothing is lost but the two minutes it takes
~/.config/leed/credentials.jsonYou are signed out of every environment. leed auth login puts it back
raw-content/.build/One rebuild
raw-content/.husky/, .vscode/, .claude/skills/The next leed site command reinstalls them, because the version comparison now sees a mismatch it cannot satisfy
leed.config.jsonNot recoverable from the CLI. Every leed site command stops at “Could not locate leed.config.json”, and nothing regenerates it locally

If you lose leed.config.json, open the profile menu in the CMS, choose Developer Setup, and either download a fresh copy or re-run the one-line installer — it reuses your existing session, skips the prerequisites it already satisfied, and rewrites the file. That path is described on installing the Leed CLI.

A corrupt credentials.json behaves differently from a missing one in a way worth knowing: a file the CLI cannot parse reads as an empty file, so the symptom is “not logged in” rather than an error. The same is true of an import manifest, which is treated as “start over”.

The 500 KB validation ceiling that FILE_VALIDATION_MAX_SIZE overrides is one of several product limits that have nothing to do with your plan — they are collected in limits that are not plan limits, and the rule it enforces is on leed site validate, commit and push. Large files that genuinely belong on your site go through the asset pipeline instead of the repository — see static files and caching. When one of these paths or variables turns out to be the cause of a failure you are looking at, the message is in the catalog on CLI troubleshooting and exit codes.

ESC