Get started

one serve

Local web UI for editing machine-level profiles, with manual credential entry and no AI exposure.

6 min readUpdated 3 days agoEdit on GitHub

one serve starts a small HTTP service bound only to 127.0.0.1 and opens a browser so you can edit ~/.config/one/config.json and ~/.config/one/credentials.json. These are the same (domain, backend) profile sections managed by one configure: Infisical credentials, S3 AK/SK, kubeconfig context, registry token, and dotenv names.

Why not let AI edit the file directly: it contains API keys, kubeconfig paths, and registry tokens. The risk of leaking them is higher than the value of saving a few manual inputs. one serve physically keeps those fields out of command-line and agent context.

Usage

one serve [options]

The process blocks in the foreground. Press Ctrl-C to stop. Every mutation writes directly to ~/.config/one/{config,credentials}.json using 0600 files and atomic rename, sharing the same storage layer as one configure <verb> <pair>.

Arguments

ArgumentDescription
--host <host>Bind host. Only loopback is accepted (127.0.0.1, localhost, ::1). Non-loopback returns SERVE_BIND_FORBIDDEN
--port <n>Listen port. Default 0 lets the kernel pick a free port
--openOpen browser after startup. Default true; pass --open=false for CI, headless, WSL, or remote SSH
-o, --output <fmt>json / yaml / text; default is TTY-aware auto detection

Interactive Mode

one serve has no terminal wizard. It starts a local web UI and handles sensitive profile fields through browser forms.

For local human setup, run one serve. Scripts, CI, and agents usually use --open=false only to receive the URL; they should not bypass the browser UI to read cleartext credentials.

Output

After binding, stdout emits one startup envelope and then blocks:

{
  "schema": "one-cli/serve/v1",
  "status": "listening",
  "url": "http://127.0.0.1:54321/?token=8bRxr7N-GN1Q...",
  "host": "127.0.0.1",
  "port": 54321,
  "token": "8bRxr7N-GN1Q..."
}

The ?token= in the URL is a one-time 32-byte session token generated for this process. The same token is written as an HttpOnly; SameSite=Strict cookie on the first GET /. When the process exits, the token is invalid; restart creates a new URL.

Security Model

one serve owns profile files, and profile files own credentials. /api/* has three independent defenses:

LayerThreat blockedBehavior
Host header checkDNS rebinding, where an attacker domain resolves to 127.0.0.1Host must match the bound 127.0.0.1:<port> or localhost:<port>, otherwise 421 Misdirected Request
Origin check for mutationsCross-origin POST / script requestsPOST/PUT/DELETE Origin must equal the service origin, otherwise 403 Forbidden
Session tokenStale tab reuse and CSRF/api/* must include the correct token through cookie or ?token=, otherwise 401 Unauthorized

Credentials are masked by default. GET /api/configure* returns values such as clientSecret: "********", accessKeySecret: "********", and password: "********". The UI's reveal button calls ?reveal=1 to fetch cleartext.

Out of scope:

  • Multi-user access
  • 0.0.0.0 / LAN exposure; SERVE_BIND_FORBIDDEN refuses it
  • Live push when external processes edit profile files; refresh the browser after one configure ... add

Examples

Default: Random Port + Auto-open Browser

one serve
# profile UI started: http://127.0.0.1:54321/?token=...
# Browser opens automatically; Ctrl-C exits

CI / Headless / WSL: Print URL Only

one serve --open=false

Fixed Port For Testing Or Screenshots

one serve --port 17900

Container / Remote SSH

one serve binds to 127.0.0.1. For a remote machine, use SSH port forwarding:

# remote
one serve --open=false --port 17900

# local
ssh -L 17900:127.0.0.1:17900 remote-host
# Open the URL printed on the remote side, replacing the host with 127.0.0.1

Do not try --host 0.0.0.0; it is rejected with SERVE_BIND_FORBIDDEN.

REST API

The web UI uses these same routes. All routes require token plus Host match; mutating routes also require Origin match.

MethodPathMeaningResponse schema
GET/api/configureAll profile sectionsone-cli/serve-configure-config/v1
GET/api/configure/{domain}/{backend}One section; ?reveal=1 returns cleartextone-cli/serve-configure-section/v1
POST/api/configure/{domain}/{backend}Upsert body {name, profile, use?}one-cli/serve-configure-upsert/v1
DELETE/api/configure/{domain}/{backend}/{name}Remove profileone-cli/serve-configure-remove/v1
PUT/api/configure/{domain}/{backend}/defaultSet default profile with body {name}one-cli/serve-configure-use/v1

Legal (domain, backend) values include env/infisical, env/dotenv, deploy/aws-s3, deploy/aliyun-oss, deploy/tencent-cos, deploy/minio, deploy/rustfs, deploy/r2, deploy/kustomize, deploy/vercel, deploy/cloudflare, deploy/edgeone, and container/docker. Other combinations return 404.

Probe example:

curl -s "http://127.0.0.1:<port>/api/configure?token=<token>" | jq '.config | keys'

Common Errors

CodeRecovery
SERVE_PORT_BUSYChoose another port, or use --port 0
SERVE_BIND_FORBIDDENBind only to loopback; use SSH tunneling for remote access
SERVE_TOKEN_INVALIDRestart one serve and use the new URL
SERVE_PAYLOAD_INVALIDPOST/PUT body is invalid JSON or missing required fields such as name
PROFILE_FILE_INVALIDRepair or delete ~/.config/one/config.json / credentials.json and recreate profiles
PROFILE_BACKEND_INVALIDURL (domain, backend) is not a legal pair

Full table: Error codes.