mirror of
https://github.com/anomalyco/opencode.git
synced 2026-07-24 04:01:10 -04:00
fix: align diagnostics cli with yargs
This commit is contained in:
@@ -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 <count>", "Number of log lines to include", "500")
|
||||
.option("-o, --output <path>", "Output path for the report archive")
|
||||
.option("--skip-diagnostics", "Skip running health checks", false)
|
||||
.option(
|
||||
"--anonymization <level>",
|
||||
"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<boolean> {
|
||||
const rl = readline.createInterface({
|
||||
|
||||
@@ -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 <ms>", "Timeout per category in milliseconds", "10000")
|
||||
.option(
|
||||
"--category <names...>",
|
||||
"Run only specific categories (runtime, config, providers, integrity)"
|
||||
)
|
||||
.option("--skip <ids...>", "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<void> {
|
||||
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
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
*/
|
||||
|
||||
import * as os from "os";
|
||||
import {
|
||||
import type {
|
||||
CheckResult,
|
||||
CheckOptions,
|
||||
CheckReport,
|
||||
|
||||
@@ -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 = [
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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";
|
||||
|
||||
@@ -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 = {
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -78,6 +78,6 @@ export function setContextValue<K extends keyof LogContext>(
|
||||
): void {
|
||||
const current = asyncLocalStorage.getStore();
|
||||
if (current) {
|
||||
(current as Record<string, unknown>)[key] = value;
|
||||
current[key] = value;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user