@vigilbase/cli
Version:
Vigilbase CLI — security scanners, agent tools, and Vigilbase One from the terminal.
171 lines (170 loc) • 7.2 kB
TypeScript
/**
* The interactive terminal UI of the one command and `submit`: a step
* timeline on stderr with inputs for the email and the code, an account
* picker, Yes/No questions, spinners and a live collection progress bar.
*
* It is used only on an interactive terminal (`terminalFeatures`: stdin and
* stderr are TTYs, TERM is not dumb, CI is not set, no `--json`); everywhere
* else the commands keep their plain lines and prompts. What it shows follows
* the same rules as the plain output: the masked address from vigilbase.com,
* never the address, the code, the token or the session code once entered;
* account names only through `displayText`; counts, never values, from the
* snapshot.
*
* Every label is built from the session's symbols (`ellipsis`, `join`), so an
* ASCII-only console (the Linux console, legacy Windows consoles) gets no
* character outside ASCII.
*/
import { type Writer } from '../../lib/format.js';
import { Tui, type SignalSource, type TtyInput, type TtyOutput } from '../../lib/tui.js';
import type { CollectProgress } from './collector.js';
import type { EmailVerificationView } from './email-verification.js';
/** Sign-in attempts offered after the first one fails. */
export declare const MAX_SIGN_IN_RETRIES = 2;
export type CollectionPlan = {
zones: number;
plannedReads: number;
maxRequests: number;
};
export type CollectedSummary = {
zones: number;
total: number;
ok: number;
notCollectable: number;
denied: number;
/** Other HTTP errors and reads that could not be collected. */
failed: number;
path: string;
bytes: number;
/** `2 member emails`, `3 DNS names`, … (counts only). */
contents: string[];
inspect: string;
};
export type UploadSummary = {
replayed: boolean;
passed?: number;
warnings?: number;
failed?: number;
notCollected: number;
resultsUrl?: string;
opening: boolean;
requester: string;
};
export type CheckupTuiOptions = {
mode: 'text' | 'json';
stdin: TtyInput;
stderr: TtyOutput;
/** stdout; when it is not a terminal, the result lines are written there too (plain). */
stdout?: {
isTTY?: boolean;
};
env: NodeJS.ProcessEnv;
/** The command's writer, for those plain result lines. */
writer: Writer;
platform?: NodeJS.Platform;
signals?: SignalSource;
exit?: (code: number) => void;
frameMs?: number;
now?: () => number;
};
export declare class CheckupTui {
readonly tui: Tui;
private readonly writer;
private readonly now;
private readonly echoResults;
private constructor();
/** The terminal UI, or null for the plain output. */
static open(options: CheckupTuiOptions): CheckupTui | null;
/** What Ctrl-C prints at this stage. */
set cancelMessage(message: string);
/**
* A writer for the lines the commands print outside the steps below
* (warnings, notes): `Warning: …` becomes `▲`, anything else a `│` line,
* so nothing is written through the live frame.
*/
get lines(): Writer;
intro(): void;
/** `…`, or `...` on an ASCII-only console: for masked IDs and cut text built outside this class. */
get ellipsis(): string;
/** `a · b · c`, or `a - b - c` on an ASCII-only console. */
join(parts: readonly string[]): string;
/** `label…` (`label...` in ASCII): an activity in progress, without its ellipsis. */
private ongoing;
/** `◒ label…` while `work` runs; `label` has no ellipsis. */
busy<T>(label: string, work: () => Promise<T>): Promise<T>;
/** `◒ label…` until stopped; `label` (also in `update`) has no ellipsis. */
spinner(label: string): {
update(label: string): void;
stop(): void;
};
/** `◇ title detail` */
step(title: string, detail?: string): void;
warning(text: string): void;
/** A Yes/No question; the answer stays on screen (`◇ question › Yes`). Ctrl-D answers no. */
confirm(question: string, options: {
initial: boolean;
detail?: string;
}): Promise<boolean>;
/** A question a flag answered: `◇ question › Yes (--yes)`. */
answeredByFlag(question: string, flag: string): void;
/**
* The verification's inputs: the address is shown while it is typed and
* sent, then replaced by the masked address vigilbase.com returns; the code
* is shown while it is typed, then masked (●●●●●●). A notice (a refused
* address, a wrong code) appears inside the next input, which is drawn in
* the same place.
*/
emailView(): EmailVerificationView;
private retryClock;
/**
* cf's interactive sign-in with the terminal handed over (cf prints and
* reads directly); after a failed or cancelled sign-in, "Try signing in
* again?" up to MAX_SIGN_IN_RETRIES times. `check` is whoami afterwards.
*/
signIn<T>(run: () => Promise<number>, check: () => Promise<T>): Promise<T>;
signedIn(summary: string): void;
/** The account picker; `label` is already safe (`displayText`). Null on Ctrl-D. */
chooseAccount(accounts: ReadonlyArray<{
id: string;
label: string;
detail: string;
}>): Promise<string | null>;
/** `◇ Account Acme Corp a1b2…8f90`, the last line before the collection. */
account(label: string, detail: string): void;
/**
* `◒ Collecting ████░░ zones 1/2 · 40/97 reads · ~20 s left`, redrawn on
* every progress event. The estimate is the larger of what the request
* budget allows for the reads left and the pace so far.
*/
collecting(plan: CollectionPlan): {
update(progress: CollectProgress): void;
stop(): void;
};
/** `◇ 84 reads · 72 ok · 10 not collectable · 2 refused`, the saved file and what it contains. */
collected(summary: CollectedSummary): void;
/** `◆ Upload 13 KB to vigilbase.com? › Yes / No`; Enter on the initial state means No. */
confirmUpload(input: {
bytes: number;
where: string;
requester: string;
flag: string | null;
}): Promise<boolean>;
/** The upload and the scoring, which happen in one request. */
uploading<T>(bytes: number, work: () => Promise<T>): Promise<T>;
/**
* `└ ✓ Done. 10 passed · 2 failed`, the results link (underlined, an OSC 8
* link where supported) and the email note. `plainLines` are the plain
* result lines, also written to stdout when stdout is not a terminal.
*/
uploaded(summary: UploadSummary, plainLines: readonly string[]): void;
/** Dim lines under the end of the timeline (the logout hint), then the command on a line of its own. */
footnote(text: string, command?: string): void;
/** Before an upload error is shown: the `submit` command for the saved snapshot, on a line of its own. */
uploadLater(command: string): void;
/**
* `■ message` with the hint under it, for an error that ends the command;
* `runCli` then does not print it again. After Ctrl-C nothing more is shown.
*/
fail(error: unknown): void;
close(): void;
}