module

Noir::CLI

Constants

GLOBAL_FLAGS = ["--no-color", "--no-spinner"]

Flags the router itself owns, valid anywhere on the command line — including before the verb, which is where noir help documents them ("Strip ANSI color from every command's output"). verb_index is what lets that placement work: without it the router only ever looked at argv.first, so noir --no-color scan ./app fell through to the v0 bare-flag path and died with Base path does not exist: scan.

KNOWN_COMMANDS = Catalog::NAMES

Known top-level verbs. The router falls back to scan when ARGV[0] is not one of these (preserving the noir -b ./app v0 usage pattern).

Class methods

apply_global_color_flag!(argv : Array(String)) : Nil

Disable Crystal's Colorize globally when the user asks for plain output via --no-color or the NO_COLOR env var. Applied at the router layer so every subcommand (list / cache / config / rules / completion / version / help / scan) picks it up. Scan's own parser still sees --no-color and threads it through NoirRunner for the in-scan logger.

Source
die(message : String, code : Int32 = 1) : NoReturn
Source
name(label : String) : String
Source
no_color_env?

NO_COLOR follows the convention at https://no-color.org: any non-empty value disables color, with the explicit exception of "0".

Source
route_library_logs_to_stderr!

Point Crystal's global Log at STDERR.

The stdlib configures that logger, at the bottom of its own log.cr, with a Log::IOBackend whose IO defaults to STDOUT — and stdout is where Noir's report goes. So anything logged through the global Log, by Noir or by a shard Noir pulls in, lands in the middle of the -f json / -f sarif / -f yaml document and breaks every downstream parser.

Not hypothetical, and not limited to code this repo owns:

  • the har shard warns through the global Log for an unparsable startedDateTime or cookie expires, so a browser capture whose timestamps are not ISO-8601 printed a WARN line and a full Crystal backtrace ahead of the JSON — noir scan ./captures -f json | jq . died with Invalid numeric literal at line 1, column 14;
  • NOIR_ACP_RAW_LOG=1 deliberately un-mutes the acp shard's acp.client / acp.transport sources, which then wrote their protocol diagnostics to the same stdout.

Every diagnostic Noir writes itself already goes to STDERR through NoirLogger. This makes the same invariant hold for the code Noir does not own, once, at the process entry point.

The severity threshold is left exactly where the stdlib default puts it (Info) — this changes the stream, not what gets logged — and is deliberately not wired to LOG_LEVEL, so a variable that happens to be exported for some other tool cannot start adding lines to a Noir run.

Sync rather than the default async dispatcher: these are rare diagnostics, and an async backend flushes from its own fiber, which can reorder them against — or lose them behind — NoirLogger's STDERR writes and the process exiting.

Source
section(label : String) : String

Shared accent helpers so every -h page styles its section labels and inline command names the same way. Green for headers (USAGE, SUBJECTS, ACTIONS, OPTIONS, ...). Cyan for the named items inside.

Source
verb_index(argv : Array(String)) : Int32

Index of the first token that is not one of the leading global flags, i.e. where the subcommand verb would be. Returns argv.size when argv holds nothing but global flags.

Source

Nested types