576 lines
36 KiB
HTML
576 lines
36 KiB
HTML
<!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><b>OLEDi Commander</b></p>
|
||
<p>Software Requirements Specification</p>
|
||
<p>Windows 전용 시맨틱 런처 & 워크스페이스 매니저</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><br/></p>
|
||
<h1><b>목차</b></h1>
|
||
<p><br/></p>
|
||
<h1><b>1. 개요 (Overview)</b></h1>
|
||
<h2><b>1.1 프로젝트 목적</b></h2>
|
||
<p>OLEDi Commander는 macOS 생산성 도구 Alfred에서 영감을 받아, Windows 환경에서 동등하거나 그 이상의 생산성을 제공하기 위해 설계된 키보드 우선(Keyboard-first) 런처 겸 워크스페이스 매니저입니다.</p>
|
||
<p>전통적인 마우스 중심 UI 조작을 Alt+Space 단축키 하나로 대체하여, 개발자·파워유저가 컨텍스트 전환 비용 없이 빠르게 작업을 수행할 수 있도록 합니다.</p>
|
||
|
||
<h2><b>1.2 범위 (Scope)</b></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><b>1.3 용어 정의 (Glossary)</b></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><b>1.4 참조 문서</b></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><br/></p>
|
||
<h1><b>2. 시스템 아키텍처</b></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><b>2.1 모듈 간 데이터 흐름</b></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><b>2.2 Prefix 라우팅 테이블</b></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><br/></p>
|
||
<h1><b>3. 기능 요구사항</b></h1>
|
||
<h2><b>3.1 워크스페이스 스냅샷 & 시프트 (The Shifter)</b></h2>
|
||
<p>"어떤 위치에 어떤 크기로" 떠 있는지까지 관리하는 워크스페이스 레이아웃 엔진입니다. 단순 앱 실행을 넘어 창의 상태(State)를 완전히 복원합니다.</p>
|
||
|
||
<h3><b>3.1.1 Snapshot Capture</b></h3>
|
||
<ul>
|
||
<li>현재 화면에 있는 모든 가시(Visible) 업무용 창의 배치를 "프로필"로 저장</li>
|
||
<li>저장 정보: HWND, 프로세스 실행 경로(EXE), 창 제목, Rect (좌상단 x/y, 폭, 높이), 최소화/최대화/일반 상태(ShowCmd)</li>
|
||
<li>저장 위치: settings.json의 "profiles" 배열</li>
|
||
<li>저장 명령어: !save <프로필명> 또는 UI의 저장 버튼</li>
|
||
<li>시스템 트레이 UI(Task Bar) 및 바탕화면은 캡처 제외</li>
|
||
</ul>
|
||
|
||
<h3><b>3.1.2 Instant Restore</b></h3>
|
||
<ul>
|
||
<li>!<프로필명> 입력 시 해당 프로필의 창 배치를 강제 복원</li>
|
||
<li>복원 순서: 1) 저장된 EXE 경로로 프로세스 존재 확인 → 2) HWND 유효성 검사 → 3) SetWindowPos 호출</li>
|
||
<li>앱이 실행 중이지 않은 경우: 자동으로 EXE를 실행한 후 창 생성을 최대 3초간 대기</li>
|
||
<li>3초 내 창이 생성되지 않으면 해당 앱을 건너뛰고 나머지 복원 진행, 결과 알림 표시</li>
|
||
<li>관리자 권한 창(elevated process)은 복원 시 UAC 프롬프트 없이 위치만 조정 (권한 상승 없이 처리 가능한 범위 내에서 수행)</li>
|
||
</ul>
|
||
|
||
<h3><b>3.1.3 Profile 관리</b></h3>
|
||
<ul>
|
||
<li>목록 보기: 런처 UI에서 !<엔터> 입력 시 저장된 프로필 전체 목록 표시</li>
|
||
<li>이름 변경: !rename <현재명> <새이름></li>
|
||
<li>삭제: !delete <프로필명> (확인 다이얼로그 필요)</li>
|
||
<li>내보내기/가져오기: settings.json 파일 직접 공유 가능 (JSON 스키마 준수 시)</li>
|
||
</ul>
|
||
|
||
<h3><b>3.1.4 Multi-Monitor 지원</b></h3>
|
||
<ul>
|
||
<li>각 모니터의 DPI 및 좌표계 독립 저장</li>
|
||
<li>모니터 구성 변경 감지: WM_DISPLAYCHANGE 메시지 수신 시 활성 프로필과 현재 모니터 구성 비교</li>
|
||
<li>모니터 불일치 시 동작 옵션 (settings.json으로 설정 가능):</li>
|
||
<li>"fit": 사용 가능한 모니터에 좌표 스케일링하여 배치</li>
|
||
<li>"skip": 해당 모니터의 창만 건너뜀</li>
|
||
<li>"warn": 복원 전 경고 팝업 표시 (기본값)</li>
|
||
</ul>
|
||
|
||
<h2><b>3.2 시맨틱 커맨드 런처 (The Alfred)</b></h2>
|
||
<p>키보드만으로 마우스 클릭 수십 번의 가치를 만들어냅니다. Alt+Space → 명령어 입력의 단일 패턴으로 모든 작업을 처리합니다.</p>
|
||
|
||
<h3><b>3.2.1 Smart Aliases</b></h3>
|
||
<p>긴 경로나 복잡한 명령을 짧은 별칭으로 등록합니다. settings.json의 "aliases" 배열로 관리됩니다.</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><b>3.2.2 Fuzzy Engine</b></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>대소문자 구분 없음, 한글 초성 검색 지원 (예: "ㅂㅅ" → "Visual Studio")</li>
|
||
</ul>
|
||
|
||
<h3><b>3.2.3 Clipboard Transformer</b></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의 "clipboardTransformers" 배열에 추가하며, 정규식 기반 변환과 외부 스크립트 호출을 지원합니다. 자세한 내용은 섹션 7.4를 참조하십시오.</p>
|
||
|
||
<h2><b>3.3 동적 API Alias (#)</b></h2>
|
||
<p># prefix를 사용하는 Alias는 외부 API를 호출하여 동적 결과를 표시합니다. 각 API 연결은 "apiAdapters" 설정으로 정의합니다.</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><br/></p>
|
||
<h1><b>4. UI/UX 디자인 요구사항</b></h1>
|
||
<h2><b>4.1 런처 윈도우</b></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><b>4.2 결과 리스트</b></h2>
|
||
<ul>
|
||
<li>최대 5~7개 항목 표시 (기본 5개, settings.json에서 조정)</li>
|
||
<li>각 항목: 아이콘(32x32) + 제목 + 부제목(경로 또는 설명)</li>
|
||
<li>키보드 탐색: ↑↓ 화살표, Enter로 실행, Esc로 닫기, Tab으로 자동완성</li>
|
||
<li>마우스 클릭으로도 실행 가능</li>
|
||
<li>결과 없음 시: "일치하는 항목이 없습니다" 메시지 표시</li>
|
||
</ul>
|
||
|
||
<h2><b>4.3 접근성 (Accessibility)</b></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><br/></p>
|
||
<h1><b>5. 비기능 요구사항 (NFR)</b></h1>
|
||
<h2><b>5.1 성능</b></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><b>5.2 신뢰성 & 안정성</b></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><b>5.3 보안</b></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><b>5.4 유지보수성</b></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><b>5.5 호환성</b></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><br/></p>
|
||
<h1><b>6. 예외 처리 및 엣지 케이스</b></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><br/></p>
|
||
<h1><b>7. 데이터 스키마 (settings.json)</b></h1>
|
||
<p>모든 사용자 설정과 Profile, Alias는 단일 JSON 파일로 관리됩니다. 파일 위치: %APPDATA%\OLEDiCommander\settings.json</p>
|
||
|
||
<p>{</p>
|
||
<p> "version": "1.0",</p>
|
||
<p> "hotkey": "Alt+Space",</p>
|
||
<p> "launcher": {</p>
|
||
<p> "opacity": 0.96,</p>
|
||
<p> "maxResults": 7,</p>
|
||
<p> "theme": "system",</p>
|
||
<p> "position": "center-top"</p>
|
||
<p> },</p>
|
||
<p> "indexPaths": [</p>
|
||
<p> "%USERPROFILE%\\Desktop",</p>
|
||
<p> "%APPDATA%\\Microsoft\\Windows\\Start Menu"</p>
|
||
<p> ],</p>
|
||
<p> "monitorMismatch": "warn",</p>
|
||
<p> "profiles": [</p>
|
||
<p> {</p>
|
||
<p> "name": "dev",</p>
|
||
<p> "windows": [</p>
|
||
<p> {</p>
|
||
<p> "exe": "C:\\Program Files\\Microsoft VS Code\\Code.exe",</p>
|
||
<p> "title": "Visual Studio Code",</p>
|
||
<p> "rect": { "x": 0, "y": 0, "width": 1280, "height": 1080 },</p>
|
||
<p> "showCmd": "Normal",</p>
|
||
<p> "monitor": 0</p>
|
||
<p> }</p>
|
||
<p> ]</p>
|
||
<p> }</p>
|
||
<p> ],</p>
|
||
<p> "aliases": [</p>
|
||
<p> { "key": "@blog", "type": "url", "target": "https://swarchitect.net/admin" },</p>
|
||
<p> { "key": "#jira", "type": "api", "adapter": "jira", "query": "assignee=currentUser() ORDER BY updated DESC" },</p>
|
||
<p> { "key": "~proj", "type": "folder", "target": "C:\\Dev\\Projects" },</p>
|
||
<p> { "key": ">build", "type": "batch", "target": "cmd /c C:\\Dev\\build.bat", "showWindow": false }</p>
|
||
<p> ],</p>
|
||
<p> "clipboardTransformers": [</p>
|
||
<p> { "key": "$myRule", "type": "regex", "pattern": "(\\d{4})-(\\d{2})-(\\d{2})", "replace": "$3/$2/$1" }</p>
|
||
<p> ],</p>
|
||
<p> "apiAdapters": [</p>
|
||
<p> { "id": "jira", "baseUrl": "https://yourorg.atlassian.net", "credentialKey": "jira_pat" }</p>
|
||
<p> ],</p>
|
||
<p> "plugins": [</p>
|
||
<p> { "path": "C:\\OLEDiPlugins\\MyPlugin.dll", "enabled": true }</p>
|
||
<p> ]</p>
|
||
<p>}</p>
|
||
<p><br/></p>
|
||
<h1><b>8. 개발자 확장 가이드 (Plugin & Skill Development)</b></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><b>8.1 settings.json으로 Alias 추가하기</b></h2>
|
||
<p>가장 간단한 확장 방법입니다. settings.json 파일을 텍스트 에디터로 열어 "aliases" 배열에 항목을 추가합니다.</p>
|
||
|
||
<p>// URL 열기 Alias 추가</p>
|
||
<p>{ "key": "@notion", "type": "url", "target": "https://notion.so/your-workspace" }</p>
|
||
|
||
<p>// 폴더 열기 Alias 추가</p>
|
||
<p>{ "key": "~dl", "type": "folder", "target": "%USERPROFILE%\\Downloads" }</p>
|
||
|
||
<p>// 배치 파일 실행 Alias 추가 (출력 창 표시)</p>
|
||
<p>{ "key": ">deploy", "type": "batch", "target": "cmd /c C:\\deploy.bat", "showWindow": true }</p>
|
||
|
||
<p>// 배치 파일 실행 Alias 추가 (백그라운드 실행)</p>
|
||
<p>{ "key": ">silent", "type": "batch", "target": "powershell -File C:\\task.ps1", "showWindow": false }</p>
|
||
|
||
<table>
|
||
<tr><th><b>팁: 환경변수 사용</b><br/>"target" 값에 %USERPROFILE%, %APPDATA%, %TEMP% 등 Windows 환경변수를 사용할 수 있습니다.<br/>앱 실행 시 자동으로 확장됩니다.</th></tr>
|
||
</table>
|
||
|
||
<h2><b>8.2 Clipboard Transformer 규칙 추가하기</b></h2>
|
||
<p>settings.json의 "clipboardTransformers" 배열에 변환 규칙을 추가합니다. 정규식(regex) 또는 외부 스크립트(script) 타입을 지원합니다.</p>
|
||
|
||
<p>// 정규식 변환: 날짜 형식 YYYY-MM-DD → DD/MM/YYYY</p>
|
||
<p>{</p>
|
||
<p> "key": "$date",</p>
|
||
<p> "type": "regex",</p>
|
||
<p> "pattern": "(\\d{4})-(\\d{2})-(\\d{2})",</p>
|
||
<p> "replace": "$3/$2/$1",</p>
|
||
<p> "description": "ISO 날짜를 로컬 날짜 형식으로 변환"</p>
|
||
<p>}</p>
|
||
|
||
<p>// PowerShell 스크립트 호출 변환</p>
|
||
<p>{</p>
|
||
<p> "key": "$ps",</p>
|
||
<p> "type": "script",</p>
|
||
<p> "command": "powershell -NoProfile -Command \"$input | ConvertTo-Json\"",</p>
|
||
<p> "timeout": 5000,</p>
|
||
<p> "description": "클립보드 텍스트를 PowerShell로 처리"</p>
|
||
<p>}</p>
|
||
|
||
<p>스크립트 타입의 경우, 앱은 클립보드 텍스트를 stdin으로 전달하고 stdout 결과를 클립보드에 저장합니다. timeout(ms) 초과 시 원본 텍스트를 유지합니다.</p>
|
||
|
||
<h2><b>8.3 JSON 스킬 파일로 API Adapter 추가하기</b></h2>
|
||
<p>새로운 API 서비스(예: Notion, Linear, Slack)를 연결하려면 .skill.json 파일을 작성합니다. 파일은 %APPDATA%\OLEDiCommander\skills\ 폴더에 저장합니다.</p>
|
||
|
||
<p>// %APPDATA%\OLEDiCommander\skills\notion.skill.json</p>
|
||
<p>{</p>
|
||
<p> "id": "notion",</p>
|
||
<p> "name": "Notion 페이지 검색",</p>
|
||
<p> "version": "1.0",</p>
|
||
<p> "prefix": "#notion",</p>
|
||
<p> "credential": {</p>
|
||
<p> "type": "bearer_token",</p>
|
||
<p> "credentialKey": "notion_api_token"</p>
|
||
<p> },</p>
|
||
<p> "request": {</p>
|
||
<p> "method": "POST",</p>
|
||
<p> "url": "https://api.notion.com/v1/search",</p>
|
||
<p> "headers": { "Notion-Version": "2022-06-28" },</p>
|
||
<p> "body": { "query": "{{INPUT}}", "page_size": 10 }</p>
|
||
<p> },</p>
|
||
<p> "response": {</p>
|
||
<p> "resultsPath": "results",</p>
|
||
<p> "titleField": "properties.title.title[0].plain_text",</p>
|
||
<p> "subtitleField": "url",</p>
|
||
<p> "actionUrl": "url"</p>
|
||
<p> },</p>
|
||
<p> "cache": { "ttl": 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><b>8.4 C# .dll 플러그인으로 새 ActionHandler 개발하기</b></h2>
|
||
<p>완전히 새로운 명령 타입, 복잡한 UI, 또는 OS 레벨 기능이 필요한 경우 C# 플러그인을 개발합니다.</p>
|
||
|
||
<h3><b>8.4.1 인터페이스 정의</b></h3>
|
||
<p>// OLEDiCommander.SDK NuGet 패키지 설치 후 사용</p>
|
||
<p>using OLEDiCommander.SDK;</p>
|
||
|
||
<p>/// <summary></p>
|
||
<p>/// 모든 플러그인은 이 인터페이스를 구현해야 합니다.</p>
|
||
<p>/// </summary></p>
|
||
<p>public interface IActionHandler</p>
|
||
<p>{</p>
|
||
<p> // 이 핸들러가 처리할 prefix (예: "@", "#", "!") 또는 null (Fuzzy 결과에만 등록)</p>
|
||
<p> string? Prefix { get; }</p>
|
||
|
||
<p> // 런처 결과 리스트에 표시할 항목을 반환합니다.</p>
|
||
<p> // query: prefix 이후의 입력 텍스트</p>
|
||
<p> Task<IEnumerable<LauncherItem>> 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><b>8.4.2 예제: 계산기 플러그인</b></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 => "="; // "=2+3" 입력 시 이 핸들러 호출</p>
|
||
|
||
<p> public PluginMetadata Metadata => new("calculator", "계산기", "1.0", "YourName");</p>
|
||
|
||
<p> public async Task<IEnumerable<LauncherItem>> 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($"= {result}", "Enter로 복사", null, result.ToString())];</p>
|
||
<p> }</p>
|
||
<p> catch</p>
|
||
<p> {</p>
|
||
<p> return [new LauncherItem("수식 오류", "올바른 수식을 입력하세요", 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><b>8.4.3 플러그인 배포 및 등록</b></h3>
|
||
<ul>
|
||
<li>프로젝트를 빌드하여 MyPlugin.dll 생성</li>
|
||
<li>.dll을 임의 폴더에 저장 (예: C:\\OLEDiPlugins\\MyPlugin.dll)</li>
|
||
<li>settings.json의 "plugins" 배열에 경로 등록:</li>
|
||
</ul>
|
||
<p>{ "path": "C:\\OLEDiPlugins\\MyPlugin.dll", "enabled": true }</p>
|
||
<ul>
|
||
<li>OLEDi Commander 재시작 → 시스템 트레이 아이콘 우클릭 → "플러그인 재로드"</li>
|
||
</ul>
|
||
|
||
<table>
|
||
<tr><th><b>개발 팁: 핫 리로드</b><br/>개발 중에는 시스템 트레이 → "개발자 모드" 활성화 시 플러그인 파일 변경 감지 후 자동 재로드됩니다.<br/>OLEDiCommander.SDK.dll은 NuGet 패키지(OLEDiCommander.SDK)로 배포됩니다.<br/>단위 테스트 시 MockLauncherContext를 주입하여 UI 없이 IActionHandler를 테스트할 수 있습니다.</th></tr>
|
||
</table>
|
||
|
||
<h2><b>8.5 확장 체크리스트</b></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><br/></p>
|
||
<h1><b>9. 설치 및 배포</b></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><b>10. 미결 사항 및 향후 계획 (v2.0)</b></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><b>변경 이력</b></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>
|