using System.Text.RegularExpressions;
using ClaudeDo.Data.Environment;
using ClaudeDo.Installer.Core;
using ClaudeDo.Installer.Core.Interfaces;
namespace ClaudeDo.Installer.Checks;
public sealed record ClaudeCliLookupResult(
ResolvedExecutable? Resolved,
int ExitCode,
string Output,
Version? ParsedVersion);
///
/// Resolves the claude CLI and runs `--version` at most once per install run, shared across
/// ClaudeCliCheck, ClaudeVersionCheck, ClaudeAuthCheck, and PermissionModeAutoCheck.
///
public sealed class ClaudeCliLookup
{
private static readonly Regex VersionPattern = new(@"\d+(\.\d+){1,3}", RegexOptions.Compiled);
private readonly SemaphoreSlim _gate = new(1, 1);
private ClaudeCliLookupResult? _cached;
public ClaudeCliLookup(IProcessRunner processRunner, string? pathOverride = null, string? pathExtOverride = null)
{
ProcessRunner = processRunner;
PathOverride = pathOverride;
PathExtOverride = pathExtOverride;
}
public IProcessRunner ProcessRunner { get; }
public string? PathOverride { get; }
public string? PathExtOverride { get; }
public async Task ResolveAsync(InstallContext ctx, CancellationToken ct)
{
if (_cached is not null) return _cached;
await _gate.WaitAsync(ct);
try
{
_cached ??= await ResolveCoreAsync(ctx, ct);
return _cached;
}
finally
{
_gate.Release();
}
}
private async Task ResolveCoreAsync(InstallContext ctx, CancellationToken ct)
{
var resolved = ExecutableResolver.Resolve(ctx.ClaudeBin, PathOverride, PathExtOverride);
if (resolved is null)
{
return new ClaudeCliLookupResult(null, ExitCode: -1, Output: string.Empty, ParsedVersion: null);
}
var (fileName, arguments) = ProcessCommand.For(resolved, "--version");
var (exitCode, output) = await ProcessRunner.RunAsync(fileName, arguments, null, ct);
var parsedVersion = exitCode == 0 ? ParseVersion(output) : null;
return new ClaudeCliLookupResult(resolved, exitCode, output, parsedVersion);
}
private static Version? ParseVersion(string output) =>
VersionPattern.Match(output) is { Success: true } match && Version.TryParse(match.Value, out var version)
? version
: null;
}