Without a config file, users must pass --api-key, --format, --output-dir, and --region on every single invocation. That friction drives them toward shell aliases that are fragile, .env files that aren't portable, and wrapper scripts that become undocumented infrastructure. ISO 25010:2011 usability.operability scores CLIs on whether persistent preferences can be set once and forgotten. A CLI that requires 6 flags per invocation is not operationally usable in real workflows — teams will script around it rather than into it, breaking the tool's intended composability.
Medium because the absence of config file support imposes per-invocation repetition that degrades usability in team workflows without causing data loss or security failures.
Add config file discovery using cosmiconfig (Node.js) or a standard ~/.config location (Python/Go). CLI flags must always override config file values:
import { cosmiconfig } from 'cosmiconfig'
const explorer = cosmiconfig('mytool')
const result = await explorer.search() // finds .mytoolrc, mytool.config.js, etc.
const fileConfig = result?.config ?? {}
// CLI flags override config — spread order matters
const config = { ...fileConfig, ...cliFlags }
import tomllib
from pathlib import Path
def load_config() -> dict:
for loc in [Path('mytool.toml'), Path.home() / '.config' / 'mytool' / 'config.toml']:
if loc.exists():
with open(loc, 'rb') as f:
return tomllib.load(f)
return {} # no config file is not an error
Document which config file locations are checked in --help output or a mytool config --locations subcommand.
ID: cli-quality.config-distribution.config-file
Severity: medium
What to look for: Enumerate all configuration sources (config files, environment variables, CLI flags). For each, check for config file support: cosmiconfig, rc, configparser, viper, config crate. Look for config file loading from standard locations: .mytoolrc, .mytoolrc.json, mytool.config.js, ~/.config/mytool/config.json, or pyproject.toml [tool.mytool] section. Check that command-line flags override config file values. Check that a missing config file doesn't cause an error (defaults should be used).
Pass criteria: The CLI supports a configuration file for persistent options. Config file values are overridable by command-line flags. Missing config file is handled gracefully (defaults used, no error). Config file locations follow platform conventions (XDG on Linux, ~/Library/Preferences on macOS, or project-local dotfiles) — at least 1 configuration file format supported (JSON, YAML, TOML, or RC file). Report: "X configuration sources found."
Fail criteria: No config file support — all options must be passed as flags every time, or config file is required (crashes if missing), or config file values cannot be overridden by flags.
Skip (N/A) when: The CLI has 2 or fewer options total — config file overhead is not warranted. All checks skip when no CLI entry point is detected.
Detail on fail: "No configuration file support found — users must pass --api-key and --format on every invocation" or "Config file is loaded from hardcoded path '/etc/mytool.conf' — non-standard and requires root to edit"
Remediation: Config files save users from repetitive flag typing:
// Node.js — cosmiconfig for standard config file discovery
import { cosmiconfig } from 'cosmiconfig'
const explorer = cosmiconfig('mytool')
const configResult = await explorer.search()
const fileConfig = configResult?.config ?? {}
// CLI flags override config file values
const config = {
...fileConfig,
...cliFlags, // spread CLI flags last to override
}
# Python — standard config locations
from pathlib import Path
import tomllib
def load_config():
locations = [
Path('mytool.toml'),
Path.home() / '.config' / 'mytool' / 'config.toml',
]
for loc in locations:
if loc.exists():
with open(loc, 'rb') as f:
return tomllib.load(f)
return {} # defaults, no error