
Good OSS documentation tells contributors how your project works. Good tooling lets them contribute without having to learn everything first.
That line is the whole reason we shipped a contributor CLI inside the Sora UI monorepo this month. Not another consumer install command — we already have @soralabsoss/sora-cli for that. This one is for people opening PRs against the registry itself: the folks adding Motion primitives, Base UI / Radix UI components, and (soon) Catalog showcases.
Sora UI is large enough now that adding one component is not just creating a file. It's a small distributed system: registry source, registry-item.json, docs MDX, sidebar metadata, optional manual demos, demoProps keys that must match export names, and a registry:build pass that only publishes what the docs actually reference. The architecture is intentional. The cognitive load on a first-time contributor is also real.
We'd rather encode the conventions in tooling than ask every new contributor to become a maintainer before their first PR lands.
The problem: one component, many touchpoints
Pick a Motion primitive as an example. A complete contribution might touch:
UI tier (/ui) adds framework choice (Base vs Radix), install targets, and a different MDX tree. Catalog pages (/catalog) are layout showcases on top of primitives that already exist.
CONTRIBUTING.md documents all of this clearly. Flow 1 (UI), Flow 2 (Motion), Flow 3 (Catalog) are accurate. We've also got apps/www/registry/README.md for demoProps, manual demos, and the shadcn-style registry contract.
Clear documentation is necessary. It is not sufficient.
Docs tell you what to do. They don't write the fifth file for you at 1am. They don't stop you from putting MyEffect in demoProps when the export is MyAwesomeEffect. They don't run registry:build and remind you that an undocumented folder was skipped because nobody added the MDX slug yet.
The contributor experience today
If you've contributed to a shadcn-style registry before, the shape will feel familiar — and that's part of the point. Sora UI follows the same registry-item.json convention as shadcn/ui, extended with Motion-specific rules: prefers-reduced-motion, JSDoc props for TypeTable, cn(...) overrides, credits for external inspiration.
What changed as the library grew is surface area:
- Three product pillars — Motion, Catalog, and UI — each with its own content tree and routing.
- Base UI and Radix UI variants under
/ui, not a single component folder. - Registry build logic that merges items, resolves transitive dependencies, and skips undocumented registry folders so
public/r/*.jsonstays aligned with what the docs site can actually render.
A motivated contributor can follow the flows and ship a solid PR. We've seen it. But the failure modes are repetitive: forgotten meta.json entry, demoProps key mismatch, manual demo folder missing when preview expects it, or a registry folder that builds but never appears on the site because docs weren't wired.
Those aren't "you didn't read the README" problems. They're repeatable scaffolding problems — exactly the kind of work a CLI should own.
Why documentation alone isn't enough
Documentation scales knowledge. Tooling scales correctness and speed.
When conventions live only in prose:
- Every contributor re-derives the file list from scratch.
- Naming drift shows up in review (
demoPropsvs export name vs registryname). - CI catches issues late — after
registry:buildor the registry integrity tests fail. - Agents and scripts can't reliably follow a 200-line contributing guide; they need flags and deterministic output.
We still want contributors to understand the architecture eventually. We just don't want understanding the architecture to be the gate for the first useful commit.
Introducing the Sora UI contributor CLI
packages/www-cli is an internal workspace CLI — invoked from the monorepo root, not published to npm. It deliberately stays separate from @soralabsoss/sora-cli: consumers install components into their apps; contributors scaffold components inside our registry.
What create actually scaffolds
Motion (create primitive) writes:
registry/primitives/<category>/<name>/index.tsx— stub component with repo conventions ("use client",cnfrom@workspace/ui, reduced-motion hook placeholder)registry-item.json—demoPropskeyed by the generated export namecontent/docs/motion/<name>.mdx— preview, installation, andTypeTableskeleton- Patches
content/docs/motion/meta.jsonin the correct---Section---block - Optionally a manual demo under
registry/demo/primitives/...(--with-demo) - Runs
registry:buildunless you pass--skip-build
UI (create ui) writes the parallel structure under registry/ui/{base|radix}/, content/ui/, demo defaults in non-interactive mode, and the nested slug in content/ui/meta.json (base/my-widget).
After scaffolding, the CLI prints a contributor checklist: export name, demoProps key, next docs steps, and the preview URL (/motion/<name> or /ui/base/<name>).
The creative work — the actual animation, accessibility, credits, variant demos — stays human. The CLI handles the parts that should never be different between PRs.
Built for humans, scripts, and agents
We aligned the interface with Command Line Interface Guidelines — non-interactive by default in CI, explicit when it isn't:
| Flag | Purpose |
|---|---|
--yes / --no-input | No prompts; require name + category/framework |
--dry-run | Print paths and meta updates; write nothing |
--quiet | Minimal output; silent registry:build |
--skip-build | Scaffold only; run build yourself |
Help text includes copy-paste examples and points back to CONTRIBUTING.md — docs and tooling cross-reference each other instead of competing.
From scaffolding to validation
Scaffolding is Phase 1 and 2. Validation is the next layer.
Today, registry:build already warns when it skips undocumented items — and those logs now suggest the matching create:primitive or create:ui command when a folder looks like a scaffold target. Post-build, apps/www/scripts/test-registry-integrity.mts catches broken registry JSON.
What's on the roadmap inside www-cli is a read-only doctor command: registry folder without MDX, meta.json slug without a file, demoProps keys that don't match exports, undocumented entries that would be skipped at build time. Think of it as the contributor-side cousin of sora-cli doctor — same spirit, different job. Consumer doctor checks whether your project can install from the registry; contributor doctor checks whether the registry PR is internally consistent.
Automated tests already guard the scaffold path: bun run test:www-cli runs integration fixtures that create and tear down temp registry entries with --skip-build, and an optional slow path exercises full registry:build when WWW_CLI_RUN_REGISTRY_BUILD=1.
The philosophy: CLI doesn't hide architecture — it encodes it
A bad internal CLI would generate opaque magic and leave maintainers cleaning up surprises in review.
We aimed for the opposite:
- Templates live in the repo next to the conventions they implement — when
demoPropsrules change, the generator changes in the same PR. - Generated files are normal source — readable stubs, not minified blobs. Reviewers see the same structure they'd expect from a careful human scaffold.
- The same names the docs site uses — export names, registry
namefields, MDX slugs, meta sections — are computed once inpackages/www-cli/src/lib/naming.tsand friends. - Two CLIs, two audiences — never merge consumer install logic into contributor scaffolding.
sora-clifetches into your repo;www-clicreates ours.
The CLI is documentation that executes.
Why this matters for open source
Open source maintainers often optimize for review quality and forget contribution friction. Both matter.
When the first PR from a new contributor is mostly boilerplate done correctly:
- Review focuses on animation, API design, and docs prose — not "you forgot meta.json."
- Drive-by fixes become realistic — fix a typo and add a primitive without a two-hour archaeology session.
- Agents can run
bun run create:primitive ... --yesin a branch and hand off a PR with the right skeleton; humans finish the motion.
Sora UI's promise is still copy-paste ownership for users. For contributors, the parallel promise is: you shouldn't need maintainer context to open a valid PR.
What's next
Phase 3 — create catalog: scaffold content/catalog/<slug>.mdx and flat meta.json entries for layout showcases — the lightest tier, no new registry folder by default.
Contributor doctor: read-only audits with actionable fixes (bun run create:…, fix demoProps, add MDX).
More validation in CI: tighter coupling between scaffold output and test:registry.
If you're contributing today, start with CONTRIBUTING.md and try:
Pick Motion or UI, name your component, and spend your time on the animation — not on remembering which meta.json section marker goes where.
If you're consuming Sora UI in your app, nothing changes: keep using npx @soralabsoss/sora-cli add <name> --yes. This CLI is for the people building the registry you install from.
Implementation lives in packages/www-cli on the Sora UI monorepo. Questions and PRs welcome.