Skip to main content

@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/icons

That 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/cli

Requires Node 20 or newer.

Authenticate

tendee login

Opens 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-lockfile
Never pass a token as a command-line argument — it lands in your shell history and in the process list.

Two 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/icons

Building 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> --yes

sync needs neither scan nor push nor link. You can be in the first workflow without ever touching the second.

Commands

CommandWhat it does
tendee loginBrowser login. Requires a human
tendee login --stdinRead 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 initInstall a skill file so a coding agent knows how to drive this CLI
tendee --versionPrint 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.

Two things this CLI deliberately does not do. There is no delete — removing icons or collections happens in the dashboard. And there is no Figma import: pulling icons out of a Figma file is a dashboard flow too. Both are product decisions, not missing flags.

Targets

react, vue, svelte, web-component, react-native, swift, compose, sprite.

Default output directories, overridden by --out:

targetdefault
react, vue, svelte, web-component, react-nativesrc/icons
swiftTendeeIcons
composesrc/main/kotlin/io/tendee/icons
spritepublic/icons

A repository without a src/ directory almost certainly wants an explicit --out.

What lands on disk

For the React target:

  • one <PascalName>.tsx per icon,
  • tendee-icon.ts, exporting the TendeeIconProps and TendeeIcon types,
  • tendee.lock.json at 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 exportedwhat Tendee generates
AlertCircleCircleAlert
Loader2LoaderCircle
HistoryRotateCcwClock

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  -> PanelLeft

Rewrite 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-lockfile

Writes 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 init

Writes 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.