Skip to content

feat: startup banner with Elastic brand colors#256

Merged
JoshMock merged 3 commits into
mainfrom
feat/startup-banner
Apr 30, 2026
Merged

feat: startup banner with Elastic brand colors#256
JoshMock merged 3 commits into
mainfrom
feat/startup-banner

Conversation

@MattDevy
Copy link
Copy Markdown
Contributor

@MattDevy MattDevy commented Apr 30, 2026

Summary

image

Adds a multi-color ASCII art startup banner shown when the CLI is invoked with no arguments (the help/landing screen). The Elastic brand gradient sweeps across five rows: pink → yellow → teal → blue → light-blue.

  • New src/lib/logo.ts module handles detection and rendering
  • banner: false config file field to suppress per-user
  • ELASTIC_NO_BANNER=1 env var to suppress globally
  • Automatically silent when stdout is not a TTY

Token-safe by default

When an LLM or script invokes the CLI as a subprocess, stdout is piped (no TTY). renderLogo detects this and returns an empty string — no tokens wasted on decorative output. This is unconditional: no env var or flag needed on the caller side.

The suppression hierarchy (highest to lowest precedence):

  1. No TTY — always silent (LLM tool use, pipes, CI)
  2. ELASTIC_NO_BANNER=1 — always silent
  3. banner: false in config file — silent for that user's session
  4. Default — shown

Color tiers

The banner degrades gracefully based on terminal capability:

Env Escape format Colors
COLORTERM=truecolor \x1b[38;2;R;G;Bm Exact Elastic brand hex values
TERM=*256* \x1b[38;5;Nm Closest 256-color approximations
Basic ANSI TTY \x1b[9Xm Bright named colors
No TTY / NO_COLOR Silent or plain text

Config schema changes

banner is a top-level field alongside commands:

current_context: default
banner: false
contexts:
  default:
    elasticsearch:
      url: https://...

Threaded through the full config pipeline: StructuralConfigSchemaConfigFileSchemaresolveContextResolvedConfig. Also preserved on read/write round-trips in writer.ts with stable key ordering (current_contextcontextscommandsbanner).

Open question: scope of the banner

Currently the banner only appears on bare elastic (no args). This is intentional — showing it on every subcommand invocation would quickly become noise. If we want to show it in other contexts (e.g. elastic --help, or a future interactive/REPL mode), we should be deliberate about which entry points trigger it rather than adding it broadly.

Test plan

  • elastic (no args, real TTY) — banner renders with Elastic gradient colors
  • elastic | cat (piped) — no banner output
  • ELASTIC_NO_BANNER=1 elastic — no banner output
  • NO_COLOR=1 elastic — plain text fallback ( elastic CLI v0.1.0-alpha.1)
  • Config with banner: false — no banner output
  • Config without banner field — banner shows (default enabled)
  • elastic stack es search ... — no banner (banner is no-args only)
  • elastic config context list — no banner

Renders a multi-color ASCII art logo on bare `elastic` invocation
(the no-args help screen). The Elastic brand gradient runs across
five rows: pink -> yellow -> teal -> blue -> light-blue.

Three ways to suppress:
- No TTY (pipe / subprocess / LLM tool use): always silent
- ELASTIC_NO_BANNER=1 env var: always silent
- banner: false in the config file: silent for that user's session

Config integration:
- banner field added to ConfigFileSchema and StructuralConfigSchema
- Threaded through loader.ts (resolveContext + loadConfig pipeline)
- Preserved on read/write round-trips in writer.ts (readRawConfig,
  serializeConfig with stable key ordering after commands)
- ResolvedConfig carries banner?: boolean to the CLI entrypoint
@MattDevy MattDevy marked this pull request as ready for review April 30, 2026 14:05
@github-actions
Copy link
Copy Markdown

github-actions Bot commented Apr 30, 2026

MegaLinter analysis: Success

Descriptor Linter Files Fixed Errors Warnings Elapsed time
✅ COPYPASTE jscpd yes no no 8.06s
✅ REPOSITORY gitleaks yes no no 78.56s
✅ REPOSITORY git_diff yes no no 0.51s
✅ REPOSITORY secretlint yes no no 13.9s
✅ REPOSITORY trivy yes no no 18.5s
✅ TYPESCRIPT eslint 7 0 0 4.23s

See detailed reports in MegaLinter artifacts
Set VALIDATE_ALL_CODEBASE: true in mega-linter.yml to validate all sources, not only the diff

MegaLinter is graciously provided by OX Security
Show us your support by starring ⭐ the repository

When the user passes --inline-secrets, there is no reason to call
getSecretStore() (which runs `security -h` on macOS with a 2-second
timeout). Skip the probe entirely by passing null as the store and
short-circuiting isAvailable() in applyFieldUpdates.

This removes ~2s of latency from integration tests that use
--inline-secrets, preventing flaky timeouts against Bun's 5000ms limit.
…entries exist

purgeContextSecrets now checks for resolver expressions first and
returns early if there are none — skipping the OS keychain probe
entirely. The probe (PowerShell on Windows, `security` on macOS) only
runs when there is actually a $(…) expression to delete.

This prevents the Windows CI integration tests from timing out: contexts
created with --inline-secrets have no resolver expressions, so the 5-second
PowerShell probe was occurring for nothing.
@MattDevy
Copy link
Copy Markdown
Contributor Author

N.B. I also fixed a flakey test on bun windows in this PR

@MattDevy MattDevy removed the request for review from ssh-esh April 30, 2026 15:29
Copy link
Copy Markdown
Member

@JoshMock JoshMock left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👏 love it!

@JoshMock JoshMock merged commit bc6e83a into main Apr 30, 2026
18 checks passed
@JoshMock JoshMock deleted the feat/startup-banner branch April 30, 2026 17:19
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants