LLM을 사용하여 작은 터미널 명령어 도우미 만들기
요약
터미널 명령어 입력 오류를 LLM을 통해 즉시 수정하고 제안해주는 오픈소스 도구 'nudge'를 소개합니다. 사용자의 의도를 파악하여 오타 수정부터 복잡한 명령어 제안까지 매끄러운 워크플로를 제공합니다.
핵심 포인트
- 실패한 명령어를 분석하여 올바른 명령어를 제안하는 Go 기반 프로토타입
- 단순 오타는 로컬에서 즉시 처리하여 네트워크 호출 없이 빠른 응답 제공
- 명령어 검토, 편집, 실행, 취소로 이어지는 직관적인 인터랙션 설계
- LLM 코딩 에이전트를 활용해 단 한 시간 만에 프로토타입 구현
저는 터미널의 근육 기억 (muscle memory) 때문에 정기적으로 시간을 허비하곤 합니다.
저는 Windows와 Unix 계열 쉘 (Unix-like shells)을 모두 사용하기 때문에, 잘못된 환경에서 올바른 명령어를 기억하거나, Git 서브커맨드 (subcommand)를 뒤바꾸거나, 유효한 바이너리 (binary)에 잘못된 서브커맨드를 사용하는 일이 발생합니다. 해결 방법은 보통 찾기 쉽습니다. 문제는 흐름이 끊기는 것이 비용이 많이 드는 부분입니다. 멈추고, 검색하고, 답변을 현재 쉘 (shell)에 맞게 다시 변환하고, 다시 시도해야 합니다.
이 아이디어는 새로운 것이 아닙니다. 실패한 명령어를 도와주는 프로젝트들이나 aichat과 같은 터미널 통합 LLM (terminal-integrated LLMs)들이 이미 존재합니다. 하지만 저는 한 가지 일을 잘 수행하는 것을 원했습니다. 바로 LLM에 의해 구동되는 매끄러운 워크플로 (workflow) 내에서 명령어를 수정하는 것입니다.
저는 의도적으로 범위를 좁힌 도구를 원했습니다. 명령어가 실패했을 때, 제가 의도했을 법한 명령어를 제안하고, 제가 검토할 수 있게 하며, 확인 후에만 실행하는 도구 말입니다.
Fable 프로모션 기간 동안, 저는 잘 정의된 프롬프트 (prompt)가 어디까지 도달할 수 있는지 확인하기 위해 LLM 코딩 에이전트 (coding agent)를 사용했습니다. 아주 적은 유도만으로도 약 한 시간 만에 사용 가능한 Go 프로토타입 (prototype)을 만들어냈습니다.
이 프로젝트는 현재 오픈 소스입니다: nudge.
제가 원했던 워크플로 (workflow)
핵심 상호작용은 의도적으로 작게 설계되었습니다:
- 평소처럼 명령어를 실행합니다.
- 명령어가 실패하면,
fix(또는 그냥nudge)를 입력하여 제안된 수정안을 받습니다. - 수정안을 검토한 후, Enter를 눌러 실행하거나,
e를 눌러 먼저 편집하거나,n을 눌러 취소합니다.
가장 간단한 경우는 단순한 오타이며, 이 경우 모델에 아예 도달하지도 않습니다:
PS> git pshu
git: 'pshu' is not a git command. See 'git --help'.
...
(typo fix for ...) 라벨은 도구가 네트워크 호출 없이 10ms 미만 내에 로컬에서 답변했음을 알려주는 것입니다.
동일한 실수의 쉘 간 버전은 여기에 존재하지 않는 바이너리 (binary)를 찾는 경우입니다. 바이너리가 없기 때문에 쉘의 command-not-found 훅 (hook)이 작동하며, 저는 아무것도 입력할 필요가 없습니다:
PS> printenv
`printenv` isn't a valid command. Did you mean:
→ Get-ChildItem env: (list all environment variables)
...
이 도구를 실제로 만들게 된 동기는 더 좁은 범위의 사례입니다. 바이너리(binary)는 존재하지만, 어떤 훅(hook)도 실행되지 않고 맞춤법 검사기(spell-checker)도 도움을 줄 수 없는 상황, 즉 호출(invocation) 자체가 잘못된 경우입니다. fix는 쉘 히스토리(shell history)에서 이전 명령어와 종료 코드(exit code)를 읽어오기 때문에 바로 이런 상황을 위해 존재합니다.
PS> dotnet install dotnet-ef
Could not execute because the specified command or file was not found.
...
또한, 이 도구는 평문으로 기술된 의도(intent)도 받아들입니다. 이는 입력값만 다를 뿐 동일한 루프입니다.
PS C:
epos> just create dir mynewproj and init repo there
`just create dir mynewproj and init repo there` isn't a valid command. Did you mean:
→ mkdir mynewproj; cd mynewproj; git init (디렉토리를 생성하고, 해당 디렉토리로 이동한 뒤, git 저장소를 초기화합니다)
...
마지막 줄의 프롬프트(prompt)가 핵심입니다. cd가 실제 나의 세션(session)에 적용되었습니다. 만약 자식 프로세스(child process)에서 실행되는 제안이었다면, 저는 여전히 C:\repos에 머물러 있었을 것입니다.
중요한 제약 사항은 컨텍스트 스위칭(context switching)이었습니다. 저는 터미널에 임베디드된 일반적인 채팅 경험을 원한 것이 아니었습니다. 실패한 명령어에 밀접하게 붙어 있는 하나의 빠른 복구 루프(repair loop)를 원했습니다.
에이전트가 만들어낸 결과물
초기 프로토타입은 저의 일상적인 사용에는 충분했습니다. 이후의 작업을 통해 합리적으로 공유할 수 있는 수준으로 발전시켰습니다.
- 빠른 로컬 매처(local matcher)가 모델을 호출하지 않고도
git pshu와 같은 단순한 오타를 처리합니다. - 모델 기반의 제안(model-backed suggestions)이 알 수 없는 명령어, 잘못된 서브커맨드(subcommand), 그리고 평문 영어 의도를 처리합니다.
- 쉘 통합(shell integration)이 PowerShell, bash, zsh, fish를 지원하므로, 수정 사항이 실패한 명령어와 그 종료 코드를 사용할 수 있습니다.
rm -rf, 강제 푸시(force-push), 또는 하드 리셋(hard reset)과 같이 파괴적일 수 있는 제안은 명시적으로y를 입력해야 합니다. Enter 키만으로는 부족합니다.- 품질, 개인정보 보호 및 비용 요구 사항에 따라 클라우드 제공업체 또는 로컬 Ollama 호환 모델을 모두 사용할 수 있습니다.
안전 가드(safety guard)는 상호작용이 의도적으로 느려지는 유일한 지점입니다. 모델이 제안을 파괴적(destructive)이라고 판단하면, 프롬프트(prompt)가 변경되며 반사적인 Enter 입력으로는 더 이상 이를 수락할 수 없게 됩니다.
PS> and uninstall dotnet-ef globally
`and uninstall dotnet-ef globally`는 유효한 명령어가 아닙니다. 다음을 의도하셨나요:
→ dotnet tool uninstall --global dotnet-ef (dotnet-ef 도구를 전역적으로 삭제)
...
이러한 구분은 중요합니다. 모델 호출(model call)은 문제 해결에 해석이 필요할 때는 유용하지만, 명령어가 단순히 오타인 경우에는 낭비입니다. 도구는 쉬운 경우에는 즉각적이고 오프라인으로 작동해야 하며, LLM은 그 가치를 증명할 수 있는 상황에서만 사용해야 합니다.
엔지니어링은 첫 한 시간 이후에 이루어졌다
"에이전트가 한 시간 만에 만들었다"는 말은 첫 번째 사용 가능한 버전에 대해서는 사실이지만, 개발 과정 전체를 설명하기에는 불충분합니다.
저는 운영 체제 전반에 걸친 셸(shell) 동작 테스트, 설치 및 설정 개선, 그리고 위험한 동작이 실수로 실행되지 않도록 하는 데 추가적인 시간을 소비했습니다. 특히 셸 상태(shell state)는 까다롭습니다. 제안된 cd 명령어는 즉시 사라지는 자식 프로세스(child process)가 아니라 현재 셸 세션(shell session)에 영향을 미쳐야 합니다. 설치 또한 제품의 일부입니다. CLI가 생성된 환경에서만 작동한다면 실제로 유용하다고 할 수 없습니다.
코딩 에이전트(coding agents)를 사용할 때 제가 가장 유용하다고 느끼는 패턴은 다음과 같습니다:
- 에이전트에게 좁고 관찰 가능한 문제를 부여합니다.
- 빠르게 작동하는 수직적 슬라이스(vertical slice)를 확보합니다.
- 결과물을 테스트, 오류 처리, 설정 및 검토를 위한 시작점으로 취급합니다.
에이전트는 구현을 가속화했습니다. 하지만 명령어가 불확실하거나, 파괴적이거나, 셸 특화적이거나, 클라우드 모델로 전송될 때 어떤 일이 일어나야 하는지를 결정해야 하는 필요성까지 없애주지는 않았습니다.
세션 비용은 얼마였나?
7월 15일의 한 세션에서 보고된 토큰(token) 사용량은 다음과 같습니다:
| 출력 (Output) | 캐시 쓰기 (Cache write) | 캐시 읽기 (Cache read) | 신규 입력 (Fresh input) |
|---|---|---|---|
| 382,094 | 418,616 | 35,691,065 | 551 |
저는 프로(Pro) 구독 기간 동안 사용했기 때문에 토큰당 비용이 청구되지 않았습니다. 제가 사용 중이던 모델의 API 요율과 비교했을 때, 동일한 세션은 대략 $63의 비용이 들었을 것입니다.
이 수치에는 맥락이 필요하며, 총액보다는 그 구성 방식이 더 흥미롭습니다. 입력의 거의 대부분은 일반 입력 요율의 10분의 1로 청구되는 캐시 읽기(cache reads)였습니다. 동일한 3,570만 토큰을 정가로 계산했다면 세션 비용은 $375에 육박했을 것입니다. 즉, 캐싱(caching)이 약 6배의 비용 절감 효과를 내고 있는 것이며, 이러한 추정치는 모델, 제공업체 및 캐시 가격에 따라 달라집니다. 또한 이것은 CLI를 사용하는 비용이 아닙니다. 단일 수정 작업은 대략 300~500개의 입력 토큰과 60개의 출력 토큰을 소모하며, 이는 1센트의 아주 작은 부분에 불과합니다. 이것은 프로젝트를 탐색하고, 생성하고, 다듬는 과정이 포함된 확장된 에이전트 기반 개발(agentic development) 세션의 비용입니다.
개인용 도구로서 $63가 자동으로 저렴하다고 할 수는 없습니다. 하지만 이는 유용한 비교 지점이 됩니다. 저는 반복되는 마찰 지점으로부터 작동하는 도구를 만들어내기까지, 모든 부분을 처음부터 직접 구축했을 때 예상했던 것보다 훨씬 더 빠르게 도달했습니다. 이것이 가치가 있는지는 해당 도구가 얼마나 자주 시간을 절약해 주는지, 그리고 실험 그 자체에 얼마나 가치를 두는지에 달려 있습니다.
이 접근 방식이 잘 작동하는 경우
LLM 지원 개발(LLM-assisted development)은 다음 조건들이 모두 충족될 때 가장 설득력이 있습니다:
- 문제가 시연 및 테스트할 수 있을 만큼 구체적이다.
- 첫 번째 버전이 의도적으로 작게 만들어질 수 있다.
- 개발자가 결과의 정확성을 판단할 수 있다.
- 잘못된 답변에 대한 비용이 검토, 확인 및 테스트를 통해 통제 가능하다.
터미널 명령어 도우미는 이러한 조건에 부합합니다. 명확한 입력과 출력을 가지며, 위험한 작업을 실행하기 전에 확인을 요구할 수 있습니다.
이 결과를
해당 저장소(repository)에는 설치 지침, 셸 통합 (shell integration), 개인정보 보호 관련 참고 사항, 지원되는 제공업체(providers), 그리고 개발 테스트가 포함되어 있습니다:
github.com/eduardsjermaks/nudge
저는 이것을 일회성 데모로 취급하기보다 실제 터미널 워크플로 (workflow)로서 계속 사용하고 있습니다. 이것이 바로 이런 종류의 프로젝트에 대한 진정한 시험대입니다. 즉, LLM이 저장소를 생성할 수 있는지 여부가 아니라, 결과물인 도구가 새로움이 사라진 후에도 여전히 유용하게 유지되는지 여부입니다.
AI 자동 생성 콘텐츠
본 콘텐츠는 Dev.to AI tag의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.
원문 바로가기