versenilvis/iris · 언어 Go 1.25 · 라이선스 0BSD(무제한) · 상태 beta · 코어 소스 ~37,900줄 · 명령어 사양 521개 파일 · 메모리 <15MB · Windows 미지원 · 제작 versenilvis · 최신 커밋 2026년)
IRIS는 "셸 위에 얹는 자동완성 오버레이"다. 네가 터미널에 git che까지 치면, 화면을 망가뜨리지 않고 프롬프트 바로 아래에 「git checkout … 」 같은 제안 상자를 그려 준다. Tab을 누르면 그 제안이 프롬프트에 채워진다. 이게 전부지만, 이걸 어떤 셸에서든·SSH 너머에서든·앱 없이 바이너리 하나로 해낸다는 게 특징이다.
VS Code에서 doc.까지 치면 회색으로 getElementById가 뜨고, 방향키로 고르고 Tab으로 확정하는 그 경험 — IRIS는 그 경험을 셸 프롬프트에 그대로 재현한다.
차이는 "구현 위치"다. 에디터는 자기 창 안에서 자유롭게 그린다. IRIS는 진짜 터미널 화면이라는, 마음대로 못 그리는 캔버스 위에 ANSI 이스케이프 코드로 조심조심 상자를 그렸다 지운다. 그래서 tmux·전체화면 TUI·스크롤백을 건드리지 않는다.
이 도구가 급부상한 배경엔 Fig의 죽음이 있다. Fig는 터미널 자동완성으로 인기를 끈 상용 앱이었는데, 2024년 9월 공식 종료(sunset) 후 Amazon Q Developer로 흡수됐다. Amazon Q는 클라우드 로그인이 필요하고 무겁다. "가볍고 로컬에서 도는 Fig"를 찾던 수요가 통째로 빈자리가 됐고, IRIS가 정확히 그 자리를 노렸다.
IRIS의 세일즈 포인트는 "무엇을 안 했는가"에 있다. GUI 없음, Electron 없음, macOS 전용 래퍼 없음, 계정 없음, 텔레메트리 없음, 백그라운드 데몬 없음. 단일 네이티브 Go 바이너리 하나다.
| 항목 | IRIS | Fig |
|---|---|---|
| 플랫폼 | Linux, macOS | macOS 전용 |
| 엔진 | 네이티브 Go (TTY) | Electron |
| UI | 인라인 오버레이 | GUI 팝오버 |
| 원격 SSH | TTY 네이티브, 이식 가능 | macOS GUI에 묶임 |
| tmux | 지원 | 제한적 |
| 메모리 | < 15MB | Electron 런타임 |
zsh-autosuggestions 같은 셸 플러그인도 훌륭하다. 하지만 (1) 보통 특정 셸 전용이고(zsh/fish), (2) 셸 시작 시간을 늘리며, (3) SSH로 접속한 서버마다 셸별로 다시 설정해야 한다. IRIS는 바이너리 하나 + 설정 하나로 대부분의 셸을 커버하고, 셸 초기화에 끼어들지 않아 시작이 빠르다.
셸 플러그인 = 각 방마다 따로 단 전등 스위치. 방(zsh·bash·fish)마다 규격이 다르고, 새 방(SSH 서버)에 갈 때마다 다시 배선해야 한다. IRIS = 들고 다니는 손전등. 어느 방이든 들어가서 켜기만 하면 된다 — 방의 배선(셸 설정)을 건드리지 않는다.
IRIS는 순수 Go 프로젝트다. 프론트엔드/백엔드 구분이 없고, 대신 "터미널 제어 계층 / 완성 로직 계층 / 데이터 계층"으로 나뉜다.
/proc 탐색, 시그널 처리 등 저수준 OS 호출.iris init, iris config 같은 서브커맨드 CLI 뼈대. 겸사겸사 다른 Cobra 앱의 __complete를 호출해 동적 완성도 얻는다.IRIS를 이해하는 가장 좋은 방법은 "내가 g를 누른 순간부터 화면에 상자가 뜨기까지"를 따라가는 것이다. 전체 그림은 이렇다.
모든 것의 출발점. IRIS는 실행되면 네 셸을 PTY로 감싸 자식 프로세스로 띄운다. 셸 종류는 --shell 플래그 → 설정 → /proc/<pid>/comm을 부모 방향으로 거슬러 올라가며 자동 감지 → $SHELL → 최후엔 bash 순으로 결정된다.
그다음 두 개의 펌프(pump)가 동시에 돈다:
TermWrite가 sync.Mutex로 stdout 접근을 직렬화한다.naiveBuffer 문자열에 쌓고, Tab·Enter·Esc·Ctrl+C 같은 특수 키를 해석해 제안 렌더·삽입을 트리거한다.명령 경계(한 명령이 끝나고 새 프롬프트가 시작되는 순간)는 셸별 훅으로 감지한다: zsh는 preexec/precmd, bash는 PROMPT_COMMAND, fish는 fish_postexec. 이들이 IRIS_CMD_STOP 신호를 IPC로 보내 "지금 명령 실행 중" 상태를 IRIS에 알린다.
IRIS는 git commit -m이 무슨 구조인지 안다. 각 명령은 Spec(최상위) → Subcommand(재귀) → Option(플래그) → Generator(동적 값 생성 함수)로 정의된다. 예컨대 git checkout 뒤엔 브랜치 목록을 Generator가 git branch를 실제로 실행해 채워 준다.
Lookup 알고리즘은 이렇게 동작한다:
# 입력: "git com"
1. 토큰화 → ["git", "com"]
2. 트리 순회 → 루트 "git" 노드에서 시작
3. "com"은 "commit"과 정확히 일치하지 않음 → 순회 중단
4. prefix="git", partial="com" 으로 문맥 확정
5. "git"의 서브커맨드 중 "com"으로 시작하는 것 수집
→ 결과: git commit
플래그 우선순위(flag gating)가 영리하다. 인자를 칠 땐 파일·서브커맨드가 우선(Priority 30), 플래그는 뒤로(10). 하지만 사용자가 -나 --를 직접 치는 순간 "아, 플래그를 원하는구나" 하고 플래그를 최상단으로 끌어올린다(80). 셸 별칭(alias)은 .bashrc/.zshrc를 스캔해 최우선으로 두고, gr = go run 같은 별칭은 확장해서 서브커맨드까지 제안한다.
제안 후보는 넘친다. 순서를 정하는 게 스코어링의 일이다. 세 가지가 결합된다:
점수 = 빈도 × e−λΔt. 자주, 그리고 최근에 쓴 명령일수록 높은 점수를 받고, 시간이 지나면 지수적으로 감쇠한다. "어제 100번 쓴 명령"보다 "방금 3번 쓴 명령"이 더 위로 올 수 있다.
git add → git commit, go build → ./iris 처럼 연달아 나오는 쌍을 학습한다. 직전 명령의 스켈레톤이 매칭되면, 다음에 올 법한 제안에 우선순위 부스트를 준다.
ExtractSkeleton()이 git commit -m "feat: test"를 git commit으로 정규화한다. 구체적 인자·플래그를 벗겨내 "구조"만 비교하므로, 매번 다른 커밋 메시지에도 같은 워크플로로 인식된다.
여기에 더해 internal/ai/defaults.go엔 문맥 규칙이 하드코딩돼 있다. 예를 들어 방금 git status를 쳤으면 다음 빈 줄에 git diff를 제안하고, 머지 중이면 git commit, 리베이스 중이면 git rebase --continue, 직전 명령이 실패(exit≠0)했으면 그 명령을 "재시도"로 제안한다.
여기부터는 켜야 도는 옵션 기능이다. 로컬(Ollama)이나 클라우드(Groq 등 OpenAI 호환 /v1/chat/completions) 제공자를 붙이면, 문맥을 담은 프롬프트를 보내 AI 제안을 받아 온다.
포인트는 디바운스와 취소다. 타이핑할 때마다 요청을 날리면 낭비이자 지연이다. 그래서:
debounce_ms=500ms)를 리셋한다.context.WithCancel로 즉시 취소한다.EnvSnapshot(현재 디렉토리·직전 명령·종료 코드·최근 기록 3개)을 만들어 전송한다.overlay.InjectAISuggestion()으로 회색 고스트 텍스트가 되어 인라인 렌더된다.가장 까다로운 부분. 실제 터미널 위에 상자를 그렸다가 지워야 하는데, 프롬프트나 스크롤백을 망가뜨리면 안 된다. IRIS의 해법:
\0337(커서 저장)과 \0338(커서 복원) 이스케이프로, 상자를 다 그린 뒤 정확히 프롬프트 위치로 돌아온다.boxWidth)으로 레이아웃 일관성을 유지한다.#BD93F9 보라, 선택 항목 배경 #44475A).남의 화이트보드에 포스트잇 붙이기. 화이트보드(터미널)엔 이미 남이 쓴 글(셸 프롬프트·스크롤백)이 있다. IRIS는 지우개로 지우는 게 아니라, 붙였다 뗐다 자국 안 남는 포스트잇처럼 제안 상자를 얹는다. 뗀 자리(DECRC로 복원한 커서)는 원래 그대로다. 그래서 tmux든 vim이든 밑에서 돌던 것이 멀쩡하다.
기여자용 기능으로, SIGUSR1 시그널을 받으면 syscall.Exec로 PTY 셸 세션을 죽이지 않고 바이너리만 제자리 교체한다. 코드 고치고 즉시 반영하는 개발 루프를 위한 장치다.
구조에서 읽히는 설계 의도가 분명하다. "엔진(root·spec·scoring·ai)과 데이터(commands 521개)를 완전히 분리"했다. 새 명령어 지원을 추가하려면 엔진을 건드릴 필요 없이 commands/에 사양 파일 하나만 더 넣으면 된다. 그래서 파일 수가 521개까지 늘어날 수 있었다.
기능(명령어 지원)을 코드 로직이 아니라 선언적 데이터(Spec 구조체)로 표현하면, 기여자가 로직을 몰라도 데이터만 추가해 기능을 늘릴 수 있다. 리뷰 부담도 낮아진다. Cobra·kubectl 등 대형 CLI가 공통으로 쓰는 확장 전략이다.
"프로그램이 다른 프로그램의 터미널이 되는" 경험은 흔치 않다. creack/pty로 자식 셸을 띄우고, raw mode로 키를 낚아채고, 입출력을 중계하는 이 패턴은 터미널 멀티플렉서(tmux)·터미널 에뮬레이터·자동화 도구(expect)의 기초다. root/wrapper.go 한 파일이 교과서다.
DECSC/DECRC 커서 저장·복원, \033[K 줄 지우기, 스크롤 방지 트릭 — TUI 라이브러리가 뒤에서 숨겨 주던 것을 날것으로 다뤄 본다. overlay.go에서 "터미널은 좌표계 캔버스"라는 감각을 얻는다.
frecency(빈도×최신성 감쇠)는 브라우저 주소창·에디터 최근 파일에도 쓰이는 보편 알고리즘이다. 여기에 워크플로 시퀀스 학습·스켈레톤 정규화를 얹은 internal/scoring/은 "간단한 규칙으로 똑똑해 보이게 만드는" 좋은 사례다.
두 개의 펌프 고루틴, sync.Mutex로 stdout 보호, AI 요청의 context.WithCancel 취소, 그리고 goleak로 누수 검증까지 — Go 동시성의 "제대로 된 마무리"를 통째로 볼 수 있다.
| 항목 | 요구/권장 |
|---|---|
| OS | Linux, macOS (Windows 미지원) |
| 셸 | zsh · bash · fish (자동 감지, 없으면 bash 폴백) |
| 메모리 | < 15MB (Electron 대비 극소) |
| 런타임 의존성 | 없음 — 단일 정적 Go 바이너리 |
| 빌드 시 | Go 1.25+ (직접 빌드할 경우) |
| AI 기능(선택) | Ollama(로컬) 또는 Groq 등 OpenAI 호환 API 키 |
| 설치 | curl -sSL .../install.sh | sh (아키텍처 자동 감지) |
IRIS는 아직 beta다. 커스텀 테마 없음, 커밋 이력이 짧고 자동완성 정확도도 성숙 중이다. 프로덕션 서버의 로그인 셸에 바로 물리기보다, 개인 개발 환경에서 먼저 검증하길 권한다.
curl | sh 설치는 편하지만 스크립트를 서버가 그대로 실행하는 방식이다. 실행 전 scripts/install.sh를 한 번 눈으로 훑어보는 습관이 좋다.
IRIS를 설치해 git·docker 자동완성을 체험한 뒤, commands/에서 사양 파일 하나(예: git/git.go)를 열어 구조를 파악하자. 그다음 네가 자주 쓰는 CLI(예: gh, terraform)의 미니 Spec을 하나 정의해 서브커맨드 2~3개를 제안에 띄워 보라.
spec/lookup_test.go를 읽고 테스트를 돌려, "git com" 입력이 어떻게 partial="com"으로 쪼개지고 git commit을 뽑아내는지 디버거로 한 스텝씩 따라가자. 새 테스트 케이스를 하나 추가해 보라.
internal/scoring/frecency.go의 감쇠 상수 λ를 바꿔 가며, 제안 순서가 어떻게 달라지는지 관찰하자. "최근 것을 더/덜 중시"하도록 튜닝하고, frecency_test.go로 결과를 검증하라.
IRIS를 참고해, creack/pty로 셸을 감싸고 raw mode로 키를 받아 "친 글자 수를 프롬프트 아래에 실시간 표시"하는 30줄짜리 장난감을 직접 짜 보자. DECSC/DECRC로 커서를 복원하는 감각을 손에 익힌다.
internal/ai/client.go의 Client 인터페이스를 구현해, Ollama 로컬 모델로 고스트 텍스트 제안을 받아 보자. 디바운스·취소가 실제로 요청 수를 줄이는지 로깅해 확인하라.
| 주차 | 주제 | 무엇을 / 왜 |
|---|---|---|
| 1주차 | 터미널 기초 | TTY vs PTY, canonical vs raw mode, 터미널 제어의 역사(termios). "화면이 사실은 바이트 스트림"이라는 관점 잡기. |
| 2주차 | ANSI/VT100 이스케이프 | CSI·SGR·DECSC/DECRC 시퀀스 직접 출력해 커서 옮기고 색칠하기. overlay.go 재현. |
| 3주차 | Go 동시성 정석 | goroutine·channel·context 취소·sync.Mutex. IRIS의 두 펌프 + AI 취소 구조 분석. |
| 4주차 | PTY 실전 | creack/pty로 셸 감싸기, 시그널(SIGWINCH 리사이즈·SIGUSR1 리로드) 처리. |
| 5주차 | 랭킹 알고리즘 | frecency·마르코프 시퀀스·퍼지 매칭. 추천 UX가 규칙 몇 줄로 얼마나 좋아지는지. |
| 6주차 | CGo-free 빌드·배포 | modernc.org/sqlite, 정적 링크, GOOS/GOARCH 크로스 컴파일, install.sh 릴리스 파이프라인. |
| 7주차 | LLM 통합 패턴 | OpenAI 호환 API 추상화, 디바운스·취소·캐시. 로컬(Ollama) vs 클라우드 트레이드오프. |
| 8주차 | 종합 프로젝트 | 나만의 미니 자동완성/TUI 도구를 처음부터 빌드해 배포까지. |
-/--를 치면 플래그를 최상단으로.git commit -m "x"→git commit)한 뒤, 연속 쌍(add→commit)을 학습해 다음 명령을 예측.\0337)·복원(\0338)하는 이스케이프 시퀀스. 오버레이를 그린 뒤 프롬프트 자리로 정확히 돌아오는 핵심.docs/dev/ (ai·scoring·overlay·root·spec·history 등 10종) — 이 딥다이브의 1차 소스