From f6d87a0facf3d526628dbcad90a16a0b39482ba7 Mon Sep 17 00:00:00 2001 From: Roman Meerson Date: Wed, 5 Aug 2026 13:20:43 +0200 Subject: [PATCH] feat(analytics): prompt for user email when absent during report generation When no email is stored in config, interactively ask the user to enter it before generating the report and persist it for future runs. Ctrl+C cancels report generation. Non-TTY environments (CI, pipes) skip the prompt silently. --- src/cli/commands/analytics/index.ts | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/src/cli/commands/analytics/index.ts b/src/cli/commands/analytics/index.ts index b02b6054e..8d3d6fca8 100644 --- a/src/cli/commands/analytics/index.ts +++ b/src/cli/commands/analytics/index.ts @@ -4,6 +4,7 @@ import { Command } from 'commander'; import chalk from 'chalk'; +import inquirer from 'inquirer'; import { AnalyticsAggregator } from './aggregator.js'; import { AnalyticsFormatter } from './formatter.js'; import { AnalyticsExporter } from './exporter.js'; @@ -150,6 +151,26 @@ export async function runAnalytics(options: AnalyticsOptions, source: AnalyticsS // omit email gracefully } + if (userEmail === undefined && process.stdout.isTTY) { + console.log(chalk.yellow('\n Warning: your email is not configured. It will be included in the report metadata and saved for future runs.')); + try { + const { email } = await inquirer.prompt<{ email: string }>([{ + type: 'input', + name: 'email', + message: 'Enter your email address:', + validate: (v: string) => /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(v.trim()) || 'Please enter a valid email address', + }]); + userEmail = email.trim(); + await ConfigLoader.saveUserEmail(userEmail).catch(() => { /* non-fatal */ }); + } catch (err) { + if (err instanceof Error && (err.name === 'ExitPromptError' || err.name === 'AbortPromptError')) { + console.log(chalk.dim('\n Report generation cancelled.')); + return; + } + throw err; + } + } + const { index: costIndex, summary } = costResult; const payload = buildPayload(analytics, costIndex, summary, { rangeLabel: options.last ?? (options.from || options.to ? 'custom' : 'all'),