어떤 프로그램이든 에이전트용 CLI로 바꿔 주는 오픈소스 CLI-Anything
CLI-Anything이 만드는 CLI는 에이전트 지침서, 명령, 프로젝트 파일, 백엔드, 실제 프로그램, 결과 검증의 여섯 층으로 이뤄진다. 층마다 맡은 일과 확인할 점을 뜯어 본다.
화면을 보고 클릭하는 방식으로 에이전트에게 40단계짜리 작업을 맡기면, 단계마다 스크린샷을 한 장씩 새로 받는다고 가정할 때 입력 토큰만 4만7만 2천 개가 스크린샷 값으로 나갑니다. Anthropic 문서가 스크린샷 한 장을 약 1,0001,800 입력 토큰이라고 밝혀 둔 값에 40을 곱한 편집부 계산입니다. CLI-Anything(저장소 HKUDS/CLI-Anything)은 이 비용 구조를 건너뛰려고 프로그램 쪽에 에이전트용 명령줄 입구를 만들어 붙입니다. 3월 8일에 저장소가 생긴 뒤 별이 약 5만 7천 개(10월 27일 UTC 확인)까지 쌓였습니다. 이 글은 그 입구가 겉으로는 명령 한 줄인데 안쪽에서 어떤 층을 지나 실제 파일을 만드는지 위에서 아래로 뜯어 봅니다. 층마다 맡은 일이 달라서, 문제가 생겼을 때 어느 층을 의심해야 하는지도 함께 짚습니다.
클릭 한 번마다 사진 한 장을 받는 순환
화면 방식, 곧 컴퓨터 사용(computer use)의 구조는 단순합니다. 모델이 screenshot, left_click, type, key, scroll 같은 동작을 요청하면 개발자 쪽 코드가 샌드박스 화면에서 실행하고 결과를 돌려줍니다. 스크린샷은 이미지로 돌아오지만 클릭 같은 동작은 짧은 확인 글만 돌려주므로, 클릭이 먹었는지 보려면 스크린샷을 다시 받아야 합니다. Anthropic 문서는 이 왕복이 반복되어 긴 작업에서 스크린샷이 빠르게 쌓인다고 적습니다.
문서의 한계 목록도 구체적입니다. 클릭이 작은 대상을 빗나갈 수 있고, 모델이 결과를 확인하지 않고 성공했다고 가정하고 넘어가는 일이 있고, 드롭다운과 스크롤바는 마우스로 다루기 까다로워 키보드 단축키를 권합니다. 해상도도 정확도에 영향을 줍니다. 일반 데스크톱 작업은 1024×768이나 1280×720, 웹 앱은 1280×800이나 1366×768을 권하고, 1920×1080을 넘기는 것은 피하라고 합니다. 최근 문서는 17개 도구를 묶은 새 도구 묶음 computer_toolset_20260801을 기준으로 설명하며, 화면 일부를 원본 해상도로 확대해 보는 zoom 동작이 들어 있습니다. 도구는 계속 개선되고 있지만 동작마다 이미지를 받는 순환 자체는 그대로입니다.
아래 그림은 같은 한 걸음, 곧 무언가를 바꾸고 결과를 확인하는 일이 두 방식에서 어떻게 오가는지 나란히 놓은 것입니다. 왼쪽 화살표에 붙은 이미지가 오른쪽에서는 글자로 바뀌는 지점을 보면 됩니다.
그림 1. 화면 방식은 확인이 필요할 때마다 이미지를 받고, 명령 방식은 JSON 한 줄을 받는다
오른쪽 순환에서 눈여겨볼 곳은 마지막 두 줄입니다. 값싼 읽기 전용 명령(info)으로 상태만 다시 읽을 수 있어서, 확인을 위해 이미지를 새로 받을 필요가 없습니다. 2026년 6월 2일 arXiv에 올라온 기술 보고서(「CLI-Anything: Towards Agent-Native Computer Use」)는 화면 기반 에이전트의 약점을 네 가지로 꼽습니다. 좌표 기반 동작이라 화면이 바뀌면 깨지고, 애니메이션과 응답 지연에 타이밍을 맞춰야 하고, 시각 정보를 계산 가능한 상태로 옮기며 구조가 사라지고, 사람의 손동작을 흉내 내느라 모델이 잘하는 구조화된 데이터 처리를 못 살린다는 것입니다. 다만 이 보고서는 두 방식을 같은 작업으로 맞대어 성공률을 잰 표를 싣지 않았습니다. 프로젝트 README의 로드맵에도 에이전트 작업 완료율 벤치마크 모음이 아직 체크되지 않은 항목으로 남아 있습니다. 그러니 위 그림은 비용과 구조의 차이를 그린 것이고, 어느 쪽이 더 자주 성공하는지에 대한 측정값은 아닙니다.
명령 한 줄이 내려가는 여섯 층
생성된 CLI는 에이전트가 친 명령 한 줄을 여섯 층에 차례로 통과시켜 실제 파일을 만듭니다. 여섯 층은 하니스 지침서(HARNESS.md)와 README 예시를 편집부가 층으로 나눠 부르는 이름이고, 문서 자체가 번호를 매긴 것은 아닙니다. 그림에서 위쪽 가로줄은 에이전트가 직접 만지는 면, 오른쪽 위는 디스크에 남는 상태, 아래쪽은 실제 프로그램이 일하는 구간, 왼쪽 아래는 결과를 확인하는 단계입니다.
그림 2. 명령은 1층에서 5층까지 내려가고, 6층에서 확인한 결과가 JSON으로 다시 1층으로 올라온다
화면 방식은 이 구조가 겉으로 드러나지 않지만 이쪽은 층마다 실패하는 모양이 다릅니다. 증상에서 층을 거꾸로 짚는 용도로 아래 표를 정리했습니다. 대응은 지침서와 SKILL.md에 적힌 동작을 바탕으로 한 편집부의 정리입니다.
| 보이는 증상 | 먼저 의심할 층 | 근거가 되는 문서의 동작 |
|---|---|---|
command not found |
2층(설치·PATH) | 시험도 which로 설치된 명령을 찾고, 없으면 pip install -e .를 안내 |
| “원본 프로그램을 찾을 수 없다” | 4층(백엔드) | 실행 파일이 없으면 설치 방법과 함께 오류를 내도록 지침 |
| 프로젝트 JSON에는 바꾼 값이 있는데 결과물엔 없음 | 3~5층 사이(렌더링 간극) | 프로젝트 수준 효과가 단순 렌더 도구에서 조용히 빠질 수 있다고 지침이 경고 |
| 종료 코드는 0인데 열리지 않는 파일 | 6층(검증) | 시험이 PDF 시작 바이트나 ZIP 구조를 직접 열어 확인 |
1~2층: 에이전트가 읽는 SKILL.md와 JSON 출력 명령
맨 위 두 층만 에이전트에게 보입니다. 1층의 SKILL.md는 그 CLI를 쓰는 법을 에이전트가 읽도록 적은 문서입니다. GIMP용 문서를 열어 보면 이름과 설명이 담긴 머리말, 명령 그룹별 표, 예제 명령, 그리고 “항상 --json을 쓸 것”, “이미지 이동·그리기·합성 모드·필터를 바꾼 뒤에는 저장된 프로젝트 상태만 믿지 말고 내보낸 결과를 확인할 것” 같은 에이전트용 지침이 들어 있습니다. 4월 18일에는 모든 CLI의 SKILL.md가 저장소 최상위 skills/ 폴더로 모였고, 에이전트에는 npx skills add HKUDS/CLI-Anything --skill <이름> -g -y로 하나씩 설치합니다.
2층은 Python의 Click으로 만든 명령줄입니다. 지침서는 모든 명령이 --json을 지원해야 한다고 못 박고, 인자 없이 실행하면 대화형 REPL로 들어가도록(invoke_without_command=True) 만들라고 합니다. 한 줄 명령은 스크립트와 파이프라인에, REPL은 상태를 이어 가는 에이전트 세션에 맞춘 이중 구조입니다. GIMP CLI의 소스를 보면 오류도 {"error": ..., "type": ...} 모양의 JSON으로 나오고 종료 코드가 1이라, 에이전트가 오류 종류를 글로 읽고 다음 행동을 고를 수 있습니다. 에이전트는 --help와 which cli-anything-이름으로 명령을 스스로 찾습니다. 이 두 층이 에이전트와 프로그램 사이의 계약이어서, 지침서는 구현 순서에서도 변경 명령보다 조회 명령(info, list, status)을 먼저 만들고, 변경 명령은 논리적 작업 하나에 하나씩 두라고 적습니다.
3층: 프로젝트 파일에 남는 상태와 되돌리기
세 번째 층은 상태입니다. 화면 방식에서는 상태가 창 안 어딘가에 있어서 에이전트가 볼 수 없지만, 이 CLI는 상태를 이름 붙은 파일로 남깁니다. 프로그램의 원래 형식(ODF 문서, MLT XML, SVG)이거나, 그렇지 않으면 JSON 프로젝트 파일입니다. GIMP CLI가 같은 상태를 다루는 방법은 세 가지입니다.
| 쓰는 방식 | 상태가 남는 곳 | 되돌리기 |
|---|---|---|
| 한 줄 명령 | 실행이 끝날 때 프로젝트 파일에 자동 저장 | 한 줄 명령에는 되돌리기 기록이 없음 |
한 줄 명령 + --dry-run |
디스크에 저장하지 않고 결과만 출력 | 저장 자체가 없음 |
| 인자 없이 실행한 REPL | 세션 메모리, 저장 시 파일 | session undo·redo·history, SKILL.md는 최대 50단계로 안내 |
저장하는 순간의 안전도 지침서에 규정이 있습니다. 파일을 r+로 열어 잠근 뒤 그 잠금 안에서 내용을 잘라 쓰는 독점 잠금 패턴(_locked_save_json)을 쓰므로, 저장하는 동안 다른 프로세스가 같은 파일에 끼어들지 못합니다. 에이전트 여러 개가 같은 프로젝트를 만질 때 필요한 장치입니다.
GIMP의 draw text와 draw rect는 저장 시점이 아니라 렌더 시점에 적용되는 연산입니다. 그래서 프로젝트 JSON에 글자가 들어 있어도 이미지를 내보내 보기 전에는 결과를 알 수 없습니다. 지침서가 “렌더링 간극”이라 부르는 문제의 실제 모습입니다. 프로젝트 파일만 고치는 CLI가 단순한 렌더 도구로 결과를 만들면 효과가 조용히 빠질 수 있다는 경고이며, 지침서의 해법은 우선순위가 있습니다. 앱의 고유 엔진으로 렌더하는 것이 가장 낫고, 안 되면 효과를 렌더 도구 문법으로 번역하고, 그것도 안 되면 사람이 돌릴 렌더 스크립트를 내놓습니다.
4~5층: 이름은 GIMP인데 그림은 Pillow가 그린다
네 번째와 다섯 번째 층은 실제 프로그램을 부르는 자리입니다. 4층의 백엔드 모듈은 실행 파일을 shutil.which()로 찾고 subprocess.run()으로 실행하며, 없으면 설치 방법과 함께 분명하게 실패해야 합니다. 5층은 blender --background나 libreoffice --headless처럼 화면 없이 도는 원본 프로그램입니다. 지침서는 이를 첫 번째 규칙으로 못 박습니다. 실제 프로그램으로 렌더하고 내보내야 하고, Python으로 프로그램 기능을 다시 구현하는 것은 반례라는 설명이며, 든 예가 바로 “GIMP를 대신하는 Pillow 기반 이미지 합성기”입니다. README의 설계 원칙 다섯 번째도 실제 프로그램은 필수이고 대체 경로는 없으며, 백엔드가 없으면 시험이 건너뛰지 않고 실패해야 한다고 적습니다.
그런데 GIMP용 SKILL.md 첫 문장은 이 CLI가 Pillow 위에 만들어졌다고 말합니다. 문서는 GIMP가 있으면 네이티브 일괄 렌더에 권장하지만, GIMP가 없을 때나 레이어 합성에서는 내장 Pillow 경로로 렌더되고, 그리기 연산이 걸린 프로젝트에서는 GIMP가 설치되어 있어도 Pillow 경로가 안정적인 기본이라고 적었습니다. README의 백엔드 열도 하니스마다 제각각입니다.
| CLI | README가 적은 백엔드 | 읽을 점 |
|---|---|---|
| GIMP | Pillow + GEGL/Script-Fu | 이미지 라이브러리 경로를 함께 씀 |
| Blender | bpy(Python 스크립트) | Blender 내부 파이썬으로 렌더 |
| Inkscape | SVG/XML 직접 조작 | SVG 파일을 직접 고침 |
| Audacity | Python wave + sox | 표준 모듈과 sox를 조합 |
| LibreOffice | ODF 생성 + 헤드리스 LO | 문서는 직접 쓰고 내보내기만 LibreOffice |
| OpenRefine | 로컬 HTTP API | 프로그램을 띄워 두고 API 호출 |
규칙과 실제가 다르다는 것을 알고 쓰면 해석이 바뀝니다. 이름이 GIMP CLI여도 결과가 GIMP 엔진으로 나왔다는 뜻이 아니므로, 중요한 작업은 원본이 설치된 환경에서 돌려 비교하는 편이 안전합니다. 대부분의 CLI는 원본 프로그램을 따로 설치해야 하고, 요구 사항은 레지스트리 항목의 requires 칸에 적혀 있습니다(Blender는 4.2 이상).
아래는 README에 실린 LibreOffice용 CLI의 사용 장면입니다. 앞의 세 명령은 report.json만 고치고, 네 번째 명령에서 비로소 LibreOffice가 화면 없이 실행되어 PDF를 만듭니다.
그림 3. 강조한 두 줄이 5층이 일한 구간이고, 앞의 세 명령은 3층의 프로젝트 파일만 바꾼다
마지막 두 줄은 6층의 첫걸음입니다. 내보내기 메시지가 파일 크기를 알려 줘도 그 말만으로는 PDF가 정상인지 알 수 없어서, 읽기 전용 명령으로 파일의 첫 다섯 바이트를 직접 봅니다.
6층: 합격 표시가 잰 것
여섯 번째 층은 결과물을 열어서 확인하는 시험입니다. README는 시험을 네 겹으로 나눕니다. 합성 데이터로 함수 하나하나를 보는 단위 시험, 프로젝트 파일 생성이 올바른지 보는 종단(e2e) 시험(ODF의 ZIP 구조, MLT XML, SVG의 형식), 실제 프로그램을 호출해 결과를 보는 종단 시험(PDF의 %PDF- 시작 바이트, Blender로 렌더한 PNG), 설치된 명령을 subprocess로 부르는 시험입니다. 같은 합격이라도 어느 겹의 합격인지에 따라 믿을 수 있는 정도가 다릅니다.
README의 시험 표 30줄 가운데 시험 수가 많은 CLI와 종단 시험이 없는 CLI를 골라 단위 시험과 종단 시험으로 나눠 보면 모양이 다릅니다.
그림 4. 전체 시험 수와 종단 시험 비중은 CLI마다 크게 다르고, Zoom과 NotebookLM 표에는 종단 시험이 0으로 적혀 있다
Blender는 시험이 208개로 가장 많지만 종단은 58개(28%)이고, LibreOffice는 158개 중 69개(44%)가 종단입니다. Zoom은 단위 시험 22개뿐입니다. 시험이 적다고 나쁜 것은 아니고, 숫자가 무엇을 시험했는지를 먼저 보라는 뜻입니다.
README 안의 숫자끼리는 맞지 않습니다. 맨 위 배지는 2,461, 표 맨 아랫줄은 2,464라고 적었는데 표의 30개 줄을 직접 더하면 2,622입니다. 본문 중간에는 “2,280+ tests”와 “18 apps”라는 옛 표기가 남아 있습니다. 백엔드가 없으면 시험이 실패해야 한다는 원칙과 달리 표에는 건너뛴 시험이 적혀 있습니다(WaveTone 2개, Eth2 QuickStart 3개, Unreal Insights 9개, Joplin 1개). 합계보다는 CLI별 종단 시험 비중을 읽는 편이 낫습니다. 직접 확인하려면 설치한 CLI 폴더의 복사본에서 아래 명령을 돌립니다. 설치된 명령을 실제로 호출하는 시험이 돌고 종단 시험이 만든 파일 경로가 출력되며, 원본 프로그램을 실행해 파일을 만드는 시험이라 원본 폴더에서 돌리지 않습니다.
CLI_ANYTHING_FORCE_INSTALLED=1 python3 -m pytest cli_anything/<이름>/tests/ -v -s
기술 보고서의 숫자도 같은 방식으로 읽습니다. 보고서는 레지스트리 스냅샷 기준으로 하니스 CLI 65건, 외부 CLI 18건, 동반 스킬 61건과, CLI-Hub 호출의 87.8%가 에이전트라는 관측값(사람 호출의 7.19배)을 제시합니다. 이용 규모와 구성을 보여 주는 값이고, 집계 기간과 호출 수는 논문에 실려 있지 않습니다. 성공률의 근거가 아니라 에이전트가 쓰는 도구라는 정황으로 받아들이면 됩니다.
층을 만들어 내는 공장: 생성 7단계
여섯 층은 사람이 쓰지 않습니다. 생성기는 별도 프로그램이 아니라 에이전트에 붙이는 플러그인이고, 실제 작업은 에이전트가 HARNESS.md라는 표준 지침서를 따라 수행합니다. Claude Code에서는 마켓 추가와 설치 두 줄이 전부입니다.
/plugin marketplace add HKUDS/CLI-Anything
/plugin install cli-anything
설치 뒤에는 네 가지 명령을 씁니다. /cli-anything <경로 또는 저장소>는 7단계 전체로 하니스를 만들고, /cli-anything:refine <경로> [초점]은 기능 빈틈을 분석해 명령·시험·문서를 덧붙이며 여러 번 돌려도 기존 것을 지우지 않습니다. /cli-anything:test는 시험을 돌려 TEST.md를 갱신하고, /cli-anything:validate는 HARNESS.md 기준에 맞는지 검증합니다. README가 한 번의 생성으로 전체 기능을 덮지 못하는 경우가 흔하다고 한계에 적었기 때문에 refine이 공식 흐름에 들어 있습니다.
README는 7단계를 말하고, 지침서에는 6단계와 7단계 사이에 SKILL.md를 만드는 6.5단계가 더 있습니다.
- 분석: 소스를 훑어 화면 조작이 어떤 내부 함수에 대응하는지 지도를 그립니다. 백엔드 엔진, 데이터 모델, 기존 명령줄 도구, 되돌리기 체계를 찾습니다.
- 설계: 명령 묶음, 상태 모델, 출력 형식을 정하고 REPL과 한 줄 명령을 둘 다 지원하는 쪽으로 잡습니다.
- 구현: 데이터 계층, 조회 명령, 변경 명령, 백엔드 연결, 내보내기, 세션, REPL 순서로 쌓습니다.
- 시험 계획: 코드를 쓰기 전에
TEST.md에 시험 목록과 현실적인 작업 시나리오를 적습니다. - 시험 작성: 합성 데이터의 단위 시험(
test_core.py)과 실제 파일·프로그램을 쓰는 종단 시험(test_full_e2e.py)을 구현합니다. - 문서화: 시험 결과와 통과율, 빠진 범위를
TEST.md에 채웁니다. - 배포 준비:
setup.py를 만들고 PATH에 설치합니다. 6.5단계에서 만든SKILL.md가 에이전트가 찾아 쓰는 입구가 됩니다.
시험을 코드보다 먼저 계획하게 하는 순서가 눈에 띕니다. 6층의 검증을 마지막에 덧붙이지 않고 설계 단계에서 정해 둔다는 뜻입니다. 끝나면 다음 모양의 폴더가 남습니다.
그림 5. 폴더 하나하나가 앞의 여섯 층 중 하나에 대응하고, 노란 표시는 일부러 __init__.py를 두지 않는 자리다
cli_anything/ 바로 아래에 __init__.py를 두지 않는 것은 PEP 420 네임스페이스 패키지로 만들어, 여러 CLI가 같은 cli_anything.* 이름 아래 충돌 없이 함께 설치되게 하려는 장치입니다. 지침서의 함정 목록에는 초당 29.97프레임처럼 정수가 아닌 프레임 속도에서 시간 계산 오차가 쌓이므로 int()가 아닌 round()와 ±1프레임 허용 오차를 쓰라는 항목도 있습니다. 완성된 CLI는 이렇게 설치해 확인합니다.
cd gimp/agent-harness && pip install -e .
which cli-anything-gimp
cli-anything-gimp --json project new --width 1920 --height 1080 -o poster.json
Claude Code 말고 Pi, OpenClaw, Cursor, Codex, Hermes, Reasonix, Qodercli, GitHub Copilot CLI용 설치 절차도 README에 따로 있고, OpenCode·Codex·Hermes·Reasonix·Goose는 실험적 지원으로 표시되어 있습니다. Windows에서 Claude Code로 생성하면 bash가 필요해 Git for Windows나 WSL을 설치해야 하고, 없으면 cygpath: command not found가 날 수 있다고 README가 안내합니다. 입력은 소스 코드가 기본입니다. 소스가 없는 프로그램에는 같은 일을 하는 오픈소스 대안을 넣으라고 README가 권하고, 흩어진 웹 서비스의 문서나 SDK를 넣으면 명령 묶음 하나로 정리해 준다고 설명합니다. 컴파일된 바이너리만 있어 디컴파일이 필요하면 품질과 범위가 크게 떨어진다는 것이 한계 항목의 설명입니다. 또 README는 믿을 만한 하니스에는 최상위급 모델(예로 Claude Opus 4.6, Claude Sonnet 4.6, GPT-5.4)이 필요하고 작은 모델은 불완전한 CLI를 낼 수 있다고 적었습니다.
이미 만들어 둔 층을 가져오는 cli-hub
직접 생성하기 전에 레지스트리를 먼저 보는 편이 빠릅니다. 사람이 쓰는 입구는 패키지 관리자 cli-hub이고, PyPI의 cli-anything-hub 최신판은 0.4.1(7월 9일 UTC 올림)입니다.
pip install cli-anything-hub
cli-hub search image # 키워드로 찾기
cli-hub info gimp # 설치 전에 내용 확인
cli-hub install gimp # 설치
cli-hub launch gimp # 실행
명령은 list, search, info, install, update, uninstall, launch가 있고, 설치 전에 info를 거치는 이유는 레지스트리 항목 하나에 무엇이 담겼는지 보면 알 수 있습니다. registry.json의 Blender 항목은 이렇습니다.
name: blender (1.0.0, category 3d)
description: blender --background --python 으로 모델링·애니메이션·렌더링
requires: blender >= 4.2 # 원본 프로그램은 따로 설치
install_cmd: pip install git+https://github.com/HKUDS/CLI-Anything.git#subdirectory=blender/agent-harness
entry_point: cli-anything-blender
skill_md: skills/cli-anything-blender/SKILL.md
레지스트리는 두 파일로 나뉩니다. 하니스 목록 registry.json은 79건(6월 19일 갱신)이고 그중 66건이 이 저장소의 하위 폴더에서 설치됩니다. 외부 개발자의 서드파티·공식 CLI를 모은 public_registry.json은 24건(8월 20일 갱신)이며 설치 방식이 npm 11, pip 6, brew 2, uv 1, go 1, 스크립트 실행 1, 설치 명령 없음 2로 갈립니다. 에이전트가 스스로 CLI를 고르게 하려면 메타 스킬을 설치합니다.
npx skills add HKUDS/CLI-Anything --skill cli-hub-meta-skill -g -y
README는 이 스킬이 OpenClaw, Nanobot, Claude Code, Codex 등 스킬 형식을 이해하는 에이전트에서 쓰인다고 안내합니다. 프로젝트의 현재 상태는 열린 이슈 86건, 열린 풀 리퀘스트 138건, 최신 릴리스 v0.4.0(6월 25일)이고 마지막 커밋은 9월 22일(UTC)입니다. v0.4.0은 여러 CLI 설치를 묶는 CLI-Matrix 명령군과 약 30개의 새 CLI를 더했습니다. 라이선스는 Apache-2.0입니다.
바깥문이 없는 게임에 층을 끼워 넣은 사례
실제 프로그램이 명령으로 열려 있지 않은 경우, 층을 만들기 전에 문부터 내야 합니다. 기술 보고서가 든 Slay the Spire II가 그 예입니다. 게임 안에서 도는 C# 브리지가 같은 컴퓨터 안(루프백) HTTP 포트 15526으로 상태를 내보내고, 게임이 한 프레임을 처리할 때 대기열에서 가져가는 요청을 최대 10건으로 제한합니다. 그 위에 올린 하니스는 게임 상태를 15개의 결정 상태(combat_play, card_reward, map_select 등)로 정리하고, 단일 플레이용 동작 동사 24개를 거쳐, Python CLI의 Click 명령 28개(play-card, choose-map, proceed 등)로 내놓습니다.
보고서는 의미 있는 명령 하나(play-card)가 커서 이동 경로, 타이밍, 해상도가 다른 수많은 입력 기록을 하나의 안정된 동작으로 모아 준다고 설명합니다. 1층부터 6층까지의 구조 앞에 브리지라는 0층을 하나 더 둔 셈입니다.
층마다 뚫리는 구멍
에이전트가 넣는 문자열이 1~2층에서 시작해 4층까지 그대로 내려가기 때문에 보안 구멍도 층마다 나옵니다. 저장소의 SECURITY.md는 위협 모델을 한 문장으로 적습니다. 사람이 명령을 하나하나 검토하던 일반 CLI와 달리 에이전트는 사용자 프롬프트, 업로드된 파일, 다른 에이전트의 출력 같은 믿을 수 없는 입력으로 명령을 스스로 만들어 실행하며, 프롬프트 주입을 당한 에이전트가 이상한 인자를 실제 프로그램에 넘길 수 있다는 것입니다. 문서의 공격면 다섯 가지를 층에 대응해 적으면 다음과 같습니다(층 대응은 편집부 정리).
| 층 | 공격면 | 문서가 적은 완화책 |
|---|---|---|
| 2층 | 서브프로세스 인자: 악성 코덱 이름·경로·필터 값이 melt, ffmpeg, gimp에 전달됨 | 호출 전에 허용 목록으로 검증(kdenlive·shotcut은 코덱 허용 목록) |
| 3층 | XML·SVG 내용: 사용자 글이 MLT XML, SVG, Draw.io 파일에 들어감 | XML 이스케이프, ElementTree 자동 이스케이프 |
| 3층 | 경로 순회: 에이전트가 정한 출력 경로가 엉뚱한 곳에 씀 | os.path.abspath() 정규화 |
| 3층 | 자격 증명 노출: API 키가 평문 설정 파일에 저장됨 | 파일 권한 0o600 |
| 4층 | Script-Fu 삽입: 사용자 문자열이 GIMP 배치 스크립트에 섞임 | 이스케이프 함수(_script_fu_escape()) 적용 |
이 위험은 이론에 그치지 않았습니다. README 뉴스에는 3월 14일 GIMP Script-Fu 경로의 삽입 취약점을 고친 기록, 5월 19일 XML·SVG·ODF 등의 파싱을 defusedxml로 바꾼 기록, 5월 21일 Sketch CLI의 토큰 파일 처리에서 경로 순회와 심볼릭 링크 탈출을 막은 기록이 있습니다. 하니스 개발자 규칙도 shell=True 금지, 모든 서브프로세스 인자의 허용 목록 검증, 스크립트에 넣기 전 사용자 내용 이스케이프, 경로 정규화, 키를 오류 메시지에 출력하지 않기로 정리되어 있습니다. 에이전트가 쓴 코드에서 이 규칙이 지켜졌는지가 곧 설치 전 점검 항목이고, 생성된 폴더는 읽기 전용 명령으로 훑어볼 수 있습니다.
cd gimp/agent-harness
grep -rn "shell=True" cli_anything/ || echo "shell=True 없음"
grep -rn "subprocess" cli_anything/*/utils/ cli_anything/*/core/
sed -n 1,40p setup.py
head -60 cli_anything/gimp/tests/TEST.md
공개 레지스트리는 설치 방식이 제각각입니다. 외부 개발자가 올린 24건 가운데 curl로 받은 스크립트를 바로 bash로 실행하는 형태가 1건 있고, 설치 명령이 비어 있는 항목도 2건 있습니다. 설치 전에 cli-hub info <이름>으로 출처와 설치 방식을 읽고, 쓰기 권한이 있는 명령(파일 삭제·덮어쓰기, 외부 서비스 호출)은 복사본이나 테스트 계정으로 먼저 돌려 보세요.
생성된 CLI에서 취약점을 찾으면 공개 이슈를 열지 말고 GitHub의 비공개 취약점 신고(Security 탭)나 메인테이너에게 직접 알리라고 SECURITY.md가 안내합니다. 접수 확인은 48시간 안, 치명적인 문제는 7일 안에 수정하는 것이 목표입니다.
같은 문제를 다르게 푸는 길들
명령줄 입구가 모든 프로그램의 답은 아닙니다. 프로그램이 이미 공식 명령줄이나 API를 잘 갖췄다면 그쪽을 쓰면 되고, CLI-Anything을 거칠 이유가 없습니다. 프로그램이 MCP 서버를 열어 두었다면 도구 호출로 바로 붙이는 길이 있습니다. 하니스 지침서에도 MCP 백엔드 패턴이 있어 서버가 있는 프로그램을 CLI로 한 번 더 감싸는 쪽으로 이어 갈 수 있고, README의 Safari CLI(safari-mcp 기반)가 그런 사례입니다. MCP의 구조는 다들 붙이는 MCP가 뭐길래, AI에게 도구를 연결하는 표준 입문에, 브라우저를 MCP로 에이전트에 붙이는 실제 사례는 코딩 에이전트가 직접 크롬을 열어 성능과 콘솔을 확인한다, Chrome DevTools MCP에 있습니다.
화면 방식이 남는 자리도 분명합니다. 보고서는 상태가 외부에서 보이지 않게 닫혀 있는 프로그램, 명령으로 옮길 수 없는 기능, 화면 없이는 렌더가 안 되는 경우, 눈으로 보고 판단해야 하는 창작 결정을 한계로 인정합니다. 상태가 파일로 남고 헤드리스 렌더가 되는 프로그램에 같은 작업을 반복해서 시키고 결과를 파일 형식이나 픽셀 값으로 확인할 수 있다면 명령줄 입구가 맞고, 그렇지 않다면 화면 방식이나 MCP가 맞습니다. 쓰는 프로그램이 레지스트리에 있는지는 cli-hub search와 cli-hub info 두 줄이면 알 수 있고, 그 결과가 여섯 층 가운데 어디까지 이미 만들어져 있는지를 알려 줍니다.