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”
A stored session for the file’s environmentCredentials for production, staging or dev, whichever the file names“Not authenticated.”, with both login commands printed under it
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 --globalInit 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. Creates the project folder. A directory named after your domain, slugified — acme.com becomes acme-com — inside the directory holding leed.config.json.
  2. Loads your credentials for the environment the configuration names. Without them it stops at “Not authenticated.” and prints both leed auth login forms as the hint.
  3. Selects your organization, when the configuration carries a companyId. This is best-effort: a failure here is ignored, because the session may already have the right organization selected.
  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. Clones the repository, or re-points the one already on disk at a remote URL carrying the token just issued.
  6. Checks your git identity. A missing 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 embeds the GitLab token it was just issued, in the form https://access-token:<token>@gitlab.com/…. It is stored in plain text in raw-content/.git/config.

If raw-content/ is already on disk, init does not re-clone. It refreshes the origin URL with the freshly issued token and leaves your working tree untouched — which is exactly why re-running init is the supported repair for a repository token that has stopped authenticating.

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/Three 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 -d --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. Note that Run Webserver passes -d: it is a debug build, which is faster to iterate on but is deliberately not recorded as your pre-commit build. Run Build before committing, or read why 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

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

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-builder,site-preview,site-publish}/
        ├── .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, a lost project folder, an expired repository token — 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 remote URL is refreshed.

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