Philosophy

The CLI should be simple at first and grow as the user learns. Make simple things simple. Make complex things discoverable and memorable.

A new user should be able to run ntn login and ntn workers list without reading docs. A power user should be able to script deployments with --json, --yes, and environment variables — and discover those capabilities naturally through --help, hints in error messages, and shell completions.

We build for two audiences from the start: humans in a terminal (TTY) and programs that invoke the CLI as a subprocess (non-TTY). The non-TTY case covers shell scripts, CI pipelines, and AI agents. Every design decision — structured output, non-interactive fallbacks, meaningful exit codes, progressive disclosure — serves both audiences. A CLI that is good for humans is good for agents, and a CLI that is good for agents is good for humans.

Noun-verb command structure

Commands follow a <noun> <verb> pattern: the resource comes first, then the action. Use plural nouns unless it reads poorly.

# Good
ntn workers list
ntn workers delete 11111111-1111-4111-8111-111111111111 --yes
ntn files get 22222222-2222-4222-8222-222222222222
ntn workers deploy
ntn files create < photo.png

# Bad
ntn list-tokens
ntn create-token my-token
ntn token list          # singular — use plural

This structure groups related operations under a single noun, making the command tree discoverable (ntn workers --help lists all worker operations) and predictable (if ntn workers list exists, ntn files list probably does too).

Provide UNIX-style short aliases for common verbs. These let experienced users work at the speed they expect from standard CLI tools. Use visible_alias so aliases appear in --help; use alias for legacy or internal aliases that should work but not be advertised.

Canonical Alias Notes
list ls Every list command gets this
delete rm
unset rm Also aliased to delete
new init Legacy compat, hidden alias

Prefer aliases drawn from coreutils and POSIX (ls, rm, cp, mv, cat) over inventing new shorthands. A user who knows UNIX should be able to guess the alias without reading docs.

Be utilitarian, not cute

Do not anthropomorphize the CLI. No emoji in output. No clever puns or jokes in help text. Messages should be terse, factual, and actionable. The CLI is a tool, not a personality.

# Good
error: No workspace selected.
  hint: Run `ntn login` first, or set NOTION_WORKSPACE_ID.

# Bad
Oops! 😅 Looks like you forgot to log in! Try `ntn login` to get started!

Always handle non-TTY

Every interactive feature must check for a terminal and degrade gracefully when one is not available. This matters for scripts, CI pipelines, and AI agents that invoke the CLI as a subprocess.

Check both stdin and stderr (not stdout, which carries data):

if !std::io::stdin().is_terminal() || !std::io::stderr().is_terminal() {
    return Err(CliError::auth_with_hint(
        "Cannot open browser for login in a non-interactive environment.",
        "Set NOTION_API_TOKEN and NOTION_WORKSPACE_ID for non-interactive auth.",
    ));
}

For every interactive prompt, there must be a non-interactive equivalent:

Interactive Non-interactive equivalent
Browser login NOTION_API_TOKEN + NOTION_WORKSPACE_ID
Workspace picker NOTION_WORKSPACE_ID
Confirmation prompt --yes