using Microsoft.AspNetCore.Mvc; using Nexus.Api.Helpers; namespace Nexus.Api.Controllers; [ApiController] [Route("api/v1/docs")] public class DocsController : ControllerBase { [HttpGet] public IResult GetAll() { var workspaceRoot = "/mnt/workspace-iris"; var results = new List(); void ScanDir(string dir, string category) { if (!Directory.Exists(dir)) return; foreach (var file in Directory.GetFiles(dir, "*.*")) { var ext = Path.GetExtension(file).ToLowerInvariant(); if (ext is not (".md" or ".json" or ".txt" or ".yaml" or ".yml" or ".html" or ".css")) continue; var fi = new FileInfo(file); results.Add(new { name = fi.Name, path = file.Replace(workspaceRoot, "").TrimStart('/'), category, type = ext.Replace(".", ""), size = fi.Length, modifiedAt = fi.LastWriteTimeUtc }); } } ScanDir("/mnt/workspace-iris/nexus-phases", "phases"); ScanDir("/mnt/workspace-iris/skills", "skills"); ScanDir("/mnt/workspace-iris", "workspace"); ScanDir("/home/node/.openclaw/workspace/nexus", "nexus"); ScanDir("/home/node/.openclaw/workspace/nexus/phases", "nexus-phases"); return Results.Ok(results.OrderByDescending(x => ((DateTime)((dynamic)x).modifiedAt)).Take(100)); } [HttpGet("{**path}")] public async Task GetFile(string path) { if (string.IsNullOrWhiteSpace(path)) return Results.BadRequest("Path required."); string? resolvedPath = null; foreach (var root in new[] { "/mnt/workspace-iris", "/home/node/.openclaw/workspace/nexus" }) { if (PathSecurityHelper.TryResolveSafePath(root, path, out var candidate) && System.IO.File.Exists(candidate)) { resolvedPath = candidate; break; } } if (resolvedPath is null) return Results.NotFound(); var content = await System.IO.File.ReadAllTextAsync(resolvedPath); var fi = new FileInfo(resolvedPath); return Results.Ok(new { name = fi.Name, path = resolvedPath.Replace("/mnt/workspace-iris/", "").Replace("/home/node/.openclaw/workspace/nexus/", ""), content, size = fi.Length, modifiedAt = fi.LastWriteTimeUtc }); } }