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 initThe command takes no options of its own. Everything it needs comes from the configuration file and from your stored session.
| Precondition | What it means | If it is missing |
|---|---|---|
A reachable leed.config.json | The 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 file | The file has not already been consumed by an init | “Site configuration already complete! Try running a build leed site build” |
| git 2.31 or newer | The 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 company | Credentials 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 account | The credentials endpoint is gated on the developer override, not on your plan | The developer-credentials fetch is refused and init stops |
| A global git identity | user.name and user.email set with git config --global, neither of them blank | Init 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
- Checks your git version. Anything older than 2.31 stops the run before a folder is created.
- Creates the project folder. A directory named after your domain, slugified —
acme.combecomesacme-com— inside the directory holdingleed.config.json. - Loads your credentials for the environment and company the configuration names. Without them it stops at “Not authenticated.” and prints both
leed auth loginforms as the hint. Init never switches a session’s active organization: each company has its own session. - 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.
- Stores the token and clones the repository. The token is filed in
~/.config/leed/auth.jsonunder 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. - Checks your git identity. A missing or blank
user.nameoruser.emailis reported, not fatal. - Installs the project tooling — dependencies, git hooks, VS Code files and the Claude Code skills.
- 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 buildThe 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
| Path | What it is | Overwrites an existing file? |
|---|---|---|
raw-content/package.json | A minimal @leed/site manifest | No — copied only when the repository has none |
raw-content/node_modules/, bun.lock | The four devDependencies below | Managed by bun install |
raw-content/.husky/ | Three git hooks plus the scripts they call | Yes, every file, on every version change |
raw-content/.vscode/tasks.json | Twelve leed tasks | Yes |
<project>/<domain-slug>.code-workspace | A VS Code workspace pointing at raw-content | Yes |
raw-content/.claude/skills/ | Five Claude Code skills | Yes — and skills removed in a newer release are deleted |
<project>/<domain-slug>/leed.config.json | The finalized configuration | Written 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
huskyThe 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 label | What it runs |
|---|---|
| Run Webserver | leed site build --serve |
| Build | leed site build |
| Build (debug) | leed site build -d |
| Site Validation | leed site validate |
| Site Validation - Reset (Dry-Run) | leed site validate -r --dry-run |
| Site Validation - Reset | leed site validate -r |
| Commit Changes | leed site commit -m <prompted message> |
| Push to Staging | leed site push |
| Pull Latest from Staging | git pull |
| Login | leed auth login --browser |
| Logout | leed auth logout |
| Auth Status | leed 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 withleed asset uploadand finding, retagging or exporting existing assets.site-settings— changing CMS settings from the command line withleed 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.jsonAfter:
~/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.