Disclaimer:
kmsg는 Kakao Corp. 의 공식 도구가 아닙니다. 사용자는 본인 계정/환경에서 관련 법규, 서비스 약관, 회사 보안 정책을 준수할 책임이 있습니다. 이 도구 사용으로 발생할 수 있는 계정 제한, 오작동, 데이터 손실, 기타 손해에 대한 책임은 사용자에게 있습니다. LOCO Protocol 이 아닌 AX 를 사용한 이유와 계정 제재 가능성에 대한 제 개인적인 판단은 왜 KakaoTalk 의 LOCO Protocol 을 사용하지 않나요? 항목을 참고해 주세요.
kmsg 는 macOS에서 카카오톡 메시지를 CLI 로 읽고 보내는 도구입니다. 단순한 수동 CLI 를 넘어, AI Agent 또는 Hook 이벤트 등의 자동화 파이프라인에 연결하기 쉽도록 구현했습니다. kmsg 는 openclaw 의 창시자인 steipete 가 만든 iMessage 컨트롤을 위한 CLI 도구인 imsg 에 영감을 받아 작성되었습니다.
record.mp4
요구사항:
- macOS 13+
- macOS용 KakaoTalk 설치
brew install channprj/tap/kmsg특정 릴리즈를 고정해서 설치하려면 exact version formula를 사용할 수 있습니다. tap에는 최신 10개 릴리즈만 유지됩니다.
brew install channprj/tap/kmsg@0.2.7mkdir -p ~/.local/bin && curl -fL https://github.com/channprj/kmsg/releases/latest/download/kmsg-macos-universal -o ~/.local/bin/kmsg && chmod +x ~/.local/bin/kmsg설치 확인은 아래와 같이 진행합니다.
kmsg status권한 팝업이 뜨면 허용해 주세요.
kmsg status
kmsg -v
kmsg send "본인, 친구, 또는 단톡방 이름" "안녕하세요"
kmsg send --chat-id "chat_7f42c5e1d9ab" "안녕하세요"
kmsg send "본인, 친구, 또는 단톡방 이름" "$(date '+%Y-%m-%d %H:%M:%S') 테스트"
kmsg send "본인, 친구, 또는 단톡방 이름" "테스트" --keep-window
kmsg send-image "본인, 친구, 또는 단톡방 이름" "/path/to/image.png"
kmsg mcp-server
kmsg chats
kmsg chats --json
kmsg read "본인, 친구, 또는 단톡방 이름" --limit 20
kmsg read "본인, 친구, 또는 단톡방 이름" --limit 20 --keep-window
kmsg read "본인, 친구, 또는 단톡방 이름" --limit 20 --json
kmsg read "본인, 친구, 또는 단톡방 이름" --limit 20 --deep-recovery
kmsg watch "본인, 친구, 또는 단톡방 이름"
kmsg watch "본인, 친구, 또는 단톡방 이름" --json
kmsg watch "본인, 친구, 또는 단톡방 이름" --json --poll-interval 0.5
kmsg inspect --window 0 --depth 20 --debug-layout옵션은 빌드 기준 kmsg --help, kmsg <command> --help 출력과 동일하게 관리됩니다.
kmsg status [--verbose]--verbose: 상세 상태 출력
kmsg chats [--verbose] [--limit <limit>] [--trace-ax] [--json] [--keep-window]-v, --verbose: 상세 정보 출력-l, --limit <limit>: 최대 채팅 목록 개수 (기본값: 20)--trace-ax: AX 탐색/재시도 로그 출력--json:chat_id를 포함한 구조화 JSON 출력-k, --keep-window:chats실행 중 자동으로 연 창을 유지
기본 출력에는 각 채팅의 chat_id가 함께 표시됩니다. chat_id는 로컬 registry(~/.kmsg/chat-registry.json)에 저장되는 synthetic ID이며, 채팅방 이름을 기준으로 생성/재사용됩니다. 같은 이름의 방이 여러 개면 registry가 별도 ID를 유지하고, 방 이름이 바뀌면 새 ID로 취급합니다. chats가 실행 중 창을 자동으로 열었다면 기본적으로 종료 시 닫고, --keep-window일 때만 유지합니다.
kmsg read <chat> [--limit <limit>] [--debug] [--trace-ax] [--keep-window] [--deep-recovery] [--json]-l, --limit <limit>: 최대 메시지 개수 (기본값: 20)--debug: raw element 디버그 정보 출력--trace-ax: AX 탐색/재시도 로그 출력-k, --keep-window: 자동으로 연 채팅창과 리스트창 유지--deep-recovery: 빠른 탐색 실패 시 deep recovery 수행--json: JSON 형식으로 출력
kmsg watch <chat> [--poll-interval <seconds>] [--trace-ax] [--keep-window] [--deep-recovery] [--json] [--include-system]--poll-interval <seconds>: 폴링 주기 (기본값:0.2, 범위:0.2...10.0)--trace-ax: AX 탐색/재시도 로그 출력-k, --keep-window: 자동으로 연 채팅창 유지--deep-recovery: 빠른 탐색 실패 시 deep recovery 수행--json: 새 메시지 이벤트를 pretty JSON 객체로 계속 출력--include-system: 날짜 구분선 등 시스템성 메시지도 포함
watch는 시작 시 현재 대화를 baseline으로만 잡고 출력하지 않으며, transcript가 늦게 로딩되는 경우를 피하기 위해 최대 1-2초 정도 silent warm-up으로 baseline을 안정화한 뒤 감시를 시작합니다. 또한 파싱된 날짜/시각을 기준으로 watch 시작 시점 이전 메시지는 출력하지 않습니다. 안전한 시각을 추정할 수 없는 행은 보수적으로 숨길 수 있습니다. steady-state에서는 마지막으로 성공한 transcript context를 우선 재사용하고, transcript root도 AX cache happy path를 사용해 200ms polling을 견딜 수 있게 했습니다. 기본 모드는 일반 대화 메시지만 출력하고, --include-system일 때만 시스템성 메시지를 포함합니다. --json에서는 이벤트마다 별도 JSON 객체가 stdout으로 출력되고, --trace-ax는 계속 stderr에만 기록됩니다.
kmsg send <recipient> <message> [--dry-run] [--trace-ax] [--no-cache] [--refresh-cache] [--keep-window] [--deep-recovery]
kmsg send --chat-id <chat-id> <message> [--dry-run] [--trace-ax] [--no-cache] [--refresh-cache] [--keep-window] [--deep-recovery]--chat-id <chat-id>:kmsg chats에서 출력된chat_id로 전송--dry-run: 실제 전송 없이 시뮬레이션--trace-ax: AX 탐색/재시도 로그 출력--no-cache: 이번 실행에서 AX path cache 비활성화--refresh-cache: 이번 실행에서 AX path cache 강제 재구성-k, --keep-window: 자동으로 연 채팅창과 리스트창 유지--deep-recovery: 빠른 탐색 실패 시 deep recovery 수행
--chat-id 전송은 local registry에서 채팅방 이름을 역조회한 뒤 기존 search/open 경로로 채팅을 엽니다. registry에 없는 ID는 즉시 실패합니다. send는 기본적으로 전송 후 채팅창과 카톡 리스트창을 정리하고, --keep-window일 때만 둘 다 유지합니다.
kmsg send-image <recipient> <image-path> [--trace-ax] [--no-cache] [--keep-window] [--deep-recovery]--trace-ax: AX 탐색/재시도 로그 출력--no-cache: 이번 실행에서 AX path cache 비활성화-k, --keep-window: 자동으로 연 채팅창과 리스트창 유지--deep-recovery: 빠른 탐색 실패 시 deep recovery 수행
send-image는 기본적으로 전송 후 채팅창과 카톡 리스트창을 정리하고, --keep-window일 때만 둘 다 유지합니다. 확인 시트가 매우 빠르게 사라지거나 생략되는 경우도 성공으로 처리하도록 되어 있습니다.
kmsg inspect [--depth <depth>] [--window <window>] [--show-attributes] [--show-path] [--show-frame] [--show-index] [--show-flags] [--show-actions] [--debug-layout] [--row-summary] [--row-range <start:end>]-d, --depth <depth>: 최대 탐색 깊이 (기본값: 4)-w, --window <window>: inspect 대상 창 인덱스 (기본값: main window)--show-attributes: 각 요소의 AX attribute 출력--show-path: 각 요소의 AX 경로 출력--show-frame: 각 요소 frame 출력--show-index: sibling index 출력--show-flags: 상태 플래그(enabled/focused/selected/editable) 출력--show-actions: 지원 AX action 출력--debug-layout:path/frame/index/flags를 한 번에 켜는 레이아웃 디버그 번들--row-summary: 메시지 row 요약 출력--row-range <start:end>:--row-summary결과를 특정 범위만 출력 (inclusive, zero-based)
kmsg help cachestatus(default): 캐시 상태 출력clear: 캐시 삭제export <output-path>: 캐시 JSON 내보내기import <input-path>: 캐시 JSON 가져오기warmup [--recipient <recipient>] [--trace-ax] [--keep-window]:send/chatshappy-path 경로 캐시 워밍업
kmsg는 손쉬운 사용(Accessibility) 권한이 필요합니다.
앱이 자동 요청에 실패하면:
- 시스템 설정 열기
개인정보 보호 및 보안 > 손쉬운 사용kmsg토글 켜기
read 명령은 --json 플래그로 구조화된 결과를 반환할 수 있습니다.
kmsg read "본인, 친구, 또는 단톡방 이름" --limit 20 --json{
"chat": "홍길동",
"fetched_at": "2026-02-26T01:23:45.678Z",
"count": 20,
"messages": [
{
"author": "홍길동",
"time_raw": "00:27",
"body": "밤이 깊었네"
}
]
}chat: 실제로 읽은 채팅방 제목fetched_at: 메시지 수집 시각(ISO-8601 UTC)count: 반환된 메시지 개수messages[].author: 작성자 이름 ((me)는 내 메시지 또는 작성자 추론이 불가능한 경우)messages[].time_raw: UI에서 읽힌 시각 문자열(없으면null)messages[].body: 메시지 본문
--json사용 시 JSON은stdout으로만 출력됩니다.--trace-ax로그는stderr로 분리되므로 OpenClaw 같은 파이프 연동에서 안전하게 사용할 수 있습니다.
watch --json은 배열 하나를 반환하는 대신, 새 메시지가 감지될 때마다 pretty JSON 객체 하나씩을 연속 출력합니다.
kmsg watch "본인, 친구, 또는 단톡방 이름" --json예시 이벤트:
{
"chat": "홍길동",
"detected_at": "2026-03-25T10:20:30.123Z",
"event": "message",
"message": {
"author": "홍길동",
"time_raw": "10:20",
"body": "새 메시지"
}
}--include-system을 같이 쓰면 event 값이 system인 객체도 함께 출력될 수 있습니다.
chats --json도 동일하게 stdout에만 JSON을 출력하며, 각 채팅 항목에 chat_id와 last_message를 포함합니다. 첫 실행이나 캐시 self-heal 시에는 더 느릴 수 있지만, 이후 실행은 저장된 happy-path AX cache를 우선 사용합니다.
kmsg 는 MCP 로 붙여서 사용할 수 있습니다.
현재 구조는 다음처럼 나뉩니다.
- MCP:
kmsg_read,kmsg_send,kmsg_send_image - 실시간 감시:
kmsg watch "<chat>" --json
즉, OpenClaw 연동은 보통 아래 두 프로세스로 구성합니다.
kmsg mcp-server
kmsg watch "채팅방 이름" --jsonOpenClaw MCP 설정 예시:
{
"mcpServers": {
"kmsg": {
"command": "kmsg",
"args": ["mcp-server"],
"env": {
"KMSG_DEFAULT_DEEP_RECOVERY": "false",
"KMSG_TRACE_DEFAULT": "false"
}
}
}
}추천 운영 순서는 아래와 같습니다.
watch --json으로 새 메시지 감지- OpenClaw 가 답변 초안 생성
- 필요하면 사용자 승인
kmsg_send또는kmsg send로 전송
상세 운영 가이드는 docs/openclaw.md 를 참고하세요. 설정 템플릿은 docs/openclaw.mcp.example.json 에 있습니다.
기존 tools/kmsg-mcp.py 는 레거시/개발용 참조 구현으로 남아 있지만, 설치형 사용 흐름의 기본 진입점은 kmsg mcp-server 입니다.
git clone https://github.com/channprj/kmsg.git
cd kmsg
swift build -c release
install -m 755 .build/release/kmsg ~/.local/bin/kmsgkmsg send "본인, 친구, 또는 단톡방 이름" "테스트" --trace-ax
kmsg send --chat-id "chat_7f42c5e1d9ab" "테스트" --dry-run
kmsg send "본인, 친구, 또는 단톡방 이름" "테스트" --dry-run --trace-ax
kmsg send "본인, 친구, 또는 단톡방 이름" "테스트" --no-cache
kmsg send "본인, 친구, 또는 단톡방 이름" "테스트" --refresh-cache
kmsg chats --json
kmsg send-image "본인, 친구, 또는 단톡방 이름" "/path/to/image.png" --trace-ax
KMSG_AX_TIMEOUT=0.25 kmsg send "본인, 친구, 또는 단톡방 이름" "테스트"
kmsg inspect --window 0 --depth 20 --debug-layout
kmsg inspect --window 0 --depth 20 --row-summary
kmsg inspect --window 0 --depth 20 --row-summary --row-range 10:35
kmsg cache status
kmsg cache warmup --recipient "본인, 친구, 또는 단톡방 이름" --trace-ax
kmsg cache warmup --recipient "본인, 친구, 또는 단톡방 이름" --keep-window
kmsg cache export ./ax-cache.json
kmsg cache import ./ax-cache.json
kmsg read "본인, 친구, 또는 단톡방 이름" --deep-recovery --trace-ax
kmsg send "본인, 친구, 또는 단톡방 이름" "테스트" --deep-recovery --trace-ax
kmsg send-image "본인, 친구, 또는 단톡방 이름" "/path/to/image.png" --deep-recovery --trace-ax --keep-window--deep-recovery는 빠른 창 탐색이 실패할 때만 relaunch/open 복구를 추가로 수행합니다.
기본적으로 자동으로 연 카카오톡 창은 명령 종료 시 닫히며, --keep-window(또는 -k)로 유지할 수 있습니다.
메시지 읽기/보내기가 기대와 다르면 아래 순서로 상태를 수집해 주세요.
# 1) 대상 채팅창 구조 확인
kmsg inspect --window 0 --depth 20 --debug-layout
# 1-1) 메시지 row 파싱 진단
kmsg inspect --window 0 --depth 20 --row-summary
kmsg inspect --window 0 --depth 20 --row-summary --row-range 10:30
# 2) 읽기 경로/AX 로그 확인
kmsg read "본인, 친구, 또는 단톡방 이름" --limit 20 --trace-ax
# 3) 보내기 경로/AX 로그 확인
kmsg send "본인, 친구, 또는 단톡방 이름" "테스트" --trace-ax --dry-runAXTextArea, value: "..."는 실제 메시지 본문 후보입니다.AXStaticText, value: "5\n00:27"같은 값은 보통 카운트/시간 메타 정보입니다.--debug-layout을 켜면path/frame/index/flags가 함께 출력되어 위치 기반 분석이 쉬워집니다.--row-summary는read파서 기준으로 row별authorCandidates,time,buttonTitles를 빠르게 점검할 때 유용합니다.- 이슈 보고 시
inspect출력과--trace-ax출력을 함께 첨부하면 원인 파악이 빨라집니다.
개발을 진행하거나 버그 수정을 원할 때 Coding Agent에게 아래 정보와 함께 요청하면 좋습니다.
- 실행한 명령어:
kmsg read ... --trace-ax,kmsg inspect ... - 기대 결과: 무엇이 보여야 하는지
- 실제 결과: 현재 무엇이 출력되는지
- 관련 로그:
inspect본문 구간 (AXRow > AXCell > AXTextArea) +trace-ax
kmsg read가 메시지 본문 대신 시간/숫자를 출력합니다.
inspect 결과를 기준으로 AXRow > AXCell > AXTextArea.value를 우선 추출하도록 수정해 주세요.
README 디버깅 가이드도 함께 업데이트해 주세요.
v* 태그를 푸시하면 GitHub Actions가 자동으로 빌드해서 kmsg-macos-universal 파일을 Releases에 업로드합니다.
같은 workflow가 channprj/homebrew-tap 리모트까지 자동으로 동기화하며, TAP_REPO_TOKEN이 없거나 tap push가 실패하면 릴리즈도 실패합니다.
배포 전에 VERSION 파일 값을 먼저 업데이트하세요.
# gh 토큰이 만료됐으면 재로그인
gh auth login -h github.com
# 배포 태그 생성/푸시
git tag v0.2.3
git push origin v0.2.3필요하면 Actions를 수동 실행할 수 있습니다.
workflow_dispatch에서 tag를 비워두면 VERSION 파일을 읽어 v<version>으로 자동 생성합니다.
tag를 직접 입력할 경우 vX.Y.Z 형식만 허용됩니다.
# 태그를 직접 지정해서 실행
gh workflow run release.yml -f tag=v0.2.4
# tag 미지정 시 VERSION(예: 0.2.4) 기반으로 실행
gh workflow run release.ymlchannprj/homebrew-tap저장소를 만들고Formula/디렉터리를 준비합니다.kmsg저장소 Secrets에TAP_REPO_TOKEN을 추가합니다.- 권한:
homebrew-tap저장소contents: write
- 권한:
- tap 저장소 기본 브랜치가
main이 아니면.github/workflows/release.yml의TAP_REPO_REF값을 맞춰 주세요.
이제 TAP_REPO_TOKEN은 선택이 아니라 필수입니다.
secret이 없으면 바이너리 release만 만들고 끝내지 않고, workflow 전체가 실패해서 tap 반영 누락을 바로 잡도록 동작합니다.
릴리즈 후 사용자는 아래 명령으로 설치할 수 있습니다.
brew install channprj/tap/kmsg
brew install channprj/tap/kmsg@0.2.7kmsg formula는 항상 최신 릴리즈를 가리키고, kmsg@X.Y.Z formula는 최근 10개 exact release만 유지됩니다.
이미 다른 버전을 설치했다면 아래처럼 링크를 전환할 수 있습니다.
brew unlink kmsg
brew link --overwrite kmsg@0.2.7kmsg 는 macOS 전용 도구이고, 핵심 기능이 AppKit, ApplicationServices, AXUIElement, CGEvent 같은 macOS native API 위에 올라가 있습니다. Swift 는 이 API 들과의 연결이 가장 직접적이고 안정적이며, Objective-C 계열 프레임워크와의 상호운용도 자연스럽습니다.
반대로 Python 은 런타임/배포 의존성이 늘어나고, Rust 는 macOS framework binding 과 FFI 관리 복잡도가 커지기 때문에 현재 목적에는 Swift 쪽이 구현과 유지보수 모두 더 실용적이었습니다.
AX 는 macOS Accessibility(손쉬운 사용) API 를 뜻합니다. 앱의 창, 버튼, 입력창, 텍스트 같은 UI 요소를 AXUIElement 로 읽고 제어할 수 있게 해주는 시스템 인터페이스입니다.
KakaoTalk 은 공식적으로 CLI 를 제공하지 않습니다. 그래서 실제 사용자가 보고 클릭하는 UI 를 시스템 레벨에서 읽고 조작할 수 있는 현실적인 방법이 AX 입니다.
kmsg 의 채팅 목록 읽기, 검색, 메시지 전송, 이미지 전송, 창 제어가 모두 이 레이어 위에서 동작하기 때문에 Accessibility permission 이 필요합니다.
LOCO Protocol 을 쓰려면 사실상 비공개 동작을 리버스 엔지니어링해야 하는데, 저는 이 접근이 명백한 약관 위반으로 해석될 여지가 크다고 봅니다. 실제로 이런 방식의 자동화나 비공식 프로토콜 사용과 관련해 계정이 영구정지된 사례도 이미 적지 않게 알려져 있어서, 기능적으로 가능하더라도 감수해야 하는 리스크가 너무 크다고 판단했습니다.
반면 AX 방식은 사용자가 실제로 보고 조작하는 KakaoTalk UI 를 macOS 의 공식 Accessibility API 로 읽고 제어하는 접근이라, 적어도 LOCO 를 직접 건드리는 방식보다는 상대적으로 안전할 것이라고 판단했습니다. 다만 이것도 어디까지나 제 개인적인 판단일 뿐이며, 카카오의 공식 입장이나 카카오 법무팀의 해석과는 다를 수 있습니다. 실제 약관 해석과 제재 기준은 카카오 측 판단이 우선합니다.
조금은 빨라질 수 있지만, 체감 성능 향상은 제한적일 가능성이 큽니다. 이 CLI 의 주된 지연은 언어 런타임보다 AXUIElement 호출, UI 탐색, KakaoTalk 창 활성화/복구, 실제 앱 응답 시간에서 발생합니다.
Rust 로 바꾸면 cold start 나 순수 stdio/JSON 처리 같은 부분은 약간 유리할 수 있지만, 전체 end-to-end latency 는 크게 달라지지 않을 가능성이 높습니다. 대신 macOS native framework binding 과 유지보수 비용은 Swift 보다 높아질 수 있습니다.
This project is strongly inspired by steipete and his works.
