You do not need an account, an install, a sales call, or our permission. Tell an AI you already use what your business does, bring back one file, and watch a working application start in this browser — database, API, forms, rules and workflows.
What runs in the browser is a demo — the same model, compiled for a single tab, so you can click through it in seconds. The real application is the download: a .zip with the full NestJS and TanStack Start source and a docker-compose.yml that runs it, with PostgreSQL and the reporting platform, on Docker Desktop or any cloud server with Docker. What is in it, and how to run it ↓
Or start from one of the eight worked examples
Each one is a complete model — entities, rules, state machines, sagas, access control and the %%report queries a reporting layer is derived from. Pick the one nearest your business; it opens in the browser demo with the model already loaded, and nothing is installed or uploaded. Each one builds two applications: the one you click through, and Enterprise Reporting beside it, with a sign-in of its own and one reporting role per role the model declares. The demo shows reporting as a preview; the Download the deployable app (.zip) button on the same page gives you the real thing, the platform included.
.mmd from the prompt below? Drop it in. It is read in your tab and uploaded nowhere.
This is section 1 of llms.txt — the working-session protocol — in plain English. The AI you prompt reads the same procedure in machine-readable form, and the same one prompt works whether you are starting a new application or changing one you already have.
Starting fresh? Write what you would tell a new hire on their first day: what you sell or service, every kind of record you keep, who touches each one, the states things move through, what needs approving and by whom, and what happens when a step fails.
Already have a .mmd? Attach it, and say what you want changed. Attaching the file is what tells the AI to change your model rather than write a new one.
Give the AI the prompt below. It runs a working session with you: it asks how you want to work, researches your business domain as widely as it can, attacks its own research and plan as a sceptical expert would — even when you attached a model — then puts what survived in front of you as premises, proposes two versions of the model — a minimal one and a complete one — and walks through every record type with you before it writes the file and validates it against our published checker.
You get back a single .mmd file — the whole model, not a document describing it.
Upload that file and watch it become a working demo — PostgreSQL compiled to WebAssembly, an API, forms laid out the way the model says, business rules and workflows running. No install. No signup. Nothing leaves your machine.
The demo lives in one tab and is gone when you clear it. When it looks right, press Download the deployable app (.zip) on the same page: that is the production application, and it runs under Docker. How to run it →
Copy it as it is and replace only the bracketed part. It points the AI at llms.txt, the one document that covers both jobs: attach your .mmd and it changes that model and hands back the whole file; attach nothing and it builds a new model from your description.
Read https://www.appwithai.org/llms.txt in full, and follow section 1 — model working session — exactly, fetching the chunks it names only when a step needs them. If I have attached a .mmd file, it is my existing model: change that file — read it, inventory it, apply only what I ask, and give me back the whole file with before-and-after counts. Never rebuild it from memory. If I have attached nothing, build a new model from my description. If you are ChatGPT, the TinyFish plugin must be installed and signed in before you start. If it is not, stop and tell me how to install it; do nothing else until it works. Ask me first how I want to work: walk me through it, one decision at a time, or just build it. If I write JUST BUILD IT, do the whole thing in one pass and list every assumption. Before you build anything, put on your thinking cap: research my business domain as widely and deeply as you can, then review your own research and plan adversarially, as a sceptical expert would, and fix what that review finds. Only then start building and reviewing with me. Do this even if I attached a .mmd. My business, or what I want changed: [Describe it here. What you sell or service. Every kind of record you keep and how they relate. Who does what. The states each record moves through. What has to be approved, and by whom. What happens when a step fails. Or, with your .mmd attached: the change you want.] Fill in the whole Application Dictionary, as llms.txt section 3.7 requires: every reference column marked FK so it becomes a lookup, every closed list bound to an enum so it becomes a dropdown, help text on every entity, column and section that says something a new employee needs to know, and every form laid out with %%section as headed sections of two-column rows. Deliver exactly one file I can download: <business-name>.mmd. It must open on a %% line with %%meta name: before the first diagram, and every line in it must be Mermaid or an EML %% directive — no Markdown, no headings, no prose. A document that describes the model is not the model; the checker scores that EML004. Send no other file — no specification document, no report, no README. If you can only answer with a document, put the entire model in the first fenced mermaid block of that document, before any prose. Then, in your reply text: what you understood (or, for a change, what changed and the before-and-after counts), the checker result with its counts, the audit's last line, and anything you assumed. If the checker cannot be reached from where you are running, say which host failed and how, deliver the model anyway, and keep going — never invent counts, and never hold a phase open waiting for a network call. Read https://www.appwithai.org/guide/check-model-standalone.mjs, then run node check-model-standalone.mjs <business-name>.mmd and the same with --audit, and report the checker's verdict line (it starts OK — or FAILED —), the audit's last line, and both exit codes.
Paste the prompt, replace the bracketed part with your description, and attach nothing. The AI will ask how you want to work, then put its understanding of your business in front of you before it writes a line.
Attach your .mmd to the same message, and replace the bracketed part with the change you want. The AI reads and counts your model first, applies only what you asked, and hands back the whole file — never a patch — with the before-and-after counts that prove nothing was lost. It also fills any gaps in the original’s dictionary, and names each one.
ChatGPT cannot reliably read this protocol or the checker on its own, so the prompt needs the TinyFish plugin. In ChatGPT open Plugins, search TinyFish, choose Install plugin and sign in to your TinyFish account. In a Business, Enterprise or Edu workspace an administrator may have to approve it first. TinyFish’s own instructions. If it is not installed, the prompt makes ChatGPT stop and say so instead of guessing. Other assistants do not need it.
Walk me through it is the default for anything serious: one decision at a time, with real options, and a checked file at every step. Just build it — write JUST BUILD IT in your description — gets you the finished file in one reply, with every assumption listed for you to correct. Either way the AI researches and reviews its own plan first, and tells you what the research found and which objections changed the plan.
Then upload the .mmd and press Generate, then Run. Got a report instead of a file? Copy its mermaid block into the checker page, check it, and download a real .mmd from there.
The generated application has no screen, label, lookup, dropdown or explanation that your model does not declare, so llms.txt makes every one of them mandatory and the checker reports any that is missing:
%%section entity: SupportCase; name: Case; help: The number the customer quotes, where the case stands, and what is wrong.; rows: case_number, status | subject | description %%section entity: SupportCase; name: Triage; help: What kind of problem it is, how urgent, and who is working it.; rows: case_type, priority | origin, owner_id
Each line is one section of one form: a heading, the sentence under it, and rows of one or two fields — | between rows, , between the two fields of a row. A row of one spans the full width. Both generated applications, the one in your browser and the deployable one, draw exactly this.
The Model Assistant runs the same document in the browser: you bring an OpenAI or Claude key, load your model, say what you want changed, and the result is put through the real checker before you are offered a download. Your key stays in your browser and goes straight to the provider; the page says above its own key field what that exposes.
Open the Model Assistant →The file is Mermaid — the diagram language — with a small set of %% directives that Mermaid treats as comments and our generator treats as meaning. You can read it, and a Mermaid viewer can draw it.
%%meta name: Field Service
%%meta kind: erd
%%enum JobStatus: scheduled, dispatched, on_site, complete, cancelled
erDiagram
Customer {
string id PK
string name
email email UK
phone phone OPTIONAL
}
Engineer {
string id PK
string first_name
string last_name
}
Job {
string id PK
string customer_id FK
string engineer_id FK
string status
datetime scheduled_for
money quoted_total
}
Customer ||--o{ Job : "raises"
Engineer ||--o{ Job : "works"
%%entity Customer help: A household or business we service, and who pays for each visit.
%%field Customer.name help: The name on the invoice and on every job card the engineer carries.
%%field Customer.email help: Where booking confirmations and invoices go; one customer per address.
%%field Customer.phone help: Rung by the engineer when running late or unable to get in.
%%entity Engineer help: A field engineer who can be sent to a job.
%%field Engineer.first_name help: How customers are told who is coming.
%%field Engineer.last_name help: Used with the first name on job cards and in the dispatch board.
%%entity Job help: A visit an engineer makes to a customer site.
%%field Job.customer_id help: Who the visit is for, and who is invoiced for it.
%%field Job.engineer_id help: Who is sent; empty until dispatch assigns someone.
%%field Job.status enum: JobStatus
%%field Job.status help: Where the visit stands; only a dispatched job can be marked on site.
%%field Job.scheduled_for help: When the engineer is due, which is what the customer was promised.
%%field Job.quoted_total help: What the customer was quoted, before parts used on site.
%%section entity: Customer; name: Customer; help: Who we visit and how to reach them before we arrive.; rows: name | email, phone
%%section entity: Engineer; name: Engineer; help: Who can be sent out to a job.; rows: first_name, last_name
%%section entity: Job; name: Visit; help: Whose job it is, who is going, and when they are due.; rows: customer_id, engineer_id | status, scheduled_for
%%section entity: Job; name: Price; help: What the customer has been quoted for the visit.; rows: quoted_total
%%rbac role:dispatcher on Job.update
The generated application reads a live Application Dictionary — one row per table, column, tab and lookup — and it is derived from this file alone. Four habits decide whether the screens come out usable:
The checker reports all four. Section 3.7 of llms.txt explains why each one matters.
The same checker the generator runs is published as a module and as a one-command runner. A model that passes here is a model the generator will accept — and a second runner scores whether it is finished, which a clean report does not tell you.
curl -sO https://www.appwithai.org/guide/check-model.mjs
node check-model.mjs my-business.mmd
It runs the three passes the protocol asks for, prints the checker's own report, and exits 0 when the file is clean, 1 when the generator would refuse it.
The checker page runs the published checker.js and fixer.js in your tab, repairs what is repairable, explains what is not, and hands back a downloadable .mmd once the run is clean.
It is the way in if your model arrived as a fenced block inside a chat report rather than as a file.
Open the checker →A clean report says the generator would accept the file. It does not say the model is done — nothing in the checker requires a model to have a lifecycle, a rule, an access rule or a word of help text. One entity with a primary key and a name is a valid model with none of them, and the generator accepts it — the checker reports only three dictionary warnings.
curl -sO https://www.appwithai.org/guide/audit-model.mjs
node audit-model.mjs my-business.mmd
Twenty-three checks against the delivery checklist — the FK modifier on every reference, an enum behind every status, a state machine with a beginning and an end, help that says something rather than restating a column's own name. Its last line is the score, 23 passed, 0 failed. That bare entity above is accepted by the checker and fails eight of them, which is why both are published. What it scores →
One model gives you both. The demo is for looking at a model in seconds; the .zip is the application you run for real users. Both come from the same page — chapter 09 — after you press Generate.
| Browser demo | Deployable app (.zip) | |
|---|---|---|
| What it is | A compact runtime that reads the compiled model — built to show it working | The full NestJS + Fastify + Kysely API and TanStack Start front end, as source code you own |
| Where it runs | One browser tab, nothing installed | Docker — Docker Desktop on your machine, or any server with Docker |
| Database | PostgreSQL compiled to WebAssembly, kept in that browser’s storage | PostgreSQL 18 with pgvector, on a Docker volume that survives restarts |
| Who can use it | Only you, in that tab | Anyone you give the address to |
| Enterprise Reporting | A preview, drawn in the platform’s own layout | The platform itself — SQL editor, natural-language query, scheduled jobs — built from its source |
| State machines | Shown, but a status can be set to anything | Enforced: the API refuses a status change the model’s diagram does not draw |
| Use it for | Trying a model, showing it to someone, checking it before you build | Development, testing and production |
About five hundred files — what the command-line generator writes, assembled in your browser when you press the button:
backend/ — the NestJS API: one module per entity, the rules engine, workflows, access control, database migrations and seeds, and its Dockerfilefrontend/ — the TanStack Start web application, its Dockerfile, and public/manual.html, the user manual generated from your modelreporting/ — the reporting pack derived from your model (saved queries, reports, charts, a dashboard and one reporting role per role), the Dockerfile that builds the platform, and a README.md listing every accounttests/ — an end-to-end test suite that drives the APIdocker-compose.yml, .env.example, a single-image Dockerfile, model/model.eml.mmd and GitHub Actions workflows under .github/There is no bun.lock: the Docker build installs the dependencies and writes it.
docker compose up startspostgres PostgreSQL 18 + pgvector
electric syncs the dictionary to browsers
(internal, never published)
backend the API :4001
frontend the application :4000
report Enterprise Reporting :3100
report-seeder loads your reports, then exits
Open http://localhost:4000 for the application and http://localhost:3100 for reporting. They have separate sign-ins. In the application, admin@admin.com / admin123 or one account per role your model declares; in reporting, admin@admin.com / admin or one …@<app>.reports.example.com account per role. reporting/README.md lists every one.
The browser only ever talks to port 4000 — the front end forwards /api to the API itself. 4001 is published for curl and health checks.
cp .env.example .env docker compose up --build
The first build takes several minutes and needs internet access: it downloads the dependencies, and the reporting image fetches the platform’s source. The application migrates and seeds itself on first start. Reporting is up but empty until report-seeder finishes — docker compose logs -f report-seeder shows it working.
docker compose down stops everything and keeps the data; docker compose down -v deletes the databases too. On Windows, use PowerShell and copy .env.example .env.
Use any Linux virtual machine that runs Docker Engine with the Compose plugin — an AWS EC2 instance, an Azure or Google Cloud VM, a DigitalOcean droplet, a Hetzner server — with at least 8 GB of memory (or 4 GB plus swap). Copy the folder to it and run the same two commands.
Before anyone else can reach it, edit .env:
BETTER_AUTH_SECRET, JWT_SECRET, SESSION_SECRET and REPORT_AUTH_SECRET (openssl rand -base64 32), and REPORT_ENCRYPTION_KEY (openssl rand -hex 32). Never change REPORT_ENCRYPTION_KEY afterwards — it encrypts the stored database password, and a new one leaves reporting unable to connect.CORS_ORIGIN to the address people will use, such as https://crm.example.com, or sign-in answers Invalid origin.REPORT_ADMIN_PASSWORD, and change the application’s seeded passwords after the first sign-in.REPORT_REF to a commit, so a rebuild cannot pick up a newer reporting platform.Put HTTPS in front with a reverse proxy (Caddy, nginx or your cloud’s load balancer) forwarding to ports 4000 and 3100, and open only 80 and 443 in the firewall.
Services that run a single container rather than a Compose file need the stack split up by hand: a managed PostgreSQL, and the backend, frontend and report images deployed separately.
The archive explains itself as well: README.md and reporting/README.md name the addresses and accounts, and every service in docker-compose.yml carries a comment saying why it is set up the way it is.
The guide builds a seventeen-entity CRM from an empty file to a running application, one chapter at a time — and chapter 9 runs it in your browser with no file needed.