#include "data_http.h" #include "http_client.h" #include "vendor/nlohmann/json.hpp" #include #include using json = nlohmann::json; // Parse host and port from URL like "http://127.0.0.1:8484" static bool parse_url(const std::string& url, std::string& host, int& port) { auto pos = url.find("://"); std::string rest = (pos != std::string::npos) ? url.substr(pos + 3) : url; auto colon = rest.find(':'); if (colon == std::string::npos) { host = rest; port = 80; } else { host = rest.substr(0, colon); port = std::atoi(rest.substr(colon + 1).c_str()); } return !host.empty() && port > 0; } // POST a SQL query to the API and return parsed JSON, or null on failure. static json api_query(HttpClient& cli, const char* sql) { json body; body["sql"] = sql; auto res = cli.post("/api/databases/registry/query", body.dump(), "application/json"); if (!res.ok()) { if (res.status > 0) fprintf(stderr, "[http] query error %d: %s\n", res.status, res.body.c_str()); else fprintf(stderr, "[http] connection failed for query: %s\n", sql); return nullptr; } return json::parse(res.body, nullptr, false); } static int extract_int(const json& j) { if (j.is_null() || !j.contains("rows") || j["rows"].empty()) return 0; auto& val = j["rows"][0][0]; if (val.is_number()) return val.get(); if (val.is_string()) return std::atoi(val.get().c_str()); return 0; } static std::string extract_str(const json& row, size_t idx) { if (idx >= row.size() || row[idx].is_null()) return ""; if (row[idx].is_string()) return row[idx].get(); return row[idx].dump(); } static int extract_row_int(const json& row, size_t idx) { if (idx >= row.size() || row[idx].is_null()) return 0; if (row[idx].is_number()) return row[idx].get(); if (row[idx].is_string()) return std::atoi(row[idx].get().c_str()); return 0; } bool load_registry_data_http(const std::string& api_url, RegistryData& out) { std::string host; int port; if (!parse_url(api_url, host, port)) { fprintf(stderr, "[http] invalid URL: %s\n", api_url.c_str()); return false; } HttpClient cli(host, port); // Health check auto health = cli.get("/health"); if (!health.ok()) { fprintf(stderr, "[http] sqlite_api not reachable at %s\n", api_url.c_str()); return false; } fprintf(stdout, "[http] Connected to sqlite_api at %s\n", api_url.c_str()); // --- Counts --- out.stats.total_functions = extract_int(api_query(cli, "SELECT COUNT(*) FROM functions")); out.stats.total_types = extract_int(api_query(cli, "SELECT COUNT(*) FROM types")); out.stats.total_apps = extract_int(api_query(cli, "SELECT COUNT(*) FROM apps")); out.stats.total_analysis = extract_int(api_query(cli, "SELECT COUNT(*) FROM analysis")); out.stats.total_unit_tests = extract_int(api_query(cli, "SELECT COUNT(*) FROM unit_tests")); out.stats.total_proposals = extract_int(api_query(cli, "SELECT COUNT(*) FROM proposals")); out.stats.tested_functions = extract_int(api_query(cli, "SELECT COUNT(*) FROM functions WHERE tested = 1")); out.stats.pure_functions = extract_int(api_query(cli, "SELECT COUNT(*) FROM functions WHERE purity = 'pure'")); out.stats.impure_functions = extract_int(api_query(cli, "SELECT COUNT(*) FROM functions WHERE purity = 'impure'")); // --- By language --- out.by_lang.clear(); auto j = api_query(cli, "SELECT lang, COUNT(*) as cnt FROM functions GROUP BY lang ORDER BY cnt DESC"); if (!j.is_null() && j.contains("rows")) for (auto& row : j["rows"]) out.by_lang.push_back({extract_str(row, 0), extract_row_int(row, 1)}); // --- By domain --- out.by_domain.clear(); j = api_query(cli, "SELECT domain, COUNT(*) as cnt FROM functions GROUP BY domain ORDER BY cnt DESC"); if (!j.is_null() && j.contains("rows")) for (auto& row : j["rows"]) out.by_domain.push_back({extract_str(row, 0), extract_row_int(row, 1)}); // --- By kind --- out.by_kind.clear(); j = api_query(cli, "SELECT kind, COUNT(*) as cnt FROM functions GROUP BY kind ORDER BY cnt DESC"); if (!j.is_null() && j.contains("rows")) for (auto& row : j["rows"]) out.by_kind.push_back({extract_str(row, 0), extract_row_int(row, 1)}); // --- By date (last 30 days) --- out.by_date.clear(); j = api_query(cli, "SELECT date(created_at) as d, COUNT(*) as cnt FROM functions " "WHERE created_at >= date('now', '-30 days') GROUP BY d ORDER BY d"); if (!j.is_null() && j.contains("rows")) for (auto& row : j["rows"]) out.by_date.push_back({extract_str(row, 0), extract_row_int(row, 1)}); // --- Recent functions --- out.recent_funcs.clear(); j = api_query(cli, "SELECT id, name, lang, domain, kind, purity, description, created_at, tested " "FROM functions ORDER BY created_at DESC LIMIT 20"); if (!j.is_null() && j.contains("rows")) { for (auto& row : j["rows"]) { FunctionRow r; r.id = extract_str(row, 0); r.name = extract_str(row, 1); r.lang = extract_str(row, 2); r.domain = extract_str(row, 3); r.kind = extract_str(row, 4); r.purity = extract_str(row, 5); r.description = extract_str(row, 6); r.created_at = extract_str(row, 7); r.tested = extract_row_int(row, 8) != 0; out.recent_funcs.push_back(std::move(r)); } } // --- Apps --- out.apps.clear(); j = api_query(cli, "SELECT id, name, lang, domain, description, framework, repo_url, dir_path FROM apps ORDER BY name"); if (!j.is_null() && j.contains("rows")) { for (auto& row : j["rows"]) { AppRow r; r.id = extract_str(row, 0); r.name = extract_str(row, 1); r.lang = extract_str(row, 2); r.domain = extract_str(row, 3); r.description = extract_str(row, 4); r.framework = extract_str(row, 5); r.repo_url = extract_str(row, 6); r.dir_path = extract_str(row, 7); out.apps.push_back(std::move(r)); } } // --- Analysis --- out.analyses.clear(); j = api_query(cli, "SELECT id, name, lang, domain, description FROM analysis ORDER BY name"); if (!j.is_null() && j.contains("rows")) { for (auto& row : j["rows"]) { AnalysisRow r; r.id = extract_str(row, 0); r.name = extract_str(row, 1); r.lang = extract_str(row, 2); r.domain = extract_str(row, 3); r.description = extract_str(row, 4); out.analyses.push_back(std::move(r)); } } // --- Types --- out.types.clear(); j = api_query(cli, "SELECT id, name, lang, domain, algebraic, description FROM types ORDER BY name"); if (!j.is_null() && j.contains("rows")) { for (auto& row : j["rows"]) { TypeRow r; r.id = extract_str(row, 0); r.name = extract_str(row, 1); r.lang = extract_str(row, 2); r.domain = extract_str(row, 3); r.algebraic = extract_str(row, 4); r.description = extract_str(row, 5); out.types.push_back(std::move(r)); } } out.prepare_chart_data(); // Best-effort: projects (no fatal si falla) load_projects_http(api_url, out); return true; } // --------------------------------------------------------------------------- // Projects endpoints // --------------------------------------------------------------------------- bool load_projects_http(const std::string& api_url, RegistryData& out) { std::string host; int port; if (!parse_url(api_url, host, port)) return false; HttpClient cli(host, port); auto res = cli.get("/api/projects"); if (!res.ok()) { fprintf(stderr, "[http] GET /api/projects failed: %d\n", res.status); return false; } auto j = json::parse(res.body, nullptr, false); if (j.is_null()) return false; out.projects.clear(); if (j.contains("projects") && j["projects"].is_array()) { for (auto& p : j["projects"]) { ProjectRow r; if (p.contains("id") && p["id"].is_string()) r.id = p["id"]; if (p.contains("name") && p["name"].is_string()) r.name = p["name"]; if (p.contains("description") && p["description"].is_string()) r.description = p["description"]; if (p.contains("apps_count") && p["apps_count"].is_number()) r.apps_count = p["apps_count"].get(); if (p.contains("analyses_count") && p["analyses_count"].is_number()) r.analyses_count = p["analyses_count"].get(); if (p.contains("vaults_count") && p["vaults_count"].is_number()) r.vaults_count = p["vaults_count"].get(); out.projects.push_back(std::move(r)); } } if (j.contains("orphans") && j["orphans"].is_object()) { auto& o = j["orphans"]; if (o.contains("apps") && o["apps"].is_number()) out.orphan_apps = o["apps"].get(); if (o.contains("analyses") && o["analyses"].is_number()) out.orphan_analyses = o["analyses"].get(); if (o.contains("vaults") && o["vaults"].is_number()) out.orphan_vaults = o["vaults"].get(); } return true; } bool load_project_detail_http(const std::string& api_url, const std::string& id, ProjectDetail& out) { std::string host; int port; if (!parse_url(api_url, host, port)) return false; HttpClient cli(host, port); auto res = cli.get("/api/projects/" + id); if (!res.ok()) { fprintf(stderr, "[http] GET /api/projects/%s failed: %d\n", id.c_str(), res.status); return false; } auto j = json::parse(res.body, nullptr, false); if (j.is_null()) return false; out = ProjectDetail{}; out.id = id; if (j.contains("project") && j["project"].is_object()) { auto& p = j["project"]; if (p.contains("name") && p["name"].is_string()) out.name = p["name"]; if (p.contains("description") && p["description"].is_string()) out.description = p["description"]; } auto read_rows = [](const json& section) -> std::pair, std::vector>> { std::vector cols; std::vector> rows; if (!section.is_object()) return {cols, rows}; if (section.contains("columns") && section["columns"].is_array()) for (auto& c : section["columns"]) cols.push_back(c.is_string() ? c.get() : ""); if (section.contains("rows") && section["rows"].is_array()) { for (auto& row : section["rows"]) { std::vector cells; for (auto& v : row) { if (v.is_string()) cells.push_back(v.get()); else if (v.is_number_integer()) cells.push_back(std::to_string(v.get())); else if (v.is_null()) cells.push_back(""); else cells.push_back(v.dump()); } rows.push_back(std::move(cells)); } } return {cols, rows}; }; // apps: [id, name, lang, domain, framework, description, dir_path] if (j.contains("apps")) { auto [cols, rows] = read_rows(j["apps"]); for (auto& r : rows) { AppRow a; if (r.size() > 0) a.id = r[0]; if (r.size() > 1) a.name = r[1]; if (r.size() > 2) a.lang = r[2]; if (r.size() > 3) a.domain = r[3]; if (r.size() > 4) a.framework = r[4]; if (r.size() > 5) a.description = r[5]; out.apps.push_back(std::move(a)); } } // analyses: [id, name, lang, domain, description, dir_path] if (j.contains("analyses")) { auto [cols, rows] = read_rows(j["analyses"]); for (auto& r : rows) { AnalysisRow a; if (r.size() > 0) a.id = r[0]; if (r.size() > 1) a.name = r[1]; if (r.size() > 2) a.lang = r[2]; if (r.size() > 3) a.domain = r[3]; if (r.size() > 4) a.description = r[4]; out.analyses.push_back(std::move(a)); } } // vaults: [id, name, path, symlink, description, tags] if (j.contains("vaults")) { auto [cols, rows] = read_rows(j["vaults"]); for (auto& r : rows) { VaultRow v; if (r.size() > 0) v.id = r[0]; if (r.size() > 1) v.name = r[1]; if (r.size() > 2) v.path = r[2]; if (r.size() > 3) v.symlink = (r[3] == "1"); if (r.size() > 4) v.description = r[4]; out.vaults.push_back(std::move(v)); } } return true; } // --------------------------------------------------------------------------- // Function detail endpoints (Explorer) // --------------------------------------------------------------------------- bool load_function_detail_http(const std::string& api_url, const std::string& id, FunctionDetail& out) { std::string host; int port; if (!parse_url(api_url, host, port)) return false; HttpClient cli(host, port); // /api/databases//query solo acepta { "sql": ... } sin args // parametrizados (ver handleQuery en sqlite_api). Escapamos comilla // simple para que un id con apostrofe no rompa la query — los IDs del // registry son [a-z0-9_]+ asi que el escape es defensivo. std::string escaped; escaped.reserve(id.size()); for (char c : id) { if (c == '\'') escaped += "''"; else escaped.push_back(c); } std::string sql = "SELECT id, name, lang, domain, kind, purity, version, signature, " "description, code, documentation, notes, example, params_schema, " "uses_functions, uses_types, returns, error_type, file_path, " "created_at, tested FROM functions WHERE id = '" + escaped + "'"; auto j = api_query(cli, sql.c_str()); if (j.is_null() || !j.contains("rows") || j["rows"].empty()) return false; auto& row = j["rows"][0]; out = FunctionDetail{}; out.id = extract_str(row, 0); out.name = extract_str(row, 1); out.lang = extract_str(row, 2); out.domain = extract_str(row, 3); out.kind = extract_str(row, 4); out.purity = extract_str(row, 5); out.version = extract_str(row, 6); out.signature = extract_str(row, 7); out.description = extract_str(row, 8); out.code = extract_str(row, 9); out.documentation = extract_str(row, 10); out.notes = extract_str(row, 11); out.example = extract_str(row, 12); out.params_schema = extract_str(row, 13); out.uses_functions= extract_str(row, 14); out.uses_types = extract_str(row, 15); out.returns = extract_str(row, 16); out.error_type = extract_str(row, 17); out.file_path = extract_str(row, 18); out.created_at = extract_str(row, 19); out.tested = extract_row_int(row, 20) != 0; return true; } bool load_all_functions_http(const std::string& api_url, std::vector& out) { std::string host; int port; if (!parse_url(api_url, host, port)) return false; HttpClient cli(host, port); auto j = api_query(cli, "SELECT id, name, lang, domain, kind, purity, description, " "created_at, tested FROM functions ORDER BY name"); if (j.is_null() || !j.contains("rows")) return false; out.clear(); out.reserve(j["rows"].size()); for (auto& row : j["rows"]) { FunctionRow r; r.id = extract_str(row, 0); r.name = extract_str(row, 1); r.lang = extract_str(row, 2); r.domain = extract_str(row, 3); r.kind = extract_str(row, 4); r.purity = extract_str(row, 5); r.description = extract_str(row, 6); r.created_at = extract_str(row, 7); r.tested = extract_row_int(row, 8) != 0; out.push_back(std::move(r)); } return true; } bool load_unit_tests_http(const std::string& api_url, const std::string& function_id, std::vector& out) { std::string host; int port; if (!parse_url(api_url, host, port)) return false; HttpClient cli(host, port); // Mismo escape defensivo que en load_function_detail_http — los IDs son // [a-z0-9_]+ pero por consistencia escapamos comilla simple. std::string escaped; escaped.reserve(function_id.size()); for (char c : function_id) { if (c == '\'') escaped += "''"; else escaped.push_back(c); } std::string sql = "SELECT id, function_id, name, lang, file_path, code, created_at " "FROM unit_tests WHERE function_id = '" + escaped + "' ORDER BY name"; auto j = api_query(cli, sql.c_str()); if (j.is_null() || !j.contains("rows")) return false; out.clear(); for (auto& row : j["rows"]) { UnitTestRow r; r.id = extract_str(row, 0); r.function_id = extract_str(row, 1); r.name = extract_str(row, 2); r.lang = extract_str(row, 3); r.file_path = extract_str(row, 4); r.code = extract_str(row, 5); r.created_at = extract_str(row, 6); out.push_back(std::move(r)); } return true; } // --------------------------------------------------------------------------- // Mutation endpoints // --------------------------------------------------------------------------- static bool post_json(const std::string& api_url, const std::string& path, const json& body, std::string& out_body) { std::string host; int port; if (!parse_url(api_url, host, port)) { out_body = "invalid API URL: " + api_url; return false; } HttpClient cli(host, port); auto res = cli.post(path, body.dump(), "application/json"); // Mensaje util para el toast: si OK, intenta sacar "output" del JSON. // Si error, incluye status + error del body si existe. if (res.ok()) { auto j = json::parse(res.body, nullptr, false); if (!j.is_null()) { if (j.contains("output") && j["output"].is_string()) out_body = j["output"].get(); else if (j.contains("ok") && j["ok"].is_boolean()) out_body = j["ok"].get() ? "OK" : "failed"; else out_body = res.body; } else { out_body = res.body.empty() ? "OK" : res.body; } return true; } // Error path con ASCII (la fuente puede no tener em dash). Para status=0 // el http_client ya ha escrito un diagnostico de connect() en res.body. if (res.status == 0) { out_body = res.body.empty() ? "connection failed (is sqlite_api running?)" : res.body; return false; } auto j = json::parse(res.body, nullptr, false); std::string detail; if (!j.is_null() && j.contains("error") && j["error"].is_string()) detail = j["error"].get(); else if (!res.body.empty()) detail = res.body; char buf[64]; std::snprintf(buf, sizeof(buf), "HTTP %d: ", res.status); out_body = std::string(buf) + detail; return false; } bool http_post_reindex(const std::string& api_url, std::string& out_body) { return post_json(api_url, "/api/reindex", json::object(), out_body); } bool http_post_add_app(const std::string& api_url, const std::string& name, const std::string& lang, const std::string& domain, const std::string& project, const std::string& description, std::string& out_body) { json b; b["name"] = name; b["lang"] = lang; b["domain"] = domain; b["project"] = project; b["description"] = description; return post_json(api_url, "/api/add/app", b, out_body); } bool http_post_add_analysis(const std::string& api_url, const std::string& name, const std::string& project, const std::string& packages_csv, const std::string& description, std::string& out_body) { json b; b["name"] = name; b["project"] = project; b["description"] = description; // Packages como array. Split CSV. json pkgs = json::array(); std::string cur; for (char c : packages_csv) { if (c == ',' || c == ' ') { if (!cur.empty()) { pkgs.push_back(cur); cur.clear(); } } else cur.push_back(c); } if (!cur.empty()) pkgs.push_back(cur); b["packages"] = pkgs; return post_json(api_url, "/api/add/analysis", b, out_body); } bool http_post_add_vault(const std::string& api_url, const std::string& name, const std::string& project, const std::string& path, const std::string& description, std::string& out_body) { json b; b["name"] = name; b["project"] = project; b["path"] = path; b["description"] = description; return post_json(api_url, "/api/add/vault", b, out_body); } // ---- Issue 0085d: Claude usage telemetry ---- // Query against ops:call_monitor instead of registry. static json call_monitor_query(HttpClient& cli, const char* sql) { json body; body["sql"] = sql; auto res = cli.post("/api/databases/ops:call_monitor/query", body.dump(), "application/json"); if (!res.ok()) { return nullptr; } return json::parse(res.body, nullptr, false); } static double extract_row_double(const json& row, size_t idx) { if (idx >= row.size() || row[idx].is_null()) return 0.0; if (row[idx].is_number()) return row[idx].get(); if (row[idx].is_string()) return std::atof(row[idx].get().c_str()); return 0.0; } // Construye un filtro temporal `WHERE ts >= ?` literal embebido (no prepared) // reemplazando el placeholder. window_secs == 0 -> sin filtro. static std::string ts_filter(int window_secs, const char* col = "ts", const char* glue = "WHERE") { if (window_secs <= 0) return ""; char buf[128]; std::snprintf(buf, sizeof(buf), " %s %s >= (strftime('%%s','now') - %d) ", glue, col, window_secs); return std::string(buf); } bool load_claude_usage_http(const std::string& api_url, RegistryData& out, int window_secs) { // Preservar window y estado WS al recargar. bool prev_ws = out.claude.ws_connected; long long prev_last_ev = out.claude.last_event_ts; long long prev_max_id = out.claude.last_seen_call_id; out.claude = ClaudeUsageData{}; out.claude.window_secs = window_secs; out.claude.ws_connected = prev_ws; out.claude.last_event_ts = prev_last_ev; out.claude.last_seen_call_id = prev_max_id; std::string host; int port; if (!parse_url(api_url, host, port)) return false; HttpClient cli(host, port); // Probe: is ops:call_monitor known? auto probe = cli.get("/api/databases/ops:call_monitor/tables"); if (!probe.ok()) { out.claude.available = false; return true; // not an error: monitor not yet deployed } out.claude.available = true; const std::string wf_calls = ts_filter(window_secs); // " WHERE ts >= ..." const std::string wf_viol = ts_filter(window_secs); const std::string wf_calls_and = wf_calls.empty() ? std::string(" WHERE success = 0 ") : std::string(wf_calls + " AND success = 0 "); // Totals (filtradas por ventana donde aplica) { const std::string sql_calls = "SELECT COUNT(*) FROM calls" + wf_calls; out.claude.total_calls = extract_int(call_monitor_query(cli, sql_calls.c_str())); } { const std::string sql_err = "SELECT COUNT(*) FROM calls" + wf_calls_and; out.claude.total_errors = extract_int(call_monitor_query(cli, sql_err.c_str())); } { const std::string sql_viol = "SELECT COUNT(*) FROM violations" + wf_viol; out.claude.total_violations = extract_int(call_monitor_query(cli, sql_viol.c_str())); } // MCP / fn run / heredoc — herramientas registry-aware. Cubre las // variantes vistas en produccion: mcp, mcp_fn_search, mcp_fn_run, // fn_cli_run, fn_run_cli, heredoc, heredoc_py. static const char* kRegistryAwareToolCond = "(tool_used LIKE 'mcp%' OR tool_used LIKE 'heredoc%' " "OR tool_used IN ('fn_cli_run','fn_run_cli'))"; { const std::string wf_and = wf_calls.empty() ? std::string(" WHERE ") + kRegistryAwareToolCond : std::string(wf_calls + " AND " + kRegistryAwareToolCond); const std::string sql = "SELECT COUNT(*) FROM calls" + wf_and; out.claude.total_mcp = extract_int(call_monitor_query(cli, sql.c_str())); } // % calls que llamaron a una funcion del registry (function_id no vacio). { const std::string wf_and = wf_calls.empty() ? std::string(" WHERE function_id != '' ") : std::string(wf_calls + " AND function_id != '' "); const std::string sql = "SELECT COUNT(*) FROM calls" + wf_and; int reg_hits = extract_int(call_monitor_query(cli, sql.c_str())); out.claude.registry_pct = (out.claude.total_calls > 0) ? 100.0 * static_cast(reg_hits) / static_cast(out.claude.total_calls) : 0.0; } out.claude.total_copies = extract_int(call_monitor_query(cli, "SELECT COUNT(*) FROM copied_code")); out.claude.total_versions = extract_int(call_monitor_query(cli, "SELECT COUNT(*) FROM function_versions")); // Recent executions (calls table) ordenada por ts DESC { std::string sql = "SELECT id, ts, function_id, tool_used, duration_ms, success, error_class, session_id, " "COALESCE(command_snippet,'') AS command_snippet, " "COALESCE(error_snippet,'') AS error_snippet " "FROM calls" + wf_calls + " ORDER BY ts DESC LIMIT 100"; json rx = call_monitor_query(cli, sql.c_str()); if (rx.is_object() && rx.contains("rows")) { long long mx = out.claude.last_seen_call_id; for (const auto& r : rx["rows"]) { RecentExecutionRow row; row.id = (long long)extract_row_int(r, 0); row.ts = (long long)extract_row_int(r, 1); row.function_id = extract_str(r, 2); row.tool_used = extract_str(r, 3); row.duration_ms = extract_row_int(r, 4); row.success = extract_row_int(r, 5) != 0; row.error_class = extract_str(r, 6); row.session_id = extract_str(r, 7); row.command_snippet = extract_str(r, 8); row.error_snippet = extract_str(r, 9); if (row.id > mx) mx = row.id; out.claude.recent_executions.push_back(row); } out.claude.last_seen_call_id = mx; } } // Top functions by calls_total json top = call_monitor_query(cli, "SELECT function_id, calls_total, calls_7d, errors_total, error_rate, mean_duration_ms " "FROM function_stats ORDER BY calls_total DESC LIMIT 20"); if (top.is_object() && top.contains("rows")) { for (const auto& r : top["rows"]) { ClaudeUsageRow row; row.function_id = extract_str(r, 0); row.calls_total = extract_row_int(r, 1); row.calls_7d = extract_row_int(r, 2); row.errors_total = extract_row_int(r, 3); row.error_rate = extract_row_double(r, 4); row.mean_duration_ms = extract_row_double(r, 5); out.claude.top_functions.push_back(row); } } // Recent violations (filtradas por ventana) std::string sql_viol_list = "SELECT rule_id, function_id, command_snippet, severity, ts " "FROM violations" + wf_viol + " ORDER BY ts DESC LIMIT 20"; json viol = call_monitor_query(cli, sql_viol_list.c_str()); if (viol.is_object() && viol.contains("rows")) { for (const auto& r : viol["rows"]) { ClaudeViolationRow row; row.rule_id = extract_str(r, 0); row.function_id = extract_str(r, 1); row.command_snippet = extract_str(r, 2); row.severity = extract_str(r, 3); row.ts = (long long)extract_row_int(r, 4); out.claude.recent_violations.push_back(row); } } // Copied code matches json cp = call_monitor_query(cli, "SELECT app_file, app_function, registry_id, kind, similarity " "FROM copied_code ORDER BY detected_at DESC LIMIT 50"); if (cp.is_object() && cp.contains("rows")) { for (const auto& r : cp["rows"]) { ClaudeCopiedRow row; row.app_file = extract_str(r, 0); row.app_function = extract_str(r, 1); row.registry_id = extract_str(r, 2); row.kind = extract_str(r, 3); row.similarity = extract_row_double(r, 4); out.claude.copies.push_back(row); } } return true; } bool load_recent_executions_http(const std::string& api_url, int window_secs, int limit, std::vector& out, long long& out_max_id) { out.clear(); out_max_id = 0; std::string host; int port; if (!parse_url(api_url, host, port)) return false; HttpClient cli(host, port); const std::string wf = ts_filter(window_secs); char lim_buf[32]; std::snprintf(lim_buf, sizeof(lim_buf), " LIMIT %d", limit > 0 ? limit : 100); std::string sql = "SELECT id, ts, function_id, tool_used, duration_ms, success, error_class, session_id " "FROM calls" + wf + " ORDER BY ts DESC" + lim_buf; json rx = call_monitor_query(cli, sql.c_str()); if (!rx.is_object() || !rx.contains("rows")) return false; for (const auto& r : rx["rows"]) { RecentExecutionRow row; row.id = (long long)extract_row_int(r, 0); row.ts = (long long)extract_row_int(r, 1); row.function_id = extract_str(r, 2); row.tool_used = extract_str(r, 3); row.duration_ms = extract_row_int(r, 4); row.success = extract_row_int(r, 5) != 0; row.error_class = extract_str(r, 6); row.session_id = extract_str(r, 7); if (row.id > out_max_id) out_max_id = row.id; out.push_back(row); } return true; }