Design proposal · cloudflare/cf#53
Consistent terminal output for cf
Generated and hand-written commands report progress, results and errors differently. Below is today's output next to a proposed shared model, across the paths in the issue.
Today panels are real runs of a source build of cf 1.0.0-beta.13 against a local API mock, with temp paths replaced. Proposed panels are prototype renders using cf's theme and clack.
Principles
- stdout is the outputOnly the command's output, such as JSON or a raw payload. Errors returned inside that output stay in it; error messages go to stderr.
- Everything else goes to stderrPrompts, spinners, notices, success lines, warnings, error messages and logs. Color and wrapping follow stderr. Prompts run only when stdin and stderr are terminals, outside CI; spinners only when stderr is.
- One set of shapesclack log lines, the
✓success line and the error frame, used the same way in every command. Text wraps to the terminal width, except API error lines, which stay whole so they copy as one line. No literal backticks. - Copyable commandsA next step or fix starts its own line, outside the guide. Other commands, flags and environment variables in notices and warnings are styled as code.
- Quiet mode
--quiethides the banner, notices and success lines, and never skips a prompt. It leaves the spinner on;CF_QUIET=1turns it off.
│guide◇step done or prompt answered◆success in a flow✓success line●info▲warning or failed step├section└end of flow, red message when aborted┌ … └error
Today vs proposed
Success
🍊☁️ cf · v1.0.0-beta.13 ───────────────────────── Status: Disabled cf is no longer collecting usage telemetry.
🍊☁️ cf · v1.0.0-beta.13 ───────────────────────── ✓ cf is no longer collecting usage telemetry.
- Today it prints status prose on stdout.
- Proposed keeps the same message as a
✓success line on stderr.
Declined confirmation
🍊☁️ cf · v1.0.0-beta.13 ───────────────────────── │ ◇ This operation deletes the selected Workers KV namespace and all of its key-value │ pairs. Continue? │ No Aborted.
🍊☁️ cf · v1.0.0-beta.13 ───────────────────────── │ ◇ This operation deletes the selected Workers KV namespace and all of its key-value │ pairs. Continue? │ No └ Aborted.
- Today
Aborted.prints outside the guide. - Proposed prints it as clack's closing line.
Non-interactive
┌ Error │ More than one account available but unable to select one in non-interactive │ mode. │ Please set the appropriate `account_id` in your cf config file or assign it to │ the `CLOUDFLARE_ACCOUNT_ID` environment variable. │ Available accounts are (`<name>`: `<account_id>`): │ `Acme Corp`: `023e105f4ecef8ad9ca31a8372d0c353` │ `jane@acme.dev's Account`: `9a7806061c88ada191ed06f989cc3dac` │ `Acme Staging`: `01a7362d577a6c3019a474fd6f485823` └
┌ Error │ More than one account is available, but cf can't prompt for one in │ non-interactive mode. Set CLOUDFLARE_ACCOUNT_ID, or accountId in │ cloudflare.config.ts, to one of these: │ │ Acme Corp 023e105f4ecef8ad9ca31a8372d0c353 │ jane@acme.dev's Account 9a7806061c88ada191ed06f989cc3dac │ Acme Staging 01a7362d577a6c3019a474fd6f485823 └
- Today the message shows literal backticks and names
account_id, whichcloudflare.config.tsdoesn't use. - Proposed names
CLOUDFLARE_ACCOUNT_IDandaccountId, and aligns the account list. - The message comes from
@cloudflare/workers-authin workers-sdk; the Proposed text follows once that package makes it configurable.
Warning
🍊☁️ cf · v1.0.0-beta.13 ───────────────────────── ℹ Starting OAuth login flow... You are logged in with an API Token. Unset the CLOUDFLARE_API_TOKEN in the environment to log in via OAuth. ⚠ CLOUDFLARE_API_TOKEN is set and takes precedence. Unset it to log in via OAuth.
🍊☁️ cf · v1.0.0-beta.13 ───────────────────────── ▲ CLOUDFLARE_API_TOKEN is set and takes precedence. Unset it to log in via OAuth.
- Today the same advice prints twice, in two styles, across stdout and stderr.
- Proposed prints one warning on stderr.
Hand-written message
🍊☁️ cf · v1.0.0-beta.13 ───────────────────────── ℹ You are not currently logged in. → Run cf auth login to authenticate.
🍊☁️ cf · v1.0.0-beta.13 ───────────────────────── ● You are not currently logged in. To authenticate, run: cf auth login
- Today the
ℹand→lines go to stdout. - Proposed uses a clack info line on stderr, with the command on its own line.
Next steps
🍊☁️ cf · v1.0.0-beta.13 ───────────────────────── ● Creating a new Worker in /Users/jane/code/my-app ◆ Created the my-app Worker in my-app │ ├ Next steps │ cd my-app │ npm install Install dependencies │ cf dev Start a local development server │ cf deploy Deploy to Cloudflare
🍊☁️ cf · v1.0.0-beta.13 ───────────────────────── ● Creating a new Worker in /Users/jane/code/my-app ◆ Created the my-app Worker in my-app │ └ Next steps cd my-app npm install Install dependencies cf dev Start a local development server cf deploy Deploy to Cloudflare
- Today the steps sit inside the guide, so copying a line includes the
│. - Proposed closes the flow with
└and prints the steps outside the guide, with their descriptions.
Hand-written failure
🍊☁️ cf · v1.0.0-beta.13 ───────────────────────── ├ Build No Cloudflare dev-server is installed in this project. A project must declare exactly one of the following in its manifest: @cloudflare/vite-plugin (Vite-based dev server (recommended for JavaScript/TypeScript)) install: npm install --save-dev @cloudflare/vite-plugin@beta wrangler (Wrangler-based dev server (legacy Worker projects)) install: npm install --save-dev wrangler@latest cloudflare-py-dev-server (Python dev server (Pyodide via workerd)) install: pip install cloudflare-py-dev-server (or: uv add --dev cloudflare-py-dev-server) cloudflare-rs-dev-server (Rust dev server (wasm-bindgen + workerd)) install: cargo install cloudflare-rs-dev-server The manifest checked was: /Users/jane/code/(package.json|pyproject.toml|Cargo.toml)
🍊☁️ cf · v1.0.0-beta.13 ───────────────────────── ├ Build ┌ Error │ No Cloudflare dev server is installed in /Users/jane/code. │ │ Checked package.json, pyproject.toml and Cargo.toml. └ ● Install one of these: Vite plugin (recommended for JavaScript and TypeScript) npm install --save-dev @cloudflare/vite-plugin@beta Wrangler (legacy Worker projects) npm install --save-dev wrangler@latest Python (Pyodide via workerd), with pip or uv pip install cloudflare-py-dev-server uv add --dev cloudflare-py-dev-server Rust (wasm-bindgen and workerd) cargo install cloudflare-rs-dev-server
- Today the error prints as plain text, with nested parentheses and a pattern in place of the manifest path.
- Proposed puts the error in the frame and prints each install command on its own line below it.
First-run notices
🍊☁️ cf · v1.0.0-beta.13 ───────────────────────── Cloudflare collects anonymous cf usage telemetry. Run `cf cli telemetry disable` to opt out. Tip: run `cf complete <bash|zsh|fish>` to install shell completions. │ ◇ Loading [0s] [ { "id": "0f2ac74b498b48028cb68387c421e279", "title": "cache", "supports_url_encoding": true } ]
🍊☁️ cf · v1.0.0-beta.13 ───────────────────────── ● Cloudflare collects anonymous cf usage telemetry. Run cf cli telemetry disable to opt out. ● Tip: run cf complete <bash|zsh|fish|powershell> to install shell completions. │ ◇ Loading [0s] [ { "id": "0f2ac74b498b48028cb68387c421e279", "title": "cache", "supports_url_encoding": true } ]
- Today they print outside the guide, unwrapped, with literal backticks.
- Proposed shows them as clack info lines. The tip lists every shell
cf completesupports.
Redirected output
ℹ You are not currently logged in. → Run cf auth login to authenticate.
🍊☁️ cf · v1.0.0-beta.13 ─────────────────────────
(empty)
🍊☁️ cf · v1.0.0-beta.13 ───────────────────────── ● You are not currently logged in. To authenticate, run: cf auth login
- Today the message lands in
out.txt. - Proposed leaves
out.txtempty.
Prompt with redirected output
(empty)
🍊☁️ cf · v1.0.0-beta.13 ───────────────────────── ┌ Error │ --title is required. Pass --title <value> or run interactively. └
{
"id": "0f2ac74b498b48028cb68387c421e279",
"title": "cache",
"supports_url_encoding": true
}
🍊☁️ cf · v1.0.0-beta.13 ───────────────────────── │ ◇ Human-readable string name for a Workers KV namespace. │ cache │ ◇ Creating [0s]
- Today cf can't ask for the title because stdout isn't a terminal, so the command fails.
- Proposed asks on the terminal, shows progress there, and writes only the JSON to
ns.json.