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

Parse and compare the `claude` CLI's reported version.

`claude --version` prints a line like `"2.1.71 (Claude Code)"`. This
module turns that into a comparable `t:t/0` struct and provides the two
checks a host typically wants:

  * `satisfies_minimum?/2` / `check_version/2` -- a hard floor. Below the
    minimum the wrapper is known to misbehave (missing flags, different
    argument shapes), so the caller should refuse to run.
  * `status_within/3` -- a soft "drift" classification against a
    tested-against `[minimum, maximum]` window. A version above the
    maximum hasn't been verified; semantics may have drifted but the
    wrapper should generally still work, so the caller should warn
    rather than refuse.

This is the Elixir port of the Rust crate's `CliVersion` /
`CliVersionStatus` (`../claude-wrapper/src/version.rs`). Versions are
treated as three-part semver (`major.minor.patch`); any trailing
metadata such as the `(Claude Code)` suffix is ignored during parsing.

## Example

    iex> {:ok, found} = ClaudeWrapper.CliVersion.parse("2.1.71 (Claude Code)")
    iex> ClaudeWrapper.CliVersion.satisfies_minimum?(found, ClaudeWrapper.CliVersion.new(2, 1, 0))
    true
    iex> ClaudeWrapper.CliVersion.status_within(found, ClaudeWrapper.CliVersion.new(2, 1, 0), ClaudeWrapper.CliVersion.new(2, 1, 999))
    :tested

# `status`

```elixir
@type status() ::
  :tested
  | {:newer_untested, found :: t(), tested_max :: t()}
  | {:older_than_minimum, found :: t(), minimum :: t()}
```

Classification of a found version against a tested `[minimum, maximum]`
range. Returned by `status_within/3`.

  * `:tested` -- within the range (inclusive on both ends); safe to run.
  * `{:newer_untested, found, tested_max}` -- above the maximum. The
    wrapper hasn't been verified against this CLI; warn but proceed.
  * `{:older_than_minimum, found, minimum}` -- below the minimum. The
    wrapper is known to behave incorrectly; refuse to run.

# `t`

```elixir
@type t() :: %ClaudeWrapper.CliVersion{
  major: non_neg_integer(),
  minor: non_neg_integer(),
  patch: non_neg_integer()
}
```

# `check_version`

```elixir
@spec check_version(t(), t()) :: :ok | {:error, ClaudeWrapper.Error.t()}
```

Enforce a minimum version as a tagged-tuple result.

Returns `:ok` when `found` satisfies `minimum`, otherwise
`{:error, %ClaudeWrapper.Error{kind: :version_mismatch}}` whose
`:reason` is `%{found: found, minimum: minimum}`.

    iex> v = ClaudeWrapper.CliVersion.new(2, 1, 71)
    iex> ClaudeWrapper.CliVersion.check_version(v, ClaudeWrapper.CliVersion.new(2, 1, 0))
    :ok
    iex> ClaudeWrapper.CliVersion.check_version(v, ClaudeWrapper.CliVersion.new(2, 2, 0))
    {:error, %ClaudeWrapper.Error{kind: :version_mismatch, reason: %{found: %ClaudeWrapper.CliVersion{major: 2, minor: 1, patch: 71}, minimum: %ClaudeWrapper.CliVersion{major: 2, minor: 2, patch: 0}}}}

# `compare`

```elixir
@spec compare(t(), t()) :: :lt | :eq | :gt
```

Compare two versions, returning `:lt`, `:eq`, or `:gt`.

Ordering is by `major`, then `minor`, then `patch`. The shape is
suitable as the second argument to `Enum.sort/2`.

    iex> Enum.sort(
    ...>   [ClaudeWrapper.CliVersion.new(2, 1, 71), ClaudeWrapper.CliVersion.new(2, 1, 0)],
    ...>   ClaudeWrapper.CliVersion
    ...> )
    [%ClaudeWrapper.CliVersion{major: 2, minor: 1, patch: 0}, %ClaudeWrapper.CliVersion{major: 2, minor: 1, patch: 71}]

# `new`

```elixir
@spec new(non_neg_integer(), non_neg_integer(), non_neg_integer()) :: t()
```

Build a version from its three components.

    iex> ClaudeWrapper.CliVersion.new(2, 1, 71)
    %ClaudeWrapper.CliVersion{major: 2, minor: 1, patch: 71}

# `parse`

```elixir
@spec parse(String.t()) :: {:ok, t()} | {:error, ClaudeWrapper.Error.t()}
```

Parse a version from the output of `claude --version`.

Accepts a bare `"2.1.71"`, the full `"2.1.71 (Claude Code)"` form, a
leading `v` (`"v2.1.71"`), and surrounding whitespace. Only the first
whitespace-delimited token is considered, and it must be exactly three
dot-separated non-negative integers.

Returns `{:error, %ClaudeWrapper.Error{kind: :invalid_version}}` for
anything else, with the original (untrimmed) string carried in
`:reason`.

    iex> ClaudeWrapper.CliVersion.parse("  2.1.71 (Claude Code)\n")
    {:ok, %ClaudeWrapper.CliVersion{major: 2, minor: 1, patch: 71}}

    iex> ClaudeWrapper.CliVersion.parse("2.1")
    {:error, %ClaudeWrapper.Error{kind: :invalid_version, reason: "2.1"}}

# `satisfies_minimum?`

```elixir
@spec satisfies_minimum?(t(), t()) :: boolean()
```

True when `found` is greater than or equal to `minimum`.

    iex> v = ClaudeWrapper.CliVersion.new(2, 1, 71)
    iex> ClaudeWrapper.CliVersion.satisfies_minimum?(v, ClaudeWrapper.CliVersion.new(2, 1, 0))
    true
    iex> ClaudeWrapper.CliVersion.satisfies_minimum?(v, ClaudeWrapper.CliVersion.new(2, 2, 0))
    false

# `status_within`

```elixir
@spec status_within(t(), t(), t()) :: status()
```

Classify `found` against a tested-against `[minimum, maximum]` range
(both ends inclusive). See `t:status/0` for the three outcomes.

    iex> tested = ClaudeWrapper.CliVersion.status_within(
    ...>   ClaudeWrapper.CliVersion.new(2, 1, 143),
    ...>   ClaudeWrapper.CliVersion.new(2, 1, 0),
    ...>   ClaudeWrapper.CliVersion.new(2, 1, 999)
    ...> )
    iex> tested
    :tested

# `to_string`

```elixir
@spec to_string(t()) :: String.t()
```

Render a version as `"major.minor.patch"`.

The `String.Chars` protocol is also implemented, so `to_string/1` and
string interpolation work too.

    iex> ClaudeWrapper.CliVersion.to_string(ClaudeWrapper.CliVersion.new(2, 1, 71))
    "2.1.71"

---

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