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"], "exclude": ["rebrand-deck", "street-map"], "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. The optional exclude list drops tool ids from this profile's view after that merge - a community tool one brand would rather not ship stays available to every other profile, and an id that isn't there is warned about, not fatal. 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/. A pack can live in the parent repo (as brands/lolly-start/ does) or be mounted from its own repository as a git submodule - which is how a private pack such as brands/suse stays out of public clones, so an instance without access to it simply falls back to lolly-start. See Build Guide » Getting the source for checking one out.

Stand a new pack 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 is written to catalog/assets/<ns>/tokens/brand.json as the pack's core-tier tokens asset, where <ns> is <brand> with hyphens stripped (an asset id can't carry - in its first segment, so --name acme-co yields assets/acmeco/…). Ingest also derives the pack's photo-treatment and icon-theme palette documents under catalog/assets/<ns>/palette/, so uploaded photos get on-brand washes and themable icons get colour pairings out of the box (icon themes are skipped when the palette has no accent). --register upserts the pack into profiles.json; --activate also switches to it and rebuilds the catalog; --out picks a different destination and --force overwrites an existing pack. 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 · screen · 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. screen is display capture the user grants in browser-native UI (picking a screen, window or tab, via host.recorder) - distinct from capture, which rasterises a URL the tool itself names. 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 browsersigned by Lollyvector SVGПровери самGet the signed file3 paths~1.3k nodes11 groups19 KBThe URL Screenshot tile in the Utilities gallery, carrying an Add-on badge because its capture capability is unmet in a plain browsersigned by Lollyvector SVGПровери самGet the signed file3 paths~1.3k nodes11 groups19 KB

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 offsigned by Lollyvector SVGПровери самGet the signed file42 paths~11k nodes128 groups127 KBEvery feature flag as its own switch, with the gallery categories on and Strip metadata offsigned by Lollyvector SVGПровери самGet the signed file42 paths~11k nodes128 groups127 KB

Strip metadata from uploads is the privacy one of those two opt-ins: 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 exportsigned by Lollyvector SVGПровери самGet the signed file11 paths~2.2k nodes18 groups31 KBA gallery tile carrying the Experimental status badge that its manifest sets, the same field that forces a watermark on every exportsigned by Lollyvector SVGПровери самGet the signed file11 paths~2.2k nodes18 groups31 KB

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 stand out:

The services' own config