요즘 도구를 만들 때 던지는 질문이 하나 늘었습니다. 이걸 사람만 쓸까, 아니면 AI 에이전트도 호출할까입니다. 카카오톡 CLI는 처음부터 둘 다를 염두에 뒀습니다. 사람이 터미널에서 손으로 치는 명령이, 그대로 에이전트가 부르는 로컬 인터페이스가 되게 하는 것이 목표였습니다. 그런데 이 둘을 하나로 합치는 일은 생각보다 출력 설계에 달려 있었습니다. 이 글은 --json 플래그 하나로 그 이중 인터페이스를 만든 방법을 정리합니다.
사람의 출력과 기계의 출력은 다르다
사람이 읽기 좋은 출력은 색과 정렬과 요약이 들어간 표입니다. 반면 기계가 다루기 좋은 출력은 색도 정렬도 없는, 구조가 명확한 데이터입니다. 이 둘을 한 명령어가 모두 내놓으려면 스위치가 필요했습니다. 그래서 거의 모든 명령이 --json 플래그를 지원합니다. 없으면 사람이 보기 좋은 형태로, 있으면 기계가 파싱하기 좋은 구조화된 데이터로 같은 정보를 냅니다.
여기서 놓치기 쉬운 세부가 출력 스트림 분리입니다. 구조화된 결과는 표준출력으로, 진단이나 경고 메시지는 표준에러로 나눠 보냅니다. 이렇게 해야 결과를 파이프로 다른 도구에 넘길 때 경고 문구가 섞여 들어가 파싱을 깨뜨리지 않습니다. 사소해 보이지만 파이프라인에서 도구가 안정적으로 물리느냐를 가르는 지점입니다.
--json 하나가 여는 것
구조화된 출력이 있으면 도구는 갑자기 여러 세계와 연결됩니다. jq로 원하는 필드만 뽑아내고 cron으로 주기 실행하고 SQLite에 적재하고 LLM 흐름의 입력으로 넘길 수 있습니다. 같은 명령어가 사람의 손끝에서는 편한 CLI로, 스크립트 안에서는 데이터 소스로, 에이전트의 도구 목록에서는 호출 가능한 인터페이스로 동시에 작동합니다.
이 접근의 장점은 유지보수가 하나로 모인다는 점입니다. 사람용 인터페이스와 기계용 API를 따로 만들면 둘이 어긋나기 쉽지만 한 명령어에 출력 스위치만 두면 기능이 늘 때 양쪽이 저절로 같이 자랍니다. 새 명령을 만들 때 --json만 챙기면 그 명령은 태어날 때부터 에이전트가 부를 수 있는 도구가 됩니다.
에이전트를 위한 안전 구분
에이전트가 도구를 부를 수 있게 되면 새로운 위험이 생깁니다. LLM이 판단을 잘못해 위험한 명령을 실행할 수 있다는 점입니다. 그래서 에이전트용 통합 가이드를 따로 만들어 명령을 위험도에 따라 명확히 분류했습니다. 서버 통신이 없는 로컬 읽기는 가장 안전한 등급, REST 읽기는 중간, 서버에 쓰기를 일으키는 명령은 위험 등급으로 나눕니다.
이 분류가 있으면 에이전트는 어떤 명령을 마음껏 써도 되고 어떤 명령에는 사용자 확인이 필요한지 알 수 있습니다. 안전한 읽기로 정보를 모으는 일은 자유롭게 하되, 무언가를 보내거나 바꾸는 일은 명시적 허용과 확인을 거치게 합니다. 도구가 스스로 자신의 위험 지도를 에이전트에게 알려 주는 셈입니다.
권장 흐름을 문서로 못 박다
기능을 나누는 것만으로는 부족했습니다. 에이전트가 실제로 어떤 순서로 도구를 써야 하는지를 문서로 명시했습니다. 먼저 로컬로 읽어 상황을 파악하고 쓰기가 필요하면 dry-run으로 결과를 미리 확인하고 사용자 확인을 받은 뒤에만 실행하며 테스트가 필요하면 나와의 채팅으로 보내라는 흐름입니다.
이 권장 순서는 사람에게도 좋은 습관이지만 에이전트에게는 특히 중요합니다. LLM은 맥락을 잃거나 성급하게 행동할 수 있어서 도구 쪽이 안전한 순서를 명확한 절차로 제시해 주면 사고 확률이 크게 줄어듭니다. Claude Code 같은 환경에서 바로 붙여 쓸 수 있도록 스킬 형태로도 배포해 이 안전 흐름이 기본으로 따라오게 했습니다.
정리하면 이 도구는 --json이라는 작은 스위치로 사람과 에이전트의 인터페이스를 하나로 합치고 그 위에 위험도 분류와 권장 절차라는 안전층을 올렸습니다. 이 출력이 다루는 실시간 이벤트는 watch에서 hook·webhook으로 잇는 이벤트 파이프라인에서, 가장 안전한 읽기 경로는 로컬 DB 읽기에서 이어집니다. 전체 도구의 안전 우선 설계는 카카오톡 자동화 CLI 오픈소스 사례에 정리돼 있습니다.
자주 묻는 질문
--json 하나로 무엇이 달라지나요?
같은 명령어가 사람이 읽는 CLI인 동시에 기계가 파싱하는 데이터 소스가 됩니다. jq로 필드를 뽑고 cron으로 주기 실행하고 SQLite에 적재하고 LLM 흐름의 입력으로 넘길 수 있습니다. 사람용과 기계용 인터페이스를 따로 만들지 않아 기능이 늘 때 양쪽이 저절로 같이 자랍니다.
에이전트가 위험한 명령을 실행하면 어쩌나요?
명령을 위험도에 따라 분류한 가이드를 제공해 그 위험을 낮춥니다. 서버 통신이 없는 로컬 읽기는 안전 등급, 쓰기를 일으키는 명령은 위험 등급으로 나누고 위험 명령은 명시적 허용과 사용자 확인을 거치게 했습니다. "먼저 읽고, dry-run으로 미리보고, 확인 뒤에만 실행한다"는 권장 흐름도 문서로 못 박았습니다.
출력 스트림을 왜 나누나요?
구조화된 결과는 표준출력으로, 진단과 경고는 표준에러로 분리해야 결과를 파이프로 넘길 때 경고 문구가 섞여 파싱을 깨뜨리지 않기 때문입니다. 사소해 보이지만 파이프라인에서 도구가 안정적으로 물리느냐를 가르는 지점이라, 처음부터 스트림을 명확히 나눴습니다.