Ryn.Cli 0.27.4

dotnet tool install --global Ryn.Cli --version 0.27.4
                    
This package contains a .NET tool you can call from the shell/command line.
dotnet new tool-manifest
                    
if you are setting up this repo
dotnet tool install --local Ryn.Cli --version 0.27.4
                    
This package contains a .NET tool you can call from the shell/command line.
#tool dotnet:?package=Ryn.Cli&version=0.27.4
                    
nuke :add-package Ryn.Cli --version 0.27.4
                    

Ryn: Rich Yet Native

Build & Test NativeAOT NuGet License: MIT

Rich Yet Native. A lightweight, cross-platform .NET framework for building desktop apps with web UIs.

Created & maintained by Moh

Ryn gives .NET developers the Tauri experience without leaving C#. You write the UI in HTML, CSS, and JavaScript, the backend in C#, and ship a small NativeAOT binary that runs on the operating system's own webview.

Why Ryn?

  • C# backend, web frontend: write the UI in HTML/CSS/JS and the backend in C#, wired together by [RynCommand] source-generated IPC.
  • Lightweight: uses the native OS webview (WebView2, WKWebView, WebKitGTK) instead of bundling Chromium.
  • NativeAOT: small, self-contained binaries (~5 MB) with no runtime dependency.
  • Cross-platform: Windows, macOS, and Linux.
  • Plugin system: FileSystem, Dialog (native pickers), Clipboard, Shell (spawn/PTY streaming), Notification (with activation events), Audio, Tray, MenuBar (native menus + roles), Badge (dock/taskbar), GlobalShortcut (system-wide hotkeys), WebViewPane (embedded browser panes), and a signed Auto-updater.
  • Embedded browser panes: N real webviews per window (no bundled Chromium) with per-pane sessions, user agents, background colors, find-in-page, screenshots, downloads, permission prompts, crash recovery, suspension, and a CDP passthrough on Windows.
  • Native chrome control: overlay/hidden title bars driven by declarative data-webview-* attributes, traffic-light positioning, window page zoom with coordinate-safe panes/drag regions, and backdrop materials (blur / acrylic / mica).
  • Security model: ryn.json capability scopes, deny-by-default.
  • Branded by default: every window and bundled .app/installer ships with the Ryn icon, overridable per app.

Why not an existing framework?

Before building Ryn I went through the existing options for desktop .NET with a web frontend. Each is good at what it does, but none gave me what I wanted: Tauri's ergonomics (native webviews, tiny binaries, a real plugin and capability model, a scaffolding CLI) without leaving C#.

  • Tauri: the inspiration, and a great tool. The catch is the backend is Rust. Ryn exists so a .NET team can get the Tauri experience without rewriting their backend in another language.
  • Photino: the closest .NET option, a thin wrapper over the same native webviews. It's deliberately minimal, with no plugin system, no capability-based security, no IPC source generator, and no CLI or project scaffolding. Ryn adds those on top of the same idea.
  • Electron.NET and CefSharp: both bundle Chromium, so apps start around 100 MB and can't use NativeAOT. Ryn uses the OS's own webview (WebView2 / WKWebView / WebKitGTK) and ships ~5 MB AOT binaries.
  • .NET MAUI Blazor Hybrid: Microsoft's official answer, but it pulls in the whole MAUI stack and a XAML host shell. It's heavy, mobile-first, and doesn't give you a plain "bring your own HTML/CSS/JS" frontend. Ryn is desktop-only and much lighter.
  • Avalonia and Uno Platform: both are solid, but they're XAML frameworks that render their own controls rather than host a web frontend. If you want to use HTML/CSS/JS and the front-end tools you already know, that's a different model.

None of them gave me Tauri-style ergonomics on a native webview, with NativeAOT and a real security model, in C#. So I built Ryn.

At a glance

How Ryn compares on the axes that matter for a small, native, web-UI desktop app. Cells reflect Ryn's current alpha state, not a future roadmap.

Ryn Tauri v2 Photino Electron.NET MAUI Blazor Hybrid
Backend language C# Rust C# C# C#
Frontend HTML/CSS/JS HTML/CSS/JS HTML/CSS/JS HTML/CSS/JS Blazor
Renderer OS webview OS webview OS webview bundled Chromium OS webview
Hello-world binary ~5 MB ~3–10 MB ~1 MB wrapper¹ ~100+ MB tens of MB
NativeAOT yes n/a (Rust) no no no
First-party plugins 12 many none none n/a
Capability sandbox yes (deny-by-default) yes no no no
IPC source generator yes n/a no no n/a
Scaffold/dev/bundle CLI yes yes no partial dotnet
Signed auto-updater yes yes no no no
Multi-window yes² yes yes yes limited
Embedded browser panes yes experimental no yes (Chromium) no
Native app menus yes yes partial yes yes
Global shortcuts yes yes no yes no
Mobile no (desktop-only) yes no no yes

¹ Photino is a thin wrapper; the deployed size depends on your own .NET app and runtime. The point of the row is the relative weight of the webview layer, not a head-to-head app size.

² Multi-window: the API (open/track/close, per-window IPC) is complete on all platforms. On macOS a window opened after launch may currently paint only its background — a WebKit/saucer first-paint limitation; see docs/multi-window.md.

These numbers are for orientation, not a benchmark claim; they vary by platform, runtime mode (self-contained vs framework-dependent), and trimming. Ryn's ~5 MB figure is a NativeAOT hello-world on macOS arm64 (a full app pulling in every plugin is ~5.6 MB).

Non-goals

To keep the comparison honest, Ryn deliberately does not try to be some things its peers are:

  • Not a mobile framework. Ryn is desktop-only (macOS, Windows, Linux). For iOS/Android, use Tauri v2 or MAUI.
  • Not a XAML/native-control UI. Ryn hosts a web frontend in the OS webview. If you want pixel-identical, control-based rendering, use Avalonia or Uno.
  • Not pixel-perfect cross-platform rendering. Because each OS uses its own webview engine, rendering can differ subtly between WebKit, WebKitGTK, and Chromium (WebView2).
  • Not a managed-Chromium bundle. Ryn never ships Chromium; it relies on the OS webview, which is the source of its small size and also means feature support tracks the host OS.
  • Not a Blazor host (yet). Blazor WebAssembly hosting is under consideration but not implemented; today the frontend is HTML/CSS/JS.

Status

Alpha. Ryn runs on macOS, Windows, and Linux, all exercised by real third-party desktop apps. Linux's first broad Primal Launcher test found one Ryn defect—the notification plugin competing with GTK for GLib's default main context—which is fixed in v0.27.4. Treat Linux as near-full support while the notification fix completes its downstream retest and the remaining capability-specific caveats below are validated.

Platform support matrix

Legend: ✅ verified on a real app · 🟡 implemented, with a capability-specific retest or caveat remaining · ⚪ not implemented

Capability macOS Windows Linux
Window + WebView (saucer)
IPC (ryn:// scheme + local server)
FileSystem plugin
Dialogs / file pickers ✅ (osascript) ✅ (PowerShell+WinForms) ✅ (zenity/kdialog)
Clipboard (text) ✅ (X11 via xclip, Wayland via wl-clipboard)
Clipboard (image)
Notifications 🟡 (v0.27.4 fix awaiting downstream retest)
Audio playback 🟡 🟡
Shell / PTY
Tray icon ✅ (menu-only; no icon-click event)
Menu bar ✅ (accelerators display-only) ❌ (header bars are the GTK norm)
App badge ✅ (Dock) ✅ (taskbar overlay) ❌ (no portable badge surface)
Global shortcuts ❌ (Wayland needs the portal API)
WebView panes (embedded browser) ✅ (CSS zoom) ✅ (CSS zoom)
Pane extras (find, screenshot, downloads, crash recovery, suspend) ✅ (+ CDP passthrough)
Custom title bars (data-webview-*)
Window backdrop (blur / acrylic / mica) ✅ (vibrancy) ✅ (acrylic/mica) ⚪ (degrades to solid)
Auto-updater (signed) 🟡
NativeAOT publish 🟡

Native libraries are committed for osx-arm64; win-x64/linux-x64 are built in CI. Linux's core GUI and plugin paths have now been exercised in Primal Launcher; the remaining yellow entries need focused validation rather than first-run platform bring-up.

Installation

dotnet new console -n MyApp
cd MyApp
dotnet add package Ryn

The Ryn package bundles the whole framework in a single reference: Ryn.Core, Ryn.Ipc (with the [RynCommand] source generator), and Ryn.Interop (with the native webview libraries). The individual packages are also published if you'd rather reference them one at a time.

Windows: the default dotnet new console template emits top-level statements with an implicit MTA entry point, which WebView2 cannot use. Replace Program.cs with an explicit [STAThread] static void Main() (see Windows requirements) or scaffold with ryn new, which generates a Windows-safe entry point for you. On macOS and Linux either shape works.

Add plugins as needed:

dotnet add package Ryn.Plugins.FileSystem
dotnet add package Ryn.Plugins.Dialog
dotnet add package Ryn.Plugins.Clipboard
dotnet add package Ryn.Plugins.Shell
dotnet add package Ryn.Plugins.Notification
dotnet add package Ryn.Plugins.Audio
dotnet add package Ryn.Plugins.Tray
dotnet add package Ryn.Plugins.MenuBar
dotnet add package Ryn.Plugins.Badge
dotnet add package Ryn.Plugins.GlobalShortcut
dotnet add package Ryn.Plugins.WebViewPane
dotnet add package Ryn.Plugins.Updater

Install the CLI tool:

dotnet tool install -g Ryn.Cli

Option 2: Build from Source

Requires .NET 10 SDK, Git, and GitHub CLI (gh).

macOS:

git clone --recursive https://github.com/Yupmoh/Ryn.git
cd Ryn
bash build/download-native.sh         # downloads prebuilt saucer libs
dotnet build Ryn.slnx
dotnet test Ryn.slnx
dotnet run --project samples/Showcase  # run the demo app

Windows:

git clone --recursive https://github.com/Yupmoh/Ryn.git
cd Ryn
.\build\download-native.ps1           # downloads prebuilt saucer libs
dotnet build Ryn.slnx
dotnet test Ryn.slnx
dotnet run --project samples\Showcase

Linux:

git clone --recursive https://github.com/Yupmoh/Ryn.git
cd Ryn
sudo apt-get install libwebkitgtk-6.0-dev  # Ubuntu/Debian
bash build/download-native.sh
dotnet build Ryn.slnx
dotnet run --project samples/Showcase

To build native saucer libs from source (requires cmake + ninja):

bash build/build-native.sh

Check your environment with ryn doctor (or dotnet run --project src/Ryn.Cli -- doctor from source).

Creating an App

With the CLI

ryn new MyApp
cd MyApp
ryn dev

Or from source:

dotnet run --project src/Ryn.Cli -- new MyApp
cd MyApp
dotnet run

When run from within the Ryn source tree, ryn new generates project references. Otherwise it uses NuGet package references. The generated app includes a sample IPC command, a dark-themed HTML frontend, and a ryn.json capability file.

Content serving

Ryn serves frontend files via the ryn:// custom scheme, keeping everything same-origin with IPC (no CORS issues). There are three ways to provide content:

// Option 1 (ContentDirectory): serve files from disk, recommended for dev
opts.ContentDirectory = Path.Combine(AppContext.BaseDirectory, "wwwroot");

// Option 2 (Html): inline HTML string
opts.Html = "<html>...</html>";

// Option 3 (Url): external URL, e.g. a Vite dev server
opts.Url = new Uri("http://localhost:5173");

With ContentDirectory, files are read from disk on each request, so changes show up on browser refresh without restarting the app.

Windows requirements

On Windows, the entry point must use [STAThread] with a synchronous Main method. Without it, WebView2 initialization deadlocks silently. Ryn detects this at runtime and throws a clear error.

public static class Program
{
    [System.STAThread]
    public static void Main()
    {
        var app = RynApplication.CreateBuilder()
            // ...
            .Build();

        app.Run(); // synchronous, blocks until the window closes
    }
}

Do not use async Task Main or top-level statements on Windows. Both default to MTA, which is incompatible with WebView2's COM requirements.

How IPC works

Mark C# methods with [RynCommand]. The source generator builds the dispatch tables at compile time:

using Ryn.Ipc;

public static class MyCommands
{
    [RynCommand("app.greet")]
    public static string Greet(string name) => $"Hello, {name}!";

    [RynCommand("app.add")]
    public static int Add(int a, int b) => a + b;
}

Call them from JavaScript:

const greeting = await window.__ryn.invoke('app.greet', { name: 'World' });
const sum = await window.__ryn.invoke('app.add', { a: 2, b: 3 });

Every command name is plugin-prefixed (app.* for your own commands, fs.*, clipboard.*, etc. for plugins) so capabilities can grant or deny by prefix.

Supported parameter/return types: int, long, float, double, bool, string, JsonElement, primitive arrays (int[], string[]), nullable types (int?), and complex DTOs via [RynJsonContext].

Security with ryn.json

Control what the frontend can access:

{
  "capabilities": {
    "app": true,
    "fs": {
      "allow": ["readTextFile", "readDir"],
      "scope": ["$APP_DATA"]
    },
    "shell": {
      "allow": ["execute"],
      "scopedCommands": [
        { "name": "git", "args": ["status"] }
      ]
    },
    "clipboard": true,
    "notification": true
  }
}

"app": true grants your own app.* commands; plugins are granted by their prefix. The shell plugin uses scopedCommands (argv templates) rather than a bare binary list, so each allowed argument is matched by an exact literal or a regex validator.

A present ryn.json denies every command by default; only what you list is allowed. A missing ryn.json depends on the build: a Debug build falls back to allow-all for local convenience, while a Release build fails closed and denies everything (and logs a one-time startup warning) so a mis-deployed app never ships wide open. Always ship a ryn.json. Empty scope: [] or commands: [] = explicit deny-all. See SECURITY.md for the full model.

Bundling for Distribution

dotnet run --project src/Ryn.Cli -- bundle

Options:

  • --aot: enable NativeAOT publishing
  • --self-contained: include the .NET runtime
  • --icon path/to/icon.png: override the app icon (a PNG is auto-converted to .icns on macOS / .ico on Windows; an .icns/.ico is used as-is)
  • --sign "Developer ID Application: ...": code sign with hardened runtime + secure timestamp (macOS)
  • --entitlements path/to.entitlements: entitlements applied while signing (macOS)
  • --notarize: submit for Apple notarization (macOS; uses --notary-profile, default notarize)
  • --dmg: also produce a compressed .dmg disk image (macOS)
  • --version 1.0.0: set the bundle version

When no icon is supplied (via --icon or ryn.jsonbundle.icon), the bundle is branded with the Ryn default icon: a real dock icon (AppIcon.icns) on macOS, an .ico on Windows, and a hicolor PNG on Linux.

Output:

  • macOS: .app bundle with Info.plist and AppIcon.icns
  • Windows: Self-contained folder with executable + WiX .wxs
  • Linux: AppDir structure (use appimagetool to create an AppImage)

Sample Apps

Sample Description Run
HelloWindow Minimal IPC demo dotnet run --project samples/HelloWindow
Showcase Full-featured demo with all plugins dotnet run --project samples/Showcase
ViteApp URL-backed frontend for Vite dev servers dotnet run --project samples/ViteApp
TerminalApp Terminal with shell.execute and streaming metrics dotnet run --project samples/TerminalApp
FileManager File browser with breadcrumb nav and preview dotnet run --project samples/FileManager
MarkdownEditor Split-pane editor with live preview and native dialogs dotnet run --project samples/MarkdownEditor
VueApp Vue 3 + Vite frontend with typed IPC dotnet run --project samples/VueApp
MultiWindow Multiple windows with per-window IPC dotnet run --project samples/MultiWindow
DevKit Developer toolkit exercising every Ryn capability dotnet run --project samples/DevKit

Project Structure

src/
  Ryn                  Bundle package: Core + Ipc + Interop in one PackageReference
  Ryn.Core             Window management, app lifecycle, configuration, events
  Ryn.Interop          Auto-generated saucer C bindings via ClangSharp
  Ryn.Ipc              JS <> C# IPC bridge, source generator, capabilities, observability
  Ryn.Plugins.*        FileSystem, Dialog, Clipboard, Shell, Notification, Audio, Tray, MenuBar, Badge, GlobalShortcut, WebViewPane, Updater
  Ryn.Cli              CLI: new, dev, build, bundle, doctor, updater keygen
samples/               9 example applications
templates/             dotnet new template pack
tests/                 600+ xUnit tests across 7 test projects
benchmarks/            BenchmarkDotNet suites (IPC, marshaling, JSON, escaping)
docs/
  getting-started.md   Walkthrough from install to bundle
  architecture.md      IPC pipeline, threading model, security internals
  capabilities.md      Canonical ryn.json capability schema reference
  plugin-authoring.md  Guide for writing Ryn plugins
  vite-integration.md  Using Vite and TypeScript with Ryn
  custom-title-bars.md Frameless title bars: data-webview-* contract and window controls
  multi-window.md      Opening and managing multiple windows
  webview-panes.md     Embedded browser panes: layout, sessions, lifecycle, downloads
  window-backdrop.md   Backdrop materials: vibrancy, acrylic, mica
  notifications.md     Notifications and activation events
  gpu-rendering.md     GPU/WebGL behavior per platform webview
  accessibility-and-i18n.md  Current a11y / i18n stance
  ROADMAP.md           Planned work beyond the current alpha

Documentation

Author

Ryn is designed, built, and maintained by Moh. It started as a one-person effort to bring the Tauri developer experience to .NET, and it's still mostly solo-maintained. If Ryn is useful to you, a star on the repo helps. Contributions are welcome too; see CONTRIBUTING.md.

Acknowledgements

Ryn builds on saucer, the C++ webview library whose C bindings it wraps for native windows and webviews. Thanks also to the .NET, ClangSharp, and NativeAOT teams.

License

MIT © Moh

Product Compatible and additional computed target framework versions.
.NET net10.0 is compatible.  net10.0-android was computed.  net10.0-browser was computed.  net10.0-ios was computed.  net10.0-maccatalyst was computed.  net10.0-macos was computed.  net10.0-tvos was computed.  net10.0-windows was computed. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.

This package has no dependencies.

Version Downloads Last Updated
0.27.4 44 7/26/2026
0.27.3 49 7/22/2026
0.27.2 87 7/20/2026
0.27.1 90 7/19/2026
0.27.0 90 7/16/2026
0.26.0 90 7/16/2026
0.25.0 95 7/11/2026
0.24.0 89 7/11/2026
0.23.0 85 7/11/2026
0.22.1 89 7/11/2026
0.22.0 105 7/10/2026
0.21.0 99 7/10/2026
0.20.2 96 7/10/2026
0.20.1 102 7/10/2026
0.20.0 104 7/9/2026
0.19.0 92 7/9/2026
0.18.0 90 7/9/2026
0.17.0 90 7/9/2026
0.16.0 90 7/9/2026
0.15.0 91 7/9/2026
Loading failed