Files
Artificers-Scrollwork/sidecar/Program.cs
2026-06-05 20:53:53 -05:00

141 lines
4.3 KiB
C#

using System.Text.Json;
using System.Text.Json.Nodes;
using ArtificersScrollwork.Sidecar.Parsing;
using ArtificersScrollwork.Sidecar.Gumps;
/// <summary>
/// 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" }
/// </summary>
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<JsonObject>(line, options); }
catch { continue; }
if (request is null) continue;
var id = request["id"]?.GetValue<string>() ?? "";
var command = request["command"]?.GetValue<string>() ?? "";
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<string>()
?? 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<string>()
?? 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<string>()
?? throw new ArgumentException("Missing 'class' argument");
var methodName = args["method"]?.GetValue<string>()
?? 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<string>()
?? 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();
}