Devtools

In development, every CalmAPI app serves a dashboard at http://localhost:5001/_calm. It shows what your API is doing right now: requests with their MongoDB queries, logs, emails, push notifications on simulated phones, routes, resources, data and the blocks you installed, plus a playground to call the API as any user. It also shows what an AI agent is changing in your project, and who can access what.

Shell
npm run dev
# open http://localhost:5001/_calm

The collector is part of your app (calm/devtools/index.js). The interface is the @calmapi/devtools package, a dev dependency of every new project. If it is missing, /_calm shows how to install it (npm install -D @calmapi/devtools) and the JSON API keeps working.

Panels#

Requests#

A live list of every request (over Server-Sent Events): method, path, status, duration, the user who made it, and how many MongoDB queries it ran. Quick filters show Errors, Slow requests (300 ms or more) and N+1 suspects (more than 10 queries). A second view lists background queries that ran outside any request (jobs, startup).

Open a request to see:

  • Timeline: the request and each query as a waterfall, with log lines placed in time.
  • Queries: every MongoDB command with its filter, duration and document count. Repeated query shapes are flagged. Explain runs the query planner and tells you whether an index was used or the collection was scanned (COLLSCAN).
  • Request and Response: headers and bodies. Passwords, tokens, keys and other secret-looking values are redacted in both (in responses the first characters of a long value stay, so you can tell them apart), and the Authorization header is shortened.
  • Logs: everything logged during the request, linked by request id.
  • Error: the original error and stack trace, with links that open the file in VS Code.

From there you can copy the request as curl, edit it in the Playground, replay it, or replay it as the user who made it.

Requests that carry an X-Calm-Agent header (the calm_http tool sends it) are marked as sent by an AI agent, with its name, and the Agent filter shows only those.

Activity#

A live feed of what is happening to the project, read from .calm/activity.jsonl (see Watching an agent work): agent tool calls, file changes with their diffs, marked as made by an agent or by you, server restarts and crashes with their error output. Filter by agent, files or server events.

Logs#

A live tail of the app log with level filters, text search and a link from each line to the request that wrote it.

Mail#

In development, nothing is delivered: emails go to this inbox instead, password reset links included. The empty state shows a curl command (and a Playground shortcut) to trigger a reset email.

Push#

Every push notification the app sent, live, as it would appear on an iPhone lock screen, in the Android notification shade and as a desktop browser notification. Switch between closed and expanded; previews use real device widths and type sizes, so text is cut off where a phone cuts it. Each notification also shows:

  • Payloads: the exact JSON for APNs, FCM, Expo and Web Push, with its size against the 4 KB limit.
  • Warnings: titles and bodies that will be cut off, data keys that look sensitive, non-string data for FCM, payloads a provider would reject.
  • Delivery: what happened on each device, step by step: the transport that took it (or the simulator), the provider's answer, receipts that came later, whether the phone showed it, and the request that sent it.

New notification composes one with a live preview and sends it through app.push.send(); Send again reopens a sent one in the composer and Copy as code gives you the ctx.push.send() call.

Devices lists every registered device with its user, masked token, the transport it goes through and when it was last seen. Add device gives any user as many devices as you like:

  • Simulated iPhones, Android phones and browsers, each with a behavior: delivers, offline, notifications off, app uninstalled, invalid token or rate limited. They answer like real phones and never reach a real provider. See Simulated devices.
  • Real tokens pasted from your phone (an Expo or FCM token), delivered through the installed transport.

From a device's row you can change its behavior, send it a test, Rotate its token (the old one fails once and is removed, as in production) or Remove it. Click a simulated device to open it as a phone: its own lock screen with the notifications it received, the ones waiting while it is offline and the ones hidden because notifications are off. Bring online, Uninstall app, Clear and tapping a notification (which emits push.opened) are there too, and the lock screen updates live.

Without a transport block, notifications to real devices are only captured here. With push-expo or push-fcm installed (or both), real devices receive them in development too.

Routes#

Every endpoint grouped by resource, block or source, with its policy and the file that defines it. Filter by policy, and spot routes whose policy is not explicit.

Resources#

A diagram of how resources reference each other, then each resource with its policies (defaulted ones highlighted), owner field and fields. Each field shows its type and flags (hidden, readOnly, filterable...) and whether it is readable (R) and writable (W) through the API.

Data#

Browse the documents of each resource with the privacy lens: for each record, what MongoDB stores next to what the API returns, and why each missing field is missing (hidden, password, visible to owner | admin...). View as switches the viewer between anonymous and any user, so you can check exactly what Alice sees of Bob's records. Password hashes are masked.

Sample data adds 10 records that look real to the resource you are looking at: names that look like names, prices like prices, values from enums, dates in the past or the future depending on the field name, and references to records that exist (referenced resources get a few records first). Records are created through the service, so validation and hooks run. When there are no users yet, demo users are created to own the records, and their email and password are shown. From a terminal: calm seed.

Blocks#

Every block in the app, from calm add or written here:

  • Origin: the address and version it was installed from (calm.lock), or "your own block", and the files you changed since installing it.
  • Hooks: the extension points it uses, such as a guard, an authenticator, named policies, the events it listens to, a push or mail transport, what it provides on app.ext, doctor checks and devtools panels.
  • Environment: each variable it declares, set or missing (missing required ones are flagged, and the panel's badge counts blocks with missing variables). The values of secret variables never leave the server.
  • Options: its settings after calm.config.js overrides, with secret-looking keys redacted.
  • Routes: every route it adds, with its policy.

Playground#

Send requests to your API with route suggestions and sample bodies built from the resource fields. Act as signs you in as any user: devtools creates a real session for them and keeps it in memory for the Playground and replays. It is the quickest way to test policies.

Doctor#

The runtime checks of calm doctor (configuration, policies, exposed secrets, personal data exposure, unknown references), grouped by level and updated live. Run npx calm doctor in a terminal to add the static checks on your source files. See Security.

Security#

Who can access what, and what happened:

  • Access: the exposure matrix, per resource: which actions anonymous visitors, users, owners, custom roles and admins may perform, and which fields each can read and write.
  • Events: the security events of the dev server, live: failed sign-ins, lockouts, bad tokens, rejected writes, 403s.
  • Posture: sessions, password rules, lockout, rate limits, CORS, encryption keys and encrypted fields, JSON limits and security headers.

Run audit runs calm audit on a throwaway database on the dev server's MongoDB and shows the result in the matrix.

Block panels#

Blocks can add panels, listed under Blocks in the sidebar:

BlockPanel
webhooksEndpoints with their status, the latest deliveries with status codes, errors and next retries, and how many are pending
notificationsEvery notification type with its channels, and the latest inbox entries
api-keysKeys with owner, prefix, scopes, status and last use
feature-flagsEvery flag with its rollout, targeting, variants and last change
jobsQueue statistics and recent jobs
realtime-sseConnected subscribers and pending tickets
storage blocksTheir configuration

Your own block adds one with calm.devtools.panel().

Keyboard shortcuts#

KeyAction
/Focus the filter
j / kNext / previous item
EnterOpen the selected item
EscClose the detail view
1 to 9Switch panel
Ctrl + EnterSend (Playground, Push)
nNew notification (Push)
?Show the shortcuts

The theme follows your system, or pick light or dark in the top bar.

Safety model#

Devtools can read your data and create sessions, so it is locked down:

  • Development only. It is on when the mode is development (calm dev). In production it is forced off, even if calm.config.js turns it on. MongoDB command monitoring is only enabled while devtools is on.
  • This machine only. Every /_calm request must arrive on a loopback socket (127.0.0.1 or ::1), with a Host header of localhost, 127.0.0.1 or ::1, and without proxy headers (X-Forwarded-For, Forwarded). Anything else gets a plain 404, which also defeats DNS rebinding.
  • No cross-site writes. Requests that change something (act as, explain, clear, run audit, sample data, send a test notification, add or change a device) need an X-Calm-Devtools: 1 header, which a page on another site cannot send. /_calm has no CORS headers, a strict Content-Security-Policy and refuses to be framed.
  • Secrets stay out. Request and response bodies are stored with secret-looking values redacted, the Cookie header is redacted, and the Authorization header is shortened. Block environment variables marked secret are never sent to the page.
  • Bounded memory. It keeps the last 300 requests, 1,000 log lines, 100 emails, 100 push notifications, 200 background queries, 500 activity events and 300 security events. Push records never hold a full device token.

"Act as" is logged (Devtools: acting as ada@example.com) and creates an ordinary session that expires like any other.

For tools and agents#

The panels use a small JSON API under /_calm/api that you can call from scripts on the same machine (writes need the X-Calm-Devtools: 1 header):

EndpointReturns
GET /_calm/api/overviewApp, runtime, database, resources, routes, and blocks (origin, changed files, hooks, env vars without secret values, options, routes)
GET /_calm/api/requests, GET /_calm/api/requests/:idRecent requests, one request in full
GET /_calm/api/logs, GET /_calm/api/mailsRecent logs and emails
GET /_calm/api/pushesRecent push notifications: message, devices, payloads, warnings and delivery log
GET /_calm/api/devices?user=<userId>Registered and simulated devices (tokens masked), with the transport each goes through
POST /_calm/api/devices { userId, platform, behavior?, name?, token? }Adds a simulated device, or a real one when token is given
GET /_calm/api/devices/behaviorsThe simulated behaviors and what each does
PATCH /_calm/api/devices/:id { behavior?, name? }Changes a device (behavior: simulated devices only)
POST /_calm/api/devices/:id/rotateRotates a simulated device's token: returns the new device, the old one fails on the next send
GET /_calm/api/devices/:id/inboxA simulated device's lock screen
POST /_calm/api/devices/:id/onlineBrings an offline device back and shows what was waiting
POST /_calm/api/devices/:id/inbox/:pushId/openTaps a notification on the lock screen (emits push.opened)
DELETE /_calm/api/devices/:id/inbox, DELETE /_calm/api/devices/:idClears a lock screen, removes a device
POST /_calm/api/push/test { userId | deviceIds, ...message }Sends a notification through app.push.send()
POST /_calm/api/seed { resource, count }Creates sample records (up to 200) through the services
GET /_calm/api/doctorRuntime doctor findings
GET /_calm/api/activityProject activity from .calm/activity.jsonl, newest first
GET /_calm/api/securityExposure matrix, security events and posture
POST /_calm/api/auditRuns the security audit on a throwaway database and returns the report
GET /_calm/api/resources/:name/docs?as=<userId>Stored vs returned documents
POST /_calm/api/act-as { userId }A session token for that user

The calm mcp server uses this API to give coding agents the recent requests, logs, emails, push notifications and security events of your dev server, and to sign in as other users for calm_http.