When stdin or stdout is not a TTY (CI runners, `ssh -T`, Docker without `-t`, unattended job steps), any interactive surface the CLI would otherwise render as a full-screen wizard (login, node picker) degrades to a plain stdin prompt. Automation pipelines never hang waiting on a terminal UI that cannot be drawn.
## Overview Interactive surfaces (the login wizard shown at REPL launch, the compute-node picker) render as full-screen terminal UIs only when both stdin and stdout are TTYs. In any other context the CLI prints the same prompts to stderr as line-oriented `key: value` input so unattended jobs never block on a UI that cannot be drawn. The fallback applies automatically. It is not a flag. The trigger is TTY detection at process start. ## When the fallback kicks in - `ssh -T host 'delta-forge-cli ...'` (no pseudo-tty requested) - CI runners (GitHub Actions, GitLab CI, Jenkins, etc.) where steps run without an allocated terminal - Docker containers run without `-t` (interactive-only, no tty) - Cron jobs, systemd timers, and other unattended invocations - When stdin is redirected from a file or pipe ## Behavior - Credentials supplied via `--username` / `--password` flags, `DF_USERNAME` / `DF_PASSWORD` environment variables, or a selected profile suppress the prompt entirely. No fallback is needed because no prompt is shown. - When credentials are missing and the context is non-interactive, the CLI prints `Username: ` and `Password: ` to stderr and reads one line each from stdin. Password input is not echoed. - The compute-node picker (invoked via Ctrl+N or a bad `--node` value at launch) degrades to a numbered list printed to stderr followed by a `Select node number: ` prompt. - Piped-stdin mode (`cat script.sql | delta-forge-cli`) never enters the REPL, so the fallback is irrelevant; the piped script executes directly. - All output remains on the configured `--format` (table, compact, or json) and is written to stdout. Prompts and progress are on stderr. ## CI-ready recipe To guarantee no prompt ever appears, supply every required input via flags or environment and use `--force` to bypass safety confirmations: ``` DF_CONTROL_URL=$URL \ DF_USERNAME=$USER \ DF_PASSWORD=$PASS \ delta-forge-cli --profile ci --force run migrations/$SHA.sql ``` With all three environment variables set, the CLI authenticates without prompting and runs the script to completion.
# SSH without a pseudo-tty: inline prompts instead of the full-screen wizard
ssh -T host 'delta-forge-cli auth'
# Unattended CI step: pass credentials explicitly so no prompt ever appears
DF_USERNAME=svc DF_PASSWORD=$CI_SECRET \
delta-forge-cli --profile ci --force run migrations/job.sql
# Docker without `-t`: same behavior as any other non-TTY context
docker run --rm -i -e DF_CONTROL_URL -e DF_USERNAME -e DF_PASSWORD \
deltaforge/cli:latest --format json query "SELECT 1"
# Piped stdin: the shell is never entered, so no prompt fallback is needed
echo 'SELECT 1' | delta-forge-cli --format json
# Diagnose: which mode did the CLI pick?
# A non-TTY prompt looks like: 'Username: ' on stderr; the TUI wizard clears the screen.