1
0
Fork 0
Everything2Everything/src/Everything2Everything.Core/Converters/ExternalToolDetector.cs
Yun Chan 2a3d220db4 feat(ui): AI 설정창 + Codex CLI OAuth 백엔드 + ⚙ 진입점
사용자 원래 요구였던 Codex non-interactive OAuth를 실제 동작까지 검증해 통합. ChatGPT 구독으로 API 키 없이 AI 변환.

- CodexChatClient: codex exec --skip-git-repo-check --ephemeral -o <file> - (stdin 프롬프트). 실제 한글→영어 번역 검증 완료
- ExternalProcessRunner: stdin 지원 + UTF-8 인코딩(한글 깨짐 해결)
- ExternalToolDetector.IsCodexAvailable() + public 승격
- LlmProvider: codex 백엔드 + auto 폴백(키 없으면 Codex)
- SettingsWindow: OpenAI/Anthropic 키(2단계 Verify) + Codex(OAuth) + 외부도구 상태/다운로드 CTA + 모델 선택
- App.Settings를 LlmProvider와 공유(키 저장 즉시 반영)
- MainWindow 네비바 ⚙ 설정 진입점
- 디자인: Cursor/Zed/Raycast 설정 UX + Radix 상태 토큰 영감
2026-06-01 14:47:07 +09:00

146 lines
5.1 KiB
C#

using Microsoft.Win32;
namespace Everything2Everything.Core.Converters;
public static class ExternalToolDetector
{
public static bool TryFindLibreOfficeSoffice(out string sofficePath)
{
sofficePath = "";
var candidates = new List<string>();
var pf = Environment.GetFolderPath(Environment.SpecialFolder.ProgramFiles);
var pfx86 = Environment.GetFolderPath(Environment.SpecialFolder.ProgramFilesX86);
foreach (var root in new[] { pf, pfx86 })
{
if (string.IsNullOrEmpty(root)) continue;
candidates.Add(Path.Combine(root, "LibreOffice", "program", "soffice.com"));
candidates.Add(Path.Combine(root, "LibreOffice", "program", "soffice.exe"));
}
try
{
using var key = Registry.LocalMachine.OpenSubKey(@"SOFTWARE\LibreOffice\UNO\InstallPath");
if (key?.GetValue(null) is string installPath)
{
candidates.Add(Path.Combine(installPath, "soffice.com"));
candidates.Add(Path.Combine(installPath, "soffice.exe"));
}
}
catch { }
foreach (var path in candidates.Distinct())
{
if (File.Exists(path)) { sofficePath = path; return true; }
}
return false;
}
public static bool IsWordComAvailable()
{
try
{
using var key = Registry.ClassesRoot.OpenSubKey("Word.Application");
return key is not null;
}
catch
{
return false;
}
}
/// <summary>
/// FFmpeg/ffprobe 바이너리 폴더를 찾는다. (1) 앱 전용 폴더(%LOCALAPPDATA%\Everything2Everything\ffmpeg),
/// (2) 시스템 PATH 순. ffmpeg.exe와 ffprobe.exe가 모두 있는 폴더만 유효.
/// </summary>
public static bool TryFindFfmpeg(out string ffmpegDirectory)
{
ffmpegDirectory = "";
var candidates = new List<string>();
var local = Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData);
if (!string.IsNullOrEmpty(local))
candidates.Add(Path.Combine(local, "Everything2Everything", "ffmpeg"));
var pathEnv = Environment.GetEnvironmentVariable("PATH") ?? "";
foreach (var dir in pathEnv.Split(Path.PathSeparator))
if (!string.IsNullOrWhiteSpace(dir))
candidates.Add(dir.Trim());
foreach (var dir in candidates.Distinct())
{
try
{
if (File.Exists(Path.Combine(dir, "ffmpeg.exe")) && File.Exists(Path.Combine(dir, "ffprobe.exe")))
{
ffmpegDirectory = dir;
return true;
}
}
catch { /* 잘못된 경로 무시 */ }
}
return false;
}
/// <summary>
/// codex CLI(OpenAI Codex, ChatGPT 구독 OAuth 재사용) 설치 여부. npm 글로벌 + PATH에서
/// codex.cmd/codex.exe/codex.ps1을 탐지한다.
/// </summary>
public static bool IsCodexAvailable()
{
var names = new[] { "codex.cmd", "codex.exe", "codex.ps1" };
var dirs = new List<string>();
var appdata = Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData);
if (!string.IsNullOrEmpty(appdata)) dirs.Add(Path.Combine(appdata, "npm"));
var pathEnv = Environment.GetEnvironmentVariable("PATH") ?? "";
foreach (var d in pathEnv.Split(Path.PathSeparator))
if (!string.IsNullOrWhiteSpace(d)) dirs.Add(d.Trim());
foreach (var dir in dirs.Distinct())
{
try
{
foreach (var n in names)
if (File.Exists(Path.Combine(dir, n))) return true;
}
catch { /* 잘못된 경로 무시 */ }
}
return false;
}
public static bool IsH2OrestartInstalled()
{
try
{
var roots = new[]
{
Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData),
Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData),
};
foreach (var root in roots)
{
if (string.IsNullOrEmpty(root)) continue;
var loDir = Path.Combine(root, "LibreOffice", "4", "user", "uno_packages", "cache", "uno_packages");
if (Directory.Exists(loDir))
{
foreach (var dir in Directory.EnumerateDirectories(loDir, "*H2Orestart*", SearchOption.AllDirectories))
{
if (Directory.Exists(dir)) return true;
}
}
var extDir = Path.Combine(root, "LibreOffice", "4", "user", "extensions", "bundled");
if (Directory.Exists(extDir))
{
foreach (var dir in Directory.EnumerateDirectories(extDir, "*H2O*", SearchOption.AllDirectories))
{
if (Directory.Exists(dir)) return true;
}
}
}
}
catch { }
return false;
}
}