@tendee/cli
Tendee CLI
Vendors a curated icon collection from Tendee into your repository as plain source files you own and commit — no runtime dependency, no icon font, no bundle of SVGs loaded at runtime.
Overview
npx @tendee/cli sync my-collection --target react --out components/iconsThat writes one component per icon, a shared types module, and a lock file. From then on the icons are ordinary files in your project.
Install
Nothing to install — npx @tendee/cli <command> works. To pin it:
npm install --save-dev @tendee/cliRequires Node 20 or newer.
Authenticate
tendee loginOpens a browser and waits for you to approve. Tokens are stored in ~/.config/tendee/config.json and are scoped to one organization.
In CI, issue a token in the dashboard and pass it in the environment instead:
TENDEE_TOKEN=tnd_... tendee sync my-collection --target react --frozen-lockfileTwo directions
Installing a collection — someone has curated a collection and you want it in your code:
tendee collections # find the slug
tendee sync <collection> --target react --out src/icons --dry-run
tendee sync <collection> --target react --out src/iconsBuilding a collection — your project already has icons scattered around it and you are consolidating them:
tendee scan # read-only; writes .tendee/scan.json
tendee push <collection> --dry-run # preview uploads of the SVG files it found
tendee push <collection> --yes
tendee link <collection> --dry-run # preview catalog matches for icon-library imports
tendee link <collection> --yessync needs neither scan nor push nor link. You can be in the first workflow without ever touching the second.
Commands
| Command | What it does |
|---|---|
tendee login | Browser login. Requires a human |
tendee login --stdin | Read an already-issued token from stdin (CI-friendly) |
tendee collections [--json] | List collections in your organization |
tendee scan [--dir .] [--json] | Read-only project scan → .tendee/scan.json |
tendee push <collection> [--dry-run] [--yes] [--json] | Upload the SVG files the scan found |
tendee link <collection> [--dry-run] [--yes] [--json] | Link catalog icons matching the library imports the scan found |
tendee sync <collection> --target <target> [--out DIR] | Materialize a collection into source files |
tendee agent init | Install a skill file so a coding agent knows how to drive this CLI |
tendee --version | Print the installed version |
Run tendee sync --help for the full flag list. scan, push, link and collections accept --json — parse that rather than scraping the human-readable output, which is free to change. sync has no --json; its file list and rename report are text only.
Targets
react, vue, svelte, web-component, react-native, swift, compose, sprite.
Default output directories, overridden by --out:
| target | default |
|---|---|
| react, vue, svelte, web-component, react-native | src/icons |
swift | TendeeIcons |
compose | src/main/kotlin/io/tendee/icons |
sprite | public/icons |
A repository without a src/ directory almost certainly wants an explicit --out.
What lands on disk
For the React target:
- one
<PascalName>.tsxper icon, tendee-icon.ts, exporting theTendeeIconPropsandTendeeIcontypes,tendee.lock.jsonat the repository root,- a manifest under
.tendee/.
Pass --as-package to also get an index.ts barrel and a generated package.json.
Updating after the collection changes
Re-run the same sync. It reports what moved, and — this is the part that matters — names the files that still import an icon which has left the collection:
Changes since the last sync:
- RotateCcwClock.tsx
+ Sparkles.tsx
~ Camera.tsx
Still imported, but no longer in the collection:
RotateCcwClock
src/components/HistoryPanel.tsx
src/components/Timeline.tsx
Point these at another icon, or put the icon back in the collection.Without that list, removing an icon in the dashboard turns into a build error somewhere else with nothing connecting the two events.
Commit the generated files and the lock. tendee.lock.json is what lets the next person who clones the repository re-materialize the exact same icons — it is the direct analogue of package-lock.json, not build output. .tendee/ is the opposite case: its manifest records an absolute output path and is machine-local, so ignore it.
Do not hand-edit a generated file. Each one starts with Generated by Tendee. Do not edit directly. That marker is load-bearing: it is how sync decides a file is safe to overwrite, and safe to delete once its icon leaves the collection. An edit is lost on the next sync, and a file that loses the marker is never cleaned up. To change an icon, change the collection and sync again.
Component names are not your old library’s export names
This is the most common way a migration breaks. Names come from the icon’s canonical name in the registry:
| what your old library exported | what Tendee generates |
|---|---|
AlertCircle | CircleAlert |
Loader2 | LoaderCircle |
History | RotateCcwClock |
Run --dry-run first. It prints the exact file list, plus a rename map. If you have run tendee scan in this project, the map is keyed on the names in your code:
3 names in your code map to a differently named component:
AlertCircle -> CircleAlert
Loader2 -> LoaderCircle
PanelLeftIcon -> PanelLeftRewrite exactly those imports; every other binding keeps its name.
Without a scan there is nothing to key on, so the report falls back to what upstream records — CircleAlert <- AlertCircle, read right to left. That form is weaker: it lists old upstream names whether or not your project ever used them, and it cannot see conventions your library invented, such as the trailing Icon in PanelLeftIcon. Run tendee scan first and you get the better one.
Drift checking in CI
tendee sync <collection> --target react --frozen-lockfileWrites nothing. Exits non-zero if the registry has moved away from the committed lock, so a pull request can fail on icons that changed underneath it.
Already up to date means the collection has not changed since your last sync. That is a success, not a warning.
Coding agents
tendee agent initWrites a skill file describing this CLI — .claude/skills/tendee/SKILL.md for Claude Code, .cursor/rules/tendee.mdc for Cursor, or an AGENTS.md block otherwise.
Detection works off agent config that already exists (.claude/, .cursor/, AGENTS.md), so a project that has none gets AGENTS.md and says so. Pass --target claude|cursor|codex to choose.
License
MIT.