mirror of
https://github.com/KeygraphHQ/shannon.git
synced 2026-09-15 06:25:32 +02:00
* refactor(cli): list workspaces natively instead of via the worker image * feat(cli): preflight that Docker is installed and running * feat(cli): stop scans by workspace or --all, terminating their Temporal workflows * fix(worker): abort the running agent on cancellation so Temporal cancel takes effect * refactor(cli): split destructive teardown out of stop into a reset command * refactor(cli): centralise flag parsing and confirmation across commands * fix(cli): pass provider credentials to docker by name to keep secrets out of argv * feat(cli): add per-command help via <command> --help/-h and help <command> * feat(cli): replace raw docker output with clack spinners for infra and scan teardown * fix(cli): verify scan stop by re-querying container and workflow state instead of assuming success * fix(cli): resolve running state before prompting on stop and report no-op stops honestly * refactor(cli): show splash first and drive start with one spinner resolving to a clean line * fix(cli): validate --url up front so a bad value fails cleanly instead of a late crash * refactor(cli): centralize error reporting with fail() for expected errors and a crash handler that logs the stack and links the issue tracker * feat(cli): add --json/--plain machine-readable output to workspaces and status * refactor(cli): remove the workspaces command * refactor(cli): remove the status command * feat(cli): add 'progress <workspace>' — live scan progress from Temporal * fix(cli): mark metric-less agents as skipped in progress, not done * feat(cli): animate running agents in progress with a clack-style spinner * feat(cli): rename progress->status, reveal agents as they run, show live per-agent elapsed * fix(cli): mark passed-over phases as skipped live, not pending * style(cli): rename status footer 'Wall-clock' to 'Time Taken', drop the parenthetical * style(cli): drop '(sum of agents)' from status total cost line * style(cli): green filled circle for completed, Shannon gold for running * style(cli): use Shannon gold in place of green in status * feat(cli): suggest closest command or flag on typo * refactor(cli): single-source start help and drop ./repos bare-name shortcut * feat(cli): name providers and fix in multi-provider credential error * feat(cli): support --flag=value syntax and expand leading ~ in paths * refactor(cli): centralize ANSI color codes in colors.ts * feat(cli): add scans command listing completed scans with cost and duration * fix(cli): keep stdout clean off-TTY for logs and start * feat(cli): add repo link to top-level help * feat(worker): record auth-validation metrics and register resume attempts early * refactor(cli): share resume-aware workflow-id resolution and surface root-cause failures * feat(cli): add status --json, auth phase, dashboard link, and stable live redraw * refactor(cli): drop cost from status and scans output * feat(worker): surface both PDF and markdown report at run root * refactor(cli): normalize error/warning prefixing through fail and warn * feat(cli): add version --json for machine-readable output * refactor(cli): rename start --debug to --keep-container * refactor(cli): point start's progress hint at status instead of the Temporal dashboard * refactor(cli): centralize the mode-aware command prefix * refactor(cli): trim start and logs output to durable facts off-TTY * feat(cli): require typed confirmation for reset instead of --yes reset permanently wipes all Temporal data and volumes — a severe, irreversible action. Replace its default y/N confirm (bypassable with --yes) with a typed-word confirmation that has no bypass, so the wipe can only be triggered by a deliberate interactive answer. * feat(cli): surface logs and status hints after start on a TTY * feat(cli): exit 2 on usage errors, distinct from operational failures * feat(cli): add start --follow to stream logs and exit on scan outcome * refactor(cli): redesign splash with sunset-gradient wordmark and truecolor * refactor(cli): remove the uninstall command * docs: sync CLI docs with removed uninstall/workspaces, new scans and --follow * docs: fix reset confirmation — typed confirm, not --yes/-y * style(cli): restructure status footer with divider, aligned Logs/Temporal rows * feat(cli): show splash in the status command * fix(worker): validate auth-state shape, not entry count * docs: correct reset confirmation and add markdown report to run-root docs
146 lines
5.4 KiB
TypeScript
146 lines
5.4 KiB
TypeScript
/**
|
|
* Per-command help text.
|
|
*
|
|
* `shannon <command> --help`, `shannon <command> -h`, and `shannon help <command>`
|
|
* all render the matching command's usage, so a user can discover a command's
|
|
* flags without scanning the global help. The global help lives in index.ts.
|
|
*/
|
|
|
|
import { commandPrefix, getMode } from './mode.js';
|
|
|
|
interface CommandHelp {
|
|
readonly usage: readonly string[];
|
|
readonly description: string;
|
|
readonly options?: readonly (readonly [string, string])[];
|
|
readonly examples?: readonly string[];
|
|
}
|
|
|
|
const YES_OPTION: readonly [string, string] = [
|
|
'-y, --yes',
|
|
'Skip the confirmation prompt (required for non-interactive use)',
|
|
];
|
|
const HELP_OPTION: readonly [string, string] = ['-h, --help', 'Show this help'];
|
|
|
|
/**
|
|
* `start`'s flags, the single source rendered by both the per-command help here
|
|
* and the global help in index.ts, so the two can never drift.
|
|
*/
|
|
export const START_OPTIONS: readonly (readonly [string, string])[] = [
|
|
['-u, --url <url>', 'Target URL (required)'],
|
|
['-r, --repo <path>', 'Repository path (required)'],
|
|
['-c, --config <path>', 'Configuration file (YAML)'],
|
|
['-o, --output <path>', 'Copy deliverables to this directory after the run'],
|
|
['-w, --workspace <name>', 'Named workspace (auto-resumes if it exists)'],
|
|
['-f, --follow', 'Stream the scan log until it finishes'],
|
|
['--pipeline-testing', 'Use minimal prompts for fast testing'],
|
|
['--keep-container', 'Preserve the worker container after exit for log inspection'],
|
|
];
|
|
|
|
const COMMAND_HELP: Readonly<Record<string, CommandHelp>> = {
|
|
start: {
|
|
usage: ['start -u <url> -r <path> [options]'],
|
|
description: 'Start a pentest scan.',
|
|
examples: [
|
|
'start -u https://example.com -r ./my-repo',
|
|
'start -u https://example.com -r /path/to/repo -c config.yaml -w q1-audit',
|
|
'start -u https://example.com -r ./my-repo --follow',
|
|
],
|
|
},
|
|
stop: {
|
|
usage: ['stop <workspace> [--yes]', 'stop --all [--yes]'],
|
|
description: 'Stop one scan by workspace, or every scan with --all (Temporal stays up).',
|
|
options: [['--all', 'Stop all running scans'], YES_OPTION],
|
|
examples: ['stop q1-audit', 'stop --all'],
|
|
},
|
|
reset: {
|
|
usage: ['reset'],
|
|
description: 'Stop everything and permanently remove all Temporal data and volumes.',
|
|
},
|
|
logs: {
|
|
usage: ['logs <workspace>'],
|
|
description: "Tail a scan's live log until it completes.",
|
|
examples: ['logs q1-audit'],
|
|
},
|
|
status: {
|
|
usage: ['status <workspace> [--json]'],
|
|
description:
|
|
"Show one scan's phase-by-phase progress, read live from Temporal. Watches and redraws until the scan finishes on a terminal; prints one frame when piped or already finished. With --json, prints a single machine-readable snapshot and exits.",
|
|
options: [['--json', 'Output a point-in-time snapshot as JSON, then exit']],
|
|
examples: ['status q1-audit', 'status q1-audit --json'],
|
|
},
|
|
scans: {
|
|
usage: ['scans [--json]'],
|
|
description: 'List completed scans and where each report lives.',
|
|
options: [['--json', 'Output the scan list as JSON']],
|
|
examples: ['scans', 'scans --json'],
|
|
},
|
|
build: {
|
|
usage: ['build [--no-cache]'],
|
|
description: 'Build the worker Docker image (local mode only).',
|
|
options: [['--no-cache', 'Build without using the Docker layer cache']],
|
|
},
|
|
setup: {
|
|
usage: ['setup'],
|
|
description: 'Configure provider credentials interactively (npx mode only).',
|
|
},
|
|
version: {
|
|
usage: ['version [--json]'],
|
|
description: 'Show the version. With --json, prints the version and mode as a machine-readable object.',
|
|
options: [['--json', 'Output the version and mode as JSON']],
|
|
examples: ['version', 'version --json'],
|
|
},
|
|
};
|
|
|
|
/** Commands that only exist in one mode; everything else is available in both. */
|
|
const MODE_ONLY: Readonly<Record<string, 'local' | 'npx'>> = {
|
|
build: 'local',
|
|
setup: 'npx',
|
|
};
|
|
|
|
/** Whether a command has its own help page (and so responds to `--help`/`-h`). */
|
|
export function isHelpableCommand(command: string): boolean {
|
|
return command in COMMAND_HELP;
|
|
}
|
|
|
|
/**
|
|
* User-facing command names available in the current mode, for "did you mean?"
|
|
* suggestions. Derived from the same table that backs per-command help, so the
|
|
* suggestion set can never drift from the commands that actually exist.
|
|
*/
|
|
export function availableCommands(): readonly string[] {
|
|
const mode = getMode();
|
|
const commands = Object.keys(COMMAND_HELP).filter((command) => (MODE_ONLY[command] ?? mode) === mode);
|
|
return [...commands, 'help'];
|
|
}
|
|
|
|
/** Print the help page for one command. No-op if the command has no page. */
|
|
export function printCommandHelp(command: string): void {
|
|
const help = COMMAND_HELP[command];
|
|
if (!help) return;
|
|
|
|
const prefix = commandPrefix();
|
|
const baseOptions = command === 'start' ? START_OPTIONS : (help.options ?? []);
|
|
const options = [...baseOptions, HELP_OPTION];
|
|
const flagWidth = Math.max(...options.map(([flag]) => flag.length));
|
|
|
|
const lines: string[] = ['', help.description, '', 'USAGE'];
|
|
for (const line of help.usage) {
|
|
lines.push(` ${prefix} ${line}`);
|
|
}
|
|
|
|
lines.push('', 'OPTIONS');
|
|
for (const [flag, desc] of options) {
|
|
lines.push(` ${flag.padEnd(flagWidth)} ${desc}`);
|
|
}
|
|
|
|
if (help.examples && help.examples.length > 0) {
|
|
lines.push('', 'EXAMPLES');
|
|
for (const example of help.examples) {
|
|
lines.push(` ${prefix} ${example}`);
|
|
}
|
|
}
|
|
|
|
lines.push('');
|
|
console.log(lines.join('\n'));
|
|
}
|