arkor dev
Launch Arkor Studio locally.
Boots Studio, the local web UI, on http://localhost:4000. Studio is where you click Run training to spawn arkor start against your src/arkor/index.ts, watch the run stream in, and chat with the resulting adapter in the Playground.
arkor dev itself does not start a training run; it only serves the UI plus a small loopback API the SPA talks to.
Synopsis
arkor dev [options]pnpm devnpm run devyarn devbun devOptions
| Flag | Default | Description |
|---|---|---|
-p, --port <port> | 4000 | Port to bind. The displayed URL uses localhost, but the listener binds 127.0.0.1 directly so it cannot end up IPv6-only on hosts where /etc/hosts lists ::1 before 127.0.0.1. The CLI parses the value as `Number(opts.port) |
--open | off | Open the Studio URL in a browser after the server is up. |
Behavior
Launch sequence
- Credential bootstrap. If
~/.arkor/credentials.jsondoes not exist, the CLI always tries to bootstrap an anonymous session: it calls/v1/auth/cli/config, then requests an anonymous token from/v1/auth/anonymous. The pre-bootstrap line depends on whether the deployment advertises OAuth: when OAuth is configured the CLI printsNo credentials on file. Bootstrapping an anonymous session. Run `arkor login --oauth` to sign in to your account instead.so you can upgrade to a real account whenever you want; on anon-only deployments it printsNo credentials on file. Requesting an anonymous token.instead, omitting the OAuth hint becausearkor login --oauthwould fail there. Either way, it never auto-launches the OAuth flow. Once the token lands,arkor devprintsAnonymous id: <id>. Arkor Cloud uses this id to recognise this client across sessions. Keep `<home>/.arkor/credentials.json` to stay signed in as the same anonymous identity.(the path is the resolvedcredentialsPath(), typically~/.arkor/credentials.jsonon Linux and macOS). Only when the deployment advertises OAuth, a follow-up warn (Anonymous sessions aren't guaranteed to persist. Sign in with `arkor login --oauth` to tie future work to your Arkor Cloud account.) fires alongside the success line so the upgrade hint is visible at issuance time. On anon-only deployments the warn is suppressed because pointing atarkor login --oauthwould surface a command that fails. Transport failures (fetch failed) are handled differently depending on when they hit. If/v1/auth/cli/configalready succeeded and/v1/auth/anonymousthen fails the same way, the CLI warns and continues; the Studio server retries on the first/api/credentialshit. If/v1/auth/cli/configitself is unreachable, the same transport error is rethrown andarkor devexits fast (restore connectivity and re-run). If/v1/auth/anonymousis rejected with a 4xx (for example because anonymous sign-in is disabled on this deployment), it surfaces an error wrapping the HTTP status and pointing atarkor login --oauth(full message:Failed to bootstrap an anonymous session (HTTP <status>). This deployment may require sign-in. Run `arkor login --oauth` and try again.). - CSRF token. A 32-byte token (base64url, ~43 chars) is generated for this launch. It is injected into
index.htmlas<meta name="arkor-studio-token">so the same-origin SPA can read it. Cross-origin tabs cannot read the meta and are rejected by the/api/*middleware. - Listener. Hono on
127.0.0.1:<port>. TheHostheader guard accepts both127.0.0.1andlocalhost, so the URL the CLI prints (http://localhost:<port>) works without surprising DNS-rebinding fallout. - Token persistence (best-effort, after a successful bind). Once the listener is up, the token is written to
~/.arkor/studio-token(mode0600) so the studio-app Vite dev server (pnpm --filter @arkor/studio-app dev) can pick it up. Binding first means a doomed second launch on a busy port never touches the file (see "Port collision" below). If writing fails (read-only$HOME, locked-down umask),arkor devcontinues; only the standalone Vite dev workflow is affected.
When the process exits (normal exit, SIGINT, SIGTERM, or SIGHUP) the studio-token file is removed on a best-effort basis. A crash can leave the file on disk; the next arkor dev rotates it.
Loopback and CSRF model
The Studio server enforces three checks on every /api/* request:
- The
Hostheader must be127.0.0.1orlocalhost(defense against DNS rebinding). - The CSRF token must be present as the
X-Arkor-Studio-Tokenheader. The job-event stream also accepts?studioToken=...becauseEventSourcecannot send custom headers; mutation routes do not accept query-string tokens. Token comparison istimingSafeEqual. - CORS is intentionally not configured: the SPA is same-origin so CORS adds no value, and reflecting
*would let "simple" cross-origin POSTs (text/plain,urlencoded) skip preflight. Without a token, the middleware rejects them.
This means arkor dev is safe on a shared dev machine: another tab cannot read the meta, a stale tab from a previous launch holds an old token that no longer matches, and an attacker page in a different origin cannot forge requests.
Port collision
arkor dev does not auto-pick a free port. If the chosen port is already taken (another arkor dev left running, an unrelated dev server, etc.), the listener's EADDRINUSE is caught and arkor dev exits non-zero with Port <port> is already in use. Another `arkor dev` may be running; pass --port to choose a different one. Pick a different port with -p <port>, or stop whatever else is bound to it.
The token file (~/.arkor/studio-token) is written only after the port binds successfully, so a failed second launch on a busy port never overwrites or deletes the token a healthy first instance depends on.
Errors
| Symptom | What it means | Fix |
|---|---|---|
Port 4000 is already in use. Another `arkor dev` may be running; pass --port to choose a different one. | Another process holds the port (the underlying error is EADDRINUSE). | Stop it, or use --port <other>. |
Could not reach <baseUrl> (fetch failed). Studio will keep running and retry on first /api/credentials hit. | /v1/auth/cli/config already succeeded, but the follow-up /v1/auth/anonymous hit a transport error. The Studio server starts and will retry. | Bring connectivity back; the SPA recovers on its next /api/credentials poll without restarting arkor dev. |
TypeError: fetch failed (or an equivalent transport error that exits arkor dev immediately) | /v1/auth/cli/config itself was unreachable, so the deployment mode could not be determined and the CLI fails fast. | Restore connectivity and re-run arkor dev. |
No credentials on file. Bootstrapping an anonymous session. Run `arkor login --oauth` to sign in to your account instead. | First arkor dev on this machine when the deployment advertises OAuth. The CLI bootstraps anonymous so Studio can start immediately; the message is informational, not an error. | Nothing required. To upgrade to a real account, run arkor login --oauth separately (it overwrites ~/.arkor/credentials.json) and refresh Studio. |
No credentials on file. Requesting an anonymous token. | Same as above on anon-only deployments (no OAuth advertised in /v1/auth/cli/config). The CLI omits the arkor login --oauth hint because that command would fail there. | Nothing required. |
Anonymous id: <id>. Arkor Cloud uses this id to recognise this client across sessions. Keep `<home>/.arkor/credentials.json` to stay signed in as the same anonymous identity. | Informational follow-up after the anonymous bootstrap completes. Surfaces the cloud-side identifier and where it lives (the path is the resolved credentialsPath(), typically ~/.arkor/credentials.json on Linux and macOS). | Nothing required. Back up the credentials file if you want to keep using the same anonymous identity from another machine. |
Anonymous sessions aren't guaranteed to persist. Sign in with `arkor login --oauth` to tie future work to your Arkor Cloud account. | Persistence nudge fired alongside the success message when the deployment is known to support OAuth. Anonymous work has no SLA on the cloud-api side, so the CLI surfaces the upgrade path before you invest real work. Suppressed on anon-only deployments. | Optional: run arkor login --oauth to tie future work to your account. Existing anonymous work stays under its current id; there is no migration path today. |
Failed to bootstrap an anonymous session (HTTP <status>). This deployment may require sign-in. Run `arkor login --oauth` and try again. | /v1/auth/anonymous rejected the request with a 4xx, so anonymous bootstrap cannot proceed. | Run arkor login --oauth to complete the browser flow, then re-run arkor dev. |
Could not write ~/.arkor/studio-token (...). The Studio at http://localhost:<port> is unaffected, but the Vite SPA dev workflow will see 403s on /api/*. | $HOME is read-only or umask blocks 0600. The bundled Studio still works; only the standalone Vite dev workflow is affected. | Run from a writable home, or only use the bundled Studio served by arkor dev. |
HTTP 403 with { "error": "Studio API is loopback-only" } (in browser devtools) | The Host header is something other than 127.0.0.1 / localhost. | Reach Studio via http://localhost:<port> or http://127.0.0.1:<port>. Reverse proxies or 0.0.0.0-bound shells will be rejected by design. |
HTTP 403 with { "error": "Missing or invalid studio token" } (in browser devtools) | The CSRF token in the page does not match the current launch. Usually a stale tab from a previous arkor dev. | Reload the tab. Token rotates on every launch. |
Examples
Default port:
Custom port plus auto-open:
pnpm dev --port 5000 --opennpm run dev -- --port 5000 --openyarn dev --port 5000 --openbun dev --port 5000 --openSee also
- Studio concept for what the UI actually does
arkor loginfor upgrading from an anonymous session to OAuth- Project structure for the
~/.arkor/and.arkor/layout