// SPDX-License-Identifier: AGPL-3.0-only // SPDX-FileCopyrightText: Copyright (C) 2026 Catcrafts® // The programs a lens is allowed to run are there: every Bash( // ...:*) entry of the base allowlist (the git verbs included), the shell // claude's Bash tool runs them with, and node and npx for the Playwright MCP // server. The suite runs inside the Flatpak build, the sandbox that ships, so // there every one must resolve on PATH; natively the test names what this // machine lacks and skips. #include import std; import Crafter.Review; namespace fs = std::filesystem; using namespace Crafter::Review; int main() { std::println("lens tools: every program a lens's allowlist names is on PATH"); std::set programs = {"bash", "node", "npx"}; for (const std::string& tool : Tools::BaseAllow()) { if (!tool.starts_with("Bash(") || !tool.ends_with(":*)")) continue; const std::string prefix = tool.substr(5, tool.size() - 8); const std::string program = prefix.substr(0, prefix.find(' ')); // A shell builtin, never a file of its own. if (program != "cd") programs.insert(program); } const std::vector path = Dyn::Split(Platform::GetEnv("PATH").value_or(""), ":"); std::vector missing; for (const std::string& program : programs) { const bool found = std::ranges::any_of(path, [&](const std::string& dir) { return !dir.empty() && access((fs::path(dir) / program).c_str(), X_OK) == 0; }); if (!found) missing.push_back(program); } const bool shipped = fs::exists("/.flatpak-info"); if (!missing.empty() && !shipped) { std::println("skipped: not on this machine's PATH: {}", Dyn::Join(missing, ", ")); return 77; } const bool ok = missing.empty() && programs.size() >= 20; std::println("{}every program a lens's allowlist names, its shell and the MCP server's node and npx resolve on PATH ({} programs){}", ok ? " ok " : " FAIL ", programs.size(), missing.empty() ? std::string() : std::format(" (missing: {})", Dyn::Join(missing, ", "))); return ok ? 0 : 1; }