Files
OmniRoute/open-sse/services/compression/engines/rtk/commandDetector.ts
Diego Rodrigues de Sa e Souza 3c9883bb73 Release v3.8.29 (#4126)
OmniRoute v3.8.29 — 115 commits since v3.8.28. Full CHANGELOG + 41 i18n mirrors. All content quality gates green (build, unit 8/8, vitest 188/188, PR test policy, quality gates extended, docs sync, quality ratchet). Remaining red CI checks are pre-existing release flakes (coverage-shard/integration/node-compat teardown), a new transitive undici advisory in electron devDeps, and a workflow-level CodeQL fail (0 open alerts). VPS-validated by the operator.
2026-06-19 06:49:01 -03:00

465 lines
12 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
export interface CommandDetectionResult {
type: string;
command: string | null;
confidence: number;
category:
| "git"
| "test"
| "build"
| "shell"
| "docker"
| "package"
| "infra"
| "cloud"
| "generic";
matchedPatterns: string[];
}
type Detector = {
type: string;
category: CommandDetectionResult["category"];
commandPatterns: RegExp[];
contentPatterns: RegExp[];
};
const COMMAND_PREFIXES = [
"git",
"make",
"terraform",
"tofu",
"opentofu",
"systemctl",
"npm",
"pnpm",
"yarn",
"vitest",
"jest",
"pytest",
"python",
"go",
"cargo",
"tsc",
"eslint",
"webpack",
"vite",
"biome",
"prettier",
"turbo",
"nx",
"playwright",
"ruff",
"mypy",
"pip",
"uv",
"poetry",
"golangci-lint",
"bundle",
"rubocop",
"kubectl",
"composer",
"gh",
"docker",
"aws",
"gcloud",
"ssh",
"rsync",
"curl",
"wget",
"ls",
"find",
"grep",
"rg",
"ag",
"ps",
"df",
"du",
];
const COMMAND_PREFIX_PATTERN = new RegExp(`^(?:${COMMAND_PREFIXES.join("|")})\\b`);
const DETECTORS: Detector[] = [
{
type: "git-status",
category: "git",
commandPatterns: [/^git\s+status\b/i],
contentPatterns: [
/^On branch /m,
/^Changes (?:not staged|to be committed)/m,
/^Untracked files:/m,
],
},
{
type: "git-branch",
category: "git",
commandPatterns: [/^git\s+branch\b/i, /^git\s+checkout\b/i, /^git\s+switch\b/i],
contentPatterns: [/^\*\s+\S+/m, /Switched to (?:a new )?branch/i, /Already on ['"][^'"]+['"]/i],
},
{
type: "git-diff",
category: "git",
commandPatterns: [/^git\s+diff\b/i, /^git\s+show\b/i],
contentPatterns: [/^diff --git /m, /^@@\s+-\d+,\d+\s+\+\d+,\d+\s+@@/m],
},
{
type: "git-log",
category: "git",
commandPatterns: [/^git\s+log\b/i],
contentPatterns: [/^commit [0-9a-f]{7,40}/m, /^Author: /m],
},
{
type: "make",
category: "build",
commandPatterns: [/^make\b/i],
contentPatterns: [/^make\[\d+\]: (?:Entering|Leaving) directory/m, /make: \*\*\* /],
},
{
type: "terraform-plan",
category: "infra",
commandPatterns: [/^terraform\s+plan\b/i],
contentPatterns: [/Terraform will perform the following actions:/, /Plan: \d+ to add/i],
},
{
type: "tofu-plan",
category: "infra",
commandPatterns: [/^(?:tofu|opentofu)\s+plan\b/i],
contentPatterns: [/OpenTofu will perform the following actions:/, /Plan: \d+ to add/i],
},
{
type: "systemctl-status",
category: "infra",
commandPatterns: [/^systemctl\s+status\b/i],
contentPatterns: [/^\s*Loaded:\s+/m, /^\s*Active:\s+/m, /^●\s+\S+\.service/m],
},
{
type: "test-vitest",
category: "test",
commandPatterns: [/^vitest\b/i, /^npm\s+(?:run\s+)?test:vitest\b/i],
contentPatterns: [/\bvitest\b/i, /^ ✓ /m, /^ /m, /Test Files\s+\d+\s+(?:passed|failed)/i],
},
{
type: "test-jest",
category: "test",
commandPatterns: [/^jest\b/i, /^npm\s+(?:run\s+)?test\b/i],
contentPatterns: [/Test Suites:\s+\d+/i, /Tests:\s+\d+/i, /^PASS\s+/m, /^FAIL\s+/m],
},
{
type: "test-pytest",
category: "test",
commandPatterns: [/^pytest\b/i, /^python\s+-m\s+pytest\b/i],
contentPatterns: [/=+\s+(?:\d+\s+)?(?:passed|failed|errors?)/i, /^E\s+/m, /^FAILED /m],
},
{
type: "test-cargo",
category: "test",
commandPatterns: [/^cargo\s+test\b/i, /^cargo\s+nextest\b/i],
contentPatterns: [
/^running \d+ tests?/m,
/^test\s+[\w:.-]+\s+\.\.\.\s+(?:ok|FAILED|ignored)/m,
/test result:\s+(?:ok|FAILED)/i,
],
},
{
type: "test-go",
category: "test",
commandPatterns: [/^go\s+test\b/i],
contentPatterns: [/^(?:ok|FAIL)\s+[\w./-]+\s+[\d.]+s/m, /^--- FAIL: /m, /^panic: /m],
},
{
type: "build-typescript",
category: "build",
commandPatterns: [/^tsc\b/i, /^npm\s+run\s+typecheck\b/i],
contentPatterns: [/TS\d{4}:/, /error TS\d{4}/i],
},
{
type: "build-eslint",
category: "build",
commandPatterns: [/^eslint\b/i, /^npm\s+run\s+lint\b/i],
contentPatterns: [/\s+\d+:\d+\s+(?:error|warning)\s+/, /✖\s+\d+\s+problems?/],
},
{
type: "build-webpack",
category: "build",
commandPatterns: [/^webpack\b/i, /^npx\s+webpack\b/i, /^npm\s+run\s+build:webpack\b/i],
contentPatterns: [
/webpack\s+\d/i,
/compiled (?:successfully|with \d+ errors?)/i,
/asset .+\.js/i,
],
},
{
type: "build-vite",
category: "build",
commandPatterns: [/^vite\s+build\b/i, /^npm\s+run\s+build\b/i, /^pnpm\s+build\b/i],
contentPatterns: [/vite v[\d.]+/i, /✓ built in/i, /transforming \(\d+\)/i],
},
{
type: "biome",
category: "build",
commandPatterns: [/^biome\b/i, /^npx\s+biome\b/i],
contentPatterns: [/lint\/[A-Za-z0-9/.-]+/, /Checked \d+ files? in/i],
},
{
type: "prettier",
category: "build",
commandPatterns: [/^prettier\b/i, /^npx\s+prettier\b/i],
contentPatterns: [/^Checking formatting\.\.\./m, /Code style issues found/i],
},
{
type: "turbo",
category: "build",
commandPatterns: [/^turbo\b/i, /^npx\s+turbo\b/i],
contentPatterns: [/^• Packages in scope:/m, /^Tasks:\s+\d+\s+successful/m],
},
{
type: "nx",
category: "build",
commandPatterns: [/^nx\b/i, /^npx\s+nx\b/i],
contentPatterns: [/^NX\s+/m, /^> nx run /m],
},
{
type: "playwright",
category: "test",
commandPatterns: [/^playwright\s+test\b/i, /^npx\s+playwright\s+test\b/i],
contentPatterns: [/Running \d+ tests? using \d+ workers?/i, /^\s+\d+ failed/m],
},
{
type: "npm-install",
category: "package",
commandPatterns: [/^(?:npm|pnpm|yarn)\s+(?:install|add|update)\b/i],
contentPatterns: [
/added \d+ packages/i,
/packages are looking for funding/i,
/audited \d+ packages/i,
],
},
{
type: "npm-audit",
category: "package",
commandPatterns: [/^(?:npm|pnpm|yarn)\s+audit\b/i],
contentPatterns: [/found \d+ vulnerabilities/i, /\b(?:low|moderate|high|critical)\b/i],
},
{
type: "ruff",
category: "build",
commandPatterns: [/^ruff\b/i, /^uv\s+run\s+ruff\b/i],
contentPatterns: [/^[\w./-]+\.py:\d+:\d+:\s+[A-Z]\d+/m, /Found \d+ errors?\./i],
},
{
type: "mypy",
category: "build",
commandPatterns: [/^mypy\b/i, /^python\s+-m\s+mypy\b/i],
contentPatterns: [/^[\w./-]+\.py:\d+:\s+error:/m, /Found \d+ errors? in \d+ files?/i],
},
{
type: "pip",
category: "package",
commandPatterns: [/^pip\s+(?:install|download|uninstall)\b/i, /^python\s+-m\s+pip\b/i],
contentPatterns: [/^Collecting /m, /^Successfully installed /m],
},
{
type: "uv-sync",
category: "package",
commandPatterns: [/^uv\s+sync\b/i, /^uv\s+pip\s+install\b/i],
contentPatterns: [/^Resolved \d+ packages?/m, /^Installed \d+ packages?/m],
},
{
type: "poetry-install",
category: "package",
commandPatterns: [/^poetry\s+install\b/i],
contentPatterns: [/^Installing dependencies from lock file/m, /^Package operations:/m],
},
{
type: "golangci-lint",
category: "build",
commandPatterns: [/^golangci-lint\b/i],
contentPatterns: [/^[\w./-]+\.go:\d+:\d+:/m, /^\d+ issues?:/m],
},
{
type: "bundle-install",
category: "package",
commandPatterns: [/^bundle\s+install\b/i],
contentPatterns: [/^Fetching gem metadata from /m, /^Bundle complete!/m],
},
{
type: "rubocop",
category: "build",
commandPatterns: [/^rubocop\b/i, /^bundle\s+exec\s+rubocop\b/i],
contentPatterns: [/^Inspecting \d+ files/m, /^[\w./-]+\.rb:\d+:\d+:\s+[A-Z]:/m],
},
{
type: "docker-ps",
category: "docker",
commandPatterns: [/^docker\s+ps\b/i],
contentPatterns: [/^CONTAINER ID\s+IMAGE\s+COMMAND/m],
},
{
type: "docker-logs",
category: "docker",
commandPatterns: [/^docker\s+logs\b/i, /^docker\s+compose\s+logs\b/i],
contentPatterns: [
/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}/m,
/\b(?:ERROR|WARN|INFO)\b/,
/^Attaching to /m,
],
},
{
type: "aws",
category: "cloud",
commandPatterns: [/^aws\b/i],
contentPatterns: [/An error occurred \([A-Za-z0-9]+\) when calling/, /^(?:upload|download): /m],
},
{
type: "gcloud",
category: "cloud",
commandPatterns: [/^gcloud\b/i],
contentPatterns: [/^ERROR: \(gcloud\./m, /^Updated property \[/m],
},
{
type: "ssh",
category: "cloud",
commandPatterns: [/^ssh\b/i],
contentPatterns: [
/Permission denied \(/,
/Host key verification failed/,
/Connection timed out/,
],
},
{
type: "rsync",
category: "cloud",
commandPatterns: [/^rsync\b/i],
contentPatterns: [/^sending incremental file list/m, /^rsync error:/m],
},
{
type: "curl",
category: "cloud",
commandPatterns: [/^curl\b/i],
contentPatterns: [/curl: \(\d+\)/, /^HTTP\/\d(?:\.\d)? \d{3}/m],
},
{
type: "wget",
category: "cloud",
commandPatterns: [/^wget\b/i],
contentPatterns: [/^--\d{4}-\d{2}-\d{2}/m, /^ERROR \d{3}:/m],
},
{
type: "json-output",
category: "generic",
commandPatterns: [/^jq\b/i, /^cat\s+.*\.json\b/i],
contentPatterns: [/^\s*[\[{][\s\S]*[\]}]\s*$/],
},
{
type: "shell-ls",
category: "shell",
commandPatterns: [/^ls(?:\s+-[A-Za-z]+)?\b/i],
contentPatterns: [/^total \d+/m, /^\S+\s+\S+\s+\d+\s+\w+\s+\d{1,2}\s+/m],
},
{
type: "shell-find",
category: "shell",
commandPatterns: [/^find\b/i],
contentPatterns: [/^(?:\.{1,2}|\/|[\w.-]+\/).+/m],
},
{
type: "shell-grep",
category: "shell",
commandPatterns: [/^(?:grep|rg|ag)\b/i],
contentPatterns: [
/^[\w./-]+\.(?:ts|tsx|js|jsx|py|go|rs|java|rb|md|json|ya?ml|txt):\d*:/m,
/^[\w./-]+\/[\w./-]+:\d*:/m,
],
},
{
type: "shell-ps",
category: "shell",
commandPatterns: [/^ps\b/i],
contentPatterns: [/^(?:USER\s+PID|\s*PID\s+)/m],
},
{
type: "shell-df",
category: "shell",
commandPatterns: [/^df\b/i],
contentPatterns: [/^Filesystem\s+.*Use%/m],
},
{
type: "shell-du",
category: "shell",
commandPatterns: [/^du\b/i],
contentPatterns: [/^\d+(?:\.\d+)?[KMGTP]?\s+\S+/m],
},
{
type: "error-stacktrace",
category: "generic",
commandPatterns: [],
contentPatterns: [
/Traceback \(most recent call last\):/,
/^\s+at\s+\S+\s+\(.+:\d+:\d+\)/m,
/^panic: /m,
/^thread '[^']+' panicked at/m,
],
},
{
type: "generic-error",
category: "generic",
commandPatterns: [],
contentPatterns: [/Error:/, /Exception:/, /Traceback \(most recent call last\):/],
},
];
export function detectCommandFromText(text: string): string | null {
const firstLines = text.split(/\r?\n/).slice(0, 4);
for (const line of firstLines) {
const trimmed = line.trim().replace(/^\$\s+/, "");
if (!trimmed) continue;
if (COMMAND_PREFIX_PATTERN.test(trimmed)) {
return trimmed;
}
}
return null;
}
export function detectCommandType(text: string, command?: string | null): CommandDetectionResult {
const detectedCommand = command?.trim() || detectCommandFromText(text);
let best: CommandDetectionResult | null = null;
for (const detector of DETECTORS) {
const matchedPatterns: string[] = [];
const commandMatched =
Boolean(detectedCommand) &&
detector.commandPatterns.some((pattern) => {
const matched = pattern.test(detectedCommand ?? "");
if (matched) matchedPatterns.push(pattern.source);
return matched;
});
const contentMatches = detector.contentPatterns.filter((pattern) => {
const matched = pattern.test(text);
if (matched) matchedPatterns.push(pattern.source);
return matched;
}).length;
if (!commandMatched && contentMatches === 0) continue;
const confidence = Math.min(1, (commandMatched ? 0.55 : 0) + contentMatches * 0.25);
if (!best || confidence > best.confidence) {
best = {
type: detector.type,
command: detectedCommand,
confidence,
category: detector.category,
matchedPatterns,
};
}
}
return (
best ?? {
type: "unknown",
command: detectedCommand,
confidence: detectedCommand ? 0.35 : 0.1,
category: "generic",
matchedPatterns: [],
}
);
}
export const detectCommandOutput = detectCommandType;