Configuration guide

Everything that shapes what a Lolly instance is - which brand it wears, which tools it exposes, what each tool is allowed to do - is configuration held in files, not settings buried in an app. This guide is the map of those files and the commands that act on them.

Profiles: brand + tool set

A profile binds a set of tool packs to a brand catalog. profiles.json at the repo root declares them:

{
  "default": "suse",
  "profiles": {
    "suse":        { "label": "SUSE",  "tools": ["community", "brands/suse/tools"], "catalog": "brands/suse/catalog" },
    "lolly-start": { "label": "Lolly Start (blank brand)", "tools": ["community", "brands/lolly-start/tools"], "catalog": "brands/lolly-start/catalog" }
  }
}

The repo-root tools/ and catalog/ are gitignored views of the active profile - never edit or commit them directly. Switch profiles with:

npm run profile            # show the active profile + what's available
npm run profile:suse       # community + SUSE tools, SUSE catalog
npm run profile:start      # blank brand: community tools + one neutral tokens asset

scripts/use-profile.ts builds the views: catalog becomes a symlink to the brand's catalog, and tools/ becomes a directory of per-tool symlinks merged from the profile's tool roots - later roots win on id collisions, so a brand pack can override a community tool of the same id. In a hosted or serverless build, pass --copy to materialise the views as real copies instead of symlinks (symlinks don't survive a function bundle). Writes through the views land in the real pack checkouts, so the normal edit → commit workflow is unchanged.

Brand packs

A brand pack is a directory (brands/<name>/) with a catalog/ and optionally its own tools/. Stand one up from a design-tokens export:

npm run ingest:brand -- <source> --name <brand> [--label "Label"] [--register|--activate]

<source> is any container Penpot / Tokens Studio export the same DTCG document in - a monolithic tokens.json, a one-file-per-set directory, or a project.penpot archive. The extracted document lands at catalog/assets/<name>/tokens/brand.json as the pack's single core-tier tokens asset. --register upserts the pack into profiles.json; --activate also switches to it and rebuilds the catalog. See Design Tokens for the token model and Quickstart for the end-user brand flow.

Brand lock

A brand's tokens asset can be marked authoritative (brandLock on its index entry). When locked, user-supplied tokens can't override the brand - installUserTokens is the single chokepoint and isLocked() the gate. The SUSE pack ships locked (its brand is non-overridable); lolly-start stays open so the blank-brand onboarding can write to it. Lock a pack when the brand must not drift; leave it open when you want end users to bring their own.

Capability gating (per tool)

Each tool.json declares the host capabilities it needs (schemas/tool.schema.json). The valid flags are:

network · filesystem · clipboard · camera · microphone · ffmpeg · wasm · capture · compose

A shell that can't provide a capability disables the tool rather than letting it fail - an ffmpeg tool is unavailable in the web PWA; a microphone/camera recording tool is unavailable in the headless CLI; a capture tool offers a browser add-on on Chromium and is marked desktop-only elsewhere. This is how one catalog serves web, desktop, and CLI without per-shell tool lists. network access is additionally allowlisted - a tool declares which hosts it may reach, and host.net enforces it.

Notice the badge: URL Screenshot declares capture, the browser cannot provide it alone, so the tile offers the add-on instead of vanishing.

The URL Screenshot tile in the Utilities gallery, carrying an Add-on badge because its capture capability is unmet in a plain browser

Curating what users see

Two mechanisms narrow the catalog without forking it:

Every feature flag as its own switch, with the gallery categories on and Strip metadata off

Strip metadata from uploads is the one opt-in (default-off) flag: turn it on and images uploaded to your catalogue are scrubbed of EXIF, location (GPS), and other embedded metadata on import. Content Credentials (C2PA provenance) are always preserved either way - a signed or AI-generated image keeps its credential whether the flag is on or off.

Maturity & watermarking

A tool's status controls trust signalling, enforced by the engine, not by convention:

Promote a tool by changing one field in its manifest. The status is not an internal note: every surface shows it, so a reader knows what they are picking up before they open it.

A gallery tile carrying the Experimental status badge that its manifest sets, the same field that forces a watermark on every export

Catalog build & validation

The manifest is the source of truth; catalog/tools/index.json is generated and must not drift.

npm run build:catalog     # index.json + asset checksums + preview bundle
npm run validate:catalog  # schema + invariants: checksums, file existence,
                          #   bindToProfile fields, palette refs, replacedBy chains

validate:catalog fails CI if the generated index drifts from the manifests, so run build:catalog after any tool.json or asset edit. Tool and asset IDs are permanent contracts - version in the manifest, never rename the path.

Engine & runtime knobs

Most runtime behaviour is fixed contract rather than deploy-time config, but two things are worth knowing:

The services' own config