// ╔══════════════════════════════════════════════════════════════════════════════╗ // ║ ENGRAM DATA STUDIO — Native El Application ║ // ║ Full-featured terminal explorer for the Engram knowledge graph ║ // ╚══════════════════════════════════════════════════════════════════════════════╝ // // Usage: el run-file studio.el // ENGRAM_URL=http://host:port el run-file studio.el // // Requires: Engram server running (default: http://localhost:8340) // ── Configuration ───────────────────────────────────────────────────────────── fn get_base_url() -> String { let e: String = env("ENGRAM_URL") if e == "" { return "http://localhost:8340" } return e } fn get_report_path() -> String { let e: String = env("ENGRAM_REPORT") if e == "" { return "/tmp/engram-studio-report.txt" } return e } // ── Box-drawing helpers ──────────────────────────────────────────────────────── fn repeat_str(s: String, n: Int) -> String { let result: String = "" let i: Int = 0 while i < n { let result: String = result + s let i: Int = i + 1 } return result } fn hline(width: Int) -> String { return repeat_str("─", width) } fn box_top(width: Int) -> String { return "┌" + hline(width) + "┐" } fn box_bot(width: Int) -> String { return "└" + hline(width) + "┘" } fn box_row(content: String, width: Int) -> String { let padded: String = str_pad_right(content, width, " ") return "│" + padded + "│" } fn double_top(width: Int) -> String { return "╔" + repeat_str("═", width) + "╗" } fn double_bot(width: Int) -> String { return "╚" + repeat_str("═", width) + "╝" } fn double_row(content: String, width: Int) -> String { let padded: String = str_pad_right(content, width, " ") return "║" + padded + "║" } fn section_header(title: String) -> String { println("") println(color_bold(box_top(62))) println(color_bold(box_row(" " + title, 62))) println(color_bold(box_bot(62))) return "" } fn divider() -> String { println(color_dim(" " + repeat_str("·", 58))) return "" } // ── API access ──────────────────────────────────────────────────────────────── fn api_get(path: String) -> String { let url: String = get_base_url() + path let resp: String = http_get(url) if str_starts_with(resp, "{\"error\"") { return "" } return resp } fn api_post(path: String, body: String) -> String { let url: String = get_base_url() + path let resp: String = http_post(url, body) if str_starts_with(resp, "{\"error\"") { return "" } return resp } // ── Safe JSON field extractors ───────────────────────────────────────────────── // These handle both raw JSON strings and parsed Value::Struct objects. fn safe_str(node: String, key: String) -> String { return json_get_string(node, key) } fn safe_int(node: String, key: String) -> Int { return json_get_int(node, key) } fn safe_float(node: String, key: String) -> Float { return json_get_float(node, key) } // ── Formatting helpers ──────────────────────────────────────────────────────── fn short_id(full_id: String) -> String { if str_len(full_id) >= 8 { return str_slice(full_id, 0, 8) } return full_id } fn format_bytes(n: Int) -> String { if n < 1024 { return int_to_str(n) + " B" } if n < 1048576 { let kb: Int = n / 1024 return int_to_str(kb) + " KB" } let mb: Int = n / 1048576 return int_to_str(mb) + " MB" } fn format_timestamp(ms: Int) -> String { if ms <= 0 { return "—" } let secs: Int = ms / 1000 let ts: Int = time_from_parts(secs, 0, "UTC") return time_format(ts, "ISO") } fn content_preview(content: String, max_len: Int) -> String { let n: Int = str_len(content) if n == 0 { return color_dim("(no content)") } if n <= max_len { return content } return str_slice(content, 0, max_len) + color_dim("…") } fn salience_bar(salience: Float) -> String { let pct: Float = salience * 10.0 let filled: Int = float_to_int(pct) let bar: String = repeat_str("█", filled) + repeat_str("░", 10 - filled) if salience >= 0.7 { return color_green(bar) } if salience >= 0.4 { return color_yellow(bar) } return color_dim(bar) } fn tier_badge(tier: String) -> String { if tier == "Semantic" { return color_cyan("[Semantic ]") } if tier == "Episodic" { return color_yellow("[Episodic ]") } if tier == "Working" { return color_green("[Working ]") } if tier == "Procedural" { return color_bold("[Procedural]") } return "[" + str_pad_right(tier, 10, " ") + "]" } fn type_badge(node_type: String) -> String { if node_type == "Memory" { return color_cyan("Memory ") } if node_type == "Concept" { return color_green("Concept ") } if node_type == "Event" { return color_yellow("Event ") } if node_type == "Entity" { return color_bold("Entity ") } if node_type == "Process" { return color_cyan("Process ") } if node_type == "InternalState" { return color_dim("IntState ") } return str_pad_right(node_type, 10, " ") } // ── Node renderer ───────────────────────────────────────────────────────────── fn render_node_row(node_json: String, idx: Int) -> String { let id: String = short_id(safe_str(node_json, "id")) let label: String = safe_str(node_json, "label") let node_type: String = safe_str(node_json, "node_type") let tier: String = safe_str(node_json, "tier") let salience: Float = safe_float(node_json, "salience") let label_col: String = str_pad_right(label, 36, " ") let sal_str: String = format_float(salience, 3) let num: String = str_pad_left(int_to_str(idx + 1), 3, " ") + ". " return " " + num + color_dim(id) + " " + label_col + " " + type_badge(node_type) + " " + sal_str } fn render_node_detail(node_json: String) -> String { let id: String = safe_str(node_json, "id") let label: String = safe_str(node_json, "label") let node_type: String = safe_str(node_json, "node_type") let tier: String = safe_str(node_json, "tier") let salience: Float = safe_float(node_json, "salience") let importance: Float = safe_float(node_json, "importance") let confidence: Float = safe_float(node_json, "confidence") let created: Int = safe_int(node_json, "created_at") let updated: Int = safe_int(node_json, "updated_at") println(" " + color_bold("ID: ") + id) println(" " + color_bold("Label: ") + color_cyan(label)) println(" " + color_bold("Type: ") + node_type) println(" " + color_bold("Tier: ") + tier_badge(tier)) println(" " + color_bold("Salience: ") + salience_bar(salience) + " " + format_float(salience, 4)) println(" " + color_bold("Importance: ") + format_float(importance, 4)) println(" " + color_bold("Confidence: ") + format_float(confidence, 4)) println(" " + color_bold("Created: ") + color_dim(format_timestamp(created))) println(" " + color_bold("Updated: ") + color_dim(format_timestamp(updated))) return "" } // ── Section: Stats Dashboard ────────────────────────────────────────────────── fn show_stats(report: String) -> String { section_header("Database Statistics") let stats_json: String = api_get("/api/stats") if stats_json == "" { println(" " + color_red("Error: could not reach Engram server")) println(" Make sure the server is running: engram-server --data-dir ") return report } let node_count: Int = safe_int(stats_json, "node_count") let edge_count: Int = safe_int(stats_json, "edge_count") let avg_sal: Float = safe_float(stats_json, "avg_salience") let db_bytes: Int = safe_int(stats_json, "db_size_bytes") println(" " + color_bold("Nodes: ") + color_cyan(int_to_str(node_count))) println(" " + color_bold("Edges: ") + color_cyan(int_to_str(edge_count))) println(" " + color_bold("Avg Salience: ") + format_float(avg_sal, 4)) println(" " + color_bold("DB Size: ") + color_dim(format_bytes(db_bytes))) let new_report: String = report + "\n=== Database Statistics ===\n" + "Nodes: " + int_to_str(node_count) + "\n" + "Edges: " + int_to_str(edge_count) + "\n" + "Avg Salience: " + format_float(avg_sal, 4) + "\n" + "DB Size: " + format_bytes(db_bytes) + "\n" return new_report } // ── Section: Node Browser ───────────────────────────────────────────────────── fn show_nodes(node_type: String, limit: Int, report: String) -> String { let title: String = "Nodes — type: " + node_type section_header(title) let path: String = "/api/nodes?node_type=" + node_type + "&limit=" + int_to_str(limit) let json_str: String = api_get(path) if json_str == "" { println(" " + color_dim("No nodes found or server unreachable")) return report } let nodes: List = json_parse(json_str) let n: Int = list_len(nodes) if n == 0 { println(" " + color_dim("(no " + node_type + " nodes)")) return report } println(" " + color_dim("Showing " + int_to_str(n) + " nodes:")) println("") let header: String = " " + str_pad_right(" # ID Label", 55, " ") + " Type Salience" println(color_dim(header)) println(color_dim(" " + repeat_str("─", 70))) let i: Int = 0 let report_section: String = "\n=== " + node_type + " Nodes ===\n" while i < n { let node: String = json_stringify(list_get(nodes, i)) println(render_node_row(node, i)) let label: String = safe_str(node, "label") let id: String = short_id(safe_str(node, "id")) let sal: Float = safe_float(node, "salience") let report_section: String = report_section + int_to_str(i + 1) + ". [" + id + "] " + label + " (sal=" + format_float(sal, 3) + ")\n" let i: Int = i + 1 } return report + report_section } // ── Section: Recent Nodes ───────────────────────────────────────────────────── fn show_recent(limit: Int, report: String) -> String { section_header("Recent Nodes (last " + int_to_str(limit) + ")") let path: String = "/api/nodes?limit=" + int_to_str(limit) let json_str: String = api_get(path) if json_str == "" { println(" " + color_dim("No nodes or server unreachable")) return report } let nodes: List = json_parse(json_str) let n: Int = list_len(nodes) if n == 0 { println(" " + color_dim("(database is empty)")) return report } println(" " + color_dim("Showing " + int_to_str(n) + " most recent nodes:")) println("") let header: String = " " + str_pad_right(" # ID Label", 55, " ") + " Type Salience" println(color_dim(header)) println(color_dim(" " + repeat_str("─", 70))) let i: Int = 0 let report_section: String = "\n=== Recent Nodes ===\n" while i < n { let node: String = json_stringify(list_get(nodes, i)) println(render_node_row(node, i)) let label: String = safe_str(node, "label") let id: String = short_id(safe_str(node, "id")) let report_section: String = report_section + int_to_str(i + 1) + ". [" + id + "] " + label + "\n" let i: Int = i + 1 } return report + report_section } // ── Section: Top Salient Nodes ──────────────────────────────────────────────── fn show_top_salient(limit: Int, report: String) -> String { section_header("Top " + int_to_str(limit) + " by Salience") let path: String = "/api/nodes?limit=" + int_to_str(limit) + "&min_salience=0.0" let json_str: String = api_get(path) if json_str == "" { println(" " + color_dim("No nodes or server unreachable")) return report } let nodes: List = json_parse(json_str) let n: Int = list_len(nodes) if n == 0 { println(" " + color_dim("(no nodes)")) return report } println(" " + color_dim("Salience ranking:")) println("") let report_section: String = "\n=== Top Salient Nodes ===\n" let i: Int = 0 while i < n { let node: String = json_stringify(list_get(nodes, i)) let id: String = short_id(safe_str(node, "id")) let label: String = safe_str(node, "label") let sal: Float = safe_float(node, "salience") let tier: String = safe_str(node, "tier") let bar: String = salience_bar(sal) let rank: String = str_pad_left(int_to_str(i + 1), 2, " ") println(" " + rank + ". " + bar + " " + format_float(sal, 3) + " " + color_dim(id) + " " + str_pad_right(label, 35, " ") + " " + color_dim(tier)) let report_section: String = report_section + rank + ". " + format_float(sal, 3) + " [" + id + "] " + label + " " + tier + "\n" let i: Int = i + 1 } return report + report_section } // ── Section: Node Detail ────────────────────────────────────────────────────── fn show_node_detail(node_id: String, report: String) -> String { section_header("Node Detail — " + str_slice(node_id, 0, 8) + "...") let path: String = "/api/nodes/" + node_id let json_str: String = api_get(path) if json_str == "" { println(" " + color_red("Node not found: " + node_id)) return report } render_node_detail(json_str) // Show neighbors let nb_path: String = "/api/neighbors/" + node_id + "?depth=2" let nb_json: String = api_get(nb_path) if nb_json != "" { let neighbors: List = json_parse(nb_json) let nb_n: Int = list_len(neighbors) if nb_n > 0 { println("") println(" " + color_bold("Neighbors (" + int_to_str(nb_n) + "):")) let j: Int = 0 while j < nb_n { let nb: String = json_stringify(list_get(neighbors, j)) // Use json_get_raw to extract nested objects as JSON strings let nb_node: String = json_get_raw(nb, "node") let nb_edge: String = json_get_raw(nb, "edge") let hops: Int = safe_int(nb, "hops") let nb_label: String = safe_str(nb_node, "label") let nb_id: String = short_id(safe_str(nb_node, "id")) let hop_str: String = str_pad_left(int_to_str(hops), 2, " ") let relation: String = safe_str(nb_edge, "relation") println(" hop " + hop_str + " " + color_dim(nb_id) + " " + color_cyan(relation) + " → " + nb_label) let j: Int = j + 1 } } } return report + "\n=== Node Detail: " + node_id + " ===\n" + "Fetched node details\n" } // ── Section: Search ─────────────────────────────────────────────────────────── fn show_search(query: String, limit: Int, report: String) -> String { section_header("Search: \"" + query + "\"") let path: String = "/api/search?q=" + query + "&limit=" + int_to_str(limit) let json_str: String = api_get(path) if json_str == "" { println(" " + color_dim("No results or server unreachable")) return report } let nodes: List = json_parse(json_str) let n: Int = list_len(nodes) println(" " + color_bold("Found " + int_to_str(n) + " results:")) println("") if n == 0 { println(" " + color_dim("(no matches)")) return report + "\n=== Search: " + query + " ===\nNo results\n" } let report_section: String = "\n=== Search: " + query + " ===\n" + int_to_str(n) + " results:\n" let i: Int = 0 while i < n { let node: String = json_stringify(list_get(nodes, i)) println(render_node_row(node, i)) let label: String = safe_str(node, "label") let report_section: String = report_section + int_to_str(i + 1) + ". " + label + "\n" let i: Int = i + 1 } return report + report_section } // ── Section: Tier Browser ───────────────────────────────────────────────────── fn show_nodes_by_tier(tier: String, limit: Int, report: String) -> String { let title: String = "Nodes — tier: " + tier section_header(title) let path: String = "/api/nodes?tier=" + tier + "&limit=" + int_to_str(limit) let json_str: String = api_get(path) if json_str == "" { println(" " + color_dim("No nodes or server unreachable")) return report } let nodes: List = json_parse(json_str) let n: Int = list_len(nodes) if n == 0 { println(" " + color_dim("(no " + tier + " tier nodes)")) return report } println(" " + tier_badge(tier) + " " + color_dim("Showing " + int_to_str(n) + " nodes")) println("") let i: Int = 0 let report_section: String = "\n=== " + tier + " Tier Nodes ===\n" while i < n { let node: String = json_stringify(list_get(nodes, i)) println(render_node_row(node, i)) let label: String = safe_str(node, "label") let id: String = short_id(safe_str(node, "id")) let report_section: String = report_section + "[" + id + "] " + label + "\n" let i: Int = i + 1 } return report + report_section } // ── Section: Spreading Activation ──────────────────────────────────────────── fn show_activation(seed_id: String, limit: Int, report: String) -> String { section_header("Spreading Activation — seed: " + str_slice(seed_id, 0, 8) + "...") let path: String = "/api/activate?seeds=" + seed_id + "&limit=" + int_to_str(limit) + "&depth=3" let json_str: String = api_get(path) if json_str == "" { println(" " + color_dim("No activation results or server unreachable")) return report } // Use json_get_raw to get the "results" array as a JSON string, then parse it let results_raw: String = json_get_raw(json_str, "results") let results: List = json_parse(results_raw) let n: Int = list_len(results) if n == 0 { println(" " + color_dim("(no spreading activation results — check seed ID)")) return report } println(" " + color_bold("Activated " + int_to_str(n) + " nodes:")) println("") let header: String = " " + str_pad_right(" # ID Label", 52, " ") + " Strength Hops" println(color_dim(header)) println(color_dim(" " + repeat_str("─", 68))) let report_section: String = "\n=== Activation from " + str_slice(seed_id, 0, 8) + " ===\n" let i: Int = 0 while i < n { let result: String = json_stringify(list_get(results, i)) // Use json_get_raw to extract nested node object as a JSON string let node: String = json_get_raw(result, "node") let strength: Float = safe_float(result, "activation_strength") let hops: Int = safe_int(result, "hops") let id: String = short_id(safe_str(node, "id")) let label: String = safe_str(node, "label") let rank: String = str_pad_left(int_to_str(i + 1), 3, " ") let strength_bar: String = salience_bar(strength) println(" " + rank + ". " + color_dim(id) + " " + str_pad_right(label, 36, " ") + " " + format_float(strength, 3) + " " + color_dim("hops:" + int_to_str(hops))) let report_section: String = report_section + rank + ". [" + id + "] " + label + " strength=" + format_float(strength, 3) + " hops=" + int_to_str(hops) + "\n" let i: Int = i + 1 } return report + report_section } // ── Section: Edge Statistics ────────────────────────────────────────────────── fn show_edges(limit: Int, report: String) -> String { section_header("Edge Explorer (sample of " + int_to_str(limit) + ")") let path: String = "/api/edges?limit=" + int_to_str(limit) let json_str: String = api_get(path) if json_str == "" { println(" " + color_dim("No edges or server unreachable")) return report } let edges: List = json_parse(json_str) let n: Int = list_len(edges) if n == 0 { println(" " + color_dim("(no edges in database)")) return report } println(" " + color_dim("Showing " + int_to_str(n) + " edges:")) println("") let header: String = " " + str_pad_right(" # From → To Relation", 56, " ") + " Weight" println(color_dim(header)) println(color_dim(" " + repeat_str("─", 68))) let report_section: String = "\n=== Edges ===\n" let i: Int = 0 while i < n { let edge: String = json_stringify(list_get(edges, i)) let from_id: String = short_id(safe_str(edge, "from_id")) let to_id: String = short_id(safe_str(edge, "to_id")) let relation: String = safe_str(edge, "relation") let weight: Float = safe_float(edge, "weight") let rank: String = str_pad_left(int_to_str(i + 1), 3, " ") println(" " + rank + ". " + color_dim(from_id) + " " + color_cyan("→") + " " + color_dim(to_id) + " " + color_green(str_pad_right(relation, 20, " ")) + " " + format_float(weight, 3)) let report_section: String = report_section + from_id + " -[" + relation + "]-> " + to_id + " w=" + format_float(weight, 3) + "\n" let i: Int = i + 1 } return report + report_section } // ── Section: Knowledge Browser (Concept nodes grouped by tag prefix) ────────── fn show_knowledge_browser(report: String) -> String { section_header("Knowledge Browser — Concept Nodes") let path: String = "/api/nodes?node_type=Concept&limit=50" let json_str: String = api_get(path) if json_str == "" { println(" " + color_dim("No concept nodes or server unreachable")) return report } let nodes: List = json_parse(json_str) let n: Int = list_len(nodes) if n == 0 { println(" " + color_dim("(no Concept nodes — these are your domain knowledge anchors)")) return report } println(" " + color_bold(int_to_str(n) + " concept nodes found:")) println("") let report_section: String = "\n=== Knowledge Browser ===\n" let i: Int = 0 while i < n { let node: String = json_stringify(list_get(nodes, i)) let id: String = short_id(safe_str(node, "id")) let label: String = safe_str(node, "label") let tier: String = safe_str(node, "tier") let sal: Float = safe_float(node, "salience") println(" " + color_green("◆") + " " + str_pad_right(label, 40, " ") + " " + color_dim(id) + " " + color_dim(tier) + " " + format_float(sal, 3)) let report_section: String = report_section + "◆ " + label + " [" + id + "] " + tier + "\n" let i: Int = i + 1 } return report + report_section } // ── Section: Mode Simulation (interactive mode preview) ────────────────────── fn show_mode_menu() -> String { let _ = section_header("Interactive Mode Preview") println(" " + color_bold("Available commands (when running interactively):")) println("") println(" " + color_cyan("stats") + " — show database statistics") println(" " + color_cyan("browse ") + " — browse nodes by type") println(" types: Memory, Concept, Event, Entity, Process, InternalState") println(" " + color_cyan("tier ") + " — browse nodes by tier") println(" tiers: Working, Episodic, Semantic, Procedural") println(" " + color_cyan("search ") + " — full-text search") println(" " + color_cyan("node ") + " — show node detail + neighbors") println(" " + color_cyan("activate ") + " — spreading activation from seed") println(" " + color_cyan("edges") + " — browse edges") println(" " + color_cyan("top") + " — top nodes by salience") println(" " + color_cyan("export ") + " — save text report to file") println(" " + color_cyan("help") + " — show this menu") println(" " + color_cyan("quit") + " — exit studio") println("") println(" " + color_dim("Tip: set ENGRAM_URL=http://host:port to connect to a different server")) return "" } // ── Export report ───────────────────────────────────────────────────────────── fn export_report(content: String, path: String) -> String { let _ = section_header("Report Export") let header: String = "ENGRAM DATA STUDIO — Report\n" + "Generated: " + time_format(time_now_utc(), "ISO") + "\n" + "Server: " + get_base_url() + "\n" + repeat_str("=", 60) + "\n" let ok: Bool = fs_write(path, header + content) if ok { println(" " + color_green("✓") + " Report saved to: " + color_bold(path)) println(" " + color_dim("Size: " + format_bytes(str_len(header + content)))) } else { println(" " + color_red("✗") + " Failed to write report to: " + path) } return "" } // ── Connectivity check ──────────────────────────────────────────────────────── fn check_connection() -> Bool { let resp: String = api_get("/api/stats") return resp != "" } // ═══════════════════════════════════════════════════════════════════════════════ // ── MAIN STUDIO ENTRY POINT ────────────────────────────────────────────────── // ═══════════════════════════════════════════════════════════════════════════════ let base_url: String = get_base_url() let WIDTH: Int = 64 // ── Banner ──────────────────────────────────────────────────────────────────── println("") println(color_bold(double_top(WIDTH))) println(color_bold(double_row(" ███████╗███╗ ██╗ ██████╗ ██████╗ █████╗ ███╗ ███╗", WIDTH))) println(color_bold(double_row(" ██╔════╝████╗ ██║██╔════╝ ██╔══██╗██╔══██╗████╗ ████║", WIDTH))) println(color_bold(double_row(" █████╗ ██╔██╗ ██║██║ ███╗██████╔╝███████║██╔████╔██║", WIDTH))) println(color_bold(double_row(" ██╔══╝ ██║╚██╗██║██║ ██║██╔══██╗██╔══██║██║╚██╔╝██║", WIDTH))) println(color_bold(double_row(" ███████╗██║ ╚████║╚██████╔╝██║ ██║██║ ██║██║ ╚═╝ ██║", WIDTH))) println(color_bold(double_row(" ╚══════╝╚═╝ ╚═══╝ ╚═════╝ ╚═╝ ╚═╝╚═╝ ╚═╝╚═╝ ╚═╝", WIDTH))) println(color_bold(double_row("", WIDTH))) println(color_bold(double_row(" DATA STUDIO — Native El Application", WIDTH))) println(color_bold(double_row("", WIDTH))) println(color_bold(double_bot(WIDTH))) println("") println(" " + color_bold("Server: ") + color_cyan(base_url)) println(" " + color_bold("Version: ") + color_dim("El-native · 10-loop pass")) println("") // ── Connection check ───────────────────────────────────────────────────────── let connected: Bool = check_connection() if connected { println(" " + color_green("●") + " " + color_bold("Connection established")) } else { println(" " + color_red("●") + " " + color_bold("Server unreachable")) println(" " + color_dim("Start the server: ENGRAM_BIND=0.0.0.0:8340 engram-server --data-dir ")) println(" " + color_dim("Or set ENGRAM_URL environment variable")) println("") println(" " + color_yellow("Continuing in offline mode — all sections will show placeholders")) } // ── Run all dashboard sections ───────────────────────────────────────────────── let report: String = "" // Section 1: Stats let report: String = show_stats(report) // Section 2: Recent nodes let report: String = show_recent(20, report) // Section 3: Top salient let report: String = show_top_salient(10, report) // Section 4: By node type — Memory let report: String = show_nodes("Memory", 10, report) // Section 5: By node type — Concept let report: String = show_nodes("Concept", 10, report) // Section 6: By node type — Event let report: String = show_nodes("Event", 8, report) // Section 7: By node type — Entity let report: String = show_nodes("Entity", 8, report) // Section 8: By node type — Process let report: String = show_nodes("Process", 8, report) // Section 9: Tier — Semantic let report: String = show_nodes_by_tier("Semantic", 10, report) // Section 10: Tier — Episodic let report: String = show_nodes_by_tier("Episodic", 10, report) // Section 11: Knowledge browser (Concept nodes) let report: String = show_knowledge_browser(report) // Section 12: Text search samples let report: String = show_search("memory", 10, report) let report: String = show_search("concept", 8, report) // Section 13: Edge explorer let report: String = show_edges(15, report) // Section 14: Interactive mode preview show_mode_menu() // ── Export report ────────────────────────────────────────────────────────────── let report_path: String = get_report_path() export_report(report, report_path) // ── Footer ──────────────────────────────────────────────────────────────────── println("") println(color_bold(" " + repeat_str("═", 60))) println(" " + color_bold("Engram Data Studio") + color_dim(" — session complete")) println(" " + color_dim(time_format(time_now_utc(), "ISO"))) println(color_bold(" " + repeat_str("═", 60))) println("")