Scaffold a new application from this template with the dotnet efn CLI (efn cp -n <name>), and do the post-create setup — database password, git repository, initial migration, running the API and the web app, and signing in with the seeded accounts. Use when asked to create/start a new project, or when a freshly generated project does not build or run yet.
Install
npx skillscat add ma496/easy-for-net/new-project Install via the SkillsCat registry.
Creating a new project with dotnet efn
Install / update the tool
dotnet tool install -g EasyForNetTool # first time
dotnet tool update -g EasyForNetTool # later
dotnet efn --version # or -v
dotnet efn cp --helpPrerequisites: the .NET SDK pinned in global.json (10.0.x), git on PATH (the tool clones the
template), Node >= 24, a reachable PostgreSQL on localhost:5432 (user postgres), and a Redis onlocalhost:6379 (docker compose up -d in the project root starts both, see step 4).
Generate
dotnet efn cp -n my-shop # createproject, short form
dotnet efn createproject --name my-shop --output projects| Option | Meaning |
|---|---|
-n, --name (required) |
Letters, - and _ only, at least two characters, starting and ending with a letter. Parts split on -/_ are joined in PascalCase (my-shop → MyShop) for namespaces, project files and the solution; the kebab-case form (my-shop) names the folder and the npm packages. |
-o, --output |
Parent directory (relative to the current directory, or absolute). The project is created in <output>/<kebab-name>, which must not exist yet. Defaults to the current directory. |
-m, --multilanguage |
true keeps all eight locales; false (the default) ships English only. Any other value is refused. |
-e, --examples |
true keeps the UI component example pages under /ui; false (the default) leaves them out, with their sidebar group, search entries and translation keys. Any other value is refused. |
The tool clones https://github.com/ma496/EasyForNet.git into%LOCALAPPDATA%/EasyForNet/Templates/<tool version> and checks out the tag v<tool version>, so the
template always matches the installed tool. An existing cache directory is reused without fetching;
delete it to force a fresh clone. A failed clone or checkout removes the cache it created.
What you get:
src/backend/{Source,Tests}(noMigrations),src/frontend/web, and a<Name>.slnxwith both
backend projects added.appsettings.Development.json/appsettings.Testing.jsoncopied fromappsettings.json, andsrc/frontend/web/.env.developmentcopied from.env.example.- A root
.envfordocker-compose.prod.yml(git-ignored), written from.env.docker.examplewith the
project's names,DOMAIN=localhostand fresh random database, Redis and administrator passwords and
JWT key, plus aREADME.mdfor the project. - A git repository holding everything generated in one commit,
Initial project(skipped with a
message when git is missing; when the commit is refused — nouser.name— the repository is left
initialized for you to commit). - Root files
.editorconfig,.gitignore,.gitattributes,global.json,package.json(the gate
and task-loop scripts) andagentic.config.json, plus.config(pinneddotnet-ef) and.vscode. - The Docker files:
docker-compose.yml(development PostgreSQL and Redis),docker-compose.prod.yml,docker-compose.coolify.yml,.env.docker.exampleanddocker/. CLAUDE.mdand the agentic layer:.claude(agents, commands, hooks,settings.json, status
line, skills,memory/README.md),scripts/(the loop's engine), and an empty task-loop skeleton —specs/{README,TEMPLATE}.md,docs/AGENTIC_WORKFLOW.mdanddocs/capabilities/README.md
(.agent-queue/and.claude/memory/lessons/appear when the loop first writes to them).
Namespaces, project file names, InternalsVisibleTo, the ReflectionCache.AddFrom… call, the npm
package names (web and root), agentic.config.json's project.name, the app's display name and the.claude markdown are rewritten to the project name. The generator does not run npm install
or dotnet ef. It prints the seeded administrators' generated password and the next steps when done.
Post-create setup
Run these from the new project's root.
Database password. Development and Testing connect as
postgres/postgres— thedocker-compose.ymldefault — to the databases<name>and<name>_test(the snake_case project name:my_appforMyApp); the trackedappsettings.jsonkeeps a literal{password}placeholder. If the developer uses their own
PostgreSQL, ask them to change the per-environment files themselves: the hooks refuse Claude
reading or editingappsettings.*.jsonand.env*. Development and Testing already have a
generatedAuth:Jwt:Key.Git repository. Already initialized with one commit. If the generator reported that the commit
failed, finish it (the task loop reads diffs and the runner refuses a dirty tree):git add . git commit -m "Initial project"Initial migration. Build first:
dotnet efreads project metadata without restoring, so it
fails on a project whose packages were never restored.dotnet tool restore dotnet build <Name>.slnx dotnet ef migrations add Initial --project src/backend/SourceDevelopment and Testing apply migrations on startup, so
dotnet ef database update --project src/backend/Sourceis optional there. Commit the migration.Redis. Outside the Testing environment the API needs Redis (
ConnectionStrings:Redis,
defaultlocalhost:6379) and prefixes every key withRedis:InstanceName, which the generator set
to<name>:(<name>_test:in Testing) so several apps can share one Redis server. The project'sdocker-compose.ymlruns PostgreSQL (user and passwordpostgres, unlessDEV_POSTGRES_PASSWORD
says otherwise — match it in the connection strings) and Redis with the passwordredis(orDEV_REDIS_PASSWORD, matched inConnectionStrings:Redisaslocalhost:6379,password=<password>) for development:docker compose up -dThe ports are bound to loopback and the passwords are development defaults; a shared or deployed
Redis needs a strong password or ACL (and TLS) inConnectionStrings:Redis, supplied through
secure configuration. The backend tests (dotnet test) do not need Redis.Run the API.
dotnet run --project src/backend/SourceIt listens on
http://localhost:5000(routes under/api). Swagger is served in Development,
health at/health, and the Hangfire dashboard at/hangfirefor a signed-in platform account
only. Every start — in every environment — reconciles the permission catalogue and seeds, when
absent:- the Default tenant (identifier
default); admin— the platform account: no tenant membership, signs in to platform scope
(tenants, editions, platform roles, Hangfire);tenantadmin— administrator of the Default tenant, with sample notifications.
Both take the password the generator printed: the
Seedsection (PlatformAdminPassword,TenantAdminPassword) ofappsettings.jsonand the Development/Testing files, random per project
and applied only when an account is created.- the Default tenant (identifier
Run the web app.
cd src/frontend/web npm install npm run devIt serves
http://localhost:3000(the originWeb:Domainsallows for CORS)..env.development
pointsAPI_INTERNAL_URL(the Next.js server's own fetches) athttp://localhost:5000/api; the
browser calls the API as the relative/api, which the dev proxynpm run devstarts routes to it. The host
is the tenant, so the sign-in page has no tenant field: sign in astenantadminathttp://default.localhost:3000(the Default tenant), and asadminathttp://app.localhost:3000
(the platform). Sign up for a new tenant onhttp://app.localhost:3000.Tests and the gate.
dotnet test src/backend/Testsneeds PostgreSQL reachable with theappsettings.Testing.jsonconnection string and theInitialmigration in place; the run
migrates and seeds<name>_testand deletes it at the end; it needs no Redis. From the root,npm run gate
(-- --fastskips the production web build) runs the build, backend tests, web
lint/typecheck/vitest, the engine and hook tests andnext build; it runsnpm ciin the web app
whennode_modulesis missing.Before deploying anywhere non-development, supply
Auth:Jwt:Key(>= 32 chars),Auth:Jwt:IssuerandAuth:Jwt:Audiencethrough secure configuration — startup refuses to boot
with the placeholder key outside Development/Testing.Web:Domainsmust list the real front-end
origins,Database:ApplyMigrationsOnStartupdefaults to false there (rundotnet ef database updateexplicitly), and the seeded accounts' passwords should be changed in the
app. For the Docker stack, the generated root.envalready holds every secretdocker-compose.prod.ymlneeds (SEED_ADMIN_PASSWORDfor the administrators); point DNS A records forapp.<DOMAIN>and*.<DOMAIN>at the server, setDOMAIN,PUBLIC_URL(https://app.<DOMAIN>),TENANT_URL_TEMPLATE(https://{tenant}.<DOMAIN>) and the SMTP values, thendocker compose -f docker-compose.prod.yml --env-file .env up -d --build. To put it on a VPS
behind Coolify instead, push the project and runnpm run deploy:vps -- --host <ip> --domain <domain> --email <email>(see the README's Deploy to a VPS section).
Then
The generated project's CLAUDE.md, .claude/skills and specs/README.md carry the code guides and
the spec-driven workflow: save a spec under specs/ and run npm run loop, or npm run queue /npm run auto -- "<task>". Review agentic.config.json — project.workflow (solo, or team
when several developers share the repository), project.branch / project.baseBranch,
conventions, departments — since it is the one project-specific file of the loop. This scaffolding
guide and template-maintenance are not in the generated project.