@spotagency/cli
v1.3.0
Published
Install Spot UI components into a project.
Downloads
93
Readme
@spotagency/cli
Installs Spot UI components into a project. Components are copied into your
source tree, not imported from a package — once installed they are yours to
edit, and spot upgrade will ask before touching anything you changed.
npx @spotagency/cli login
npx @spotagency/cli init
npx @spotagency/cli add button section-galleryRequires Node 20+, and a project on Next.js 16 with Tailwind CSS 4.
Installing
Three ways, in rough order of preference for client work:
As a project dev dependency — the version is pinned in package.json and
committed, so everyone on the project and CI run the same CLI:
pnpm add -D @spotagency/cli
pnpm spot add buttonWith npx, nothing installed — always current, but you type the full
package name each time:
npx @spotagency/cli@latest add buttonGlobally — gives you a bare spot everywhere:
npm i -g @spotagency/cliConvenient, but it goes stale silently and drifts between machines, which is worth avoiding on work that runs over months.
Updating
pnpm update @spotagency/cli # dev dependency
npm i -g @spotagency/cli@latest # global
npx @spotagency/cli@latest … # npx, forcing a fresh resolveCheck what you are on with spot --version, and what is current with
npm view @spotagency/cli version. Plain npx @spotagency/cli can reuse a
cached copy, so pin @latest when it matters.
Updating the CLI does not touch files already installed in your project — use
spot upgrade for those.
Upgrading from 1.0.0
1.0.0's init removed your @import "tailwindcss";, because the Spot base
stylesheet imported Tailwind itself. It no longer does, so a project set up with
1.0.0 needs that line back — as the first line of your global stylesheet, above
the Spot block:
@import "tailwindcss";
/* spot ui — managed by @spotagency/cli, edits below are overwritten */Order matters here. Until you run spot upgrade, the old base.css still
supplies Tailwind and everything works; the moment you upgrade, it stops. So add
the line first, then upgrade:
spot upgrade && spot doctorspot doctor checks exactly this, and says so plainly if it is missing.
Getting access
The registry is private. You need a token from Spot Agency — one per client project, so access can be revoked per project.
spot loginThe token is verified against the registry before anything is stored, then
written to ~/.config/spot/credentials.json with 0600 permissions.
In CI, set the environment variable instead — it takes precedence over the stored file, so nothing needs to be written to disk:
SPOT_REGISTRY_TOKEN=… spot add --all --yesspot whoami confirms which registry you reach and where the token came from.
Setting up a project
Run this once, in the project root:
spot initIt will:
- detect your global stylesheet (
src/app/globals.css,app/globals.css, …) - ask where components, blocks, helpers and styles should go
- write
spot.json - install the standard token layers
- add an import block to your stylesheet
Where files go
init asks rather than assuming, because not every project is shaped like
create-next-app:
Primitives (Button, Container, Section) @/components/spothub-ui/ui
Sections and blocks @/components/spothub-ui/blocks
Helpers (cn, formatters) @/lib/spothub-ui
Token stylesheets @/styles/spothub-uiThe defaults namespace everything under spothub-ui/, so installed files sit
together and stay obviously ours rather than mixing into folders the project
already uses. Answer with whatever suits your project instead —
@/design-system/primitives, @/features/blocks, @/utils, anything. Imports inside the installed files are
rewritten to match, so button.tsx will import cn from wherever you put it.
init then prints the directories those aliases actually resolve to, and waits
for you to confirm. @/ is resolved through your tsconfig.json paths, so
the alias alone does not tell the whole story — a project without an @/*
mapping resolves it differently, and it is better to see that before any files
are written than after.
Change your mind later by editing aliases in spot.json; the next add
writes to the new location.
For a scripted setup, pass them as flags and skip every prompt:
spot init obersaxen \
--css src/app/globals.css \
--ui @/design-system/primitives \
--lib @/utils \
--yesYour stylesheet
The block is appended below your existing @import "tailwindcss";, which is
never removed or rewritten — the token layers extend Tailwind's theme, so all
they need is to come after it:
@import "tailwindcss";
/* spot ui — managed by @spotagency/cli, edits below are overwritten */
@import "../styles/spot/base.css";
@import "../styles/spot/themes/obersaxen.css";
/* end spot ui */Only the region between the markers is managed. Everything else in the file is
left alone, and re-running init or spot theme rewrites just that block.
You do not need an
@sourceline. Registry files are copied into your own source tree, which Tailwind 4 already scans. (The@sourcegotcha applies to Tailwind classes living innode_modules, which is not how this works.)
Adding components
spot add button # one item
spot add section-gallery # pulls in base, container, section and cn
spot add gallery # short aliases work too
spot add --all # everything in the registry
spot add # pick from a listDependencies are resolved for you: section-gallery needs base, container,
section and cn, so all five arrive together. Missing npm packages are
installed with whichever package manager your lockfile indicates. Use
--no-install to get the command printed instead of run.
Writes are batched and atomic — if anything fails, the whole set is rolled back rather than leaving the project half-installed.
Keeping up to date
spot list # everything available, and what is installed here
spot view button # files, dependencies and version for one item
spot diff # how your copies differ from the registry
spot upgrade # pull newer versions
spot doctor # check this project's setupspot.json records a hash of every file this CLI wrote. That is what lets
upgrade tell "you edited this" apart from "you never touched it":
- untouched files are replaced silently
- edited files prompt — keep yours, take the registry's, or show the diff
- in a non-interactive run, edited files are skipped, never overwritten.
Pass
--overwriteif you really do want them replaced.
Themes
A new project starts on the standard tokens and nothing else. Themes are opt-in:
spot theme winterInstalls the theme if it is missing, updates spot.json, and adds the import
to the managed CSS block. Retheming a project is this one command.
A theme is a fixed, build-time brand — it redefines variables on :root, so a
build has exactly one. That makes it right for a client brand and wrong for
anything that varies per page. If the CMS decides the look (a season, a
campaign, a per-tenant palette), do not reach for a theme: scope your own
overrides to an attribute and set it from the page data, so one build serves
them all.
[data-season="winter"] { --role-accent: #47759d; }Commands
| Command | What it does |
| --- | --- |
| login | Store a registry token for this machine |
| logout | Forget the stored token |
| whoami | Check the token and the registry it reaches |
| init [theme] | Create spot.json, install tokens, wire up the CSS. No theme unless you name one |
| add <items…> | Install items and everything they depend on |
| list | Show every item, and which are installed here |
| view <item> | Show one item: files, dependencies, version |
| diff [items…] | Compare installed files against the registry |
| upgrade [items…] | Pull newer versions, asking about local edits |
| theme <name> | Switch the project theme |
| doctor | Check the token, the stylesheet, and where each alias resolves |
Options
| Flag | Applies to | Meaning |
| --- | --- | --- |
| --all | add | Every item in the registry |
| --overwrite | add, upgrade | Replace locally modified files without asking |
| -y, --yes | most | Take the default answer to every prompt |
| --no-install | add, init, upgrade | Print the package-manager command instead of running it |
| --registry <url> | all | Override the registry URL |
| --token <token> | login | Supply the token non-interactively |
| --css <path> | init | Path to the global stylesheet |
| --ui <alias> | init | Where primitives go (default @/components/spothub-ui/ui) |
| --blocks <alias> | init | Where blocks go (default @/components/spothub-ui/blocks) |
| --lib <alias> | init | Where helpers go (default @/lib/spothub-ui) |
| --styles <alias> | init | Where token CSS goes (default @/styles/spothub-ui) |
| --cwd <path> | all | Run as if started in this directory |
spot.json
Committed to the client repo.
{
"$schema": "https://spothub-ui.vercel.app/schema/spot.json",
"registry": "https://spothub-ui.vercel.app/r",
"theme": "obersaxen",
"aliases": {
"ui": "@/components/spothub-ui/ui",
"blocks": "@/components/spothub-ui/blocks",
"lib": "@/lib/spothub-ui",
"styles": "@/styles/spothub-ui",
"css": "src/styles/global.css"
},
"installed": {
"button": {
"version": "0.1.0",
"files": { "components/ui/button.tsx": "sha256-…" }
}
}
}Change an alias and the next add writes there, rewriting imports to match.
@/ is resolved through your tsconfig.json paths, so a project with or
without src/ both work without configuration. css is a plain project-relative
path, not an import alias.
Hashes are per file rather than per item, because an item such as base ships
more than one file and you might have edited only one of them.
Troubleshooting
No spot.json found — run spot init in the project root. Every command
except login/logout/whoami/init needs it.
The registry rejected this token — the token was revoked or belongs to a
different registry. spot login again; spot whoami shows which registry the
current project points at.
A component's classes have no effect — run spot doctor. It checks that the
managed block is present, that @import "tailwindcss"; is there at all, and that
it sits above the block; the token layers are @theme extensions and generate
nothing without Tailwind loaded first. If all three pass, check that your build
uses Tailwind 4 — Tailwind 3 will not read @theme layers.
Projects set up with a CLI older than 1.1 will fail that middle check: init
used to replace the Tailwind import. Add @import "tailwindcss"; back as the
first line of the file.
Files landed somewhere unexpected — spot doctor prints the resolved
directory for each alias. Fix aliases in spot.json, delete the misplaced
files, and run spot add again.
spot upgrade keeps skipping a file — that file has local edits. Run
spot diff to see them, then either keep them or pass --overwrite.
