Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
38 changes: 22 additions & 16 deletions src/cli.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,19 +41,26 @@ const MENU_GROUPS: MenuGroup[] = [
{
heading: "",
items: [
{ command: ["scan"], label: "scan", outcome: "Check all your MCP servers", recommended: true },
{ command: ["scan", "deep"], label: "scan deep", outcome: "^ plus invoke tools to verify they actually work" },
{ command: ["suggest"], label: "suggest", outcome: "Discover MCP servers for your stack" },
{ command: ["score"], label: "score", outcome: "Health score (0-100) for a specific server" },
{ command: ["test"], label: "test <cmd>", outcome: "Test a specific MCP server", recommended: true },
{ command: ["scan"], label: "scan", outcome: "Check all your configured MCP servers" },
{ command: ["scan", "deep"], label: "scan deep", outcome: "^ plus invoke tools to verify they work" },
],
},
{
heading: "CI / Regression Testing",
items: [
{ command: ["watch"], label: "watch", outcome: "Run a check, diff against previous, alert on regressions" },
{ command: ["record"], label: "record", outcome: "Capture a session for offline replay or CI" },
{ command: ["diff"], label: "diff", outcome: "Compare two runs for regressions" },
{ command: ["test"], label: "test", outcome: "Test a single server by command" },
{ command: ["watch"], label: "watch", outcome: "Run check, diff against previous, alert regressions" },
{ command: ["lock"], label: "lock", outcome: "Snapshot MCP server schemas into a lock file" },
{ command: ["lock", "verify"], label: "lock verify", outcome: "Verify servers match the lock file" },
{ command: ["diff"], label: "diff", outcome: "Compare two run artifacts for regressions" },
{ command: ["history"], label: "history", outcome: "Show health score trends over time" },
],
},
{
heading: "Scoring & Badges",
items: [
{ command: ["score"], label: "score", outcome: "Health score (0-100) for a specific server" },
{ command: ["badge"], label: "badge", outcome: "Generate an SVG health badge for README" },
],
},
];
Expand Down Expand Up @@ -219,17 +226,16 @@ async function main(): Promise<void> {
"",
` ${c(ANSI.bold, "Quick Start")}`,
"",
` ${c(ANSI.dim, "$")} ${c(ANSI.cyan, `${bin} scan`)} Check all your MCP servers`,
` ${c(ANSI.dim, "$")} ${c(ANSI.cyan, `${bin} scan deep`)} ^ plus invoke tools to verify they work`,
` ${c(ANSI.dim, "$")} ${c(ANSI.cyan, `${bin} suggest`)} Discover MCP servers for your stack`,
` ${c(ANSI.dim, "$")} ${c(ANSI.cyan, `${bin} score`)} ${c(ANSI.dim, "<cmd>")} Health score (0-100) for any server`,
` ${c(ANSI.dim, "$")} ${c(ANSI.cyan, `${bin} test`)} ${c(ANSI.dim, "<cmd>")} Test a specific MCP server`,
` ${c(ANSI.dim, "$")} ${c(ANSI.cyan, `${bin} scan`)} Check all your configured servers`,
` ${c(ANSI.dim, "$")} ${c(ANSI.cyan, `${bin} scan deep`)} ^ plus invoke tools to verify they work`,
"",
` ${c(ANSI.bold, "CI / Regression Testing")}`,
"",
` ${c(ANSI.dim, "$")} ${c(ANSI.cyan, `${bin} watch`)} ${c(ANSI.dim, "<cmd>")} Run check, diff against previous, alert regressions`,
` ${c(ANSI.dim, "$")} ${c(ANSI.cyan, `${bin} record`)} ${c(ANSI.dim, "<cmd>")} Capture a session for offline replay`,
` ${c(ANSI.dim, "$")} ${c(ANSI.cyan, `${bin} diff`)} ${c(ANSI.dim, "<a> <b>")} Compare two runs for regressions`,
` ${c(ANSI.dim, "$")} ${c(ANSI.cyan, `${bin} badge`)} ${c(ANSI.dim, "<cmd>")} Generate a health badge for README`,
` ${c(ANSI.dim, "$")} ${c(ANSI.cyan, `${bin} watch`)} ${c(ANSI.dim, "<cmd>")} Diff against previous run, alert regressions`,
` ${c(ANSI.dim, "$")} ${c(ANSI.cyan, `${bin} lock`)} Snapshot server schemas (like package-lock)`,
` ${c(ANSI.dim, "$")} ${c(ANSI.cyan, `${bin} lock verify`)} Verify no schema drift since last lock`,
` ${c(ANSI.dim, "$")} ${c(ANSI.cyan, `${bin} diff`)} ${c(ANSI.dim, "<a> <b>")} Compare two runs for regressions`,
"",
` ${c(ANSI.dim, `Run ${bin} <command> --help for details on any command.`)}`,
"",
Expand Down
4 changes: 2 additions & 2 deletions src/commands/record-replay.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ export function registerRecordReplayCommands(program: Command, bin: string): voi
// ── record ─────────────────────────────────────────────────────────────

program
.command("record")
.command("record", { hidden: true })
.passThroughOptions()
.description("Record a server session to a cassette file for replay.")
.argument("[command...]", "Server command and arguments to run.")
Expand Down Expand Up @@ -71,7 +71,7 @@ export function registerRecordReplayCommands(program: Command, bin: string): voi
// ── replay ─────────────────────────────────────────────────────────────

program
.command("replay")
.command("replay", { hidden: true })
.description("Replay a cassette file offline — no live server needed.")
.argument("<cassette>", "Path to a cassette JSON file.")
.option("--no-color", "Disable colored output.")
Expand Down
16 changes: 16 additions & 0 deletions src/reporters/terminal.ts
Original file line number Diff line number Diff line change
Expand Up @@ -199,6 +199,22 @@ function renderRunTerminal(artifact: RunArtifact): string {
lines.push(formatCheck(check));
}

// Show security-lite findings prominently even without --security flag
const secLite = artifact.checks.find(ch => ch.id === "security-lite");
if (secLite && secLite.status !== "pass" && secLite.evidence.length > 0) {
const diagnostics = secLite.evidence[0]?.diagnostics ?? [];
if (diagnostics.length > 0) {
lines.push("");
lines.push(co(ANSI.red, " Security:"));
for (const d of diagnostics.slice(0, 3)) {
lines.push(` ${co(ANSI.dim, "→")} ${d}`);
}
if (diagnostics.length > 3) {
lines.push(` ${co(ANSI.dim, ` ...and ${diagnostics.length - 3} more (run with --security for full scan)`)}`);
}
}
}

return lines.join("\n");
}

Expand Down
Loading