에이전트형 AI는 터미널에서 명령을 실행할 수 있습니다. 즉, 우리가 직접 만든 도구(CLI)를 AI가 사용할 수 있게 할 수 있다는 의미입니다.
하지만 사람이 사용하는 것을 전제로 만들어진 도구를 그대로 AI에게 사용하게 하면 원활하지 않을 때가 있습니다. 출력 형태가 제각각이라 오독하거나, 실패했는지 성공했는지 알기 어렵거나, 어떤 도구를 써야 할지 혼란스러울 수 있습니다.
본 기사에서는 AI 에이전트로부터 호출되는 것을 전제로 한 CLI 제작 방법을 소개합니다. 다루는 예제는 업무 효율화 도구들을 모아 놓은 devtools-workspace와 그 공통 라이브러리인 devtools-common입니다. 이들 모두 특정 회사나 시스템의 이름을 포함하지 않은 범용 버전으로 공개되어 있습니다.
AI에게 도구를 제공하는 방법으로는 MCP 서버도 잘 알려져 있습니다. 하지만 이 도구군에서는 의도적으로 'GitHub에서 clone → 설정(Set up) → Copilot을 통해 명령 호출'이라는 방식을 채택했습니다. 그 이유는 문서에 다음과 같이 명시되어 있기 때문입니다.
| 선택 | 이유 |
|---|
| clone + CLI + 프롬프트 파일 (채택) | 추가적인 설정이나 상주 프로세스가 필요 없습니다. Copilot은 평소처럼 터미널에서 명령을 실행하기만 하면 됩니다. 도구는 Copilot 없이도 단독으로 사용할 수 있습니다. |
| MCP 서버 (현재 보류) | Copilot이 도구를 자동으로 찾을 수 있다는 장점은 있지만, MCP 활성화 및 설정 파일 확인, 조직 정책 확인 등이 필요하여 초보자가 첫발을 내딛기에는 부담이 됩니다. |
핵심 포인트는 '도구는 AI가 없어도 단독으로 사용 가능해야 한다'는 것입니다. AI에 의존할 수 없을 때도 사람이 직접 사용할 수 있고, 테스트 역시 일반적인 CLI로 작성할 수 있습니다.
또한, 각 CLI의 출력 형식과 종료 코드를 통일해 놓으면, 필요할 때 MCP와 같은 얇은 Wrapper를 나중에 추가하는 것도 가능합니다. CLI를 먼저 만들어 두는 것은 되돌리기 어려운 선택이 아닙니다.
AI에게 도구의 결과를 읽게 할 때는 사람을 위한 정형화된 출력이 적합하지 않습니다. 시각적인 요소가 바뀔 때마다 오독할 위험이 생기기 때문입니다.
그래서 모든 도구에서 공통의 '리포트 형식'을 정의했습니다. -f json 옵션을 추가하면 다음과 같은 형태로 결과를 반환합니다.
{
"schema_version": "1.0",
"tool": "fieldcheck",
...
}
여기서 고안한 부분은 세 가지입니다.
- AI는 건수를 보고 자세히 읽을지 판단할 수 있습니다.
summary 필드를 통해 전체 내용을 한눈에 파악할 수 있습니다. -
- '무엇이 다르며, 어떻게 수정해야 하는지'까지 작성되어 있어, AI가 사람에게 설명하기 쉽습니다.
expected, actual, suggestion 필드가 존재합니다. -
- 형식을 호환되지 않는 방식으로 변경했을 때는 메이저 버전을 올립니다. 사용하는 쪽은 메이저 버전이 예상과 일치하는지 확인할 수 있습니다.
schema_version 필드가 존재합니다.
사람이 읽기 위한 Markdown이나 HTML 형식도 준비되어 있지만, 이 경우 '외관은 예고 없이 변경될 수 있음'을 명시하고 있습니다. AI나 다른 도구에서 사용할 때는 JSON을 사용하기로 약속했습니다.
로그나 진행 상황, 경고는 모두 표준 에러(stderr)로 출력합니다. 표준 출력(stdout)에는 리포트만 출력합니다.
doc2md 報告書.pdf -d out/ -f json > report.json
이렇게 하면 출력을 그대로 파일에 저장해도 로그가 섞여서 손상되는 일이 없습니다. AI가 명령의 결과를 읽을 때도, 어디부터가 리포트인지 혼동하지 않습니다.
AI는 명령어의 종료 코드를 보고 성공/실패를 판단합니다. 그래서 모든 도구에서 의미를 통일했습니다.
| 코드 | 의미 | 사용하는 쪽의 처리 방법 |
|---|---|
0 | 정상 종료. 지적 사항 없음 | 성공 |
1 | 정상적으로 처리는 했으나, 지적 사항이 있음 | 리포트의 findings를 확인합니다. |
2 | 사용법/설정 파일/입력 오류 (전제가 무너진 경우) | 데이터가 아닌, 인자(argument), 설정, 컬럼명을 수정해야 합니다. |
3 | 예상치 못한 실행 시간 에러 | 결함으로 보고합니다. |
특히 중요한 것은 1과 2를 분리했다는 점입니다.
- 1은 '도구는 제대로 작동했고, 데이터에 문제가 발견된 경우'
- 2는 '애초에 도구 사용법 자체가 잘못된 경우'
이것이 같은 번호라면, AI는 인자의 오류를 '데이터의 문제'로 오해하고 데이터를 수정하려고 시도할 수 있습니다. 번호를 분리해 놓으면, AI가 무엇을 고쳐야 할지 혼동하지 않습니다.
구현 측면에서는 공통 라이브러리에 종료 코드를 정의하여 모든 도구에서 재사용하고 있습니다.
class ExitCode(IntEnum):
OK = 0 # 정상 종료・지적 사항 없음
FINDINGS = 1 # 정상적으로 처리했지만 오류 중 심각도 높은 지적이 있음
...
도구를 늘리면 AI가 어떤 것을 사용해야 할지 혼란스러워합니다. 그래서 워크스페이스의 AGENTS.md에 '언제, 어떤 도구를 사용할지' 목록을 작성하고 있습니다. 발췌하면 다음과 같은 형태입니다.
| 하고 싶은 일 | 명령어 |
|---|
| Word / Excel / PowerPoint / PDF / draw.io 내부 내용을 읽고 싶다 | doc2md <파일> -d out/ -f json → out/<stem>.md를 읽는다 |
| 표 항목 간의 정합성을 확인하고 싶다 | fieldcheck validate <데이터> -r rules.yaml -f json |
| 설계서의 신구 버전 차이점을 보고 싶다 | design-diff excel --type api --before 구버전.xlsx --after 신버전.xlsx |
| Office 문자열을 일괄로 치환하고 싶다 | 반드시 먼저 office-replace <디렉토리> --rules rules.csv --dry-run으로 확인한 후 본 작업 |
핵심은 도구 이름이 아니라 '하고 싶은 일'로 접근할 수 있도록 했다는 점입니다. AI는 'Excel 내용을 읽고 싶다'라는 상황에서, 사용해야 할 도구에 도달할 수 있습니다.
주의사항도 함께 작성합니다.
- 기존 도구로 충분한 것을 새로운 스크립트로 만들지 않는다.
doc2md의 JSON 결과에 loss.* 경고가 있다면, 변환 과정에서 내용이 누락되었을 가능성이 있다. 사용자에게 전달해야 한다 - 종료 코드 2는 데이터 오류가 아니라 전제 조건의 오류이다. 데이터가 아닌 전제를 확인한다.
마지막 항목은 약속 3의 종료 코드를 AI 지침으로 다시 한번 강조한 것입니다.
파일을 수정하는 도구에는 --dry-run 옵션을 마련했습니다.
office-replace samples/input --rules samples/rules.xlsx -r --dry-run
--dry-run에서는 어느 부분이 치환될지에 대한 리포트만 출력하고, 파일은 실제로 수정하지 않습니다. 게다가 본 작업에서도 원본 파일을 수정하지 않고 다른 폴더에 출력합니다.
AGENTS.md에도 '반드시 먼저 --dry-run'이라고 명시했기 때문에, AI가 바로 수정하는 일은 없습니다. 되돌릴 수 없는 작업일수록 확인 절차를 도구와 AI 지침 양쪽에 모두 포함시키는 것이 좋습니다.
업무 문서를 다루는 도구이므로 데이터 취급에도 신경을 썼습니다.
- 도구는 PC 내부에서만 작동하며, 네트워크에 연결하지 않습니다. 생성형 AI의 API도 호출하지 않습니다.
- 임시 파일은 작성자만 읽을 수 있는 폴더에 만들고, 처리 후 반드시 삭제합니다.
- 로그에는 본문 내용을 출력하지 않고, 건수나 파일 이름만 출력합니다.
로그에 본문을 출력하지 않기 위해, 공통 라이브러리에는 본문 대신 길이만 반환하는 함수를 준비했습니다.
def describe_text(text: str) -> str:
"""본문 대신 로그로 출력할 요약 (길이만)."""
return f"<text {len(text)} chars>"
AI 사용 시 주의사항도 작성했습니다. Copilot에는 채팅 내용이나 Copilot이 읽은 파일/명령어 결과가 전송됩니다. 따라서, 원본 문서를 직접 열게 하기보다는 변환 결과를 읽게 하거나, 검증은 도구에 맡기고 지적이 있었던 줄만 설명하도록 사용하는 방법을 권장합니다.
- 출력 시 최소한
-f json을 추가한다. summary와 findings 목록을 반환한다.
- 로그를 표준 에러(stderr)로 이동시킨다. 표준 출력(stdout)에는 리포트만 출력한다.
- 종료 코드를 결정한다. 적어도 '지적 사항 있음'과 '사용법 오류'는 다른 번호를 사용해야 한다. 수정하는 도구에
dry-run을 추가한다.
- 리포지토리에 '하고 싶은 일 → 명령어' 표와 주의사항을 작성한
AGENTS.md를 둔다. - AI에게 한 번 사용하게 해본다. AI가 막혔거나 잘못 읽은 부분을 AGENTS.md나 도구 출력에 반영한다.
마지막 단계가 중요합니다. AI가 막힌 부분은 사람이 신입일 때 막히는 부분입니다.
사람이 읽을 수 있는 형식만 제공한다.
줄 긋기(罫線)나 색상으로 된 표는 사람에게는 보기 좋지만, AI에게는 읽기 어려울 수 있습니다. 기계가 읽는 별도의 형식을 준비해야 합니다.
로그와 리포트를 같은 출력에 혼합하지 않기
표준 출력(stdout)에 로그가 섞이면 결과를 파일로 저장할 때 깨집니다. AI 역시 어디부터가 결과인지 알 수 없게 됩니다.
모든 실패를 동일한 종료 코드로 처리하기
'데이터 문제'와 '사용법 오류'를 구분할 수 없다면, AI는 잘못된 부분을 고치려고 시도합니다.
도구 사용처 분리를 명시하지 않기
도구가 많아질수록 AI가 어떤 것을 사용해야 할지 선택하기 어려워집니다. AGENTS.md에 '하고 싶은 일'에서 끌어낼 수 있는 목록을 배치합시다.
AI 에이전트에게 CLI는 손과 도구입니다. 도구가 다루기 쉽다면, AI의 작업 품질도 향상됩니다.
- 기계가 읽을 수 있는 출력(JSON)과 표준 출력/표준 에러의 분리 사용
- 의미가 통일된 종료 코드
- '하고 싶은 일'에서 끌어낼 수 있는 AGENTS.md
- 위험한 작업을 수행하기 전
--dry-run 기능
Harness 아티클에서 Tools는 'PC 및 사내 시스템 계정'에 해당한다고 썼습니다. 우리가 직접 만드는 CLI는 이 Tools를 늘리는 것입니다. AI가 사용하도록 전제하고 만들어 놓으면, 사람에게도 사용하기 쉬운 도구가 됩니다.
이 글은 Zenn에도 게시되어 있습니다.