leed site init

leed site init turns a downloaded leed.config.json into a working project: it clones your content repository, installs the tooling that keeps the workflow enforceable, and writes a finalized configuration file next to the checkout. The installer runs it for you as its last step, so most people never type it. You run it yourself on a second machine, or after downloading a fresh leed.config.json from Developer Setup because the old project folder is gone or its repository token has expired.

Synopsis and preconditions

leed site init

The command takes no options of its own. Everything it needs comes from the configuration file and from your stored session.

PreconditionWhat it meansIf it is missing
A reachable leed.config.jsonThe file the CMS gave you, in the current directory or its parent“Could not locate leed.config.json. Run this application from the directory where your repo exists.”
initialized is false in that fileThe file has not already been consumed by an init“Site configuration already complete! Try running a build leed site build”
git 2.31 or newerThe clone authenticates through git’s config environment, which older git ignores“git 2.31 or newer is required to authenticate against the content repository.” — checked before anything is created
A stored session for the file’s environment and companyCredentials for production, staging or dev, whichever the file names, bound to the file’s companyId“Not authenticated.”, with both login commands printed under it. A session bound to a different company is refused rather than used
Developer access on your accountThe credentials endpoint is gated on the developer override, not on your planThe developer-credentials fetch is refused and init stops
A global git identityuser.name and user.email set with git config --global, neither of them blankInit finishes anyway and prints the two commands to run — but leed site commit refuses until you run them

The configuration file is looked for in exactly two places: the directory you are standing in, and its parent. Nothing deeper up the tree is searched, which is why the installer leaves you in ~/leed with the file directly beside you.

What it does, in order

  1. Checks your git version. Anything older than 2.31 stops the run before a folder is created.
  2. Creates the project folder. A directory named after your domain, slugified — acme.com becomes acme-com — inside the directory holding leed.config.json.
  3. Loads your credentials for the environment and company the configuration names. Without them it stops at “Not authenticated.” and prints both leed auth login forms as the hint. Init never switches a session’s active organization: each company has its own session.
  4. Fetches developer credentials from the CMS. This is the call that mints a fresh GitLab token for your content repository. A 401 is “Session expired or invalid.”; a 422 is “No active organization. Download a fresh leed.config.json from the CMS.” — the signal that the file you are holding is stale rather than that your session is.
  5. Stores the token and clones the repository. The token is filed in ~/.config/leed/auth.json under your company and handed to git for the clone; it is never written into the remote URL. An existing checkout is re-pointed at the credential-free remote instead.
  6. Checks your git identity. A missing or blank user.name or user.email is reported, not fatal.
  7. Installs the project tooling — dependencies, git hooks, VS Code files and the Claude Code skills.
  8. Writes the finalized configuration into the new project folder and deletes the file you started from.

When it finishes it prints where to go next:

	Initialization is complete, cd into your new project directory and try running:

		leed site build

The repository it clones

The content repository lives at gitlab.com/leed-ai/customers/<environment>/<your-domain-slug>/raw-content, and init checks it out on the staging branch. That default matters more than it looks: leed site commit and leed site push accept no other branch, so a fresh checkout is already on the only branch that can ship.

The origin URL init writes carries no credential. The GitLab token it was just issued is stored in ~/.config/leed/auth.json, filed under your company, and supplied to git on each fetch, push and clone the CLI runs. For git you run yourself, raw-content/.git/config names the leed __credential helper, which answers GitLab with the same stored token.

If raw-content/ is already on disk, init does not re-clone. It stores the freshly issued token, points origin at the credential-free remote and leaves your working tree untouched.

A clone that fails does not abort the run. Init reports what git said, finishes configuring the project, and leaves you with a site directory whose content repository is not yet usable — so the error is worth reading rather than scrolling past.

What it installs into the repository

PathWhat it isOverwrites an existing file?
raw-content/package.jsonA minimal @leed/site manifestNo — copied only when the repository has none
raw-content/node_modules/, bun.lockThe four devDependencies belowManaged by bun install
raw-content/.husky/Three git hooks plus the scripts they callYes, every file, on every version change
raw-content/.vscode/tasks.jsonTwelve leed tasksYes
<project>/<domain-slug>.code-workspaceA VS Code workspace pointing at raw-contentYes
raw-content/.claude/skills/Five Claude Code skillsYes — and skills removed in a newer release are deleted
<project>/<domain-slug>/leed.config.jsonThe finalized configurationWritten fresh; the source file is deleted

Git hooks

Init installs pre-commit, pre-push and commit-msg under .husky/, along with the three scripts they run. pre-commit and pre-push both check for git config --get leed.invoked, a flag only the CLI sets, and commit-msg checks that the message carries the structured payload the CLI writes. A bare git commit or git push fails with exit 18 and a message naming the leed command to use instead.

Those hooks are the reason validate, commit and push is a requirement rather than a recommendation. Everything else about git — git status, git log, git diff, git pull --rebase — keeps working normally.

When .husky/ does not yet exist, init runs bunx husky init first to register the hooks path with git, then copies its own files over the top.

Dependencies

Four packages are installed as devDependencies with bun install --silent --dev:

tailwindcss
@tailwindcss/forms
@tailwindcss/typography
husky

The exact versions are pinned by the CLI release you have installed, so they move when the CLI does. package.json itself is copied only when the repository does not already have one — if you have added scripts or dependencies of your own, init installs alongside them and never replaces the file.

VS Code

A <domain-slug>.code-workspace file is written beside the checkout, in the project folder rather than inside the repository. It opens raw-content directly, because that is where git, .claude/ and every file you are allowed to edit live. .vscode/tasks.json goes inside the repository, so it travels with the workspace.

The twelve VS Code tasks
Task labelWhat it runs
Run Webserverleed site build --serve
Buildleed site build
Build (debug)leed site build -d
Site Validationleed site validate
Site Validation - Reset (Dry-Run)leed site validate -r --dry-run
Site Validation - Resetleed site validate -r
Commit Changesleed site commit -m <prompted message>
Push to Stagingleed site push
Pull Latest from Staginggit pull
Loginleed auth login --browser
Logoutleed auth logout
Auth Statusleed auth status

Commit Changes prompts for the message, defaulting to update site content. Run Webserver skips minification because --serve implies -d, but that implied -d does not cost it its recording: it still counts as your pre-commit build. Build (debug) passes -d explicitly and does not count. The rule is on leed site build.

Site Validation - Reset discards your changes to every protected file and now asks before it does. From a VS Code task there is a terminal to answer on, so the prompt appears normally.

Claude Code skills

Five skills are installed under .claude/skills/, so an agent working in the repository knows the toolchain without being taught it each time:

  • site-builder — templates, layouts, partials, CSS, JavaScript and custom pages, with the Handlebars helpers, Tailwind v4, Alpine.js and Leed conventions the build expects.
  • site-preview — the local dev-server lifecycle: build, serve, restart, stop.
  • site-publish — the validate → build → commit → push workflow, including what to do when validation refuses or a rebase conflicts.
  • site-assets — uploading local files with leed asset upload and finding, retagging or exporting existing assets.
  • site-settings — changing CMS settings from the command line with leed settings, and publishing them.

The configuration file moves

This is the step that surprises people. Init does not edit leed.config.json in place. It writes a new, finalized copy inside the project folder it created — carrying initialized: true, the resolved location, and version markers for the husky, VS Code and skill files it just installed — and then deletes the original.

Before:

~/leed/
└── leed.config.json

After:

~/leed/
└── acme-com/
    ├── leed.config.json          ← the finalized copy
    ├── acme-com.code-workspace
    └── raw-content/
        ├── .claude/skills/{site-assets,site-builder,site-preview,site-publish,site-settings}/
        ├── .husky/{pre-commit,pre-push,commit-msg,…}
        ├── .vscode/tasks.json
        ├── package.json
        ├── node_modules/
        └── src/

Every field in the finalized file is described on leed.config.json, files and environment.

Running it twice

Once initialized is true, init refuses:

	Site configuration already complete! Try running a build `leed site build`

Nothing is touched. That refusal is checked before anything else runs, so it costs nothing and cannot half-apply.

To genuinely re-initialize — a new machine or a lost project folder — download a fresh leed.config.json from Developer Setup, put it in the directory you want the project created under, and run leed site init there. If raw-content/ still exists at that location, your working tree survives and only the stored token and the remote are refreshed. An expired repository token alone needs only leed auth rotate.

Keeping the installed files current

Init records the CLI version it installed the managed files with, in three places: huskyVersion, vscodeVersion and the skills’ own skillsVersion. Every later leed site command compares those against the version of the CLI running, and reinstalls whichever set has fallen behind before it does its own work — then rewrites leed.config.json with the new markers.

That sync is the answer to “why did those files come back”. After the CLI updates itself, the next leed site build you run replaces your .husky/ hooks, your .vscode/tasks.json and your .claude/skills/ with the current versions. Skills that a newer release removed are deleted rather than left orphaned. The files are Leed’s, they are on the read-only list, and edits to them do not survive — editable and read-only files draws the line.

Init is step six of the installer, and the folder it produces is described top to bottom in your site repository and what Leed writes into your repo. With the site cloned, the loop you will actually live in is local development, and every other command you can run in this repository is indexed at the CLI command reference. If a step failed with a message this page did not explain, it is cataloged in CLI troubleshooting and exit codes.

ESC