#!/usr/bin/env bun import { readFile, writeFile } from "fs/promises"; import { join } from "path"; import { parse } from "csv-parse/sync"; // ============================================================================= // Security: HTML Escaping to prevent XSS // ============================================================================= /** * Escape HTML special characters to prevent XSS attacks */ function escapeHtml(str: string | undefined | null): string { if (str === undefined || str === null) return ""; return String(str) .replace(/&/g, "&") .replace(//g, ">") .replace(/"/g, """) .replace(/'/g, "'"); } /** * Escape a string for safe use in JavaScript context (inside single quotes) */ function escapeJsString(str: string | undefined | null): string { if (str === undefined || str === null) return ""; return String(str) .replace(/\\/g, "\\\\") .replace(/'/g, "\\'") .replace(/"/g, '\\"') .replace(/\n/g, "\\n") .replace(/\r/g, "\\r") .replace(/<\/script>/gi, "<\\/script>"); } const args = process.argv.slice(2); if (args.includes("-h") || args.includes("--help")) { console.log(` dashboard-builder - Generate interactive HTML dashboards from data Usage: skills run dashboard-builder -- file= [options] Options: -h, --help Show this help message file= Input data file (.csv or .json) (required) title= Dashboard title (default: Dashboard) Examples: skills run dashboard-builder -- file=sales.csv skills run dashboard-builder -- file=metrics.json title="Monthly Report" `); process.exit(0); } const fileArg = args.find(a => a.startsWith("file="))?.split("=")[1]; const titleArg = args.find(a => a.startsWith("title="))?.split("=")[1] || "Dashboard"; async function main() { if (!fileArg) { console.log("Usage: skills run dashboard-builder -- file= [title=...]"); process.exit(1); } try { const content = await readFile(fileArg, "utf-8"); let data: any[] = []; if (fileArg.endsWith(".json")) { data = JSON.parse(content); } else if (fileArg.endsWith(".csv")) { data = parse(content, { columns: true, skip_empty_lines: true }); } else { throw new Error("Unsupported file format. Use .csv or .json"); } if (data.length === 0) throw new Error("No data found"); // Analyze columns const keys = Object.keys(data[0]); const numericKeys = keys.filter(k => !isNaN(Number(data[0][k]))); const categoryKeys = keys.filter(k => isNaN(Number(data[0][k]))); const labelKey = categoryKeys[0] || keys[0]; const valueKey = numericKeys[0] || keys[1]; const labels = data.map(d => d[labelKey]); const values = data.map(d => Number(d[valueKey])); // Sanitize labels for safe JSON embedding (strip script tags etc) const sanitizedLabels = labels.map((l: string) => String(l).replace(/<[^>]*>/g, "")); // Generate HTML with proper escaping const html = ` ${escapeHtml(titleArg)}

${escapeHtml(titleArg)}

`; const outputDir = process.env.SKILLS_EXPORTS_DIR || "."; const outputPath = join(outputDir, "dashboard.html"); await writeFile(outputPath, html); console.log(`Dashboard generated: ${outputPath}`); } catch (error: any) { console.error("Error:", error.message); process.exit(1); } } main();