Claude Code reads this file natively. Other agents (Cursor, Windsurf, etc.) read
AGENTS.md, which points here. This file is the single source of truth for the project's conventions, stack, and working rules.
A CLI installer for dAppBooster, the dApp boilerplate for EVM chains. It has two modes:
- Interactive (default): a React + Ink TUI. It asks for the project name, the installation mode, the optional packages and a final review. Then it clones the template, cleans it up, installs the packages and shows the post-install steps.
- Non-interactive: driven by flags, for AI agents and CI.
--niturns it on, and so does a stdout that is not a TTY. It prints JSON to stdout. Run--infoto discover the features, then pass--nameand--mode, plus--featuresin custom mode.
| Category | Technology | Notes |
|---|---|---|
| Language | TypeScript (strict mode) | Extends @sindresorhus/tsconfig |
| Framework | React + Ink | Terminal UI framework |
| Arg parsing | meow | CLI flag parsing for non-interactive mode |
| Package manager | pnpm | Never npm or yarn |
| Linting/formatting | Biome 2 | Run pnpm lint before committing |
| Testing | Vitest + @vitest/coverage-v8 | |
| Dead code | knip | pnpm knip fails on unused files, exports, and dependencies |
| Secret scanning | gitleaks | Pinned in .gitleaks-version; the hooks install it into bin/ |
| Git hooks | Husky + lint-staged + commitlint | pnpm i installs them; see Git hooks |
| Node | v24.15.0+ | engines.node is the published floor, matched to what dAppBooster needs; CI and development use .nvmrc (24) |
| Naming | camelCase vars/functions, PascalCase components/types | Biome enforces the same two cases for filenames |
- Semicolons: as needed (Biome
asNeededleaves them out unless ASI requires one) - Quotes: single
- Print width: 100
- Trailing commas: all (Biome default)
- Indent: spaces, width 2
- Imports: explicit
.jsextensions (ESM,"type": "module")
- Use pnpm only (never npm or yarn), for this installer and for the scaffolded project
- Treat
dist/as build output. Never edit it directly - Never interpolate user input (
projectName) into shell command strings. UseexecFile(args array) instead source/config.tsis the single source of truth for what the installer knows about the template: the features, the paths cleanup removes, the repository to clone and the package manager. CLI--helptext maintains its own copy.- The env vars
DAPPBOOSTER_REPO_URLandDAPPBOOSTER_REFoverride the repository and ref to clone.getRepo()reads them. Use them for forks and pre-release testing. - Components are presentation-only. Business logic lives in
source/operations/.
See architecture.md for the full architecture guide, including data flow, how to add features, and security patterns.
Entry: source/cli.tsx parses args with meow and routes between the interactive and non-interactive paths.
- Interactive path:
source/app.tsx, a step-based state machine that renders each installer step in sequence with React + Ink - Non-interactive path:
source/nonInteractive.tsvalidates flags, runs the operations one after another, and prints JSON
Key directories:
source/operations/: business logic as plain async functions, shared by both pathssource/components/steps/: TUI step components, presentation-onlysource/components/: reusable UI components (Ask, Divider, MainTitle, Multiselect)source/__tests__/: the Vitest suite
| Command | Purpose |
|---|---|
pnpm build |
Compile source/ to dist/ |
pnpm dev |
The same, in watch mode |
pnpm typecheck |
Types only, no output. Two passes: source/ for the build, then the tests |
pnpm test |
Run the Vitest suite |
pnpm test:coverage |
The same, with a coverage report |
pnpm lint |
Biome check, warnings included |
pnpm lint:fix |
Biome check with --write |
pnpm knip |
Report unused files, exports, and dependencies |
Run the built CLI from a scratch directory. It scaffolds the new project into the folder you start
it from, so node dist/cli.js in this repo would write into the repo itself.
demo.svg in the readme is an animated SVG of a real wizard run. Regenerate it after any change to
the terminal UI:
pnpm build
./scripts/record-demo.pyThe script scaffolds a real EVM project into a temporary directory, so it needs network and takes a
few minutes. It cleans up after itself and overwrites demo.svg.
Things worth knowing before touching it:
- svg-term-cli does the conversion, through
pnpm dlx. It is not a dependency. The flags--window --width 92 --height 23 --padding 10produce the size of the committed image. If you change them, change the readme's<img>size too. - There is no way to script
asciinemahere. It ignores piped stdin,scriptrefuses to start unless its own stdin is a tty, andnode-ptyhas no prebuilt binary for this machine. The script uses Python's standard-libraryptyinstead, which needs nothing installed. - It waits for each prompt to appear in the output rather than sleeping a fixed time, so it does not break when a step gets slower.
- A
pnpmshim onPATHmakespnpm dlx dappboosterrundist/cli.js. The recording shows the real command line, but runs the code in the working tree. - The script trims the recording to 15 seconds so the loop stays short. After that point comes the package install, a long stretch of near-static output that reads as a frozen image.
- The script merges output that arrives within 150 milliseconds into one frame. That cuts the file about five-fold, because the spinner redraws every 80 milliseconds. It changes when bytes reach the recording, never which bytes.
- Framework: Vitest + V8 coverage
- Run tests:
pnpm test/pnpm test:coverage - Structure:
source/__tests__/mirrors thesource/layout. Operations tests live insource/__tests__/operations/ - What to test: the non-interactive flow agents use (validation, JSON output), operations (correct shell commands), config, utils
- What not to test: React/Ink components
- Mocking pattern: Operations tests mock
exec/execFilefromsource/operations/exec.js.exec.test.tsmockschild_process.spawndirectly to test the helpers themselves. Non-interactive tests mock the entire operations layer - Coverage: Focus on the interface agents use. Coverage leaves out the test files and
source/components/
pnpm i installs three hooks through Husky:
- commit-msg runs commitlint against the message.
- pre-commit runs lint-staged in two passes.
.lintstagedrc.format.mjslets Biome write the staged files first, then.lintstagedrc.mjsruns the read-only checks (typecheck, tests, knip). There are two passes because a reformat that lands mid-parse makes the checks fail at random. The hook ends by scanning the staged changes for secrets. - pre-push runs lint, typecheck and tests, then scans the outgoing commits for secrets.
Secret scanning uses a pinned gitleaks. scripts/install-gitleaks.sh downloads that exact release
into bin/ and verifies its sha256, so local runs and CI apply the same version and rules. To scan
the history by hand:
./scripts/install-gitleaks.sh
./bin/gitleaks git --redact --verbose --exit-code 1 ..github/workflows/pr.yml runs on every pull request, in this order:
- Biome
- Typecheck, build and knip
- The test suite on
.nvmrc, and again on the Node 24.15.0 floor - commitlint over the commit range and the PR title
- gitleaks over the full history
Changing only the PR title re-runs just commitlint.
pr-assign.yml assigns the author to their own pull request. add-to-project.yml adds new issues
and pull requests to the project board. It needs the ADD_TO_PROJECT_PAT secret, because the
built-in token cannot write org projects.
Use Conventional Commits: type(scope): subject.
- Scope is optional:
feat: add loginandfeat(auth): add loginare both valid - Subject uses imperative mood, lowercase after the colon, no trailing period
- Body (optional): goes after a blank line and explains what and why
commitlint.config.js allows these types: build, chore, ci, docs, feat, fix,
hotfix, perf, refactor, release, revert, style, test, wip.
-
Every PR must reference an issue (
Closes #)No related issue? Use
No related issue.as the first line of the Summary section. -
Mirror the issue's acceptance criteria in the PR
-
Self-review your diff before requesting peer review
-
Keep PRs small and focused: one issue, one PR
-
PR titles use the same conventional commit format, and CI checks them
-
The
create-prskill at.claude/skills/create-pr/reads.github/PULL_REQUEST_TEMPLATE.mdand fills every section
GitHub form dropdowns (like the Priority field in issue templates) only work through the web UI. When you create an issue with the gh CLI or the REST API, dropdown values become plain body text that you cannot query and that differs from issue to issue. Labels are the reliable way to store structured metadata through the API.
Priority (bugs, features, and epics):
| Label | Description |
|---|---|
priority: critical |
Blocking work, system down, or security issue |
priority: high |
Address in the current sprint |
priority: medium |
Address soon |
priority: low |
Nice to have, can wait |
You can query labels: gh issue list --label "priority: high".
The create-issue skill at .claude/skills/create-issue/ applies these labels when it creates issues with the CLI. Bug, feature, and epic templates include a Priority dropdown for web UI users, but labels are the source of truth for programmatic workflows.
- Do not commit secrets, API keys, or credentials. The hooks run gitleaks; do not bypass them with
--no-verify - Do not modify CI/CD pipelines without team review
- Do not skip tests or linting to make a build pass
- Pin third-party GitHub Actions to a commit SHA with the version in a trailing comment
- Raising
engines.nodebreaks installs for everyone below the new floor. Treat it as a release decision, not a cleanup inkandreactstay on 5 and 18.ink-dividerstill depends onink5, so moving toink7 would load two copies of the renderer- When in doubt, ask. Don't assume
- Prefer small, focused diffs over broad refactors
- Preserve existing UX unless the task explicitly changes it
- Avoid introducing new patterns when a project pattern already exists
- Update docs only when behavior or workflow changes
Run all five before declaring work done. CI runs the same set.
pnpm lintpnpm typecheckpnpm buildpnpm testpnpm knip
The GitHub Actions workflow .github/workflows/release.yml runs on GitHub release events. A pre-release does a dry run; a full release publishes to npm.
It publishes with npm publish, not pnpm publish, because npm supports OIDC trusted publishers. That is also why the job asks for id-token: write instead of carrying an npm token.