From 120203d6d2e64ddd2d091db7f617f74b882ca872 Mon Sep 17 00:00:00 2001 From: Marco Pesani Date: Wed, 1 Apr 2026 00:40:37 +0200 Subject: [PATCH] feat: add --no-interactive for headless CI - resolveInteractive(): --no-interactive, CI=true, or non-TTY stdin - Exit early with clear message when OAuth is required but interaction is disabled - Document in README; register flag on Commander root Made-with: Cursor --- README.md | 6 +++++- src/index.ts | 21 +++++++++++++++++++++ 2 files changed, 26 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 947aa8a..efbe320 100644 --- a/README.md +++ b/README.md @@ -22,6 +22,10 @@ On first run, the CLI opens your browser for OAuth authorization. Credentials ar Generate an API key at [bitrefill.com/account/developers](https://www.bitrefill.com/account/developers) and pass it via the `--api-key` option or the `BITREFILL_API_KEY` environment variable. This skips the OAuth flow entirely. +### Non-interactive / CI + +In environments without a TTY (e.g. CI, Docker, scripts), or when `CI=true`, the CLI cannot complete browser-based OAuth. Pass `--no-interactive` to fail fast with a clear message, or use `--api-key` / `BITREFILL_API_KEY` instead. + ```bash # Option bitrefill --api-key YOUR_API_KEY search-products --query "Netflix" @@ -39,7 +43,7 @@ Node does not load `.env` files automatically. After editing `.env`, either expo ## Usage ```bash -bitrefill [--api-key ] [--json] [options] +bitrefill [--api-key ] [--json] [--no-interactive] [options] ``` ### Human-readable output (default) diff --git a/src/index.ts b/src/index.ts index 3ae4df1..82c1bcd 100644 --- a/src/index.ts +++ b/src/index.ts @@ -52,6 +52,13 @@ function resolveJsonMode(): boolean { return process.argv.some((arg) => arg === '--json'); } +function resolveInteractive(): boolean { + if (process.argv.includes('--no-interactive')) return false; + if (process.env.CI === 'true') return false; + if (!process.stdin.isTTY) return false; + return true; +} + function createOutputFormatter(jsonMode: boolean): OutputFormatter { return jsonMode ? createJsonFormatter() : createHumanFormatter(); } @@ -262,6 +269,16 @@ async function main(): Promise { const mcpUrl = resolveMcpUrl(apiKey); const useOAuth = !apiKey && !process.env.MCP_URL; + if (useOAuth && !resolveInteractive()) { + formatter.error( + new Error( + 'Authorization required but running in non-interactive mode.\n' + + 'Use --api-key or set BITREFILL_API_KEY to authenticate without a browser.' + ) + ); + process.exit(1); + } + // Phase 1: connect and discover tools const { client, transport } = await createMcpClient( mcpUrl, @@ -289,6 +306,10 @@ async function main(): Promise { .option( '--json', 'Output raw JSON (TOON decoded); use with jq. Non-result messages go to stderr.' + ) + .option( + '--no-interactive', + 'Disable browser-based auth and interactive prompts (auto-detected in CI / non-TTY)' ); program