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; }