using System.Text.Json; using System.Text.Json.Nodes; using ArtificersScrollwork.Sidecar.Parsing; using ArtificersScrollwork.Sidecar.Gumps; /// /// Entry point for the ASW C# sidecar. /// Reads newline-delimited JSON requests from stdin, writes responses to stdout. /// /// Request: { "id": "uuid", "command": "cmd_name", "args": { ... } } /// Response: { "id": "uuid", "ok": true, "data": { ... } } /// | { "id": "uuid", "ok": false, "error": "message" } /// var options = new JsonSerializerOptions { PropertyNamingPolicy = JsonNamingPolicy.SnakeCaseLower, WriteIndented = false, }; ScriptIndexer? indexer = null; CallChainTracer? tracer = null; while (true) { var line = Console.ReadLine(); if (line is null) break; JsonObject? request; try { request = JsonSerializer.Deserialize(line, options); } catch { continue; } if (request is null) continue; var id = request["id"]?.GetValue() ?? ""; var command = request["command"]?.GetValue() ?? ""; var reqArgs = request["args"] as JsonObject ?? new JsonObject(); try { JsonNode? data = command switch { "index_scripts" => HandleIndexScripts(reqArgs), "get_class" => HandleGetClass(reqArgs), "trace_method" => HandleTraceMethod(reqArgs), "extract_gump" => HandleExtractGump(reqArgs), "search" => HandleSearch(reqArgs), _ => throw new InvalidOperationException($"Unknown command: {command}"), }; WriteResponse(id, true, data, null, options); } catch (Exception ex) { WriteResponse(id, false, null, ex.Message, options); } } // ── Command handlers ────────────────────────────────────────────────────────── JsonNode HandleIndexScripts(JsonObject args) { var path = args["path"]?.GetValue() ?? throw new ArgumentException("Missing 'path' argument"); indexer = new ScriptIndexer(); var index = indexer.IndexDirectory(path); // Build a compilation for the tracer tracer = new CallChainTracer(indexer.Compilation ?? throw new InvalidOperationException("Compilation not available after indexing")); return JsonSerializer.SerializeToNode(index, options)!; } JsonNode HandleGetClass(JsonObject args) { EnsureIndexer(); var className = args["class"]?.GetValue() ?? throw new ArgumentException("Missing 'class' argument"); var info = indexer!.GetClass(className) ?? throw new KeyNotFoundException($"Class '{className}' not found in index"); return JsonSerializer.SerializeToNode(info, options)!; } JsonNode HandleTraceMethod(JsonObject args) { EnsureIndexer(); if (tracer is null) throw new InvalidOperationException("Tracer not initialized — send index_scripts first"); var className = args["class"]?.GetValue() ?? throw new ArgumentException("Missing 'class' argument"); var methodName = args["method"]?.GetValue() ?? throw new ArgumentException("Missing 'method' argument"); var flowNode = tracer.TraceMethod(className, methodName); return JsonSerializer.SerializeToNode(flowNode, options)!; } JsonNode HandleExtractGump(JsonObject args) { EnsureIndexer(); throw new NotImplementedException("extract_gump not yet implemented (Phase 4)"); } JsonNode HandleSearch(JsonObject args) { EnsureIndexer(); var query = args["query"]?.GetValue() ?? throw new ArgumentException("Missing 'query' argument"); var results = indexer!.Search(query); return JsonSerializer.SerializeToNode(results, options)!; } void EnsureIndexer() { if (indexer is null) throw new InvalidOperationException("Scripts not indexed — send index_scripts first"); } static void WriteResponse(string id, bool ok, JsonNode? data, string? error, JsonSerializerOptions opts) { var response = new JsonObject { ["id"] = id, ["ok"] = ok, }; if (ok && data is not null) response["data"] = data; else if (!ok) response["error"] = error ?? "Unknown error"; Console.WriteLine(JsonSerializer.Serialize(response, opts)); Console.Out.Flush(); }