appCN CLI
@app-cn/cli is a thin, RN-aware wrapper around the shadcn CLI. Two commands: init (one-shot project setup) and add (install a component).
init
One-shot setup for a fresh Expo or bare React Native app. Detects your package manager, installs NativeWind + Reanimated + gesture-handler, patches tailwind/babel/metro/global.css, and registers the @app-cn shadcn registry in components.json.
npx @app-cn/cli@latest initDetects Expo vs bare RN and your package manager. Safe to re-run.
Re-running init on an already-configured project produces zero diffs. If a file already contains the appCN setup, the CLI leaves it alone. If a file exists but conflicts (e.g. a hand-rolled metro.config.js that does not wrap with withNativeWind), the CLI prints the required diff and aborts — it never silently mutates Metro or app entry files.
add
Install a single appCN component into the current project. Delegates to shadcn after preflighting the registry and ensuring components.json knows about @app-cn.
npx @app-cn/cli@latest add buttonReplace `button` with any slug from the components index.
Under the hood, appcn add <slug> spawns <pm> dlx shadcn@latest add @app-cn/<slug> so the resulting file writes are identical to running shadcn directly. The CLI adds RN-specific preflight: registry HEAD check, components.json patch, and clearer error messages.
Environment
Override the registry base URL for local development or self-hosted forks.
# .env
APPCN_REGISTRY_URL=http://localhost:3000/rDefaults to https://appcn.vercel.app/r. The CLI uses this for the HEAD check; the URL written into components.json uses the same value so consumers point at the registry they actually fetched from.
What init writes
The exact diff init produces on a fresh `npx create-expo-app` project.
tailwind.config.js— addspresets: [require("@app-cn/ui/tailwind-preset")]and content globs covering./app,./components.babel.config.js— adds thenativewind/babelpreset and ensuresreact-native-worklets/plugin(Reanimated 4) orreact-native-reanimated/plugin(Reanimated 3) is last in the plugin list.metro.config.js— wrapsgetDefaultConfigwithwithNativeWind(..., { input: "./global.css" }). Aborts if an existing file does not match the expected shape.global.css— Tailwind directives and the appCN CSS variables. Prints the one-line import to add to your app entry; never auto-edits_layout.tsx.components.json— deep-mergesregistries["@app-cn"]: "https://appcn.vercel.app/r/{name}.json"and defaultaliases. Prompts on conflict; never overwrites existing keys without confirmation.tsconfig.json— ensurespaths["@/*"]resolves to./*for shadcn-style imports.
Why a custom CLI?
Couldn't you just point people at shadcn directly?
You can — that's the shadcn (URL) tab on every component page. The CLI exists for one job: init. React Native + Expo have a non-trivial setup surface (NativeWind babel + metro + global CSS + Reanimated plugin ordering) that shadcn cannot do for you because it is React Native-agnostic. After init, the difference between appcn add and shadcn add @app-cn/... is just which tool runs the HEAD check.