pub fn extract_cmd_from_meta(meta: &str) -> Option<&str> {
let after = meta.split_once("cmd=").map(|(_, rest)| rest)?;
if after.is_empty() { None } else { Some(after) }
}
pub fn classify_bash_command(cmd: &str) -> Option<&'static str> {
let trimmed = cmd.trim();
if trimmed.is_empty() {
return None;
}
let first_raw = first_meaningful_token(trimmed)?;
let first = first_raw.to_ascii_lowercase();
Some(match first.as_str() {
"git" => "git",
"gh" => "gh",
"hub" => "gh", "svn" => "svn",
"hg" => "hg",
"jj" => "jj",
"docker" | "docker-compose" => "docker",
"podman" => "podman",
"kubectl" | "kubernetes" | "k9s" => "kubectl",
"helm" => "helm",
"terraform" | "tofu" => "terraform",
"ansible" | "ansible-playbook" => "ansible",
"cargo" | "rustc" | "rustup" => "cargo",
"python" | "python3" | "py" => "python",
"pip" | "pip3" | "pipx" | "uv" | "poetry" => "pip",
"node" | "npx" => "node",
"npm" | "yarn" | "pnpm" | "bun" => "npm",
"go" => "go",
"ruby" | "irb" => "ruby",
"gem" | "bundle" => "ruby",
"java" => "java",
"mvn" | "gradle" | "./gradlew" => "java",
"swift" => "swift",
"dotnet" => "dotnet",
"make" | "cmake" | "ninja" | "meson" => "make",
"curl" | "wget" | "http" | "httpie" => "curl",
"ssh" | "scp" | "rsync" | "sftp" => "ssh",
"ping" | "traceroute" | "dig" | "nslookup" | "mtr" => "net",
"aws" => "aws",
"gcloud" | "gsutil" => "gcloud",
"az" => "az",
"doctl" => "doctl",
"flyctl" | "fly" => "fly",
"vercel" => "vercel",
"wrangler" => "wrangler",
"heroku" => "heroku",
"ls" | "find" | "fd" | "tree" => "fs",
"grep" | "rg" | "ag" | "ack" => "grep",
"cat" | "head" | "tail" | "less" | "more" | "bat" => "fs",
"cp" | "mv" | "rm" | "mkdir" | "rmdir" | "touch" | "ln" | "chmod" | "chown" => "fs",
"tar" | "zip" | "unzip" | "gzip" | "gunzip" | "7z" => "archive",
"psql" | "pg_dump" | "pg_restore" => "postgres",
"mysql" | "mariadb" => "mysql",
"sqlite3" | "sqlite" => "sqlite",
"redis-cli" => "redis",
"jq" | "yq" | "fx" => "jq",
"vim" | "nvim" | "emacs" | "nano" | "code" => "editor",
"ps" | "top" | "htop" | "pgrep" | "pkill" | "kill" | "killall" => "proc",
"systemctl" | "service" | "launchctl" | "brew" | "apt" | "apt-get" | "yum" | "dnf"
| "pacman" => "pkg",
"sudo" | "time" | "env" | "watch" | "xargs" | "tee" | "echo" | "printf" | "sleep"
| "true" | "false" | "test" | "[[" | "[" => "shell",
"bash" | "sh" | "zsh" | "fish" | "exec" => "shell",
"source" | "." => "shell",
"ollama" | "llama" | "llama.cpp" => "ollama",
"huggingface-cli" | "hf" => "huggingface",
_ => return None,
})
}
fn first_meaningful_token(cmd: &str) -> Option<&str> {
let bytes = cmd.as_bytes();
let mut i = 0;
while i < bytes.len() {
while i < bytes.len() && bytes[i].is_ascii_whitespace() {
i += 1;
}
if i >= bytes.len() {
return None;
}
let token_start = i;
while i < bytes.len() && (bytes[i].is_ascii_alphanumeric() || bytes[i] == b'_') {
i += 1;
}
if i > token_start && i < bytes.len() && bytes[i] == b'=' {
i += 1; if i < bytes.len() && (bytes[i] == b'"' || bytes[i] == b'\'') {
let q = bytes[i];
i += 1;
while i < bytes.len() && bytes[i] != q {
i += 1;
}
if i < bytes.len() {
i += 1; }
} else {
while i < bytes.len() && !bytes[i].is_ascii_whitespace() {
i += 1;
}
}
continue;
}
let mut j = token_start;
while j < bytes.len() && !bytes[j].is_ascii_whitespace() {
j += 1;
}
return Some(&cmd[token_start..j]);
}
None
}