UNPKG

@vigilbase/cli

Version:

Vigilbase CLI — security scanners, agent tools, and Vigilbase One from the terminal.

89 lines (88 loc) • 3.51 kB
/** * Terminal formatting for local checkups. Progress shows counts only, never * account or zone names, and is written to stderr. Every other line goes * through the CLI writer, which strips terminal controls from untrusted text. */ import { sanitizeTerminal } from '../../lib/format.js'; const SPINNER = ['⠋', '⠙', '⠹', '⠸', '⠼', '⠴', '⠦', '⠧', '⠇', '⠏']; /** * Untrusted single-line text (account names from cf): controls and line * breaks collapsed, length bounded. The terminal UI passes its `ellipsis` * (`...` on an ASCII-only console). */ export function displayText(value, max, ellipsis = '…') { const flat = sanitizeTerminal(value.replace(/[\s\u0085\u2028\u2029]+/g, ' ')).trim(); return flat.length > max ? `${flat.slice(0, Math.max(0, max - ellipsis.length))}${ellipsis}` : flat; } /** `0123…cdef` (`0123...cdef` with the ASCII `ellipsis` of the terminal UI). */ export function maskId(id, ellipsis = '…') { return id.length > 8 ? `${id.slice(0, 4)}${ellipsis}${id.slice(-4)}` : id; } /** `9f2c…a71e` */ export function shortSha(sha256) { return `${sha256.slice(0, 4)}…${sha256.slice(-4)}`; } export function formatBytes(bytes) { if (bytes < 1024) return `${bytes} B`; if (bytes < 1024 * 1024) return `${Math.max(1, Math.round(bytes / 1024))} KB`; return `${(bytes / 1024 / 1024).toFixed(1)} MB`; } /** Local `HH:MM` for an ISO timestamp. */ export function formatClock(iso) { const date = new Date(iso); if (Number.isNaN(date.getTime())) return iso; return `${String(date.getHours()).padStart(2, '0')}:${String(date.getMinutes()).padStart(2, '0')}`; } export function formatDuration(seconds) { if (seconds < 90) return `~${Math.max(1, Math.round(seconds))} s`; return `~${Math.round(seconds / 60)} min`; } /** `3 reads denied (HTTP 403) · 60 reads not collectable with cf` */ export function readsSummary(counts, denied) { const parts = []; if (denied > 0) parts.push(`${denied} ${denied === 1 ? 'read' : 'reads'} denied (HTTP 403)`); const otherHttp = counts.httpError - denied; if (otherHttp > 0) parts.push(`${otherHttp} ${otherHttp === 1 ? 'read' : 'reads'} failed with another HTTP error`); if (counts.collectionFailed > 0) parts.push(`${counts.collectionFailed} ${counts.collectionFailed === 1 ? 'read' : 'reads'} could not be collected`); if (counts.notCollected > 0) parts.push(`${counts.notCollected} reads not collectable with cf`); return parts.join(' · '); } /** A single updating stderr line, or nothing when progress is disabled. */ export function startCountsProgress(enabled, stream = process.stderr) { if (!enabled) return { update: () => undefined, stop: () => undefined }; let text = ''; let frame = 0; let closed = false; let width = 0; const draw = () => { if (closed) return; const line = `${SPINNER[frame++ % SPINNER.length]} ${sanitizeTerminal(text).replace(/[\n\t]/g, ' ')}`; width = Math.max(width, line.length); stream.write(`\r${line.padEnd(width)}`); }; const timer = setInterval(draw, 80); timer.unref?.(); return { update(next) { text = next; draw(); }, stop() { if (closed) return; closed = true; clearInterval(timer); stream.write(`\r${' '.repeat(width)}\r`); }, }; }