Skip to content

CLI Cross-Platform Compatibility

This page documents how the lf CLI behaves across macOS, Linux, and Windows, as well as in headless and CI environments. It explains the design decisions behind the cross-platform support layer and lists known limitations per platform.


Supported platforms

PlatformStatusMinimum version
macOS (Apple Silicon & Intel)Fully supportedmacOS 12 Monterey
Linux (x64, arm64)Fully supportedglibc ≥ 2.17 (Ubuntu 18.04+, Debian 10+, RHEL 8+)
Windows (x64)SupportedWindows 10 build 1903+
WSL 1 / WSL 2SupportedUbuntu 20.04+ inside WSL
Docker (official Node.js images)Supportednode:22-alpine, node:22-bookworm-slim, etc.
GitHub Actions, GitLab CI, CircleCISupportedUse actions/setup-node@v4 with Node.js 22

Node.js version enforcement

The CLI startup banner runs a synchronous Node.js version check before any module is loaded:

Error: @lenserfight/cli requires Node.js v22 or newer (you have v18.20.4).
Download the latest LTS from https://nodejs.org/en/download

This check is injected into the compiled output at build time, so it fires even before the ES Module runtime parses the bundle. If you see a raw ERR_REQUIRE_ESM instead, the check did not run — which means you are using a very old binary. Reinstall with npm install -g @lenserfight/cli@latest.

Why ≥ 22?

The CLI and its dependencies (most notably citty) are distributed as ES Modules (.mjs). Stable, synchronous interop between CommonJS callers and ESM packages requires Node.js 22.12+. Requiring ≥ 22 covers the entire stable v22 LTS line.


Terminal and color output

TTY detection

Color output and the interactive TUI dashboard are enabled only when process.stdout.isTTY is true. When stdout is piped or redirected (e.g., lf lenses | grep foo), all ANSI sequences are stripped automatically.

NO_COLOR standard

The CLI honours the no-color.org specification exactly:

  • NO_COLOR set to any value (including an empty string) → color disabled
  • NO_COLOR not present in the environment → color enabled (if TTY)
bash
NO_COLOR=1 lf lenses list      # plain text
NO_COLOR= lf lenses list       # also plain text (empty string counts)
unset NO_COLOR; lf lenses list  # color if connected to a TTY

TERM=dumb

Setting TERM=dumb also disables color output and the alt-screen TUI. This is the expected behavior for editors and tools that embed terminal output.

Windows terminal support

The Windows Console (conhost.exe, Windows Terminal, PowerShell 7+, Git Bash, WSL) supports ANSI escape codes from Windows 10 1903 onwards. The CLI does not apply any Windows-specific workarounds — standard TTY detection is sufficient.


File system and paths

Path separators

All internal path handling uses Node.js path.join and path.resolve, which produce platform-native separators (\ on Windows, / on Unix). String literals in the source that contain forward slashes are always passed through resolve() before use, so they are correct on all platforms.

Configuration directories

User-level configuration and credentials are stored in the platform-appropriate directory:

PlatformConfig directory
macOS~/Library/Application Support/lenserfight/
Linux$XDG_CONFIG_HOME/lenserfight/ (falls back to ~/.config/lenserfight/)
Windows%APPDATA%\lenserfight\

Project-level config lives in .lenserfight/ inside the project root. Local battle state is stored at .lenserfight/local-battles/.

File permissions

On Unix, credential files (auth.json) are created with mode 0600 (owner read/write only). On Windows, Node.js ignores POSIX permission mode arguments; file security is enforced at the directory level through NTFS access control lists instead.


Process spawning and shell commands

URL opening (lf docs open, lf media play)

Opening a URL in the default system browser uses spawnSync with an array of arguments rather than a shell template string. This prevents shell metacharacter injection if the URL contains special characters:

PlatformCommand used
macOSopen <url>
Windowscmd /c start "" <url>
Linuxxdg-open <url>

If the opener fails (e.g., no browser available in a headless container), the CLI prints the URL to the console so it can be opened manually.

which / where command detection

When the doctor or onboarding subsystem checks whether a tool is installed (Docker, Supabase CLI, etc.):

  • Unix: which <cmd>
  • Windows: where <cmd>

The platform is detected at runtime via process.platform.

Bash scripts (combine-seeds.sh)

The lf dev seed command runs supabase/combine-seeds.sh when it is present. On Windows, this requires bash to be in PATH. If bash is not found, the CLI skips the script with a helpful message:

combine-seeds.sh found but bash is not in PATH on Windows.
Install Git for Windows (https://git-scm.com) or enable WSL, then run:
  bash supabase/combine-seeds.sh

Install Git for Windows to get a bash.exe that works in the standard Windows PATH.

shell: true spawning

Commands that launch external processes (e.g., supabase start, pnpm install) use shell: true without specifying a shell path. On Unix this resolves to /bin/sh; on Windows to cmd.exe. Commands that have been tested on both are noted in their respective command references. If you encounter a platform-specific failure, open an issue with the exact command and OS version.


Shell completion

lf completion generates completion scripts for:

ShellCommand
Bashlf completion bash >> ~/.bash_completion
Zshlf completion zsh >> ~/.zshrc
Fishlf completion fish > ~/.config/fish/completions/lf.fish

PowerShell completion is not yet generated automatically. To add basic tab completion for PowerShell, add the following to your $PROFILE:

powershell
Register-ArgumentCompleter -Native -CommandName lf -ScriptBlock {
    param($wordToComplete, $commandAst, $cursorPosition)
    # Invoke lf --help and parse subcommands for a basic fallback
}

See Shell Completion for full installation instructions per shell.


CI and headless environments

Authentication

Use a developer or service token instead of interactive browser login:

bash
export LENSERFIGHT_API_KEY=lf_svc_...
lf run exec --lens my-lens-slug

Disabling color / TUI

In CI environments, stdout is typically not a TTY, so color and the TUI are automatically disabled. To explicitly disable color regardless of TTY state:

bash
NO_COLOR=1 lf lenses list

Docker

dockerfile
FROM node:22-alpine
RUN npm install -g @lenserfight/cli
ENV LENSERFIGHT_API_KEY=lf_svc_...
RUN lf doctor --check api

GitHub Actions

yaml
- uses: actions/setup-node@v4
  with:
    node-version: '22'
- run: npm install -g @lenserfight/cli
- run: lf run exec --lens my-lens-slug
  env:
    LENSERFIGHT_API_KEY: ${{ secrets.LF_API_KEY }}

Known limitations

LimitationPlatforms affectedWorkaround
Bash scripts require bash in PATHWindowsInstall Git for Windows or WSL
Shell completion not generated for PowerShellWindowsManual Register-ArgumentCompleter
chmod 600 silently ignoredWindowsNTFS ACL provides equivalent protection
Interactive TUI requires a real TTYCI, DockerUse --json flags for machine-readable output
lf top (runtime monitor) requires a color-capable terminalAllFalls back to lf top stream in non-TTY