Files
AX-Copilot-Codex/AXCommander_SRS_v1.0.html

576 lines
36 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<!DOCTYPE html>
<html lang="ko"><head><meta charset="utf-8">
<title>AXCommander_SRS_v1.0</title>
<style>
body { font-family: "맑은 고딕", "Malgun Gothic", sans-serif; font-size: 11pt; color: #1a1a1a; line-height: 1.7; max-width: 900px; margin: 0 auto; padding: 40px 20px; }
h1 { font-size: 22pt; color: #1F3864; border-bottom: 3px solid #4472C4; padding-bottom: 8px; margin-top: 32px; }
h2 { font-size: 16pt; color: #2E75B6; border-bottom: 2px solid #B8CCE4; padding-bottom: 4px; margin-top: 28px; }
h3 { font-size: 13pt; color: #44546A; margin-top: 22px; }
h4 { font-size: 12pt; color: #44546A; margin-top: 18px; }
p { margin: 6px 0; }
table { border-collapse: collapse; width: 100%; margin: 12px 0; }
th { background: #2E75B6; color: white; padding: 8px 10px; text-align: left; font-size: 10pt; }
td { padding: 6px 10px; border: 1px solid #D6E4F0; font-size: 10pt; }
tr:nth-child(even) td { background: #EBF3FB; }
ul, ol { margin: 6px 0 6px 24px; }
li { margin: 3px 0; }
code { font-family: Consolas, "D2Coding", monospace; background: #F0F0F0; padding: 1px 4px; border-radius: 3px; font-size: 10pt; }
.footer { text-align: right; color: #999; font-size: 9pt; margin-top: 40px; border-top: 1px solid #ddd; padding-top: 8px; }
</style></head><body>
<p>&lt;b&gt;OLEDi Commander&lt;/b&gt;</p>
<p>Software Requirements Specification</p>
<p>Windows 전용 시맨틱 런처 &amp; 워크스페이스 매니저</p>
<table>
<tr><th><b>항목</b></th><th><b>내용</b></th></tr>
<tr><td>문서 버전</td><td>v1.0</td></tr>
<tr><td>작성일</td><td>2026-03-21</td></tr>
<tr><td>최종 검토일</td><td>2026-03-21</td></tr>
<tr><td>상태</td><td>초안 (Draft)</td></tr>
<tr><td>대상 플랫폼</td><td>Windows 10 / 11 (x64)</td></tr>
<tr><td>구현 언어</td><td>C# / .NET 8 / WPF</td></tr>
</table>
<p>&lt;br/&gt;</p>
<h1>&lt;b&gt;목차&lt;/b&gt;</h1>
<p>&lt;br/&gt;</p>
<h1>&lt;b&gt;1. 개요 (Overview)&lt;/b&gt;</h1>
<h2>&lt;b&gt;1.1 프로젝트 목적&lt;/b&gt;</h2>
<p>OLEDi Commander는 macOS 생산성 도구 Alfred에서 영감을 받아, Windows 환경에서 동등하거나 그 이상의 생산성을 제공하기 위해 설계된 키보드 우선(Keyboard-first) 런처 겸 워크스페이스 매니저입니다.</p>
<p>전통적인 마우스 중심 UI 조작을 Alt+Space 단축키 하나로 대체하여, 개발자&#183;파워유저가 컨텍스트 전환 비용 없이 빠르게 작업을 수행할 수 있도록 합니다.</p>
<h2>&lt;b&gt;1.2 범위 (Scope)&lt;/b&gt;</h2>
<p>본 문서는 OLEDi Commander 1.0 릴리스를 대상으로 하며, 아래 두 핵심 모듈을 포함합니다.</p>
<ul>
<li>The Shifter: 워크스페이스 창 배치 스냅샷 및 복원</li>
<li>The Alfred: 시맨틱 커맨드 런처 (별칭, 클립보드 변환, Fuzzy 검색)</li>
</ul>
<p>아래 항목은 v1.0 범위에서 제외됩니다.</p>
<ul>
<li>macOS / Linux 지원</li>
<li>클라우드 동기화</li>
<li>플러그인 마켓플레이스 UI (v2.0 예정)</li>
</ul>
<h2>&lt;b&gt;1.3 용어 정의 (Glossary)&lt;/b&gt;</h2>
<table>
<tr><th><b>용어</b></th><th><b>정의</b></th></tr>
<tr><td>HWND</td><td>Windows API에서 창(Window)을 식별하는 핸들 값</td></tr>
<tr><td>Rect</td><td>창의 좌상단·우하단 좌표로 이루어진 사각형 구조체</td></tr>
<tr><td>Profile</td><td>특정 시점의 창 배치 및 크기 정보를 저장한 JSON 객체</td></tr>
<tr><td>Alias</td><td>긴 경로·명령을 짧게 치환하는 사용자 정의 키워드</td></tr>
<tr><td>Fuzzy Search</td><td>오타·부분 입력도 유사 항목을 찾아주는 비정확 검색</td></tr>
<tr><td>Global Hook</td><td>어떤 앱이 포커스를 갖고 있어도 키 입력을 감지하는 OS 훅</td></tr>
<tr><td>Plugin</td><td>CommandResolver에 새로운 ActionHandler를 추가하는 확장 단위</td></tr>
<tr><td>Skill</td><td>Plugin의 다른 표현. 사용자 정의 실행 규칙 묶음</td></tr>
<tr><td>settings.json</td><td>사용자 설정 및 Alias를 저장하는 로컬 JSON 파일</td></tr>
</table>
<h2>&lt;b&gt;1.4 참조 문서&lt;/b&gt;</h2>
<ul>
<li>Microsoft Docs User32.dll API Reference</li>
<li>WPF Documentation (.NET 8)</li>
<li>Windows Accessibility API (MSAA / UI Automation)</li>
<li>Alfred App https://www.alfredapp.com (인터페이스 참조)</li>
</ul>
<p>&lt;br/&gt;</p>
<h1>&lt;b&gt;2. 시스템 아키텍처&lt;/b&gt;</h1>
<p>OLEDi Commander는 단일 프로세스(Single-process) 구조이며, 아래 5개 핵심 모듈로 구성됩니다. 모든 모듈은 의존성 주입(DI) 방식으로 연결되어 테스트 및 확장이 용이합니다.</p>
<table>
<tr><th><b>모듈</b></th><th><b>역할</b></th><th><b>핵심 기술</b></th></tr>
<tr><td>Input Listener</td><td>글로벌 키 훅 어떤 앱에서도 Alt+Space 감지</td><td>WH_KEYBOARD_LL, RegisterHotKey</td></tr>
<tr><td>Context Manager</td><td>열린 창의 HWND, Rect, 프로세스 경로 수집 및 복원</td><td>EnumWindows, GetWindowPlacement, SetWindowPos</td></tr>
<tr><td>Command Resolver</td><td>입력 텍스트 파싱 → ActionHandler 라우팅</td><td>Prefix 테이블, ActionHandler 인터페이스</td></tr>
<tr><td>Fuzzy Engine</td><td>파일명·키워드 부분 입력으로 빠른 유사 항목 탐색</td><td>Fuse.js 알고리즘 포팅 또는 FuzzySharp</td></tr>
<tr><td>Plugin Host</td><td>외부 .dll 또는 JSON 기반 스킬을 로드·실행</td><td>Reflection, IActionHandler 인터페이스</td></tr>
</table>
<h2>&lt;b&gt;2.1 모듈 간 데이터 흐름&lt;/b&gt;</h2>
<p>[사용자 키 입력] → Input Listener → Command Resolver → ActionHandler (Shifter / Alfred / Plugin)</p>
<p>[창 배치 저장] → Context Manager → Profile JSON → settings.json</p>
<p>[창 배치 복원] → settings.json → Context Manager → User32 API</p>
<h2>&lt;b&gt;2.2 Prefix 라우팅 테이블&lt;/b&gt;</h2>
<p>Command Resolver는 입력 텍스트의 첫 번째 토큰(prefix)을 기준으로 ActionHandler를 선택합니다. 빌트인 prefix는 다음과 같습니다.</p>
<table>
<tr><th><b>Prefix</b></th><th><b>타입</b></th><th><b>예시</b></th><th><b>동작</b></th></tr>
<tr><td>!</td><td>워크스페이스</td><td>!dev</td><td>저장된 "dev" 프로필 즉시 복원</td></tr>
<tr><td>@</td><td>URL / 웹</td><td>@blog</td><td>settings.json의 해당 URL 브라우저로 오픈</td></tr>
<tr><td>#</td><td>동적 API</td><td>#jira</td><td>설정된 API 어댑터 호출 후 결과 표시</td></tr>
<tr><td>></td><td>터미널 명령</td><td>>git status</td><td>Windows Terminal / PowerShell에서 실행</td></tr>
<tr><td>~</td><td>파일 경로</td><td>~projects</td><td>등록된 폴더 경로를 탐색기로 오픈</td></tr>
<tr><td>(없음)</td><td>Fuzzy 검색</td><td>vsc</td><td>인덱스에서 유사 파일·앱·Alias 검색</td></tr>
</table>
<table>
<tr><th><b>확장 가능성</b><br/>위 prefix 목록은 settings.json의 "prefixMap" 배열을 통해 사용자가 직접 추가·변경할 수 있습니다.<br/>플러그인 개발자는 IActionHandler 인터페이스를 구현하는 .dll을 제공하여 새로운 prefix 동작을 등록합니다.<br/>자세한 내용은 섹션 7 개발자 확장 가이드를 참조하십시오.</th></tr>
</table>
<p>&lt;br/&gt;</p>
<h1>&lt;b&gt;3. 기능 요구사항&lt;/b&gt;</h1>
<h2>&lt;b&gt;3.1 워크스페이스 스냅샷 &amp; 시프트 (The Shifter)&lt;/b&gt;</h2>
<p>&quot;어떤 위치에 어떤 크기로&quot; 떠 있는지까지 관리하는 워크스페이스 레이아웃 엔진입니다. 단순 앱 실행을 넘어 창의 상태(State)를 완전히 복원합니다.</p>
<h3>&lt;b&gt;3.1.1 Snapshot Capture&lt;/b&gt;</h3>
<ul>
<li>현재 화면에 있는 모든 가시(Visible) 업무용 창의 배치를 &quot;프로필&quot;로 저장</li>
<li>저장 정보: HWND, 프로세스 실행 경로(EXE), 창 제목, Rect (좌상단 x/y, 폭, 높이), 최소화/최대화/일반 상태(ShowCmd)</li>
<li>저장 위치: settings.json의 &quot;profiles&quot; 배열</li>
<li>저장 명령어: !save &lt;프로필명&gt; 또는 UI의 저장 버튼</li>
<li>시스템 트레이 UI(Task Bar) 및 바탕화면은 캡처 제외</li>
</ul>
<h3>&lt;b&gt;3.1.2 Instant Restore&lt;/b&gt;</h3>
<ul>
<li>!&lt;프로필명&gt; 입력 시 해당 프로필의 창 배치를 강제 복원</li>
<li>복원 순서: 1) 저장된 EXE 경로로 프로세스 존재 확인 → 2) HWND 유효성 검사 → 3) SetWindowPos 호출</li>
<li>앱이 실행 중이지 않은 경우: 자동으로 EXE를 실행한 후 창 생성을 최대 3초간 대기</li>
<li>3초 내 창이 생성되지 않으면 해당 앱을 건너뛰고 나머지 복원 진행, 결과 알림 표시</li>
<li>관리자 권한 창(elevated process)은 복원 시 UAC 프롬프트 없이 위치만 조정 (권한 상승 없이 처리 가능한 범위 내에서 수행)</li>
</ul>
<h3>&lt;b&gt;3.1.3 Profile 관리&lt;/b&gt;</h3>
<ul>
<li>목록 보기: 런처 UI에서 !&lt;엔터&gt; 입력 시 저장된 프로필 전체 목록 표시</li>
<li>이름 변경: !rename &lt;현재명&gt; &lt;새이름&gt;</li>
<li>삭제: !delete &lt;프로필명&gt; (확인 다이얼로그 필요)</li>
<li>내보내기/가져오기: settings.json 파일 직접 공유 가능 (JSON 스키마 준수 시)</li>
</ul>
<h3>&lt;b&gt;3.1.4 Multi-Monitor 지원&lt;/b&gt;</h3>
<ul>
<li>각 모니터의 DPI 및 좌표계 독립 저장</li>
<li>모니터 구성 변경 감지: WM_DISPLAYCHANGE 메시지 수신 시 활성 프로필과 현재 모니터 구성 비교</li>
<li>모니터 불일치 시 동작 옵션 (settings.json으로 설정 가능):</li>
<li>&quot;fit&quot;: 사용 가능한 모니터에 좌표 스케일링하여 배치</li>
<li>&quot;skip&quot;: 해당 모니터의 창만 건너뜀</li>
<li>&quot;warn&quot;: 복원 전 경고 팝업 표시 (기본값)</li>
</ul>
<h2>&lt;b&gt;3.2 시맨틱 커맨드 런처 (The Alfred)&lt;/b&gt;</h2>
<p>키보드만으로 마우스 클릭 수십 번의 가치를 만들어냅니다. Alt+Space → 명령어 입력의 단일 패턴으로 모든 작업을 처리합니다.</p>
<h3>&lt;b&gt;3.2.1 Smart Aliases&lt;/b&gt;</h3>
<p>긴 경로나 복잡한 명령을 짧은 별칭으로 등록합니다. settings.json의 &quot;aliases&quot; 배열로 관리됩니다.</p>
<table>
<tr><th><b>Alias 타입</b></th><th><b>예시</b></th><th><b>동작 설명</b></th></tr>
<tr><td>url</td><td>@blog → https://swarchitect.net/admin</td><td>브라우저로 URL 오픈</td></tr>
<tr><td>folder</td><td>~proj → C:\\Dev\\Projects</td><td>탐색기로 폴더 오픈</td></tr>
<tr><td>app</td><td>@term → C:\\Windows\\wt.exe</td><td>지정 실행파일 실행</td></tr>
<tr><td>batch</td><td>>build → cmd /c build.bat</td><td>커맨드 실행 (출력 창 선택)</td></tr>
<tr><td>api</td><td>#jira → JiraAdapter (토큰 설정 필요)</td><td>동적 데이터 조회 후 결과 표시</td></tr>
<tr><td>clipboard</td><td>$upper → UPPER_CASE 변환</td><td>현재 클립보드 텍스트 변환</td></tr>
</table>
<h3>&lt;b&gt;3.2.2 Fuzzy Engine&lt;/b&gt;</h3>
<ul>
<li>검색 범위: 사용자 정의 인덱스 폴더(기본: 바탕화면, 시작 메뉴, 최근 파일) + 등록된 모든 Alias</li>
<li>인덱싱: 앱 시작 시 1회 전체 인덱싱, 이후 FileSystemWatcher로 변경 감지 시 증분 업데이트</li>
<li>응답 목표: 타이핑 후 100ms 이내에 결과 표시 (로컬 인덱스 기준)</li>
<li>결과 랭킹 기준: 1) 정확히 일치하는 Alias 최우선, 2) 문자열 유사도, 3) 최근 실행 빈도 가중치</li>
<li>최대 결과 수: UI에 5~7개 표시 (설정으로 최대 20개까지 확장 가능)</li>
<li>대소문자 구분 없음, 한글 초성 검색 지원 (예: &quot;ㅂㅅ&quot;&quot;Visual Studio&quot;)</li>
</ul>
<h3>&lt;b&gt;3.2.3 Clipboard Transformer&lt;/b&gt;</h3>
<p>복사한 텍스트를 규칙에 따라 가공합니다. $ prefix로 호출하며, 변환 결과를 클립보드에 덮어쓴 후 활성 창에 붙여넣기(Ctrl+V) 시뮬레이션을 수행합니다.</p>
<table>
<tr><th><b>빌트인 변환</b></th><th><b>명령</b></th><th><b>예시</b></th></tr>
<tr><td>JSON 포맷팅</td><td>$json</td><td>{"a":1} → 들여쓰기 적용 JSON</td></tr>
<tr><td>유닉스 타임스탬프 → 날짜</td><td>$ts</td><td>1700000000 → 2023-11-14 22:13:20</td></tr>
<tr><td>날짜 → 유닉스 타임스탬프</td><td>$epoch</td><td>2023-11-14 → 1700000000</td></tr>
<tr><td>대문자 변환</td><td>$upper</td><td>hello → HELLO</td></tr>
<tr><td>소문자 변환</td><td>$lower</td><td>HELLO → hello</td></tr>
<tr><td>URL 인코딩</td><td>$urle</td><td>한글 → %ED%95%9C%EA%B8%80</td></tr>
<tr><td>URL 디코딩</td><td>$urld</td><td>%ED%95%9C%EA%B8%80 → 한글</td></tr>
<tr><td>Base64 인코딩</td><td>$b64e</td><td>text → dGV4dA==</td></tr>
<tr><td>Base64 디코딩</td><td>$b64d</td><td>dGV4dA== → text</td></tr>
<tr><td>마크다운 → 텍스트</td><td>$md</td><td>**bold** → bold</td></tr>
</table>
<p>사용자 정의 변환 규칙은 settings.json의 &quot;clipboardTransformers&quot; 배열에 추가하며, 정규식 기반 변환과 외부 스크립트 호출을 지원합니다. 자세한 내용은 섹션 7.4를 참조하십시오.</p>
<h2>&lt;b&gt;3.3 동적 API Alias (#)&lt;/b&gt;</h2>
<p># prefix를 사용하는 Alias는 외부 API를 호출하여 동적 결과를 표시합니다. 각 API 연결은 &quot;apiAdapters&quot; 설정으로 정의합니다.</p>
<table>
<tr><th><b>항목</b></th><th><b>요구사항</b></th></tr>
<tr><td>인증</td><td>Personal Access Token(PAT) 또는 OAuth 2.0. 토큰은 Windows Credential Manager에 저장</td></tr>
<tr><td>네트워크 오류</td><td>3초 내 응답 없으면 타임아웃, 오프라인 캐시(최대 1시간) 표시</td></tr>
<tr><td>결과 표시</td><td>최대 10개 항목을 런처 결과 리스트에 표시, Enter로 기본 동작(URL 오픈) 실행</td></tr>
<tr><td>빌트인 어댑터</td><td>Jira Cloud, GitHub Issues (v1.0). 추가 어댑터는 플러그인으로 제공</td></tr>
</table>
<p>&lt;br/&gt;</p>
<h1>&lt;b&gt;4. UI/UX 디자인 요구사항&lt;/b&gt;</h1>
<h2>&lt;b&gt;4.1 런처 윈도우&lt;/b&gt;</h2>
<table>
<tr><th><b>요소</b></th><th><b>명세</b></th></tr>
<tr><td>형태</td><td>화면 중앙 상단 1/3 지점에 위치하는 반투명 바(Bar) 형태 오버레이</td></tr>
<tr><td>기본 크기</td><td>너비: 680px, 높이: 54px (입력 상태). 결과 표시 시 높이 자동 확장</td></tr>
<tr><td>투명도</td><td>Opacity 0.96 (기본). settings.json에서 0.7~1.0 조정 가능</td></tr>
<tr><td>테마</td><td>시스템 다크/라이트 모드 자동 감지(WMI 또는 레지스트리 감시). 수동 고정 설정 가능</td></tr>
<tr><td>폰트</td><td>Segoe UI (영문), Malgun Gothic (한글), 16px</td></tr>
<tr><td>애니메이션</td><td>호출: 상단 20px에서 Fade-in + SlideDown (120ms). 닫힘: Fade-out (80ms)</td></tr>
<tr><td>항상 최상위</td><td>Topmost = true. 다른 창에 가려지지 않음</td></tr>
<tr><td>포커스 처리</td><td>런처 외부 클릭 시 자동 닫힘 (LostFocus 이벤트)</td></tr>
</table>
<h2>&lt;b&gt;4.2 결과 리스트&lt;/b&gt;</h2>
<ul>
<li>최대 5~7개 항목 표시 (기본 5개, settings.json에서 조정)</li>
<li>각 항목: 아이콘(32x32) + 제목 + 부제목(경로 또는 설명)</li>
<li>키보드 탐색: ↑↓ 화살표, Enter로 실행, Esc로 닫기, Tab으로 자동완성</li>
<li>마우스 클릭으로도 실행 가능</li>
<li>결과 없음 시: &quot;일치하는 항목이 없습니다&quot; 메시지 표시</li>
</ul>
<h2>&lt;b&gt;4.3 접근성 (Accessibility)&lt;/b&gt;</h2>
<ul>
<li>UI Automation(UIA) 속성 제공으로 스크린 리더 지원</li>
<li>고대비(High Contrast) 모드 지원</li>
<li>글꼴 크기 배율: 시스템 DPI 설정 반영 (100% / 125% / 150% / 200%)</li>
<li>Alt+Space 단축키는 settings.json에서 변경 가능</li>
</ul>
<p>&lt;br/&gt;</p>
<h1>&lt;b&gt;5. 비기능 요구사항 (NFR)&lt;/b&gt;</h1>
<h2>&lt;b&gt;5.1 성능&lt;/b&gt;</h2>
<table>
<tr><th><b>항목</b></th><th><b>목표 수치</b></th><th><b>측정 방법</b></th></tr>
<tr><td>Fuzzy 검색 응답</td><td>< 100ms (p95)</td><td>로컬 인덱스 10,000건 기준 자동화 벤치마크</td></tr>
<tr><td>Instant Restore (5창 기준)</td><td>< 1.5초</td><td>스톱워치 측정, 5회 평균</td></tr>
<tr><td>앱 시작 시간</td><td>< 800ms (백그라운드 Ready 상태)</td><td>프로세스 시작 → 트레이 아이콘 표시까지</td></tr>
<tr><td>메모리 사용량</td><td>유휴 시 < 80MB RSS</td><td>Process Explorer 모니터링</td></tr>
<tr><td>CPU 점유율</td><td>유휴 시 < 0.5%</td><td>10분 유휴 후 평균</td></tr>
<tr><td>인덱싱 시간</td><td>10,000파일 < 3초</td><td>초기 기동 시 측정</td></tr>
</table>
<h2>&lt;b&gt;5.2 신뢰성 &amp; 안정성&lt;/b&gt;</h2>
<ul>
<li>Global Hook 스레드 예외 시 자동 재등록 (최대 3회 재시도, 이후 트레이 경고 알림)</li>
<li>앱 크래시 시 Windows Error Reporting 연동 및 로컬 크래시 덤프 저장 (%APPDATA%\OLEDiCommander\crashes\)</li>
<li>settings.json 파싱 실패 시 기본값(default) 설정으로 폴백, 원본 파일은 .bak 확장자로 백업</li>
<li>MTBF(평균 무고장 시간) 목표: 7일 연속 사용 시 크래시 없음</li>
</ul>
<h2>&lt;b&gt;5.3 보안&lt;/b&gt;</h2>
<ul>
<li>Global Keyboard Hook(WH_KEYBOARD_LL): 키 입력을 로깅하지 않음. Alt+Space 패턴 감지 후 즉시 버퍼 파기</li>
<li>API 토큰: Windows Credential Manager(DPAPI) 저장, settings.json에 평문 토큰 기록 금지</li>
<li>플러그인 .dll: 디지털 서명 검증 권장 (v1.0에서는 경고 표시, v2.0에서 강제 서명 요구 예정)</li>
<li>UAC: 앱 자체는 일반 사용자 권한으로 실행. 관리자 권한이 필요한 동작 시 명시적 UAC 프롬프트</li>
<li>네트워크 통신: HTTPS only (TLS 1.2+). 자체 서명 인증서 거부</li>
</ul>
<table>
<tr><th><b>보안 주의사항 Windows Defender / EDR 탐지</b><br/>WH_KEYBOARD_LL 훅은 일부 보안 소프트웨어에서 키로거로 오탐될 수 있습니다.<br/>대응 방안: 앱 배포 시 코드 서명 인증서 적용, Microsoft Store 등록 검토.<br/>기업 환경 배포 시에는 GPO 화이트리스트 등록 가이드를 별도 제공합니다.</th></tr>
</table>
<h2>&lt;b&gt;5.4 유지보수성&lt;/b&gt;</h2>
<ul>
<li>모듈 간 의존성: DI 컨테이너(Microsoft.Extensions.DependencyInjection) 사용</li>
<li>로깅: Serilog 사용, 레벨(DEBUG/INFO/WARN/ERROR) 설정 가능. 로그 파일: %APPDATA%\OLEDiCommander\logs\</li>
<li>단위 테스트 커버리지: 핵심 모듈(CommandResolver, FuzzyEngine) 80% 이상 목표</li>
<li>업데이트: Squirrel.Windows 또는 MSIX 패키지 업데이트 채널 사용</li>
</ul>
<h2>&lt;b&gt;5.5 호환성&lt;/b&gt;</h2>
<ul>
<li>최소 지원 OS: Windows 10 (버전 1903, 빌드 18362) 이상</li>
<li>권장 OS: Windows 11</li>
<li>.NET 런타임: .NET 8.0 (Self-contained 배포로 별도 설치 불필요)</li>
<li>DPI 지원: System DPI 및 Per-Monitor DPI v2 (WPF DPI 자동 스케일링)</li>
<li>멀티 모니터: 동일 DPI 및 혼합 DPI 환경 모두 지원</li>
</ul>
<p>&lt;br/&gt;</p>
<h1>&lt;b&gt;6. 예외 처리 및 엣지 케이스&lt;/b&gt;</h1>
<table>
<tr><th><b>시나리오</b></th><th><b>원인</b></th><th><b>처리 방침</b></th></tr>
<tr><td>Restore 시 앱 미실행</td><td>저장된 EXE가 닫혀 있음</td><td>자동 EXE 실행 → 3초 대기 → 실패 시 건너뜀, 결과 알림</td></tr>
<tr><td>모니터 구성 변경</td><td>모니터 추가/제거/해상도 변경</td><td>settings.json의 "monitorMismatch" 정책에 따라 fit/skip/warn</td></tr>
<tr><td>단축키 충돌</td><td>타 앱이 Alt+Space 선점</td><td>등록 실패 감지 → 트레이에 경고 → 대체 단축키 제안</td></tr>
<tr><td>관리자 권한 창 제어</td><td>타 프로세스가 elevated</td><td>SetWindowPos 예외 캐치 → 해당 창 건너뜀, 로그 기록</td></tr>
<tr><td>settings.json 손상</td><td>JSON 파싱 오류</td><td>원본 .bak 저장 → 기본값 로드 → 복구 안내 메시지</td></tr>
<tr><td>Alias 중복 등록</td><td>동일 키워드 중복</td><td>나중에 추가된 항목이 우선. 경고 로그 기록</td></tr>
<tr><td>플러그인 로드 실패</td><td>.dll 서명 불일치 또는 예외</td><td>해당 플러그인 건너뜀, 오류 로그. 앱은 계속 실행</td></tr>
<tr><td>API 타임아웃</td><td>네트워크 불안정</td><td>3초 타임아웃 → 오프라인 캐시 표시 (1시간 유효) → 캐시 없으면 오류 메시지</td></tr>
<tr><td>앱 인덱스 폴더 접근 불가</td><td>권한 없는 폴더</td><td>해당 폴더 스킵, 인덱싱 완료 후 경고 로그</td></tr>
<tr><td>Fuzzy 검색 결과 없음</td><td>입력에 일치 항목 없음</td><td>"일치하는 항목이 없습니다" 표시, 입력 지속 허용</td></tr>
</table>
<p>&lt;br/&gt;</p>
<h1>&lt;b&gt;7. 데이터 스키마 (settings.json)&lt;/b&gt;</h1>
<p>모든 사용자 설정과 Profile, Alias는 단일 JSON 파일로 관리됩니다. 파일 위치: %APPDATA%\OLEDiCommander\settings.json</p>
<p>{</p>
<p> &quot;version&quot;: &quot;1.0&quot;,</p>
<p> &quot;hotkey&quot;: &quot;Alt+Space&quot;,</p>
<p> &quot;launcher&quot;: {</p>
<p> &quot;opacity&quot;: 0.96,</p>
<p> &quot;maxResults&quot;: 7,</p>
<p> &quot;theme&quot;: &quot;system&quot;,</p>
<p> &quot;position&quot;: &quot;center-top&quot;</p>
<p> },</p>
<p> &quot;indexPaths&quot;: [</p>
<p> &quot;%USERPROFILE%\\Desktop&quot;,</p>
<p> &quot;%APPDATA%\\Microsoft\\Windows\\Start Menu&quot;</p>
<p> ],</p>
<p> &quot;monitorMismatch&quot;: &quot;warn&quot;,</p>
<p> &quot;profiles&quot;: [</p>
<p> {</p>
<p> &quot;name&quot;: &quot;dev&quot;,</p>
<p> &quot;windows&quot;: [</p>
<p> {</p>
<p> &quot;exe&quot;: &quot;C:\\Program Files\\Microsoft VS Code\\Code.exe&quot;,</p>
<p> &quot;title&quot;: &quot;Visual Studio Code&quot;,</p>
<p> &quot;rect&quot;: { &quot;x&quot;: 0, &quot;y&quot;: 0, &quot;width&quot;: 1280, &quot;height&quot;: 1080 },</p>
<p> &quot;showCmd&quot;: &quot;Normal&quot;,</p>
<p> &quot;monitor&quot;: 0</p>
<p> }</p>
<p> ]</p>
<p> }</p>
<p> ],</p>
<p> &quot;aliases&quot;: [</p>
<p> { &quot;key&quot;: &quot;@blog&quot;, &quot;type&quot;: &quot;url&quot;, &quot;target&quot;: &quot;https://swarchitect.net/admin&quot; },</p>
<p> { &quot;key&quot;: &quot;#jira&quot;, &quot;type&quot;: &quot;api&quot;, &quot;adapter&quot;: &quot;jira&quot;, &quot;query&quot;: &quot;assignee=currentUser() ORDER BY updated DESC&quot; },</p>
<p> { &quot;key&quot;: &quot;~proj&quot;, &quot;type&quot;: &quot;folder&quot;, &quot;target&quot;: &quot;C:\\Dev\\Projects&quot; },</p>
<p> { &quot;key&quot;: &quot;&gt;build&quot;, &quot;type&quot;: &quot;batch&quot;, &quot;target&quot;: &quot;cmd /c C:\\Dev\\build.bat&quot;, &quot;showWindow&quot;: false }</p>
<p> ],</p>
<p> &quot;clipboardTransformers&quot;: [</p>
<p> { &quot;key&quot;: &quot;$myRule&quot;, &quot;type&quot;: &quot;regex&quot;, &quot;pattern&quot;: &quot;(\\d{4})-(\\d{2})-(\\d{2})&quot;, &quot;replace&quot;: &quot;$3/$2/$1&quot; }</p>
<p> ],</p>
<p> &quot;apiAdapters&quot;: [</p>
<p> { &quot;id&quot;: &quot;jira&quot;, &quot;baseUrl&quot;: &quot;https://yourorg.atlassian.net&quot;, &quot;credentialKey&quot;: &quot;jira_pat&quot; }</p>
<p> ],</p>
<p> &quot;plugins&quot;: [</p>
<p> { &quot;path&quot;: &quot;C:\\OLEDiPlugins\\MyPlugin.dll&quot;, &quot;enabled&quot;: true }</p>
<p> ]</p>
<p>}</p>
<p>&lt;br/&gt;</p>
<h1>&lt;b&gt;8. 개발자 확장 가이드 (Plugin &amp; Skill Development)&lt;/b&gt;</h1>
<p>이 섹션은 OLEDi Commander에 새로운 기능, 명령어, API 연결, 변환 규칙을 추가하려는 개발자를 위한 문서입니다. 확장 방법은 세 가지 경로로 제공됩니다.</p>
<table>
<tr><th><b>확장 방법</b></th><th><b>난이도</b></th><th><b>추천 대상</b></th></tr>
<tr><td>settings.json 수정</td><td>쉬움</td><td>URL Alias, 폴더 단축키, 배치 명령 추가</td></tr>
<tr><td>JSON 스킬 파일 (.skill.json)</td><td>보통</td><td>API 어댑터, 커스텀 변환 규칙</td></tr>
<tr><td>.dll 플러그인 (C# IActionHandler)</td><td>어려움</td><td>완전히 새로운 명령 타입, UI 커스터마이징</td></tr>
</table>
<h2>&lt;b&gt;8.1 settings.json으로 Alias 추가하기&lt;/b&gt;</h2>
<p>가장 간단한 확장 방법입니다. settings.json 파일을 텍스트 에디터로 열어 &quot;aliases&quot; 배열에 항목을 추가합니다.</p>
<p>// URL 열기 Alias 추가</p>
<p>{ &quot;key&quot;: &quot;@notion&quot;, &quot;type&quot;: &quot;url&quot;, &quot;target&quot;: &quot;https://notion.so/your-workspace&quot; }</p>
<p>// 폴더 열기 Alias 추가</p>
<p>{ &quot;key&quot;: &quot;~dl&quot;, &quot;type&quot;: &quot;folder&quot;, &quot;target&quot;: &quot;%USERPROFILE%\\Downloads&quot; }</p>
<p>// 배치 파일 실행 Alias 추가 (출력 창 표시)</p>
<p>{ &quot;key&quot;: &quot;&gt;deploy&quot;, &quot;type&quot;: &quot;batch&quot;, &quot;target&quot;: &quot;cmd /c C:\\deploy.bat&quot;, &quot;showWindow&quot;: true }</p>
<p>// 배치 파일 실행 Alias 추가 (백그라운드 실행)</p>
<p>{ &quot;key&quot;: &quot;&gt;silent&quot;, &quot;type&quot;: &quot;batch&quot;, &quot;target&quot;: &quot;powershell -File C:\\task.ps1&quot;, &quot;showWindow&quot;: false }</p>
<table>
<tr><th><b>팁: 환경변수 사용</b><br/>"target" 값에 %USERPROFILE%, %APPDATA%, %TEMP% 등 Windows 환경변수를 사용할 수 있습니다.<br/>앱 실행 시 자동으로 확장됩니다.</th></tr>
</table>
<h2>&lt;b&gt;8.2 Clipboard Transformer 규칙 추가하기&lt;/b&gt;</h2>
<p>settings.json의 &quot;clipboardTransformers&quot; 배열에 변환 규칙을 추가합니다. 정규식(regex) 또는 외부 스크립트(script) 타입을 지원합니다.</p>
<p>// 정규식 변환: 날짜 형식 YYYY-MM-DD → DD/MM/YYYY</p>
<p>{</p>
<p> &quot;key&quot;: &quot;$date&quot;,</p>
<p> &quot;type&quot;: &quot;regex&quot;,</p>
<p> &quot;pattern&quot;: &quot;(\\d{4})-(\\d{2})-(\\d{2})&quot;,</p>
<p> &quot;replace&quot;: &quot;$3/$2/$1&quot;,</p>
<p> &quot;description&quot;: &quot;ISO 날짜를 로컬 날짜 형식으로 변환&quot;</p>
<p>}</p>
<p>// PowerShell 스크립트 호출 변환</p>
<p>{</p>
<p> &quot;key&quot;: &quot;$ps&quot;,</p>
<p> &quot;type&quot;: &quot;script&quot;,</p>
<p> &quot;command&quot;: &quot;powershell -NoProfile -Command \&quot;$input | ConvertTo-Json\&quot;&quot;,</p>
<p> &quot;timeout&quot;: 5000,</p>
<p> &quot;description&quot;: &quot;클립보드 텍스트를 PowerShell로 처리&quot;</p>
<p>}</p>
<p>스크립트 타입의 경우, 앱은 클립보드 텍스트를 stdin으로 전달하고 stdout 결과를 클립보드에 저장합니다. timeout(ms) 초과 시 원본 텍스트를 유지합니다.</p>
<h2>&lt;b&gt;8.3 JSON 스킬 파일로 API Adapter 추가하기&lt;/b&gt;</h2>
<p>새로운 API 서비스(예: Notion, Linear, Slack)를 연결하려면 .skill.json 파일을 작성합니다. 파일은 %APPDATA%\OLEDiCommander\skills\ 폴더에 저장합니다.</p>
<p>// %APPDATA%\OLEDiCommander\skills\notion.skill.json</p>
<p>{</p>
<p> &quot;id&quot;: &quot;notion&quot;,</p>
<p> &quot;name&quot;: &quot;Notion 페이지 검색&quot;,</p>
<p> &quot;version&quot;: &quot;1.0&quot;,</p>
<p> &quot;prefix&quot;: &quot;#notion&quot;,</p>
<p> &quot;credential&quot;: {</p>
<p> &quot;type&quot;: &quot;bearer_token&quot;,</p>
<p> &quot;credentialKey&quot;: &quot;notion_api_token&quot;</p>
<p> },</p>
<p> &quot;request&quot;: {</p>
<p> &quot;method&quot;: &quot;POST&quot;,</p>
<p> &quot;url&quot;: &quot;https://api.notion.com/v1/search&quot;,</p>
<p> &quot;headers&quot;: { &quot;Notion-Version&quot;: &quot;2022-06-28&quot; },</p>
<p> &quot;body&quot;: { &quot;query&quot;: &quot;{{INPUT}}&quot;, &quot;page_size&quot;: 10 }</p>
<p> },</p>
<p> &quot;response&quot;: {</p>
<p> &quot;resultsPath&quot;: &quot;results&quot;,</p>
<p> &quot;titleField&quot;: &quot;properties.title.title[0].plain_text&quot;,</p>
<p> &quot;subtitleField&quot;: &quot;url&quot;,</p>
<p> &quot;actionUrl&quot;: &quot;url&quot;</p>
<p> },</p>
<p> &quot;cache&quot;: { &quot;ttl&quot;: 300 }</p>
<p>}</p>
<p>{{INPUT}}은 사용자가 # 이후에 입력한 텍스트로 치환됩니다. 토큰은 앱 내 설정 화면에서 입력하며 Windows Credential Manager에 암호화 저장됩니다.</p>
<table>
<tr><th><b>필드</b></th><th><b>필수</b></th><th><b>설명</b></th></tr>
<tr><td>id</td><td></td><td>스킬 고유 식별자 (영문 소문자, 하이픈 허용)</td></tr>
<tr><td>prefix</td><td></td><td>런처에서 이 스킬을 호출하는 명령어 (예: #notion)</td></tr>
<tr><td>credential.type</td><td></td><td>bearer_token / basic_auth / oauth2 중 선택</td></tr>
<tr><td>request.url</td><td></td><td>API 엔드포인트. {{INPUT}}, {{TOKEN}} 템플릿 변수 사용 가능</td></tr>
<tr><td>response.resultsPath</td><td></td><td>JSON 응답에서 결과 배열 경로 (dot notation)</td></tr>
<tr><td>response.actionUrl</td><td></td><td>Enter 시 열리는 URL 필드 경로</td></tr>
<tr><td>cache.ttl</td><td>아니오</td><td>결과 캐시 유효 시간(초). 기본값 0 (캐시 없음)</td></tr>
</table>
<h2>&lt;b&gt;8.4 C# .dll 플러그인으로 새 ActionHandler 개발하기&lt;/b&gt;</h2>
<p>완전히 새로운 명령 타입, 복잡한 UI, 또는 OS 레벨 기능이 필요한 경우 C# 플러그인을 개발합니다.</p>
<h3>&lt;b&gt;8.4.1 인터페이스 정의&lt;/b&gt;</h3>
<p>// OLEDiCommander.SDK NuGet 패키지 설치 후 사용</p>
<p>using OLEDiCommander.SDK;</p>
<p>/// &lt;summary&gt;</p>
<p>/// 모든 플러그인은 이 인터페이스를 구현해야 합니다.</p>
<p>/// &lt;/summary&gt;</p>
<p>public interface IActionHandler</p>
<p>{</p>
<p> // 이 핸들러가 처리할 prefix (예: &quot;@&quot;, &quot;#&quot;, &quot;!&quot;) 또는 null (Fuzzy 결과에만 등록)</p>
<p> string? Prefix { get; }</p>
<p> // 런처 결과 리스트에 표시할 항목을 반환합니다.</p>
<p> // query: prefix 이후의 입력 텍스트</p>
<p> Task&lt;IEnumerable&lt;LauncherItem&gt;&gt; GetItemsAsync(string query, CancellationToken ct);</p>
<p> // 사용자가 항목을 선택(Enter)했을 때 실행됩니다.</p>
<p> Task ExecuteAsync(LauncherItem item, CancellationToken ct);</p>
<p> // 플러그인 메타데이터</p>
<p> PluginMetadata Metadata { get; }</p>
<p>}</p>
<p>public record LauncherItem(</p>
<p> string Title,</p>
<p> string Subtitle,</p>
<p> string? IconPath, // null이면 기본 아이콘 사용</p>
<p> object? Data // ExecuteAsync에 전달되는 임의 데이터</p>
<p>);</p>
<p>public record PluginMetadata(</p>
<p> string Id,</p>
<p> string Name,</p>
<p> string Version,</p>
<p> string Author</p>
<p>);</p>
<h3>&lt;b&gt;8.4.2 예제: 계산기 플러그인&lt;/b&gt;</h3>
<p>using OLEDiCommander.SDK;</p>
<p>using System.Data;</p>
<p>[Export(typeof(IActionHandler))]</p>
<p>public class CalculatorHandler : IActionHandler</p>
<p>{</p>
<p> public string? Prefix =&gt; &quot;=&quot;; // &quot;=2+3&quot; 입력 시 이 핸들러 호출</p>
<p> public PluginMetadata Metadata =&gt; new(&quot;calculator&quot;, &quot;계산기&quot;, &quot;1.0&quot;, &quot;YourName&quot;);</p>
<p> public async Task&lt;IEnumerable&lt;LauncherItem&gt;&gt; GetItemsAsync(string query, CancellationToken ct)</p>
<p> {</p>
<p> try</p>
<p> {</p>
<p> var result = new DataTable().Compute(query, null);</p>
<p> return [new LauncherItem($&quot;= {result}&quot;, &quot;Enter로 복사&quot;, null, result.ToString())];</p>
<p> }</p>
<p> catch</p>
<p> {</p>
<p> return [new LauncherItem(&quot;수식 오류&quot;, &quot;올바른 수식을 입력하세요&quot;, null, null)];</p>
<p> }</p>
<p> }</p>
<p> public async Task ExecuteAsync(LauncherItem item, CancellationToken ct)</p>
<p> {</p>
<p> if (item.Data is string val)</p>
<p> Clipboard.SetText(val); // 결과를 클립보드에 복사</p>
<p> }</p>
<p>}</p>
<h3>&lt;b&gt;8.4.3 플러그인 배포 및 등록&lt;/b&gt;</h3>
<ul>
<li>프로젝트를 빌드하여 MyPlugin.dll 생성</li>
<li>.dll을 임의 폴더에 저장 (예: C:\\OLEDiPlugins\\MyPlugin.dll)</li>
<li>settings.json의 &quot;plugins&quot; 배열에 경로 등록:</li>
</ul>
<p>{ &quot;path&quot;: &quot;C:\\OLEDiPlugins\\MyPlugin.dll&quot;, &quot;enabled&quot;: true }</p>
<ul>
<li>OLEDi Commander 재시작 → 시스템 트레이 아이콘 우클릭 → &quot;플러그인 재로드&quot;</li>
</ul>
<table>
<tr><th><b>개발 팁: 핫 리로드</b><br/>개발 중에는 시스템 트레이 → "개발자 모드" 활성화 시 플러그인 파일 변경 감지 후 자동 재로드됩니다.<br/>OLEDiCommander.SDK.dll은 NuGet 패키지(OLEDiCommander.SDK)로 배포됩니다.<br/>단위 테스트 시 MockLauncherContext를 주입하여 UI 없이 IActionHandler를 테스트할 수 있습니다.</th></tr>
</table>
<h2>&lt;b&gt;8.5 확장 체크리스트&lt;/b&gt;</h2>
<p>새 기능을 추가하기 전에 아래 항목을 확인하십시오.</p>
<table>
<tr><th><b>체크 항목</b></th><th><b>확인 기준</b></th></tr>
<tr><td>Prefix 충돌 확인</td><td>기존 Prefix 테이블(섹션 2.2)과 중복 없는지 확인</td></tr>
<tr><td>에러 처리</td><td>네트워크 오류, 타임아웃, null 결과 등 모든 예외 처리 구현</td></tr>
<tr><td>CancellationToken 사용</td><td>사용자가 ESC 입력 시 진행 중인 비동기 작업 즉시 취소</td></tr>
<tr><td>캐싱 고려</td><td>API 호출 결과는 TTL 캐시 적용으로 불필요한 네트워크 요청 방지</td></tr>
<tr><td>로깅 추가</td><td>ILogger<T>를 통해 DEBUG/ERROR 레벨 로그 기록</td></tr>
<tr><td>단위 테스트</td><td>GetItemsAsync, ExecuteAsync 각각 최소 1개 이상 테스트 케이스 작성</td></tr>
<tr><td>아이콘 제공</td><td>32x32 PNG 아이콘을 LauncherItem.IconPath에 포함</td></tr>
<tr><td>설명 작성</td><td>PluginMetadata.Name, settings.json의 description 필드 작성</td></tr>
</table>
<p>&lt;br/&gt;</p>
<h1>&lt;b&gt;9. 설치 및 배포&lt;/b&gt;</h1>
<table>
<tr><th><b>항목</b></th><th><b>내용</b></th></tr>
<tr><td>배포 형태</td><td>MSIX 패키지 (Microsoft Store) 또는 Squirrel.Windows 인스톨러 (.exe)</td></tr>
<tr><td>Self-contained</td><td>.NET 8 Self-contained 배포 별도 런타임 설치 불필요</td></tr>
<tr><td>설치 경로</td><td>%LOCALAPPDATA%\Programs\OLEDiCommander\</td></tr>
<tr><td>설정 경로</td><td>%APPDATA%\OLEDiCommander\</td></tr>
<tr><td>자동 시작</td><td>HKCU Run 레지스트리 키 등록 (설치 시 옵션 선택)</td></tr>
<tr><td>자동 업데이트</td><td>앱 실행 시 GitHub Releases API 버전 확인, 백그라운드 다운로드</td></tr>
<tr><td>제거</td><td>일반 "앱 및 기능"으로 제거 가능. 설정 파일 유지 여부 선택</td></tr>
</table>
<h1>&lt;b&gt;10. 미결 사항 및 향후 계획 (v2.0)&lt;/b&gt;</h1>
<table>
<tr><th><b>항목</b></th><th><b>우선순위</b></th><th><b>비고</b></th></tr>
<tr><td>플러그인 마켓플레이스 UI</td><td>높음</td><td>서드파티 스킬 검색/설치/업데이트 통합 UI</td></tr>
<tr><td>OAuth 2.0 인증 흐름</td><td>높음</td><td>GitHub, Google 등 OAuth 기반 API 어댑터 지원</td></tr>
<tr><td>AI 자연어 명령</td><td>중간</td><td>예: "1주일 전 내 Jira 티켓 열어줘" → #jira 쿼리 자동 생성</td></tr>
<tr><td>클라우드 설정 동기화</td><td>중간</td><td>OneDrive / iCloud Drive를 통한 settings.json 동기화</td></tr>
<tr><td>플러그인 서명 강제화</td><td>높음</td><td>v2.0부터 서명 없는 .dll 실행 차단</td></tr>
<tr><td>스크립트 언어 지원</td><td>낮음</td><td>Python / Node.js 스크립트를 스킬로 직접 등록</td></tr>
<tr><td>음성 명령 입력</td><td>낮음</td><td>Windows Speech API 연동</td></tr>
</table>
<h1>&lt;b&gt;변경 이력&lt;/b&gt;</h1>
<table>
<tr><th><b>버전</b></th><th><b>날짜</b></th><th><b>작성자</b></th><th><b>변경 내용</b></th></tr>
<tr><td>v1.0</td><td>2026-03-21</td><td></td><td>초안 작성 (SRS 전체 구조 + 개발자 확장 가이드 포함)</td></tr>
</table>
<p class='footer'>Converted from: AXCommander_SRS_v1.0.docx</p>
</body></html>