← 목록으로

내 셸 단축키는 내가 어느 폴더에서 일하는지 알고 있다 (halias 제작기)

TL;DR 셸 alias를 .zshrc 대신 CLI로 관리하는 도구, halias 를 만들어 1.0까지 왔습니다. 핵심 차별점은 컨텍스트 학습이에요. 모든 단축키가 “언제, 어느 디렉토리에서” 쓰였는지 기록되고, 퍼지 검색은 지금 있는 폴더에서 자주 쓴 단축키를 위로 올립니다. macOS Spotlight가 자주 여는 앱을 위로 올리는 것과 같은 원리라, 프로젝트별 분류 같은 걸 손으로 할 필요가 없습니다. npm i -g halias 로 설치할 수 있고요. 이 글은 기능 소개보다는, 만들면서 내린 설계 결정과 두 번의 사고에 대한 기록입니다.

halias 데모: 단축키 추가, 미리보기와 함께 퍼지 검색, 사용 통계

.zshrc를 또 열면서 든 생각

alias dlog="docker compose logs -f api" 같은 걸 만들어두고 일주일 뒤에 까먹어본 적 있으신가요? 저는 자주 그랬습니다. 분명 만들었는데 이름이 기억 안 나서 결국 긴 명령을 다시 칩니다. 그러다 .zshrc를 열어보면 언제 만들었는지도 모를 alias가 수십 개 쌓여 있고, 어떤 건 살아 있는지 죽었는지도 모르겠고요.

문제를 곱씹어보니 alias 자체가 아니라 관리 방식이 문제였습니다. .zshrc는 텍스트 파일이라 검색도, 통계도, 정리도 안 됩니다. 단축키를 텍스트가 아니라 데이터로 다루면 어떨까, 여기서 시작했습니다. 추가는 대화형 CLI로, 저장은 JSON 한 파일로, 검색은 fzf로. 여기까지는 사실 흔한 발상입니다. 재미있는 건 그다음부터였어요.

분류를 시킬까, 학습을 시킬까

단축키가 서른 개쯤 쌓이면 검색 결과에서 원하는 게 바로 안 잡힙니다. 자연스럽게 “프로젝트별로 그룹을 나눌까?”라는 생각이 듭니다. 실제로 처음 설계안에는 단축키마다 “이건 어느 폴더용”이라고 지정하는 scope 필드가 있었습니다.

그리고 며칠 고민하다 지웠습니다.

사용자한테 분류를 시키는 순간 두 가지가 무너집니다. 등록할 때마다 질문이 하나 늘어서 추가가 무거워지고, 같은 이름을 여러 폴더에서 다르게 쓰고 싶을 때 모델이 꼬입니다. 무엇보다, 저부터가 그 질문에 성실하게 답할 자신이 없었어요.

그래서 방향을 뒤집었습니다. 묻지 말고 지켜보자. 단축키가 실행될 때마다 타임스탬프와 함께 현재 디렉토리를 한 줄 기록하고, 검색 정렬 점수를 이렇게 계산합니다.

score = 10 × (현재 디렉토리에서 쓴 횟수) + 1 × (전체 사용 횟수)

계수 10은 “이 폴더에서 한 번이라도 실제로 쓴 건 확실히 위로”라는 정책입니다. ~/work/myapp에서는 거기서 8번 쓴 dev가, 전체로는 12번 썼지만 여기선 1번뿐인 gs보다 위에 옵니다. 폴더를 옮기면 순서도 따라 바뀌고요. Spotlight가 자주 여는 앱을 위로 올리는 것과 같은 원리인데, 셸 단축키에 적용해보니 생각보다 훨씬 체감이 큽니다. 1.0에서는 이 학습 데이터를 직접 볼 수 있는 ha stats --by-dir도 넣었습니다.

alias 하나까지 전부 함수로 만드는 이유

그런데 “실행될 때마다 기록한다”는 게 말처럼 간단하지 않습니다. alias에는 실행 훅을 걸 방법이 마땅치 않거든요. 그래서 halias는 alias 타입이든 function 타입이든 전부 셸 함수로 통일해서 생성합니다.

# ha add gs "git status" 를 등록하면 생성되는 코드 gs() { _halias_track "gs" # 사용 기록 (탭 구분: timestamp, 이름, $PWD) git status "$@" # 인자 forwarding }

함수로 감싸면 세 가지가 공짜로 생깁니다. 통계 훅이 모든 단축키에 일관되게 붙고, alias가 못 하는 "$@" 인자 전달이 되고, zsh와 bash에서 똑같이 동작합니다. .zshrc에는 이 생성 파일을 source하는 마커 블록 딱 하나만 추가되고요. 사용자의 셸 설정 파일을 도구가 반복해서 건드리는 건, 한 번이라도 겪어보면 다시는 신뢰하고 싶지 않아지니까요.

사고의 기록 1: zsh가 자꾸 [job] done을 띄운다

솔직한 실패담도 두 개 남겨둡니다. 처음에 사용 기록을 “빠르게” 하겠다고 백그라운드로 던졌습니다.

( echo "$(date +%s) $1" >> "$HALIAS_STATS_LOG" ) &

결과는 참사였습니다. zsh는 잡 제어 알림이 기본이라, 단축키를 칠 때마다 터미널에 [2] done ...이 떴어요. 도구가 편해지라고 만든 건데 셸이 지저분해지는 거죠. 재보니 한 줄 append는 1ms도 안 걸립니다. 최적화라고 생각했던 게 사실 아무것도 최적화하지 않으면서 UX만 부수고 있었습니다. 동기 쓰기로 바꾸고 이 결정은 “다시는 건드리지 말 것”으로 문서에 박아뒀습니다.

사고의 기록 2: 단축키 하나가 전부를 죽인다

더 아픈 건 이거였습니다. 사용자가 등록한 function 본문은 임의의 셸 코드인데, 생성된 파일 전체를 통째로 source하다 보니 단축키 하나에 문법 오류가 있으면 나머지 전부가 로드에 실패했습니다. 괄호 하나 빠뜨리면 모든 단축키가 죽는 도구를 누가 믿고 쓰겠어요.

해결은 단축키마다 eval 로 격리하는 것이었습니다.

eval 'gs() { ... }' 2>/dev/null || \ printf 'halias: skipped broken shortcut %s\n' 'gs' >&2

깨진 건 한 줄 경고와 함께 건너뛰고, 나머지는 전부 살아남습니다. 이 사고 이후로 생각이 좀 바뀌었는데요. 개인 도구라도 남의 셸에 코드를 심는 순간, “내 버그가 사용자 환경을 얼마나 망가뜨릴 수 있는가”가 기능보다 먼저입니다. 1.0 전에 파괴적 작업 자동 백업(ha restore), 깔끔한 제거(ha uninstall), 실제 bash/zsh로 source해보는 테스트 스위트를 먼저 채운 것도 그래서입니다.

그래서 지금은

1.0을 내면서 데이터 포맷(JSON 스키마, 통계 로그, rc 마커 블록)을 semver로 동결했습니다. 도구를 쓰다 보면 데이터가 쌓이는데, 그 데이터가 다음 버전에서 깨질지 모른다면 쌓을 이유가 없으니까요.

npm i -g halias # 또는 brew install kwonmoto/tap/halias ha add # 첫 단축키 ha install # 셸 통합 (한 번만) ha # 퍼지 검색. 쓰다 보면 폴더별로 알아서 정렬됩니다

코드는 GitHub 에 있습니다. 컨텍스트 점수 계산(로컬×10 + 글로벌×1)이 과연 좋은 휴리스틱인지, 더 나은 방법이 있을지, 이 부분 의견을 특히 듣고 싶습니다.

대 AI 시대에 이게 살아남을 수 있을까

만들면서 스스로에게 가장 많이 던진 질문입니다. “docker 로그 봐줘”라고 하면 AI가 명령을 바로 만들어주는 시대에, 단축키 관리가 무슨 의미가 있나 싶었거든요.

반은 맞는 걱정입니다. 사람이 터미널에 직접 명령을 치는 시간은 분명히 줄고 있습니다. 그런데 하루에 수십 번 치는 명령을 매번 AI에게 물어보는 사람은 없습니다. 0ms에 실행되는 머슬 메모리와, 수백 ms와 토큰을 쓰는 생성은 쓰임새가 다른 물건입니다. 음성 비서가 나왔다고 키보드 단축키가 사라지지 않은 것과 같은 구조라고 생각해요.

그래서 halias를 AI의 경쟁자가 아니라 보완재로 보고 있습니다. AI에게 물어서 얻은 긴 명령을 한 번 실행한 뒤 ha add --last로 저장하면, 다음부터는 물어볼 필요가 없습니다. 생성은 AI가, 보존과 머슬 메모리는 halias가 맡는 분업입니다.

한 걸음 더 나가면, halias가 쌓는 “어느 폴더에서 어떤 명령을 얼마나 쓰는지” 데이터는 에이전트에게 먹일 수 있는 개인 컨텍스트이기도 합니다. 예전에 라이브러리가 에이전트에게 스스로를 설명해야 한다는 글을 쓴 적이 있는데, 개인 도구도 같은 방향이라고 생각합니다. 단축키와 디렉토리별 사용 통계를 에이전트가 조회할 수 있게 여는 것(MCP 서버)을 1.x 로드맵에 올려뒀습니다. 에이전트가 “이 사용자는 이 폴더에서 dlog를 쓰니 docker compose 프로젝트겠구나”를 아는 그림입니다.

배운 점

  • 사용자에게 묻는 대신 데이터가 답하게 하기. 명시적 분류를 지운 게 이 도구에서 제일 잘한 결정이었습니다.
  • 작은 도구일수록 신뢰가 기능보다 먼저. 남의 셸에 들어가는 코드는 백업, 격리, 제거 경로부터 챙겨야 합니다.
  • 성급한 최적화는 UX를 부순다. 1ms를 아끼려다 매 실행마다 잡 알림을 띄울 뻔했습니다.
  • 그리고 하나 더. 자기가 매일 쓰는 도구를 만들면 사용자 피드백 루프가 제일 짧습니다. 마찰이 느껴지는 그 순간이 다음 릴리즈 계획이 됩니다.