InkkOops

Runtime and CLI

This page explains how InkkOops is started, how startup work differs from attach-mode work, how the companion CLI maps onto the runtime service, and which operational guarantees you can rely on.

Entry points

  • App command line. Start the host app with InkkOops-specific flags such as --inkkoops-script or --inkkoops-recording.
  • Companion CLI. Use InkkOops.Cli to list scripts, launch startup runs, attach to a running app, or launch recording mode.

Both paths end up driving the same in-process runtime service.

App command line flags

  • --inkkoops-script <name>: run a startup script.
  • --inkkoops-recording <path>: replay a recording at startup.
  • --inkkoops-record: record user interaction and write the recording bundle on normal shutdown.
  • --inkkoops-artifacts <path>: override the run artifact root.
  • --inkkoops-record-root <path>: override the recording root.
  • --inkkoops-pipe <name>: override the attach-mode pipe name.
  • --inkkoops-disable-retained: disable retained rendering for renderer diagnostics.
  • --inkkoops-disable-dirty: disable dirty-region rendering for renderer diagnostics.

CLI commands

dotnet run --project InkkOops.Cli/InkkOops.Cli.csproj -- list

dotnet run --project InkkOops.Cli/InkkOops.Cli.csproj -- run --script "sidebar-button-richtextbox" --launch

dotnet run --project InkkOops.Cli/InkkOops.Cli.csproj -- run --script "sidebar-button-richtextbox" --attach

dotnet run --project InkkOops.Cli/InkkOops.Cli.csproj -- record --launch

The CLI resolves its launch target through a resolver abstraction. In this repository, the default resolver falls back to InkkSlinger.csproj, but you can also provide --project <path> explicitly.

Startup runs vs attach runs

Startup script run

The app launches, waits for initial layout, runs the configured startup script, writes artifacts, and exits automatically.

Startup recording replay

The app launches, converts the recording into a replay script, runs it, writes artifacts, and exits automatically.

Attach mode

The app is already running. The request arrives over the named pipe and executes without forcing the app to exit afterward.

Recording mode

The app records human interaction. The recording is written when the app closes normally.

Serialization and concurrency

Attach mode is serialized. The runtime accepts only one queued or active request at a time. If a script is already queued or running, the attach response is Busy.

Automatic exit behavior

  • Startup script runs exit automatically after completion.
  • Startup recording replays exit automatically after completion.
  • Attach-mode requests do not exit the app.
  • Recording mode writes output on normal shutdown and does not force early exit.

Examples

Startup script:

dotnet run --project InkkSlinger.csproj -- --inkkoops-script "datagrid-workbench-capture"

Startup replay with custom artifacts:

dotnet run --project InkkSlinger.csproj -- `
  --inkkoops-recording ".\artifacts\inkkoops-recordings\20260328-001157740-recorded-session\recording.json" `
  --inkkoops-artifacts "artifacts/my-replay-run"

Attach mode with explicit pipe:

dotnet run --project InkkOops.Cli/InkkOops.Cli.csproj -- run `
  --script "buttons-resize-hover-repro" `
  --attach `
  --pipe "inkkoops-dev" `
  --timeout 45000 `
  --artifacts "artifacts/inkkoops-dev"

Operational guidance

  • Use startup runs for cold-start reproducibility.
  • Use attach mode when you want to keep the app open and iterate quickly.
  • Use dedicated artifact roots when comparing multiple runs.
  • Use the renderer diagnostic switches only when isolating renderer-specific bugs.

xUnit example

using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Text.Json;
using Xunit;

public sealed class ControlsCatalogRuntimeTests
{
    [Fact]
    public void StartupScript_Run_Writes_Artifacts()
    {
        var repoRoot = Environment.CurrentDirectory;
        var artifactRoot = Path.Combine(repoRoot, "artifacts", "inkkoops-xunit");

        var startInfo = new ProcessStartInfo
        {
            FileName = "dotnet",
            WorkingDirectory = repoRoot,
            UseShellExecute = false
        };

        startInfo.ArgumentList.Add("run");
        startInfo.ArgumentList.Add("--project");
        startInfo.ArgumentList.Add("InkkSlinger.csproj");
        startInfo.ArgumentList.Add("--");
        startInfo.ArgumentList.Add("--inkkoops-script");
        startInfo.ArgumentList.Add("sidebar-button-richtextbox");
        startInfo.ArgumentList.Add("--inkkoops-artifacts");
        startInfo.ArgumentList.Add(artifactRoot);

        using var process = Process.Start(startInfo)!;
        Assert.True(process.WaitForExit(120_000));
        Assert.Equal(0, process.ExitCode);

        var runDirectory = Directory.GetDirectories(artifactRoot).Single();
        using var document = JsonDocument.Parse(File.ReadAllText(Path.Combine(runDirectory, "result.json")));
        Assert.Equal("Completed", document.RootElement.GetProperty("status").GetString());
    }
}