Skip to content

[CLAUDE ROUTINE]: CX enhancement — honor NO_COLOR (and add --no-color) so CLI output reads cleanly in CI logs and non-ANSI terminals #251

Description

@NiveditJain

Summary

The interactive installer and hook warnings emit raw ANSI escape codes (\x1B[33m…, \x1B[?25l, etc.) without checking whether the destination terminal can render them. Customers piping failproofai policies --install into a logger, running it inside a CI container, or using a screen reader currently see literal [33m characters mixed into messages.

Adopting the well-known NO_COLOR convention (plus a --no-color flag for parity) gives users a clean fallback while keeping the lovely interactive UX for everyone else.

Where

Heaviest concentration of escapes:

  • src/hooks/install-prompt.ts — lines 63, 69, 113, 119, 155, 161-164, 173, 260, 267, 359-440 (selector UI, status warnings, cursor hide/show)
  • Various hookLogWarn / hookLogInfo callsites (lib/logger.ts)
  • The \x1B[?25l / \x1B[?25h cursor-hide pair will leave a CI terminal with no cursor if the process exits before \x1B[?25h runs

Today there is no central helper, so colour decisions are scattered as inline literals.

Why this helps users

journey
    title `failproofai policies --install` in different terminals
    section TTY w/ colour (today)
      Run command: 5: User
      See styled selector: 5: User
    section CI / piped (today)
      Run command: 3: User
      See literal '[33m...': 1: User
      Grep logs for warnings: 2: User
    section CI w/ NO_COLOR (proposed)
      Run command: 5: User
      See plain readable text: 5: User
      Grep logs cleanly: 5: User
Loading
  • CI ergonomics — GitHub Actions, Docker logs, Datadog log views render plain ASCII much better than escape soup.
  • Accessibility — screen readers (especially on Windows + WSL) handle plain text predictably.
  • Industry-standard contractNO_COLOR=1 is the de facto convention; supporting it is one less surprise.
  • Defensive safety — wraps \x1B[?25l (hide cursor) in a single helper that always restores on exit, avoiding the "invisible cursor" complaint that occasionally hits Ctrl-C users.

Proposed enhancement

  1. Tiny helper in lib/logger.ts (or a new src/cli-color.ts):

    const colorEnabled = (() => {
      if (process.env.NO_COLOR) return false;
      if (process.env.FORCE_COLOR) return true;
      if (process.argv.includes("--no-color")) return false;
      return process.stdout.isTTY === true;
    })();
    
    export const c = {
      yellow: (s: string) => colorEnabled ? `\x1B[33m${s}\x1B[0m` : s,
      dim:    (s: string) => colorEnabled ? `\x1B[2m${s}\x1B[0m`  : s,
      cyan:   (s: string) => colorEnabled ? `\x1B[36m${s}\x1B[0m` : s,
      // … green, magenta, bold, reverse
    };
    export function hideCursor() { if (colorEnabled) process.stdout.write("\x1B[?25l"); }
    export function showCursor() { if (colorEnabled) process.stdout.write("\x1B[?25h"); }
  2. Sweep install-prompt.ts once and replace inline \x1B[..m with c.yellow(...), c.dim(...), etc.

  3. When colorEnabled === false, also bypass the alternate-screen / line-redraw logic and fall back to a single non-interactive prompt — a TTY-less environment can't redraw anyway.

  4. Document NO_COLOR and --no-color in failproofai --help output and README.md → Configuration → Environment variables.

Acceptance criteria

  • NO_COLOR=1 failproofai policies --install produces no escape sequences in stdout/stderr.
  • --no-color flag behaves identically.
  • process.stdout.isTTY === false (piped output) auto-disables colour.
  • Cursor is restored on exit even when interrupted (process.on("exit", showCursor)).
  • Snapshot test of install-prompt rendering in NO_COLOR mode.
  • CHANGELOG.md entry under Unreleased → Features.

Severity

Low–medium — every CI user sees this. The fix is mechanical and removes a recurring papercut.

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or request

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions