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

@NiveditJain

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
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions