leed site init

leed site init sets a site up on this machine: it fetches the site’s configuration from the CMS, clones your content repository, installs the tooling that keeps the workflow enforceable, and records the site in ~/.config/leed/sites.json. leed setup runs it for you, so most people never type it. You run it yourself to set a site up in another folder or for another company, to install Font Awesome into a site, or to create a local sandbox site.

Synopsis and preconditions

leed site init --company <companyId|domain> -e <env> [--dir <folder>]
leed site init --font-awesome
leed site init --font-awesome-pro <zip>
leed site init --sandbox [dir]
FlagWhat it does
--company <company>The company to set up, by companyId or domain
-e, --env <name>The environment, declared on leed site and read from ENV_NAME when you leave it out. Optional when the --company you name is logged in to exactly one environment
--dir <path>The folder to create the site’s folder in. Default: the current directory
--font-awesomeInstall Font Awesome Free into src/static/webfonts. Does nothing when Font Awesome fonts are already installed
--font-awesome-pro <zip>Install or upgrade your own Font Awesome Pro fonts from the Pro web package zip you downloaded
--sandbox [dir]Create a local sandbox site in dir (default ./leed-sandbox), with no Leed account
PreconditionWhat it meansIf it is missing
A site named--company and an environment, as above. Nothing is guessed, and a site still waiting to be set up is listed rather than picked“Name the site to set up: pass --company <companyId|domain> and -e <env>.”, coded invalid_arguments, with the command to rerun
Not inside a checkoutA full init clones a new checkout, so it is not run inside one“<folder> is already a site checkout; leed site init would clone another one inside it.” — run it elsewhere, or pass --dir
Not already set up thereThe site is not already registered with a checkout at that path“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 that environment and companyCredentials for production, staging or dev, bound to the company you named“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 (HTTP 403, permission_denied) and init stops
A folder that holds no other siteAn existing raw-content/ at the target must belong to the same company and environment“<path> holds another site (…); choose another folder with --dir.”
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

Init needs no file written beforehand: the site’s domain, companyId and the account that set it up all come from the CMS once it knows which company you mean. With -e and no --company, it uses that environment’s company-unbound login only when no company login is saved there; with any company login saved, --company is the only way to choose.

What it does, in order

  1. Checks your git version. Anything older than 2.31 stops the run before a folder is created.
  2. Resolves the site from --company and the environment, refusing when they do not name one.
  3. Loads your credentials for that environment and company. 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: the site’s domain and ids, and a freshly minted GitLab token for your content repository. A 401 is “Session expired or invalid.”; a 422 is “This sign-in is bound to no company; sign in again and choose one.”
  5. Creates the site folder. A directory named after your domain, slugified — acme.com becomes acme-com — inside --dir, or the current directory.
  6. Stores the token and clones the repository into raw-content/ inside that folder. 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.
  7. Checks your git identity. A missing or blank user.name or user.email is reported, not fatal.
  8. Installs the project tooling — dependencies and git hooks.
  9. Records the site in ~/.config/leed/sites.json, with the checkout’s path and the version its hooks were installed with.
  10. Installs Font Awesome, when --font-awesome or --font-awesome-pro was given. A failed font install is a warning naming the command to retry, not a failed init.

When it finishes it prints where to go next:

	Initialization is complete, cd into your site's checkout and try running:

		cd /home/you/leed/acme-com/raw-content
		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/src/static/webfonts/Font Awesome, with --font-awesome or --font-awesome-proFree is never installed over fonts already there; Pro installs or upgrades

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 --prefer-offline --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, with leed site vscode

Init no longer writes editor files, and no command rewrites them on its own. leed site vscode writes two when you ask: a <domain-slug>.code-workspace beside the checkout, in the site folder rather than inside the repository, opening raw-content directly; and .vscode/tasks.json inside the repository. Both are overwritten, so running it again restores the Leed defaults, and --dry-run shows what would change. leed setup offers the same files once, at a terminal, and keeps either one that already exists.

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 come from the plugin

Init no longer installs skills under .claude/skills/. An agent working in the repository gets the Leed skills from the Leed plugin for Claude, which leed setup offers to install, and which you can add in Claude Code with /plugin marketplace add leed-ai/ai-plugins and /plugin install leed@leed. The skill copies an older CLI installed are removed from the checkout, once, the first time the new CLI meets the site — so they cannot shadow the plugin’s newer skills.

Where the site is recorded

Init writes no configuration file into your folders. It records the site in the per-user registry, ~/.config/leed/sites.json: its company, environment, domain, the checkout’s path and the version its hooks were installed with. The checkout is identified by its own identity.json, so every leed site command works from anywhere inside it, and moving or renaming the folder needs no action — the next command run inside it records the new path.

After leed site init --company acme.com -e production --dir ~/leed:

~/leed/
└── acme-com/
    └── raw-content/                 ← the checkout, recorded in ~/.config/leed/sites.json
        ├── identity.json
        ├── .husky/{pre-commit,pre-push,commit-msg,…}
        ├── package.json
        ├── node_modules/
        └── src/

Every field in the registry is described on files and environment.

Font Awesome

A site has Font Awesome Free by default. leed site init --font-awesome, run inside a checkout that is already set up, installs Free 7.3.1 into src/static/webfonts when no Font Awesome fonts are there, and never over Pro. leed site init --font-awesome-pro <zip> installs or upgrades your own Pro fonts from the Pro web package zip you downloaded from fontawesome.com; Pro 7.0.0 and 7.3.1 are supported. Inside a set-up checkout either flag installs the fonts and does nothing else. Leed’s CI build runs --font-awesome before it builds, so a site that never committed fonts is built with Free.

Sandbox sites

leed site init --sandbox [dir] writes a local site into dir — ./leed-sandbox by default — with no Leed account, no session and no git repository, then installs Font Awesome Free into it, best effort. It builds and previews with leed site build and --serve like any site, and shows up in leed site list. Every command that needs the CMS refuses inside it with sandbox_site, exit 3, before it authenticates. To publish one, move it into Leed with /leed:site-sandbox-move in Claude.

Running it twice

Once the site is set up at a checkout, init refuses to set it up there again:

	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 set the site up again — a new machine, a lost folder, or a second copy somewhere else — run leed site init --company <companyId> -e <env> --dir <folder>, or simply leed setup. 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 git hooks with, as the site’s huskyVersion in sites.json. Every later leed site command compares it against the version of the CLI running, and reinstalls the hooks when they differ before it does its own work.

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 with the current versions. The hooks 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. VS Code files are no longer kept in sync; run leed site vscode when you want the current ones.

Init is what leed setup runs to set your site up, 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