Project anatomy
There are two kinds of files in an ayjnt project: the ones you write, and the ones the framework regenerates. Knowing which is which saves hours of future confusion.
The full tree
Files you author
agents/<name>/agent.ts
One class per folder, default-exported, extending Agent
(or McpAgent). The folder name becomes the URL prefix;
the class name becomes the DO binding (CounterAgent → COUNTER_AGENT).
agents/<name>/app.tsx (optional)
React UI for this agent. Imports useAgent from @ayjnt/<route>, which is a typed hook the framework
generates. Bundled with Bun and served at the same URL via Cloudflare
Assets.
agents/.../middleware.ts (optional)
Applies to every agent at or below this folder. Hono-style (c, next) => Response. Root → leaf chaining; the agents/middleware.ts at the top of the tree runs first,
nested ones run next, then the agent.
package.json
Standard Bun/npm manifest. The ayjnt scripts you run (dev,
build, deploy, migrate) invoke
the CLI. You own this file.
tsconfig.json
You own this file. The ayjnt new template pre-fills it
with two path aliases that make the generated hooks importable:
{
"compilerOptions": {
"paths": {
"@ayjnt/env": ["./.ayjnt/env.d.ts"],
"@ayjnt/*": ["./.ayjnt/client/*"]
}
}
}
The ./ prefix is required — TypeScript rejects path
mappings without baseUrl unless they're relative.
Files ayjnt regenerates
.ayjnt/migrations.json committed
The only file in .ayjnt/ that is checked into git. It's
the source of truth for what DO schema is in production. Every build
diffs the current file tree against this lockfile and stages a new
migration entry if anything changed. ayjnt deploy refuses to run if your git tree is
out of sync with origin/main.
.ayjnt/dist/entry.ts gitignored
The worker's actual entrypoint. It re-exports every agent class
(so the Durable Object runtime can find them by name), builds a
route table sorted longest-prefix first, and dispatches requests —
HTML goes to the Assets binding, MCP agents go to McpAgent.serve(), everything else gets forwarded to the
matching DO via getAgentByName.
.ayjnt/dist/wrangler.jsonc gitignored
The wrangler config ayjnt hands to the CLI. DO bindings, migrations,
compatibility flags, and (when you have app.tsx files)
the Assets binding are all filled in automatically.
.ayjnt/env.d.ts gitignored
Declares the GeneratedEnv type with every DO binding
pointed at its specific agent class. Import it when you write an
agent: import type { GeneratedEnv } from "@ayjnt/env".
Extend it with any extra bindings you declare in wrangler.
.ayjnt/tsconfig.json gitignored
Declares the @ayjnt/* path aliases the generated hooks
live under. Your own tsconfig.json can either inline the
paths (what ayjnt new does) or extend this file via "extends": "./.ayjnt/tsconfig.json".
.ayjnt/client/<route>/index.tsx gitignored
One file per agent. Contains the typed useAgent() hook,
bound to that agent's class and route prefix. Users import from @ayjnt/<route>, which resolves here via the path
alias. Regenerated on every build.
.ayjnt/assets/__ayjnt/<route>/* gitignored
Only exists when at least one agent has an app.tsx. Contains
the bundled JS and HTML shell for each UI. Cloudflare Assets serves
these as static files; the __ayjnt prefix keeps them out
of the user's route namespace.
The gitignore
The ayjnt new template writes this .gitignore
rule which captures the whole contract in two lines:
.ayjnt/*
!.ayjnt/migrations.json
Ignore everything in .ayjnt/ except migrations.json. Every other file
regenerates on the next ayjnt build, so tracking them
just generates noise in code review.
rm -rf .ayjnt/dist
.ayjnt/client .ayjnt/tsconfig.json .ayjnt/env.d.ts .ayjnt/assets
is always safe. The next ayjnt dev or ayjnt build regenerates everything.