using System.Diagnostics; using System.IO; using System.Text.Json; using System.Text.Json.Serialization; using AxCopilot.SDK; using AxCopilot.Services; using AxCopilot.Themes; namespace AxCopilot.Handlers; /// /// 루틴 자동화 핸들러. "routine" 프리픽스로 사용합니다. /// 등록된 루틴을 실행하면 앱·폴더·URL을 순서대로 일괄 실행합니다. /// 예: routine → 등록된 루틴 목록 /// routine morning → "morning" 루틴 실행 /// routine add morning → 루틴 추가 안내 /// 루틴 정의: %APPDATA%\AxCopilot\routines.json /// public class RoutineHandler : IActionHandler { public string? Prefix => "routine"; public PluginMetadata Metadata => new( "Routine", "루틴 자동화 — routine", "1.0", "AX"); private static readonly string RoutineFile = Path.Combine( Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), "AxCopilot", "routines.json"); private static readonly JsonSerializerOptions JsonOpts = new() { WriteIndented = true, PropertyNameCaseInsensitive = true }; // 내장 기본 루틴 private static readonly RoutineDefinition[] BuiltInRoutines = [ new("morning", "출근 루틴", [ new("app", "explorer.exe", "파일 탐색기"), new("info", "info", "시스템 정보 표시"), ]), new("endofday", "퇴근 루틴", [ new("cmd", "journal", "오늘 업무 일지 생성"), ]), ]; public Task> GetItemsAsync(string query, CancellationToken ct) { var q = query.Trim(); var all = LoadRoutines(); if (string.IsNullOrWhiteSpace(q)) { var items = new List { new("루틴 자동화", $"총 {all.Count}개 루틴 · 이름 입력 시 실행 · routines.json에서 편집", null, null, Symbol: Symbols.Info) }; foreach (var r in all) { var steps = string.Join(" → ", r.Steps.Select(s => s.Label)); items.Add(new LauncherItem( $"[{r.Name}] {r.Description}", $"{r.Steps.Length}단계: {steps} · Enter로 실행", null, r, Symbol: Symbols.Lightbulb)); } return Task.FromResult>(items); } // 루틴 검색/실행 var match = all.FirstOrDefault(r => r.Name.Equals(q, StringComparison.OrdinalIgnoreCase)); if (match != null) { var steps = string.Join(" → ", match.Steps.Select(s => s.Label)); return Task.FromResult>( [ new LauncherItem( $"[{match.Name}] 루틴 실행", $"{match.Description} · {steps}", null, match, Symbol: Symbols.Lightbulb) ]); } // 부분 매칭 var filtered = all.Where(r => r.Name.Contains(q, StringComparison.OrdinalIgnoreCase) || r.Description.Contains(q, StringComparison.OrdinalIgnoreCase)); var result = filtered.Select(r => new LauncherItem( $"[{r.Name}] {r.Description}", $"{r.Steps.Length}단계 · Enter로 실행", null, r, Symbol: Symbols.Lightbulb)).ToList(); if (!result.Any()) { result.Add(new LauncherItem( $"'{q}' 루틴 없음", $"routines.json에서 직접 추가하거나 routine으로 목록 확인", null, null, Symbol: Symbols.Warning)); } return Task.FromResult>(result); } public async Task ExecuteAsync(LauncherItem item, CancellationToken ct) { if (item.Data is not RoutineDefinition routine) return; int executed = 0; foreach (var step in routine.Steps) { try { switch (step.Type.ToLowerInvariant()) { case "app": case "url": case "folder": Process.Start(new ProcessStartInfo(step.Target) { UseShellExecute = true }); break; case "cmd": // PowerShell 명령 실행 Process.Start(new ProcessStartInfo("powershell.exe", $"-Command \"{step.Target}\"") { UseShellExecute = false, CreateNoWindow = true }); break; case "info": // 알림으로 대체 NotificationService.Notify("루틴", step.Label); break; } executed++; await Task.Delay(300, ct); // 앱 간 간격 } catch (Exception ex) { LogService.Warn($"루틴 단계 실행 실패: {step.Label} — {ex.Message}"); } } NotificationService.Notify("루틴 완료", $"[{routine.Name}] {executed}/{routine.Steps.Length}단계 실행 완료"); } private List LoadRoutines() { var list = new List(BuiltInRoutines); try { if (File.Exists(RoutineFile)) { var json = File.ReadAllText(RoutineFile); var user = JsonSerializer.Deserialize>(json, JsonOpts); if (user != null) { // 사용자 루틴이 내장 루틴을 오버라이드 foreach (var r in user) { list.RemoveAll(x => x.Name.Equals(r.Name, StringComparison.OrdinalIgnoreCase)); list.Add(r); } } } } catch (Exception ex) { LogService.Warn($"루틴 로드 실패: {ex.Message}"); } return list; } internal record RoutineDefinition( [property: JsonPropertyName("name")] string Name, [property: JsonPropertyName("description")] string Description, [property: JsonPropertyName("steps")] RoutineStep[] Steps); internal record RoutineStep( [property: JsonPropertyName("type")] string Type, [property: JsonPropertyName("target")] string Target, [property: JsonPropertyName("label")] string Label); }