# `ClaudeWrapper.DuplexIEx`
[🔗](https://github.com/genagent/claude_wrapper_ex/blob/main/lib/claude_wrapper/duplex_iex.ex#L1)

Interactive helpers for driving `ClaudeWrapper.DuplexSession` from IEx.

Spawns a long-lived `claude` subprocess, holds it across many turns,
and prints streaming text deltas to stdout as they arrive. Sibling to
`ClaudeWrapper.IEx`, which uses the simpler per-call `Query`/`Session`
flow.

## Usage

    iex> import ClaudeWrapper.DuplexIEx

    iex> start(working_dir: ".")
    Claude session started.

    iex> say("Explain the README briefly.")
    ...streams text...
    ($0.0123, 1 turn)
    :ok

    iex> say("Now suggest a one-line tagline.")
    ...streams text...
    :ok

    iex> interrupt()
    :ok

    iex> close()
    :ok

## When to use this vs `ClaudeWrapper.IEx`

Both are interactive REPL surfaces. `ClaudeWrapper.IEx` (the
pre-existing one) spawns a fresh `claude` subprocess per turn and is
the simplest thing that works for casual use. `ClaudeWrapper.DuplexIEx`
holds one subprocess open, so subsequent turns avoid CLI cold start
and the experience feels more like talking to a chat UI -- partial
tokens appear as they're generated, mid-turn cancel via `interrupt/0`
is clean, and a permission callback can react to tool requests in
flight.

## Defaults

- Working dir defaults to `File.cwd!()` so the session sees your
  current project
- With no `:on_permission` callback, the session is started with
  `--dangerously-skip-permissions` so tool calls just work in the
  REPL. **This is not safe** for prompts you do not control. For
  trustworthy use, pass an `:on_permission` callback: its presence
  automatically selects `--permission-mode default` so every tool
  request is routed to your callback instead of being bypassed
  (see `ensure_permission_mode/2`). There is no `permission_mode:`
  option -- the callback is the only knob; to force a specific CLI
  mode yourself, pass it via `extra_args: ["--permission-mode", ...]`
- All `ClaudeWrapper.Config` options (`:binary`, `:env`, `:timeout`,
  etc.) are accepted and forwarded to `ClaudeWrapper.Config.new/1`
- All `ClaudeWrapper.DuplexSession` options (`:on_permission`,
  `:extra_args`) are forwarded as well

# `close`

```elixir
@spec close() :: :ok
```

Close the current session, kill the printer, and clear stored state.

Named `close/0` (rather than the more obvious `stop/0`) to avoid
colliding with `ClaudeWrapper.Workshop.stop/0` when both modules are
imported. Mirrors the underlying `ClaudeWrapper.DuplexSession.close/1`.

# `interrupt`

```elixir
@spec interrupt(timeout()) :: :ok | {:error, term()}
```

Cancel the current in-flight turn. Returns `:ok` once the CLI
acknowledges, or `{:error, reason}` if no session is running.

# `pid`

```elixir
@spec pid() :: pid() | nil
```

Return the raw `DuplexSession` pid for direct API access, or `nil`.

# `say`

```elixir
@spec say(
  String.t(),
  keyword()
) :: :ok | {:error, term()}
```

Send a prompt in the current session. Streaming text is printed as
it arrives. Returns `:ok` on success or `{:error, reason}` on failure.

Per-call options:
  * `:timeout` -- override the 120-second default for this turn

All other options are ignored at this layer; pass DuplexSession
options (such as `:on_permission`) to `start/1` instead.

# `session_id`

```elixir
@spec session_id() :: String.t() | nil
```

Return the CLI session id for resuming later, or `nil` if no
session is running.

# `start`

```elixir
@spec start(keyword()) :: :ok | {:error, term()}
```

Start a new duplex session and a streaming printer process.

Stores the session pid and printer pid in the process dictionary
for the rest of the helpers to find. If a session is already
running for this process, it is closed first.

# `status`

```elixir
@spec status() :: :ok | :no_session
```

Print the current session's id and pid.

---

*Consult [api-reference.md](api-reference.md) for complete listing*
