diff --git a/README.md b/README.md index 6b843ad29..b40ffa990 100644 --- a/README.md +++ b/README.md @@ -256,7 +256,7 @@ OfficeCLI is self-contained. The capabilities below ship inside the binary — * #### Rendering engine — high-fidelity, built-in -OfficeCLI's keystone: a from-scratch, high-fidelity HTML rendering engine that lets an AI agent *see* the rendered document instead of guessing from the DOM. It covers shapes, charts (trendlines, error bars, waterfall, candlestick, sparklines), equations (OMML → LaTeX, rendered with KaTeX), 3D `.glb` models via Three.js, morph transitions, slide zoom, and shape effects. Per-page PNG screenshots are produced by piping the rendered HTML through a headless browser. Three modes: +OfficeCLI's keystone: a from-scratch, high-fidelity HTML rendering engine that lets an AI agent *see* the rendered document instead of guessing from the DOM. It covers shapes, charts (trendlines, error bars, waterfall, candlestick, sparklines), equations (OMML → LaTeX, rendered with KaTeX), 3D `.glb` models via Three.js, morph transitions, slide zoom, and shape effects. Per-page PNG screenshots are produced by piping the rendered HTML through a headless browser. `diagram --prop render=image` renders with the real mermaid.js in that same browser, caching mermaid.min.js locally on first use (mirror → CDN); set `OFFICECLI_MERMAID_JS=/path/to/mermaid.min.js` to point it at a local copy instead and skip the network entirely (offline / sandboxed hosts). Three modes: - **`view html`** — standalone HTML file, assets inlined. Open in any browser. - **`view screenshot`** — per-page PNG, ready for multimodal agents to read. diff --git a/src/officecli/Core/Diagram/MermaidImageRenderer.cs b/src/officecli/Core/Diagram/MermaidImageRenderer.cs index d766672f2..7f5c21b24 100644 --- a/src/officecli/Core/Diagram/MermaidImageRenderer.cs +++ b/src/officecli/Core/Diagram/MermaidImageRenderer.cs @@ -23,7 +23,9 @@ namespace OfficeCli.Core.Diagram; /// Chrome-family browser the user already has (via /// ): render mermaid.js in a page and screenshot it. /// Only mermaid.min.js (~3.5 MB) is fetched to a local cache on first use -/// (mirror → CDN); if that fails the page loads mermaid from the CDN live. +/// (mirror → CDN); if that fails the page loads mermaid from the CDN live. +/// OFFICECLI_MERMAID_JS points at a local mermaid.min.js and skips the +/// network entirely, for offline / sandboxed hosts. /// otherwise the caller falls back to the native synthesizer /// () — zero dependencies, fully editable shapes. /// @@ -180,6 +182,18 @@ private static bool SourceNeedsElk(string source) Environment.GetFolderPath(Environment.SpecialFolder.UserProfile), ".officecli", "cache"); private static string CachedJsPath => Path.Combine(CacheDir, $"mermaid-{MermaidVersion}.min.js"); + /// Explicit local mermaid.min.js override for offline / sandboxed hosts + /// that cannot reach the mirror or the CDN. Wins over the cache and skips the + /// download + refresh path entirely. + private static string? MermaidJsOverride + { + get + { + var p = Environment.GetEnvironmentVariable("OFFICECLI_MERMAID_JS"); + return !string.IsNullOrWhiteSpace(p) && File.Exists(p) ? p : null; + } + } + /// True when any image backend is available: mmdc, or a chrome-family browser. public static bool IsAvailable() => TryLocateMmdc(out _) || HtmlScreenshot.HasChromeFamily(); @@ -195,6 +209,7 @@ public static void RefreshCacheIfPresent() { try { + if (MermaidJsOverride != null) return; // explicit local asset — never touch the network if (!File.Exists(CachedJsPath)) return; // refresh only what the user actually uses using var http = new HttpClient { Timeout = TimeSpan.FromSeconds(20) }; using var req = new HttpRequestMessage(HttpMethod.Get, MirrorUrl); @@ -396,6 +411,9 @@ private static string RenderViaChrome(string mermaid, string? background) /// <script src> (a file:// for a cached/downloaded copy, else the CDN). private static string ResolveMermaidJsRef() { + if (MermaidJsOverride is { } overridePath) + return new Uri(Path.GetFullPath(overridePath)).AbsoluteUri; + try { if (File.Exists(CachedJsPath) && new FileInfo(CachedJsPath).Length > 500_000)