Packages

Prompt Runner SDK - packet-first prompt execution for Elixir and CLI workflows with verifier-owned completion, retry, repair, and git-aware repository orchestration.

Current section

Files

Jump to
prompt_runner_sdk lib prompt_runner rendering sinks file.ex
Raw

lib/prompt_runner/rendering/sinks/file.ex

defmodule PromptRunner.Rendering.Sinks.FileSink do
@moduledoc """
A sink that writes rendered output to a log file with ANSI codes stripped.
"""
@behaviour PromptRunner.Rendering.Sink
@impl true
def init(opts) do
case Keyword.fetch(opts, :io) do
{:ok, io} -> {:ok, %{path: nil, io: io, owns_io: false}}
:error -> init_from_path(opts)
end
end
defp init_from_path(opts) do
case Keyword.fetch(opts, :path) do
{:ok, path} ->
case File.open(path, [:write, :utf8]) do
{:ok, io} -> {:ok, %{path: path, io: io, owns_io: true}}
{:error, reason} -> {:error, "Failed to open #{path}: #{inspect(reason)}"}
end
:error ->
{:error, "path or io option is required"}
end
end
@impl true
def write(iodata, state) do
text = IO.iodata_to_binary(iodata)
IO.binwrite(state.io, strip_ansi(text))
{:ok, state}
end
@impl true
def write_event(_event, iodata, state) do
text = IO.iodata_to_binary(iodata)
IO.binwrite(state.io, strip_ansi(text))
{:ok, state}
end
@impl true
def flush(state) do
:file.sync(state.io)
{:ok, state}
end
@impl true
def close(%{owns_io: true} = state) do
File.close(state.io)
:ok
end
def close(_state), do: :ok
defp strip_ansi(text) do
String.replace(text, ~r/\x1b\[[0-9;]*[mABCDHJK]|\r(?!\n)/, "")
end
end