서버 일을 하다 보면 같은 명령어를 하루에도 몇 번씩 친다. 어느 포트를 누가 쓰고 있는지, 도커 컨테이너 로그를 마지막 100줄만, MySQL 테이블 크기 순서대로. 매번 기억이 나지 않아 메모장이나 예전 채팅을 뒤진다. 찾고 나면 컨테이너 이름이나 포트 번호를 바꿔서 다시 붙여 넣는다.
이 일을 2초 안에 끝내는 도구를 만들었다. 이름은 Cliply(CLI + Clip)다. 어느 프로그램에서든 Ctrl+Shift+Space를 누르면 검색 팝업이 뜨고, 몇 글자 치고 Enter를 누르면 클립보드에 복사된다. 원래 창으로 돌아가 Ctrl+V 하면 끝이다.

이 연재에서는 Cliply를 만들며 내린 결정과 부딪힌 문제를 차례로 정리한다. 첫 글에서는 전체 흐름을 먼저 본다.
어떤 앱인가
| 항목 | 내용 |
|---|---|
| 대상 | Windows 10 / 11 (x64), 1인 로컬 사용 |
| 핵심 경험 | 단축키 → 검색어 입력 → Enter → 클립보드 복사, 2초 이내 |
| 저장 | %LOCALAPPDATA%\Cliply\cliply.db (SQLite) |
| 배포 | .NET 설치가 필요 없는 단일 exe, zip 하나 |
기능은 다섯 가지로 정리된다.
- 빠른 검색 — 전역 단축키로 뜨는 팝업.
#docker(태그),@sql(언어),"구문"같은 검색 문법을 지원한다. 한글도 부분 문자열로 찾는다. - 변수 — 본문에
{{컨테이너}},{{줄수:100}}처럼 변수를 넣으면 복사할 때 입력 창이 뜨고, 마지막에 넣은 값을 기억해 둔다. - 관리 창 — 스니펫 추가·수정·삭제, 태그 칩 입력, 고정(핀), 사용 횟수.
- 가져오기 / 내보내기 — JSON 파일. 가져오기 전에 DB를 자동으로 백업한다.
- 트레이 상주 — 창 없이 트레이에 있다가 단축키로만 나타난다. 설정에서 단축키·자동 실행·테마·언어를 바꾼다.

먼저 설계 문서를 썼다
구현에 들어가기 전에 저장소 루트에 설계 문서부터 만들었다. 400줄 정도 되는 문서로, 이후 모든 결정의 기준이 됐다. 들어간 내용은 이렇다.
- 제품 개요 — 목적, 핵심 경험("2초 이내"), 대상 OS
- 기술 스택 — .NET 8, WPF, CommunityToolkit.Mvvm, EF Core + SQLite FTS5, H.NotifyIcon.Wpf, Win32
RegisterHotKey - 솔루션 구조 —
Core(순수 로직) /Data(EF Core·FTS) /App(WPF) /Tests네 프로젝트와 폴더 - DB 스키마 — 테이블·컬럼·인덱스, FTS5 가상 테이블 정의
- 기능 명세 F1~F8 — 키 조작 표, 정렬 규칙, 완료 조건
- 구현 단계 Phase 0~7 — 단계마다 할 일과 "✅ 확인" 기준
- 코딩 규칙 — 코드비하인드에는 UI 처리만, DB는
IDbContextFactory로 짧게, P/Invoke는 한 파일에 - 범위 외 — 서버 동기화, 자동 붙여넣기, 구문 강조, 클립보드 히스토리, 자동 업데이트, 영어 UI
여기서 가장 공을 들인 부분은 성능 목표와 범위 외 목록이다. "단축키 → 창 표시 100ms 이내, 스니펫 5,000개에서 검색 50ms 이내"를 숫자로 적어 두니 구현할 때마다 측정할 기준이 생겼다. 범위 외 목록은 하고 싶은 기능이 떠오를 때마다 "이건 2차"라고 선을 긋는 데 썼다.
또 하나는 2차 동기화를 1차 스키마에 미리 반영한 것이다. 지금은 혼자 한 PC에서 쓰지만 나중에 여러 PC에서 같이 쓰려면 Id 충돌과 삭제 전파가 문제가 된다. 그래서 처음부터 Id는 GUID, 모든 엔티티에 UpdatedAt, 삭제는 IsDeleted 소프트 삭제로 정했다. 이 결정은 5편에서 가져오기 충돌 처리로 바로 쓸모가 생긴다.
단계별로 진행했다
진행 규칙은 단순하게 정했다. Phase 하나를 끝내면 빌드와 테스트를 돌리고, 앱을 띄워 확인한 다음 다음 단계로 넘어간다. 각 Phase는 이슈 하나와 커밋 하나로 마무리했다.
| Phase | 내용 |
|---|---|
| 0 | 솔루션 골격, DI 호스트, 트레이 아이콘 |
| 1 | 데이터 계층: EF Core SQLite + FTS5 trigram 검색 |
| 2 | 스니펫 관리 화면, 태그 칩, 미저장 확인 |
| 3 | 전역 단축키 + 빠른 검색 팝업 |
| 4 | 변수 치환 파서와 입력 창 |
| 5 | 트레이·단일 인스턴스·설정·자동 실행·테마 |
| 6 | JSON 가져오기/내보내기, 자동 백업 |
| 7 | 파일 로그, 전역 예외 처리, 단일 exe 배포 |
테스트는 로직이 들어가는 단계마다 같이 늘렸다. 데이터 계층을 끝낸 Phase 1에서 68개, 변수 치환을 넣은 Phase 4에서 96개, 가져오기를 넣은 Phase 6에서 110개였다.
그 뒤로 몇 가지가 더 붙었다.
- 단일 exe 압축 (155.6MB → 70.3MB)
- 빠른 검색 팝업에서 마우스 클릭 선택이 안 되던 결함 수정
- 배포 zip·사용설명서·홈페이지 배포 페이지
- 한국어/영어 화면 전환, 버전 0.2.0
마지막 항목은 원래 범위 외 목록에 있던 기능이다. 홈페이지에 공개하기로 하면서 해외 방문자도 쓸 수 있어야 한다고 판단해 범위에 넣었다. 문구를 한 곳에 모아 두는 구조였기 때문에 하루 안에 끝낼 수 있었다. 이 이야기는 6편에서 다룬다.
코드는 테스트와 XAML을 합쳐 약 8천 줄이고, 최종 테스트는 122개다.
만든 방식
설계 문서로 구조·규칙·단계를 먼저 고정해 두었기 때문에, 구현하면서 방향을 다시 고민할 일이 적었고 그만큼 동작을 확인하는 데 시간을 쓸 수 있었다. 각 Phase가 끝날 때마다 실제로 앱을 띄워 단축키를 눌러 보고, 메모장에 붙여 넣어 보고, 앱을 껐다 켜서 데이터가 남는지 확인했다.
진행하면서 알게 된 함정과 결정은 설계 문서 끝의 구현 메모 절에 계속 쌓았다. 예를 들면 이런 것들이다.
- EF Core에서 키를 미리 채운 새 엔티티를 탐색 속성으로만 붙이면 INSERT가 아니라 UPDATE가 나간다
- WPF
ListBox가 이미 바꾼 선택은 바인딩으로 되돌릴 수 없다 - 복사(사용 기록)는
UpdatedAt을 바꾸지 않는다 — 동기화 때 "수정"으로 잡히지 않게
이 메모 덕분에 이 연재도 기록을 다시 읽으며 쓸 수 있었다.
연재 목차
- 자주 쓰는 명령어를 단축키 한 번으로 — 「Cliply」 개발기 (이 글)
- SQLite FTS5 trigram으로 한글·부분 문자열 검색하기 (10월 7일 공개)
- 어느 창에서든 단축키로 뜨는 WPF 검색 팝업 (10월 7일 공개)
{{이름:기본값}}변수 치환 파서와 입력 창 (10월 7일 공개)- 동기화를 대비한 스키마 — GUID, UpdatedAt, 소프트 삭제와 JSON 가져오기 (10월 7일 공개)
- 트레이 상주 앱 마무리 — 단일 인스턴스, 한/영 전환, 70MB 단일 exe (10월 7일 공개)
Cliply는 애드소프트 홈페이지의 공유도구 페이지에서 내려받을 수 있다. 다음 글에서는 이 앱의 심장인 검색, SQLite FTS5 trigram 이야기를 한다.