From 06ddafce1cd1a48d302ace7e26520da5ba2147b1 Mon Sep 17 00:00:00 2001 From: Artur Do Lago Date: Tue, 13 Jan 2026 13:42:25 +0100 Subject: [PATCH] fix: align diagnostics cli with yargs --- packages/agent-core/src/cli/cmd/bug-report.ts | 148 ++++++++----- packages/agent-core/src/cli/cmd/check.ts | 203 ++++++++++++------ .../src/diagnostics/check-engine.ts | 2 +- .../src/diagnostics/checks/config.ts | 2 +- .../src/diagnostics/checks/integrity.ts | 2 +- .../src/diagnostics/checks/providers.ts | 2 +- .../src/diagnostics/checks/runtime.ts | 2 +- .../src/diagnostics/reporters/interactive.ts | 2 +- .../src/diagnostics/reporters/json.ts | 2 +- .../src/diagnostics/reporters/minimal.ts | 2 +- packages/agent-core/src/index.ts | 2 + packages/agent-core/src/logging/context.ts | 2 +- 12 files changed, 234 insertions(+), 137 deletions(-) diff --git a/packages/agent-core/src/cli/cmd/bug-report.ts b/packages/agent-core/src/cli/cmd/bug-report.ts index c0b09f1a3ec..6dcd6427beb 100644 --- a/packages/agent-core/src/cli/cmd/bug-report.ts +++ b/packages/agent-core/src/cli/cmd/bug-report.ts @@ -3,70 +3,104 @@ * @description CLI entry point for crash report generation */ -import { Command } from "commander"; +import type { Argv } from "yargs"; import * as readline from "readline"; import { ReportGenerator } from "../../crash-report/report-generator"; +import { cmd } from "./cmd"; -export function createBugReportCommand(): Command { - return new Command("bug-report") - .alias("report") - .description("Generate a crash report for debugging") - .option("--include-session", "Include session conversation data (requires consent)", false) - .option("--log-lines ", "Number of log lines to include", "500") - .option("-o, --output ", "Output path for the report archive") - .option("--skip-diagnostics", "Skip running health checks", false) - .option( - "--anonymization ", - "Anonymization level: minimal, standard, aggressive", - "standard" - ) - .option("-y, --non-interactive", "Skip interactive prompts", false) - .action(async (options) => { - try { - // Validate anonymization level - const validLevels = ["minimal", "standard", "aggressive"]; - if (!validLevels.includes(options.anonymization)) { - console.error(`Invalid anonymization level. Choose: ${validLevels.join(", ")}`); - process.exit(2); - } +const ANONYMIZATION_LEVELS = ["minimal", "standard", "aggressive"] as const; - // Session consent prompt - let includeSession = options.includeSession; - if (options.includeSession && !options.nonInteractive) { - includeSession = await promptConsent( - "Include session data? This may contain conversation content. (y/N): " - ); - } +type BugReportArgs = { + includeSession?: boolean; + logLines?: number; + output?: string; + skipDiagnostics?: boolean; + anonymization?: (typeof ANONYMIZATION_LEVELS)[number]; + nonInteractive?: boolean; +}; - // Generate report - const generator = new ReportGenerator({ - includeSession, - logLines: parseInt(options.logLines, 10), - outputPath: options.output, - skipDiagnostics: options.skipDiagnostics, - anonymization: options.anonymization as "minimal" | "standard" | "aggressive", - nonInteractive: options.nonInteractive, - }); - - const { archivePath } = await generator.generate(); - - // Success message - console.log("\nšŸ“Ž Next steps:"); - console.log(" 1. Review the report contents for any remaining sensitive data"); - console.log(" 2. Create a GitHub issue at https://github.com/your-org/agent-core/issues"); - console.log(" 3. Attach the report archive to the issue"); - console.log(`\n Archive: ${archivePath}\n`); - - process.exit(0); - } catch (error) { - console.error( - "Report generation failed:", - error instanceof Error ? error.message : String(error) - ); +export const BugReportCommand = cmd({ + command: "bug-report", + aliases: ["report"], + describe: "Generate a crash report for debugging", + builder: (yargs: Argv) => { + return yargs + .option("include-session", { + type: "boolean", + default: false, + describe: "Include session conversation data (requires consent)", + }) + .option("log-lines", { + type: "number", + default: 500, + describe: "Number of log lines to include", + }) + .option("output", { + alias: "o", + type: "string", + describe: "Output path for the report archive", + }) + .option("skip-diagnostics", { + type: "boolean", + default: false, + describe: "Skip running health checks", + }) + .option("anonymization", { + type: "string", + choices: ANONYMIZATION_LEVELS, + default: "standard", + describe: "Anonymization level: minimal, standard, aggressive", + }) + .option("non-interactive", { + alias: "y", + type: "boolean", + default: false, + describe: "Skip interactive prompts", + }); + }, + handler: async (args) => { + try { + const typedArgs = args as BugReportArgs; + const anonymization = typedArgs.anonymization ?? "standard"; + if (!ANONYMIZATION_LEVELS.includes(anonymization)) { + console.error(`Invalid anonymization level. Choose: ${ANONYMIZATION_LEVELS.join(", ")}`); process.exit(2); } - }); -} + + let includeSession = typedArgs.includeSession ?? false; + if (includeSession && !typedArgs.nonInteractive) { + includeSession = await promptConsent( + "Include session data? This may contain conversation content. (y/N): " + ); + } + + const generator = new ReportGenerator({ + includeSession, + logLines: typedArgs.logLines ?? 500, + outputPath: typedArgs.output, + skipDiagnostics: typedArgs.skipDiagnostics ?? false, + anonymization, + nonInteractive: typedArgs.nonInteractive ?? false, + }); + + const { archivePath } = await generator.generate(); + + console.log("\nšŸ“Ž Next steps:"); + console.log(" 1. Review the report contents for any remaining sensitive data"); + console.log(" 2. Create a GitHub issue at https://github.com/your-org/agent-core/issues"); + console.log(" 3. Attach the report archive to the issue"); + console.log(`\n Archive: ${archivePath}\n`); + + process.exit(0); + } catch (error) { + console.error( + "Report generation failed:", + error instanceof Error ? error.message : String(error) + ); + process.exit(2); + } + }, +}); async function promptConsent(question: string): Promise { const rl = readline.createInterface({ diff --git a/packages/agent-core/src/cli/cmd/check.ts b/packages/agent-core/src/cli/cmd/check.ts index c98fcb545c9..260a0817007 100644 --- a/packages/agent-core/src/cli/cmd/check.ts +++ b/packages/agent-core/src/cli/cmd/check.ts @@ -3,87 +3,148 @@ * @description CLI entry point for health checks */ -import { Command } from "commander"; +import type { Argv } from "yargs"; import { CheckEngine } from "../../diagnostics/check-engine"; import { InteractiveReporter } from "../../diagnostics/reporters/interactive"; import { JsonReporter } from "../../diagnostics/reporters/json"; import { MinimalReporter } from "../../diagnostics/reporters/minimal"; import type { CheckCategory } from "../../diagnostics/types"; +import { cmd } from "./cmd"; -export function createCheckCommand(): Command { - return new Command("check") - .description("Run diagnostic health checks") - .option("--full", "Run extended checks (slower but more thorough)", false) - .option("--fix", "Attempt to auto-fix detected issues", false) - .option("--json", "Output in JSON format for scripting", false) - .option("--minimal", "Single-line output for scripts", false) - .option("-v, --verbose", "Show detailed output including check details", false) - .option("--timeout ", "Timeout per category in milliseconds", "10000") - .option( - "--category ", - "Run only specific categories (runtime, config, providers, integrity)" - ) - .option("--skip ", "Skip specific check IDs") - .option("--no-color", "Disable colored output") - .action(async (options) => { - try { - // Parse categories - let categories: CheckCategory[] | undefined; - if (options.category) { - const validCategories = ["runtime", "config", "providers", "integrity"]; - categories = options.category.filter((c: string) => - validCategories.includes(c) - ) as CheckCategory[]; +const VALID_CATEGORIES: CheckCategory[] = ["runtime", "config", "providers", "integrity"]; - if (categories.length === 0) { - console.error( - `Invalid categories. Valid options: ${validCategories.join(", ")}` - ); - process.exit(2); - } - } +type CheckArgs = { + full?: boolean; + fix?: boolean; + json?: boolean; + minimal?: boolean; + verbose?: boolean; + timeout?: number; + category?: string[]; + skip?: string[]; + color?: boolean; +}; - // Create engine with options - const engine = new CheckEngine({ - full: options.full, - fix: options.fix, - verbose: options.verbose, - timeout: parseInt(options.timeout, 10), - categories, - skip: options.skip, - }); - - // Run checks - const report = await engine.runAll(); - - // Output based on format - if (options.json) { - const reporter = new JsonReporter(); - console.log(reporter.format(report)); - } else if (options.minimal) { - const reporter = new MinimalReporter(); - console.log(reporter.format(report)); - } else { - const reporter = new InteractiveReporter({ - verbose: options.verbose, - colors: options.color !== false, - }); - console.log(reporter.format(report)); - } - - // Exit with appropriate code - if (report.summary.failed > 0) { - process.exit(1); - } - process.exit(0); - } catch (error) { - console.error( - "Health check failed:", - error instanceof Error ? error.message : String(error) +export const CheckCommand = cmd({ + command: "check", + describe: "Run diagnostic health checks", + builder: (yargs: Argv) => { + return yargs + .option("full", { + type: "boolean", + default: false, + describe: "Run extended checks (slower but more thorough)", + }) + .option("fix", { + type: "boolean", + default: false, + describe: "Attempt to auto-fix detected issues", + }) + .option("json", { + type: "boolean", + default: false, + describe: "Output in JSON format for scripting", + }) + .option("minimal", { + type: "boolean", + default: false, + describe: "Single-line output for scripts", + }) + .option("verbose", { + alias: "v", + type: "boolean", + default: false, + describe: "Show detailed output including check details", + }) + .option("timeout", { + type: "number", + default: 10000, + describe: "Timeout per category in milliseconds", + }) + .option("category", { + type: "string", + array: true, + choices: VALID_CATEGORIES, + describe: "Run only specific categories (runtime, config, providers, integrity)", + }) + .option("skip", { + type: "string", + array: true, + describe: "Skip specific check IDs", + }) + .option("color", { + type: "boolean", + default: true, + describe: "Enable colored output", + }); + }, + handler: async (args: CheckArgs) => { + try { + let categories: CheckCategory[] | undefined; + if (args.category?.length) { + categories = args.category.filter((c): c is CheckCategory => + VALID_CATEGORIES.includes(c as CheckCategory) ); - process.exit(2); + if (categories.length === 0) { + console.error(`Invalid categories. Valid options: ${VALID_CATEGORIES.join(", ")}`); + process.exit(2); + } } - }); + + const engine = new CheckEngine({ + full: Boolean(args.full), + fix: Boolean(args.fix), + verbose: Boolean(args.verbose), + timeout: args.timeout ?? 10000, + categories, + skip: args.skip, + }); + + const report = await engine.runAll(); + + if (args.json) { + const reporter = new JsonReporter(); + console.log(reporter.format(report)); + } else if (args.minimal) { + const reporter = new MinimalReporter(); + console.log(reporter.format(report)); + } else { + const reporter = new InteractiveReporter({ + verbose: args.verbose, + colors: args.color !== false, + }); + console.log(reporter.format(report)); + } + + if (report.summary.failed > 0) { + process.exit(1); + } + process.exit(0); + } catch (error) { + console.error( + "Health check failed:", + error instanceof Error ? error.message : String(error) + ); + process.exit(2); + } + }, +}); + +export async function checkEnvironment(): Promise { + const engine = new CheckEngine({ + full: false, + fix: false, + verbose: false, + timeout: 5000, + categories: ["runtime", "config"], + }); + + const report = await engine.runAll(); + if (report.summary.failed > 0) { + const reporter = new MinimalReporter(); + console.error(reporter.format(report)); + throw new Error("Environment checks failed"); + } } // Help examples diff --git a/packages/agent-core/src/diagnostics/check-engine.ts b/packages/agent-core/src/diagnostics/check-engine.ts index f668e7e792e..17cdef0cccd 100644 --- a/packages/agent-core/src/diagnostics/check-engine.ts +++ b/packages/agent-core/src/diagnostics/check-engine.ts @@ -4,7 +4,7 @@ */ import * as os from "os"; -import { +import type { CheckResult, CheckOptions, CheckReport, diff --git a/packages/agent-core/src/diagnostics/checks/config.ts b/packages/agent-core/src/diagnostics/checks/config.ts index b20d7c2fc07..42033f5f95e 100644 --- a/packages/agent-core/src/diagnostics/checks/config.ts +++ b/packages/agent-core/src/diagnostics/checks/config.ts @@ -6,7 +6,7 @@ import * as fs from "fs/promises"; import * as path from "path"; import * as os from "os"; -import { CheckResult, CheckOptions } from "../types"; +import type { CheckResult, CheckOptions } from "../types"; /** Deprecated configuration options that should be migrated */ const DEPRECATED_OPTIONS = [ diff --git a/packages/agent-core/src/diagnostics/checks/integrity.ts b/packages/agent-core/src/diagnostics/checks/integrity.ts index 5dcafb30168..180512a25cb 100644 --- a/packages/agent-core/src/diagnostics/checks/integrity.ts +++ b/packages/agent-core/src/diagnostics/checks/integrity.ts @@ -6,7 +6,7 @@ import * as fs from "fs/promises"; import * as path from "path"; import * as os from "os"; -import { CheckResult, CheckOptions } from "../types"; +import type { CheckResult, CheckOptions } from "../types"; const STALE_THRESHOLD_MS = 30 * 60 * 1000; // 30 minutes diff --git a/packages/agent-core/src/diagnostics/checks/providers.ts b/packages/agent-core/src/diagnostics/checks/providers.ts index 0312b912377..ea8ff38c40c 100644 --- a/packages/agent-core/src/diagnostics/checks/providers.ts +++ b/packages/agent-core/src/diagnostics/checks/providers.ts @@ -3,7 +3,7 @@ * @description Network and AI provider connectivity checks */ -import { CheckResult, CheckOptions } from "../types"; +import type { CheckResult, CheckOptions } from "../types"; interface ProviderConfig { name: string; diff --git a/packages/agent-core/src/diagnostics/checks/runtime.ts b/packages/agent-core/src/diagnostics/checks/runtime.ts index 441f354581c..1c842f2ddd6 100644 --- a/packages/agent-core/src/diagnostics/checks/runtime.ts +++ b/packages/agent-core/src/diagnostics/checks/runtime.ts @@ -7,7 +7,7 @@ import { execSync } from "child_process"; import * as fs from "fs/promises"; import * as path from "path"; import * as os from "os"; -import { CheckResult, CheckOptions } from "../types"; +import type { CheckResult, CheckOptions } from "../types"; /** Minimum required Bun version */ const MIN_BUN_VERSION = "1.0.0"; diff --git a/packages/agent-core/src/diagnostics/reporters/interactive.ts b/packages/agent-core/src/diagnostics/reporters/interactive.ts index 0940f0e0b1d..3f00c0236c7 100644 --- a/packages/agent-core/src/diagnostics/reporters/interactive.ts +++ b/packages/agent-core/src/diagnostics/reporters/interactive.ts @@ -3,7 +3,7 @@ * @description TTY-friendly colored output for health check results */ -import { CheckReport, CheckResult, CheckCategory } from "../types"; +import type { CheckReport, CheckResult, CheckCategory } from "../types"; // ANSI color codes const colors = { diff --git a/packages/agent-core/src/diagnostics/reporters/json.ts b/packages/agent-core/src/diagnostics/reporters/json.ts index 94514124e40..e26a76ad5e0 100644 --- a/packages/agent-core/src/diagnostics/reporters/json.ts +++ b/packages/agent-core/src/diagnostics/reporters/json.ts @@ -3,7 +3,7 @@ * @description Machine-readable JSON output for CI/scripting */ -import { CheckReport } from "../types"; +import type { CheckReport } from "../types"; export class JsonReporter { private pretty: boolean; diff --git a/packages/agent-core/src/diagnostics/reporters/minimal.ts b/packages/agent-core/src/diagnostics/reporters/minimal.ts index a015173d97b..1af054d9b76 100644 --- a/packages/agent-core/src/diagnostics/reporters/minimal.ts +++ b/packages/agent-core/src/diagnostics/reporters/minimal.ts @@ -3,7 +3,7 @@ * @description Single-line output for scripts and CI */ -import { CheckReport } from "../types"; +import type { CheckReport } from "../types"; export class MinimalReporter { format(report: CheckReport): string { diff --git a/packages/agent-core/src/index.ts b/packages/agent-core/src/index.ts index 1d0eab69cf1..bd0c75e0cb6 100644 --- a/packages/agent-core/src/index.ts +++ b/packages/agent-core/src/index.ts @@ -30,6 +30,7 @@ import { DaemonCommand, DaemonStatusCommand, DaemonStopCommand } from "./cli/cmd import { PluginCommand } from "./cli/cmd/plugin" import { SetupCommand } from "./cli/cmd/setup" import { BugReportCommand } from "./cli/cmd/bug-report" +import { CheckCommand } from "./cli/cmd/check" process.on("unhandledRejection", (e) => { Log.Default.error("rejection", { @@ -87,6 +88,7 @@ const cli = yargs(hideBin(process.argv)) .command(TuiThreadCommand) .command(AttachCommand) .command(RunCommand) + .command(CheckCommand) .command(GenerateCommand) .command(DebugCommand) .command(AuthCommand) diff --git a/packages/agent-core/src/logging/context.ts b/packages/agent-core/src/logging/context.ts index c1d399efc0a..94ef4608893 100644 --- a/packages/agent-core/src/logging/context.ts +++ b/packages/agent-core/src/logging/context.ts @@ -78,6 +78,6 @@ export function setContextValue( ): void { const current = asyncLocalStorage.getStore(); if (current) { - (current as Record)[key] = value; + current[key] = value; } }