On this page
lokal is built to be driven by scripts and coding agents as well as by people. Every command that sets something
up is safe to re-run, the list and status commands have a stable --json output, and exit codes say what went
wrong. The one thing an agent must leave to you is lokal setup, because it needs your password.
Tell your agent about lokal#
To tell an agent about lokal in your own project, add something like this to its AGENTS.md or CLAUDE.md:
## Local HTTPS URLs (lokal)
- This repo's dev servers run behind lokal: https://web.myapp.lokal → localhost:3000,
https://api.myapp.lokal → localhost:4000.
- Set up routes with `lokal project create myapp` and `lokal service add <sub> <port>` (safe to re-run).
- When a URL fails, run `lokal doctor`. Never run `lokal setup` or sudo; ask the user.Change the names and ports to match your repo.
What the agent learns from lokal --help#
An agent can also learn the workflow from the binary alone. lokal’s root help (lokal --help) ends with a short
section written for people and coding agents:
Using lokal in a project (for people and coding agents):
1. lokal status not set up? The user runs `lokal setup` once in their Terminal
(it asks for their password; don't run it with sudo yourself)
2. lokal project create <name> one per repo, e.g. the repo's directory name -> <name>.lokal
3. lokal service add <sub> <port> one per dev server: https://<sub>.<name>.lokal -> localhost:<port>
`@` is the bare https://<name>.lokal; works as soon as it returns
4. Run the dev server on that port (127.0.0.1, ::1 or localhost all work), open the https URL.
Inspect: `lokal list --json`, `lokal status --json`. Problems: `lokal doctor` says what to fix.
Node/Python/curl clients: eval "$(lokal env)". Vite "host not allowed": --host-header upstream.
Steps 2-3 are safe to re-run. Changing a port: `lokal service update <sub> --port <port>`.lokal help <command> shows each command’s details and examples.
Never run lokal setup or sudo#
lokal setup trusts lokal’s certificate authority in the macOS System keychain and installs
/etc/resolver/lokal. Both need your password, and macOS confirms the trust change in its own dialog. That’s a
decision for you, not for an agent. Apart from lokal uninstall, setup is the only command that ever uses sudo.
Everything an agent does day to day (projects, services, doctor, status) runs without it.
So an agent should check first, and ask you if setup is missing:
$ lokal status --json
{
"schema": 1,
"setup_done": false,
"running": false,
"listeners": [],
"names": [],
"certs_cached": 0
}"setup_done": false means you need to run lokal setup yourself, once, in your own Terminal.
lokal enforces this too. When no terminal is attached to type a password into, which is the case for most agents,
setup stops instead of trying: with exit code 2 (setup needs confirmation), or with exit code 4 if --yes skipped
that question and setup needs sudo. Setting LOKAL_FORBID_SUDO=1 in an agent’s
environment makes lokal exit with code 4 instead of calling sudo even when a terminal is attached, and print the
exact commands it would have run.
Safe to re-run#
An agent can run the same setup commands every time it starts work:
$ lokal project create myapp # exists already? It becomes the current project; nothing else changes
$ lokal service add web 3000 # same settings? Nothing changes, and it still waits until the URL is ready
$ lokal service add api 4000When a command returns, the URL works: the name resolves, the certificate exists and the route is live. There’s no need to sleep or retry.
A service add with different settings from the existing service fails (exit code 2) and names the command to
run instead, such as lokal service update api --port 5000. A project create with a different --domain also
fails with exit code 2.
There’s one current project for the whole Mac. An agent working next to you, or across several repos, should pass
-p <project> to service commands instead of relying on it. Projects and services
has the details.
Machine-readable output#
These commands take --json:
lokal listlokal statuslokal doctorlokal project listandlokal project showlokal service list
The output is pretty-printed, has no colour codes, and always includes "schema": 1. It’s a stable contract: it
stays backwards compatible until the schema number changes.
lokal list --json has every project and service:
{
"schema": 1,
"current_project": "myapp",
"projects": [
{
"name": "myapp",
"domain": "myapp.lokal",
"services": [
{
"subdomain": "api",
"fqdn": "api.myapp.lokal",
"url": "https://api.myapp.lokal",
"host": "localhost",
"port": 4000,
"host_header": "preserve"
}
]
}
]
}lokal status --json always has setup_done and running. While the background service runs, it also has its
pid, version, build_id, uptime_secs, its listeners, and the state of each published name in names
(registered, pending or failed: <reason>).
lokal doctor --json lists every check with an id, a status (pass, warn, fail or skip), a detail
with a fix hint, and the time it took in ms. ok is false if any check fails. Warnings don’t count:
{
"schema": 1,
"ok": true,
"checks": [
{
"id": "ca.trusted",
"status": "pass",
"detail": "macOS trusts lokal's CA",
"ms": 31
},
{
"id": "upstream.api.myapp.lokal",
"status": "warn",
"detail": "nothing listening on localhost:4000 — is your service running?",
"ms": 0
}
]
}This example shows two checks. A real run has one for each layer, plus dns., tls. and upstream. checks for
each service.
Exit codes#
| Code | Meaning |
|---|---|
0 | Success |
1 | Runtime error, or lokal doctor found at least one fail |
2 | Usage error: an unknown command or flag, a name that breaks the rules, or a conflicting service add |
3 | Setup hasn’t been run |
4 | sudo would be needed but isn’t possible: LOKAL_FORBID_SUDO=1 is set, or no terminal is attached |
Commands that need setup, such as start, upgrade, env and open, exit with 3 before setup.
Commands that change projects and services don’t: they save the change, exit 0, and print a note that lokal
isn’t set up yet. Errors go to stderr as error: <message>, often followed by a hint: line with the fix.
Built by a coding agent#
lokal v0.1 itself was built by a coding agent working from a specification, with human sign-off for every
privileged step. The build history has
the build log, the decisions and the measurements made on macOS. If you point an agent at the lokal repo to work
on lokal itself, it should read
CONTRIBUTING.md first.