An AI-Coding-Agent-friendly React starter —
npx create-eikon-react+ a curated React 19 template, plus a portable.agent/protocol any agent (Cursor, Claude Code, Codex, …) can read.Eikon (Ancient Greek εἰκών, "image / rendered form") — the word English
iconcame from. A React app, after all, is the eikon of its state.
One source template plus two things that consume it — three packages in a single pnpm workspace:
| Package | Role |
|---|---|
packages/template-react |
Source of truth. The canonical React 19 app, with @eikon:variant(...) / @eikon:feature(...) strip markers around every optional axis. Workspace-developable on its own (pnpm dev); all variants live simultaneously and tests pass with everything on. |
packages/create-eikon-react |
Distribution. The CLI published to npm as create-eikon-react. At build time scripts/sync-template.mjs mirrors template-react/ into the package's own ./template/ payload and tsup bundles the binary. At end-user runtime the CLI strips that bundled payload according to the user's choices and writes the result to disk. |
packages/preview-site (@eikon/preview) |
Try-before-scaffold playground. Reads template-react/ live (a chokidar watcher invalidates the cache when source changes), applies the same strip engine on demand, and surfaces it as a side-by-side "what file tree would I get?" + "what would the running app look like?" UI. Does not ship anything to end users. |
The template is opinionated, AI-agent-aware, and feature-first so that — humans or AI agents — anyone who edits a generated project starts from the same conventions instead of reinventing them.
flowchart TB
src[packages/template-react<br/><i>source of truth — all variants alive</i>]
src -->|"reads LIVE<br/>(fs + chokidar)"| pv[packages/preview-site<br/><i>playground</i>]
src -.->|"sync at build<br/>(scripts/sync-template.mjs)"| ce[packages/create-eikon-react<br/><i>./template payload</i>]
pv --> ux[Playground UI<br/>file tree + iframe preview]
ce --> npm[(npm: create-eikon-react)]
npm -->|"npx create-eikon-react"| user[End-user project<br/>stripped, written to disk]
The strip engine — the line-level @eikon:variant / @eikon:feature block remover — is the one piece of logic shared across all three packages. template-react defines the markers, create-eikon-react runs them on the user's machine, preview-site runs them on demand to render the playground.
template-reactexists so the React app, its variants, and its test suite live in one place. The CLI's./template/payload is generated from this directory at build time, never edited by hand.create-eikon-reactexists so end users can scaffold offline, with a single npm install, without depending on the playground server. Stripping happens on the user's machine; the bundled payload is the authoritative thing they get.preview-siteexists so users can pick a parameter combo with confidence before running the CLI. It collapses the "scaffold → install → run" feedback loop into a single page. It's a UX layer on top of the same strip engine — not a second source of truth.
The question naturally arises: with axes like platform × supabase × pm × design × ui × layout × toastPosition, shouldn't CI run all 4000+ combinations through strip → vite build? No, because three independent guard rails already cover that surface:
template-react's own test suite runs with every variant alive — a strictly stronger condition than any single stripped subset. If a variant's import or dispatch entry would break a stripped build, it breaks the workspace test suite first.- The strip engine itself is unit-tested in both
create-eikon-react(block pairing, marker syntax, file-level removal) andpreview-site(__tests__/strip-drift.test.tschecks the simulator's output against the real CLI strip). pnpm e2eruns the full chain on representative scenarios —npm packthe CLI, install the tarball into a throwaway sandbox, scaffold a project, thenpnpm install && pnpm typecheck && pnpm test && pnpm lint && pnpm buildinside the generated project. That's the smoke test for the strip → distribute → run pipeline.
A 4032-cell matrix on top of those three would catch nothing they don't, while burning CI minutes proportional to the cross-product of every axis.
npx create-eikon-react my-app
# or
pnpm create eikon-react my-appThe CLI is interactive and will ask:
- Project name (positional arg lets you skip the prompt)
- Platform target —
web(browser, default),desktop(Tauri 2 shell), ormobile(Capacitor shell) - Whether to include Supabase (auth + db + storage scaffolding)
- Package manager (pnpm / npm / bun)
- Install deps and
git initnow or later
TanStack Query ships as baseline infrastructure in every scaffold (alongside
React Router), so there's no question about it — the QueryClientProvider
is wired in src/app/providers.tsx out of the box.
Non-interactive flags exist for CI scripting:
npx create-eikon-react my-app \
--yes \
--platform desktop \
--no-supabase \
--pm pnpm \
--no-install --no-gitPicking --platform desktop adds an apps/desktop/ Tauri 2 shell next to
your web bundle; --platform mobile adds an apps/mobile/ Capacitor 6
shell. See docs/platform-targets.md for the
trade-offs and prerequisites.
-
Platform target — pick
web(default),desktop(Tauri 2 shell underapps/desktop/), ormobile(Capacitor 6 shell underapps/mobile/); the same React app powers all three. See docs/platform-targets.md. -
React 19 + TypeScript 5.6+
-
Vite 6 + Tailwind CSS v4 (CSS-first config, no
tailwind.config.js) -
animate-ui style primitives in
src/shared/ui/(motion+ Radix) -
Feature-first architecture with ESLint-enforced import boundaries
-
Vitest + Testing Library with
__tests__/colocated per feature -
React Router v7, Zustand, React Hook Form + zod, i18next (en/zh)
-
TanStack Query for server-state — baseline, wired by default
-
Optional Supabase (
@supabase/supabase-js) -
.agent/protocol — rules and skills any AI coding agent can read directly:.agent/ ├── README.md ├── rules/ # hard constraints: architecture, React, Tailwind v4, … └── skills/ # task playbooks: add-feature, add-page, write-test, …See docs/agent-protocol.md for the full specification.
.
├── packages/
│ ├── template-react/ # Source of truth — React 19 template with strip markers
│ ├── create-eikon-react/ # CLI: bundles a snapshot of template-react, strips on user's machine
│ └── preview-site/ # Playground: reads template-react live, runs strip + Vite per request
├── docs/
│ ├── architecture.md # Why feature-first, how boundaries work
│ └── agent-protocol.md # The .agent/ specification
├── package.json # Workspace root
└── pnpm-workspace.yaml
Requires Node ≥ 20.10 and pnpm ≥ 9.
pnpm install # install all workspaces
pnpm --filter @eikon/react dev # run the template standalone
pnpm --filter @eikon/react test # template tests (all variants alive)
pnpm --filter @eikon/react lint # template lint
pnpm --filter @eikon/react build # template prod build
pnpm --filter create-eikon-react build # build CLI bundle + sync template payload
pnpm --filter @eikon/preview dev # run the playground (reads template-react live)
pnpm --filter @eikon/preview test # strip-drift + simulator tests
pnpm cli # build CLI then run from sourceThe CLI ships with a self-contained e2e suite that simulates the real npx
install path:
- Builds the CLI bundle and syncs the template payload.
- Runs
npm packto produce the exact tarball thatnpm publishwould. - Installs the tarball into a throwaway sandbox so the binary is invoked the
same way
npxwould invoke it after a registry pull. - Scaffolds each configured scenario —
default,full(Supabase on),desktop(Tauri shell),mobile(Capacitor shell),variants(custom UI axis variants),variants-shadcn,variants-animate-ui,pm-npm,pm-bun— and verifies its file tree,package.jsondeps, and the contents ofsrc/app/providers.tsxafter feature stripping. - Without
--quick, also runspnpm install && pnpm typecheck && pnpm test && pnpm lint && pnpm buildinside each generated project.
pnpm e2e:quick # ~20s, no install/build — just scaffolding
pnpm e2e # ~2-5 min, full pipeline inside each scenario
pnpm e2e -- --only default # run a single named scenario
pnpm e2e -- --keep # keep the temp workspace on disk for inspectionThe e2e runner lives at packages/create-eikon-react/scripts/e2e.mjs.
Releasing the CLI is driven by the release-decision Claude skill at
.claude/skills/release-decision/SKILL.md.
It walks the commits since the last create-eikon-react@* tag, buckets
them by tarball impact, picks a semver bump, drafts the release commit,
and pauses for explicit approval before tag / push / publish. Invoke it
in a Claude session by typing /release-decision.
The underlying steps the skill orchestrates (for reference / manual operation):
# 1. Decide a version bump (skill recommends one based on commit history).
# 2. Bump packages/create-eikon-react/package.json's `version` field.
# 3. Commit, tag, push.
git commit -m "chore(create-eikon-react): release vX.Y.Z" -- packages/create-eikon-react/package.json
git tag create-eikon-react@X.Y.Z
git push origin main && git push origin create-eikon-react@X.Y.Z
# 4. Publish (prepublishOnly runs typecheck/lint/test/e2e:quick/build first).
pnpm --filter create-eikon-react publish
# 5. Post-publish smoke against the live registry.
pnpm --filter create-eikon-react smoke- docs/architecture.md — Feature-first design rationale + import boundary enforcement.
- docs/agent-protocol.md —
.agent/rulesand.agent/skillsschema and authoring guide. - docs/platform-targets.md — Web / Desktop (Tauri 2) / Mobile (Capacitor) target selection, prerequisites, and how the same React app powers all three.
- packages/template-react/.agent/README.md — Live
.agent/README inside the template.
MIT