Skip to content

channprj/kmsg

Folders and files

NameName
Last commit message
Last commit date

Latest commit

ย 

History

138 Commits
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 

Repository files navigation

kmsg

License: MIT Total downloads

kmsg logo

Disclaimer: kmsg๋Š” Kakao Corp. ์˜ ๊ณต์‹ ๋„๊ตฌ๊ฐ€ ์•„๋‹™๋‹ˆ๋‹ค. ์‚ฌ์šฉ์ž๋Š” ๋ณธ์ธ ๊ณ„์ •/ํ™˜๊ฒฝ์—์„œ ๊ด€๋ จ ๋ฒ•๊ทœ, ์„œ๋น„์Šค ์•ฝ๊ด€, ํšŒ์‚ฌ ๋ณด์•ˆ ์ •์ฑ…์„ ์ค€์ˆ˜ํ•  ์ฑ…์ž„์ด ์žˆ์Šต๋‹ˆ๋‹ค. ์ด ๋„๊ตฌ ์‚ฌ์šฉ์œผ๋กœ ๋ฐœ์ƒํ•  ์ˆ˜ ์žˆ๋Š” ๊ณ„์ • ์ œํ•œ, ์˜ค์ž‘๋™, ๋ฐ์ดํ„ฐ ์†์‹ค, ๊ธฐํƒ€ ์†ํ•ด์— ๋Œ€ํ•œ ์ฑ…์ž„์€ ์‚ฌ์šฉ์ž์—๊ฒŒ ์žˆ์Šต๋‹ˆ๋‹ค. LOCO Protocol ์ด ์•„๋‹Œ AX ๋ฅผ ์‚ฌ์šฉํ•œ ์ด์œ ์™€ ๊ณ„์ • ์ œ์žฌ ๊ฐ€๋Šฅ์„ฑ์— ๋Œ€ํ•œ ์ œ ๊ฐœ์ธ์ ์ธ ํŒ๋‹จ์€ ์™œ KakaoTalk ์˜ LOCO Protocol ์„ ์‚ฌ์šฉํ•˜์ง€ ์•Š๋‚˜์š”? ํ•ญ๋ชฉ์„ ์ฐธ๊ณ ํ•ด ์ฃผ์„ธ์š”.

kmsg ๋Š” macOS์—์„œ ์นด์นด์˜คํ†ก ๋ฉ”์‹œ์ง€๋ฅผ CLI ๋กœ ์ฝ๊ณ  ๋ณด๋‚ด๋Š” ๋„๊ตฌ์ž…๋‹ˆ๋‹ค. ๋‹จ์ˆœํ•œ ์ˆ˜๋™ CLI ๋ฅผ ๋„˜์–ด, AI Agent ๋˜๋Š” Hook ์ด๋ฒคํŠธ ๋“ฑ์˜ ์ž๋™ํ™” ํŒŒ์ดํ”„๋ผ์ธ์— ์—ฐ๊ฒฐํ•˜๊ธฐ ์‰ฝ๋„๋ก ๊ตฌํ˜„ํ–ˆ์Šต๋‹ˆ๋‹ค. kmsg ๋Š” openclaw ์˜ ์ฐฝ์‹œ์ž์ธ steipete ๊ฐ€ ๋งŒ๋“  iMessage ์ปจํŠธ๋กค์„ ์œ„ํ•œ CLI ๋„๊ตฌ์ธ imsg ์— ์˜๊ฐ์„ ๋ฐ›์•„ ์ž‘์„ฑ๋˜์—ˆ์Šต๋‹ˆ๋‹ค.

Demo

record.mp4

๋น ๋ฅธ ์‹œ์ž‘

์š”๊ตฌ์‚ฌํ•ญ:

์„ค์น˜ (Homebrew ๊ถŒ์žฅ)

brew install channprj/tap/kmsg

ํŠน์ • ๋ฆด๋ฆฌ์ฆˆ๋ฅผ ๊ณ ์ •ํ•ด์„œ ์„ค์น˜ํ•˜๋ ค๋ฉด exact version formula๋ฅผ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. tap์—๋Š” ์ตœ์‹  10๊ฐœ ๋ฆด๋ฆฌ์ฆˆ๋งŒ ์œ ์ง€๋ฉ๋‹ˆ๋‹ค.

brew install channprj/tap/kmsg@2026.0422.22

์„ค์น˜ (์ง์ ‘ ๋‹ค์šด๋กœ๋“œ)

mkdir -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 auth login
kmsg auth login --auto
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 --json --background-safe
kmsg read "๋ณธ์ธ, ์นœ๊ตฌ, ๋˜๋Š” ๋‹จํ†ก๋ฐฉ ์ด๋ฆ„" --limit 20 --deep-recovery
kmsg read "๋ณธ์ธ, ์นœ๊ตฌ, ๋˜๋Š” ๋‹จํ†ก๋ฐฉ ์ด๋ฆ„" --limit 20 --layout split-right
kmsg watch "๋ณธ์ธ, ์นœ๊ตฌ, ๋˜๋Š” ๋‹จํ†ก๋ฐฉ ์ด๋ฆ„"
kmsg watch "๋ณธ์ธ, ์นœ๊ตฌ, ๋˜๋Š” ๋‹จํ†ก๋ฐฉ ์ด๋ฆ„" --json
kmsg watch "๋ณธ์ธ, ์นœ๊ตฌ, ๋˜๋Š” ๋‹จํ†ก๋ฐฉ ์ด๋ฆ„" --json --poll-interval 0.5
kmsg inspect --window 0 --depth 20 --debug-layout

CLI ๋ช…๋ น์–ด ๋ ˆํผ๋Ÿฐ์Šค

์˜ต์…˜์€ ๋นŒ๋“œ ๊ธฐ์ค€ kmsg --help, kmsg <command> --help ์ถœ๋ ฅ๊ณผ ๋™์ผํ•˜๊ฒŒ ๊ด€๋ฆฌ๋ฉ๋‹ˆ๋‹ค.

status

kmsg status [--verbose]
  • --verbose: ์ƒ์„ธ ์ƒํƒœ ์ถœ๋ ฅ

status, chats, read, send, send-image, watch, cache warmup์€ ์นด์นด์˜คํ†ก ๋กœ๊ทธ์ธ์ด ํ’€๋ ค ์žˆ์œผ๋ฉด ์ €์žฅ๋œ ์ž๊ฒฉ ์ฆ๋ช…์œผ๋กœ ์ž๋™ ๋กœ๊ทธ์ธ์„ ์‹œ๋„ํ•ฉ๋‹ˆ๋‹ค. ์ €์žฅ๋œ ์ •๋ณด๊ฐ€ ์—†๊ฑฐ๋‚˜ ๋ถˆ์™„์ „ํ•˜๋ฉด ํ„ฐ๋ฏธ๋„์—์„œ ์•„์ด๋””/๋น„๋ฐ€๋ฒˆํ˜ธ๋ฅผ ์ž…๋ ฅ๋ฐ›์•„ ~/.config/kmsg/credentials.json์— ์ €์žฅํ•˜๊ณ , ๋น„๋ฐ€๋ฒˆํ˜ธ ์•”ํ˜ธํ‚ค๋Š” ~/.config/kmsg/credentials/์— ๋ณ„๋„๋กœ ๋ณด๊ด€ํ•ฉ๋‹ˆ๋‹ค. ๋‹จ, read --background-safe๋Š” ์นด์นด์˜คํ†ก ์‹คํ–‰/ํ™œ์„ฑํ™”/์ž๋™ ๋กœ๊ทธ์ธ์„ ํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค.

auth login

kmsg auth login [--auto] [--trace-ax]
  • --auto: ์ €์žฅ๋œ ์ž๊ฒฉ ์ฆ๋ช…์ด ์žˆ์œผ๋ฉด ๊ทธ๋Œ€๋กœ ์‚ฌ์šฉํ•˜๊ณ , ์—†์œผ๋ฉด ์ž…๋ ฅ๋ฐ›์•„ ์ €์žฅ ํ›„ ๋กœ๊ทธ์ธ
  • --trace-ax: AX ํƒ์ƒ‰/์žฌ์‹œ๋„ ๋กœ๊ทธ ์ถœ๋ ฅ

๊ธฐ๋ณธ kmsg auth login์€ ์•„์ด๋””/๋น„๋ฐ€๋ฒˆํ˜ธ๋ฅผ ์ƒˆ๋กœ ์ž…๋ ฅ๋ฐ›์•„ ์ €์žฅํ•˜๊ณ  ๋กœ๊ทธ์ธํ•ฉ๋‹ˆ๋‹ค. ๋น„๋ฐ€๋ฒˆํ˜ธ๋Š” ํ‰๋ฌธ์ด ์•„๋‹ˆ๋ผ ์•”ํ˜ธํ™”๋œ ํ˜•ํƒœ๋กœ ์ €์žฅ๋ฉ๋‹ˆ๋‹ค.

chats

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์ผ ๋•Œ๋งŒ ์œ ์ง€ํ•ฉ๋‹ˆ๋‹ค.

read

kmsg read <chat> [--limit <limit>] [--debug] [--trace-ax] [--keep-window] [--background-safe] [--deep-recovery] [--layout <layout>] [--json]
  • -l, --limit <limit>: ์ตœ๋Œ€ ๋ฉ”์‹œ์ง€ ๊ฐœ์ˆ˜ (๊ธฐ๋ณธ๊ฐ’: 20)
  • --debug: raw element ๋””๋ฒ„๊ทธ ์ •๋ณด ์ถœ๋ ฅ
  • --trace-ax: AX ํƒ์ƒ‰/์žฌ์‹œ๋„ ๋กœ๊ทธ ์ถœ๋ ฅ
  • -k, --keep-window: ์ž๋™์œผ๋กœ ์—ฐ ์ฑ„ํŒ…์ฐฝ๊ณผ ๋ฆฌ์ŠคํŠธ์ฐฝ ์œ ์ง€
  • --background-safe: ์นด์นด์˜คํ†ก ์‹คํ–‰/ํ™œ์„ฑํ™”/์ž๋™ ๋กœ๊ทธ์ธ/๊ฒ€์ƒ‰/์ฑ„ํŒ…๋ฐฉ ์—ด๊ธฐ/์ฐฝ ํฌ๊ธฐ ๋ณ€๊ฒฝ/์ž๋™ ๋‹ซ๊ธฐ๋ฅผ ํ•˜์ง€ ์•Š๊ณ , ์ด๋ฏธ ๋…ธ์ถœ๋œ ๋งค์นญ ์ฑ„ํŒ…์ฐฝ๋งŒ ์ฝ์Œ
  • --deep-recovery: ๋น ๋ฅธ ํƒ์ƒ‰ ์‹คํŒจ ์‹œ deep recovery ์ˆ˜ํ–‰
  • --layout <layout>: preserve, left, right, split-left, split-right. ๋งˆ์ผ€ํŒ… ์ž‘์—…์ž๊ฐ€ ๋ธŒ๋ผ์šฐ์ €/๊ด‘๊ณ ํˆด๊ณผ ํ™”๋ฉด์„ ๋‚˜๋ˆ  ์“ธ ๋•Œ๋Š” split-left ๋˜๋Š” split-right๋ฅผ ์‚ฌ์šฉ
  • --json: JSON ํ˜•์‹์œผ๋กœ ์ถœ๋ ฅ

watch

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์—๋งŒ ๊ธฐ๋ก๋ฉ๋‹ˆ๋‹ค.

send

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์ผ ๋•Œ๋งŒ ๋‘˜ ๋‹ค ์œ ์ง€ํ•ฉ๋‹ˆ๋‹ค.

send-image

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์ผ ๋•Œ๋งŒ ๋‘˜ ๋‹ค ์œ ์ง€ํ•ฉ๋‹ˆ๋‹ค. ํ™•์ธ ์‹œํŠธ๊ฐ€ ๋งค์šฐ ๋น ๋ฅด๊ฒŒ ์‚ฌ๋ผ์ง€๊ฑฐ๋‚˜ ์ƒ๋žต๋˜๋Š” ๊ฒฝ์šฐ๋„ ์„ฑ๊ณต์œผ๋กœ ์ฒ˜๋ฆฌํ•˜๋„๋ก ๋˜์–ด ์žˆ์Šต๋‹ˆ๋‹ค.

inspect

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)

cache

kmsg help cache
  • status (default): ์บ์‹œ ์ƒํƒœ ์ถœ๋ ฅ
  • clear: ์บ์‹œ ์‚ญ์ œ
  • export <output-path>: ์บ์‹œ JSON ๋‚ด๋ณด๋‚ด๊ธฐ
  • import <input-path>: ์บ์‹œ JSON ๊ฐ€์ ธ์˜ค๊ธฐ
  • warmup [--recipient <recipient>] [--trace-ax] [--keep-window]: send/chats happy-path ๊ฒฝ๋กœ ์บ์‹œ ์›Œ๋ฐ์—…

๊ถŒํ•œ ๋ฌธ์ œ ํ•ด๊ฒฐ

kmsg๋Š” ์†์‰ฌ์šด ์‚ฌ์šฉ(Accessibility) ๊ถŒํ•œ์ด ํ•„์š”ํ•ฉ๋‹ˆ๋‹ค.

์•ฑ์ด ์ž๋™ ์š”์ฒญ์— ์‹คํŒจํ•˜๋ฉด:

  1. ์‹œ์Šคํ…œ ์„ค์ • ์—ด๊ธฐ
  2. ๊ฐœ์ธ์ •๋ณด ๋ณดํ˜ธ ๋ฐ ๋ณด์•ˆ > ์†์‰ฌ์šด ์‚ฌ์šฉ
  3. kmsg ํ† ๊ธ€ ์ผœ๊ธฐ

JSON ์ถœ๋ ฅ

read ๋ช…๋ น์€ --json ํ”Œ๋ž˜๊ทธ๋กœ ๊ตฌ์กฐํ™”๋œ ๊ฒฐ๊ณผ๋ฅผ ๋ฐ˜ํ™˜ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

kmsg read "๋ณธ์ธ, ์นœ๊ตฌ, ๋˜๋Š” ๋‹จํ†ก๋ฐฉ ์ด๋ฆ„" --limit 20 --json
kmsg read "๋ณธ์ธ, ์นœ๊ตฌ, ๋˜๋Š” ๋‹จํ†ก๋ฐฉ ์ด๋ฆ„" --limit 20 --json --background-safe

์ถœ๋ ฅ ํ˜•์‹

{
  "chat": "ํ™๊ธธ๋™",
  "fetched_at": "2026-02-26T01:23:45.678Z",
  "count": 20,
  "messages": [
    {
      "author": "ํ™๊ธธ๋™",
      "time_raw": "00:27",
      "body": "๋ฐค์ด ๊นŠ์—ˆ๋„ค",
      "has_image": false,
      "image_count": 0,
      "link_count": 0,
      "has_attachment": false,
      "attachment_count": 0
    }
  ]
}

ํ•„๋“œ ์„ค๋ช…

  • chat: ์‹ค์ œ๋กœ ์ฝ์€ ์ฑ„ํŒ…๋ฐฉ ์ œ๋ชฉ
  • fetched_at: ๋ฉ”์‹œ์ง€ ์ˆ˜์ง‘ ์‹œ๊ฐ(ISO-8601 UTC)
  • count: ๋ฐ˜ํ™˜๋œ ๋ฉ”์‹œ์ง€ ๊ฐœ์ˆ˜
  • messages[].author: ์ž‘์„ฑ์ž ์ด๋ฆ„ ((me)๋Š” ๋‚ด ๋ฉ”์‹œ์ง€ ๋˜๋Š” ์ž‘์„ฑ์ž ์ถ”๋ก ์ด ๋ถˆ๊ฐ€๋Šฅํ•œ ๊ฒฝ์šฐ)
  • messages[].time_raw: UI์—์„œ ์ฝํžŒ ์‹œ๊ฐ ๋ฌธ์ž์—ด(์—†์œผ๋ฉด null)
  • messages[].body: ๋ฉ”์‹œ์ง€ ๋ณธ๋ฌธ
  • messages[].has_image / image_count: ๋ฉ”์‹œ์ง€์— ํฌํ•จ๋œ ์ด๋ฏธ์ง€ ์กด์žฌ ์—ฌ๋ถ€์™€ ๊ฐœ์ˆ˜
  • messages[].link_count: ๋ณธ๋ฌธ์—์„œ ๊ฐ์ง€๋œ URL ๋งํฌ ๊ฐœ์ˆ˜
  • messages[].has_attachment / attachment_count: ์ด๋ฏธ์ง€๋ฅผ ์ œ์™ธํ•œ ํŒŒ์ผ ์ฒจ๋ถ€(๋ฌธ์„œ ๋“ฑ) ์กด์žฌ ์—ฌ๋ถ€์™€ ๊ฐœ์ˆ˜. ์ด๋ฏธ์ง€๋Š” image_count๋กœ ๋ณ„๋„ ์ง‘๊ณ„๋ฉ๋‹ˆ๋‹ค.

์ฃผ์˜

  • --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๋ฅผ ์šฐ์„  ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค.

MCP ์—ฐ๋™

kmsg ๋Š” MCP ๋กœ ๋ถ™์—ฌ์„œ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ํ˜„์žฌ ๊ตฌ์กฐ๋Š” ๋‹ค์Œ์ฒ˜๋Ÿผ ๋‚˜๋‰ฉ๋‹ˆ๋‹ค.

  • MCP: kmsg_read, kmsg_send, kmsg_send_image
  • ์‹ค์‹œ๊ฐ„ ๊ฐ์‹œ: kmsg watch "<chat>" --json

์ฆ‰, OpenClaw ์—ฐ๋™์€ ๋ณดํ†ต ์•„๋ž˜ ๋‘ ํ”„๋กœ์„ธ์Šค๋กœ ๊ตฌ์„ฑํ•ฉ๋‹ˆ๋‹ค.

kmsg mcp-server
kmsg watch "์ฑ„ํŒ…๋ฐฉ ์ด๋ฆ„" --json
$HOME/.local/bin/kmsg mcp-server

OpenClaw MCP ์„ค์ • ์˜ˆ์‹œ:

{
  "mcpServers": {
    "kmsg": {
      "command": "$HOME/.local/bin/kmsg",
      "args": ["mcp-server"],
      "env": {
        "KMSG_DEFAULT_DEEP_RECOVERY": "false",
        "KMSG_DEFAULT_BACKGROUND_SAFE": "false",
        "KMSG_TRACE_DEFAULT": "false",
        "KMSG_DEFAULT_READ_LAYOUT": "split-right"
      }
    }
  }
}

mcp-server ๋Š” MCP Content-Length ํ”„๋ ˆ์ด๋ฐ๊ณผ ์ค„ ๋‹จ์œ„ JSON-RPC ์ž…๋ ฅ์„ ๋ชจ๋‘ ๋ฐ›์Šต๋‹ˆ๋‹ค. ์š”์ฒญ์ด Content-Length ๋ฐฉ์‹์ด๋ฉด ๊ฐ™์€ ๋ฐฉ์‹์œผ๋กœ ์‘๋‹ตํ•˜๊ณ , JSON ํ•œ ์ค„ ์š”์ฒญ์ด๋ฉด JSON ํ•œ ์ค„๋กœ ์‘๋‹ตํ•ฉ๋‹ˆ๋‹ค.

์ถ”์ฒœ ์šด์˜ ์ˆœ์„œ๋Š” ์•„๋ž˜์™€ ๊ฐ™์Šต๋‹ˆ๋‹ค.

  1. watch --json ์œผ๋กœ ์ƒˆ ๋ฉ”์‹œ์ง€ ๊ฐ์ง€
  2. OpenClaw ๊ฐ€ ๋‹ต๋ณ€ ์ดˆ์•ˆ ์ƒ์„ฑ
  3. ํ•„์š”ํ•˜๋ฉด ์‚ฌ์šฉ์ž ์Šน์ธ
  4. 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/kmsg

๊ณ ๊ธ‰ ์˜ต์…˜

kmsg 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)๋กœ ์œ ์ง€ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

๋””๋ฒ„๊น… ๊ฐ€์ด๋“œ (inspect / trace-ax)

๋ฉ”์‹œ์ง€ ์ฝ๊ธฐ/๋ณด๋‚ด๊ธฐ๊ฐ€ ๊ธฐ๋Œ€์™€ ๋‹ค๋ฅด๋ฉด ์•„๋ž˜ ์ˆœ์„œ๋กœ ์ƒํƒœ๋ฅผ ์ˆ˜์ง‘ํ•ด ์ฃผ์„ธ์š”.

# 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-run
  • AXTextArea, value: "..." ๋Š” ์‹ค์ œ ๋ฉ”์‹œ์ง€ ๋ณธ๋ฌธ ํ›„๋ณด์ž…๋‹ˆ๋‹ค.
  • AXStaticText, value: "5\n00:27" ๊ฐ™์€ ๊ฐ’์€ ๋ณดํ†ต ์นด์šดํŠธ/์‹œ๊ฐ„ ๋ฉ”ํƒ€ ์ •๋ณด์ž…๋‹ˆ๋‹ค.
  • --debug-layout์„ ์ผœ๋ฉด path/frame/index/flags๊ฐ€ ํ•จ๊ป˜ ์ถœ๋ ฅ๋˜์–ด ์œ„์น˜ ๊ธฐ๋ฐ˜ ๋ถ„์„์ด ์‰ฌ์›Œ์ง‘๋‹ˆ๋‹ค.
  • --row-summary๋Š” read ํŒŒ์„œ ๊ธฐ์ค€์œผ๋กœ row๋ณ„ authorCandidates, time, buttonTitles๋ฅผ ๋น ๋ฅด๊ฒŒ ์ ๊ฒ€ํ•  ๋•Œ ์œ ์šฉํ•ฉ๋‹ˆ๋‹ค.
  • ์ด์Šˆ ๋ณด๊ณ  ์‹œ inspect ์ถœ๋ ฅ๊ณผ --trace-ax ์ถœ๋ ฅ์„ ํ•จ๊ป˜ ์ฒจ๋ถ€ํ•˜๋ฉด ์›์ธ ํŒŒ์•…์ด ๋นจ๋ผ์ง‘๋‹ˆ๋‹ค.

Coding Agent์—๊ฒŒ ์š”์ฒญํ•˜๊ธฐ

๊ฐœ๋ฐœ์„ ์ง„ํ–‰ํ•˜๊ฑฐ๋‚˜ ๋ฒ„๊ทธ ์ˆ˜์ •์„ ์›ํ•  ๋•Œ Coding Agent์—๊ฒŒ ์•„๋ž˜ ์ •๋ณด์™€ ํ•จ๊ป˜ ์š”์ฒญํ•˜๋ฉด ์ข‹์Šต๋‹ˆ๋‹ค.

  1. ์‹คํ–‰ํ•œ ๋ช…๋ น์–ด: kmsg read ... --trace-ax, kmsg inspect ...
  2. ๊ธฐ๋Œ€ ๊ฒฐ๊ณผ: ๋ฌด์—‡์ด ๋ณด์—ฌ์•ผ ํ•˜๋Š”์ง€
  3. ์‹ค์ œ ๊ฒฐ๊ณผ: ํ˜„์žฌ ๋ฌด์—‡์ด ์ถœ๋ ฅ๋˜๋Š”์ง€
  4. ๊ด€๋ จ ๋กœ๊ทธ: inspect ๋ณธ๋ฌธ ๊ตฌ๊ฐ„ (AXRow > AXCell > AXTextArea) + trace-ax
kmsg read๊ฐ€ ๋ฉ”์‹œ์ง€ ๋ณธ๋ฌธ ๋Œ€์‹  ์‹œ๊ฐ„/์ˆซ์ž๋ฅผ ์ถœ๋ ฅํ•ฉ๋‹ˆ๋‹ค.
inspect ๊ฒฐ๊ณผ๋ฅผ ๊ธฐ์ค€์œผ๋กœ AXRow > AXCell > AXTextArea.value๋ฅผ ์šฐ์„  ์ถ”์ถœํ•˜๋„๋ก ์ˆ˜์ •ํ•ด ์ฃผ์„ธ์š”.
README ๋””๋ฒ„๊น… ๊ฐ€์ด๋“œ๋„ ํ•จ๊ป˜ ์—…๋ฐ์ดํŠธํ•ด ์ฃผ์„ธ์š”.

Deploy

v* ํƒœ๊ทธ๋ฅผ ํ‘ธ์‹œํ•˜๋ฉด GitHub Actions๊ฐ€ ์ž๋™์œผ๋กœ ๋นŒ๋“œํ•ด์„œ kmsg-macos-universal ํŒŒ์ผ์„ Releases์— ์—…๋กœ๋“œํ•ฉ๋‹ˆ๋‹ค. ๊ฐ™์€ workflow๊ฐ€ channprj/homebrew-tap ๋ฆฌ๋ชจํŠธ๊นŒ์ง€ ์ž๋™์œผ๋กœ ๋™๊ธฐํ™”ํ•˜๋ฉฐ, TAP_REPO_TOKEN์ด ์—†๊ฑฐ๋‚˜ tap push๊ฐ€ ์‹คํŒจํ•˜๋ฉด ๋ฆด๋ฆฌ์ฆˆ๋„ ์‹คํŒจํ•ฉ๋‹ˆ๋‹ค.

๋ฐฐํฌ ์ „์— VERSION ํŒŒ์ผ ๊ฐ’์„ ๋จผ์ € ์—…๋ฐ์ดํŠธํ•˜์„ธ์š”. ๋ฒ„์ „ ๊ทœ์น™์€ VERSIONING.md๋ฅผ ๋”ฐ๋ฆ…๋‹ˆ๋‹ค.

# gh ํ† ํฐ์ด ๋งŒ๋ฃŒ๋์œผ๋ฉด ์žฌ๋กœ๊ทธ์ธ
gh auth login -h github.com

# ๋ฐฐํฌ ํƒœ๊ทธ ์ƒ์„ฑ/ํ‘ธ์‹œ
git tag v2026.0422.22
git push origin v2026.0422.22

ํ•„์š”ํ•˜๋ฉด Actions๋ฅผ ์ˆ˜๋™ ์‹คํ–‰ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. workflow_dispatch์—์„œ tag๋ฅผ ๋น„์›Œ๋‘๋ฉด VERSION ํŒŒ์ผ์„ ์ฝ์–ด v<version>์œผ๋กœ ์ž๋™ ์ƒ์„ฑํ•ฉ๋‹ˆ๋‹ค. tag๋ฅผ ์ง์ ‘ ์ž…๋ ฅํ•  ๊ฒฝ์šฐ vYYYY.MMDD.COUNT ํ˜•์‹๋งŒ ํ—ˆ์šฉ๋ฉ๋‹ˆ๋‹ค.

# ํƒœ๊ทธ๋ฅผ ์ง์ ‘ ์ง€์ •ํ•ด์„œ ์‹คํ–‰
gh workflow run release.yml -f tag=v2026.0422.22

# tag ๋ฏธ์ง€์ • ์‹œ VERSION(์˜ˆ: 2026.0422.22) ๊ธฐ๋ฐ˜์œผ๋กœ ์‹คํ–‰
gh workflow run release.yml

Homebrew ์ž๋™ ๋™๊ธฐํ™” ์„ค์ • (์ตœ์ดˆ 1ํšŒ)

  1. channprj/homebrew-tap ์ €์žฅ์†Œ๋ฅผ ๋งŒ๋“ค๊ณ  Formula/ ๋””๋ ‰ํ„ฐ๋ฆฌ๋ฅผ ์ค€๋น„ํ•ฉ๋‹ˆ๋‹ค.
  2. kmsg ์ €์žฅ์†Œ Secrets์— TAP_REPO_TOKEN์„ ์ถ”๊ฐ€ํ•ฉ๋‹ˆ๋‹ค.
    • ๊ถŒํ•œ: homebrew-tap ์ €์žฅ์†Œ contents: write
  3. 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@2026.0422.22

kmsg formula๋Š” ํ•ญ์ƒ ์ตœ์‹  ๋ฆด๋ฆฌ์ฆˆ๋ฅผ ๊ฐ€๋ฆฌํ‚ค๊ณ , kmsg@YYYY.MMDD.COUNT formula๋Š” ์ตœ๊ทผ 10๊ฐœ exact release๋งŒ ์œ ์ง€๋ฉ๋‹ˆ๋‹ค. ์ด๋ฏธ ๋‹ค๋ฅธ ๋ฒ„์ „์„ ์„ค์น˜ํ–ˆ๋‹ค๋ฉด ์•„๋ž˜์ฒ˜๋Ÿผ ๋งํฌ๋ฅผ ์ „ํ™˜ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

brew unlink kmsg
brew link --overwrite kmsg@2026.0422.22

๊ธฐํƒ€

์™œ Swift ๋กœ ๋งŒ๋“ค์—ˆ๋‚˜์š”?

kmsg ๋Š” macOS ์ „์šฉ ๋„๊ตฌ์ด๊ณ , ํ•ต์‹ฌ ๊ธฐ๋Šฅ์ด AppKit, ApplicationServices, AXUIElement, CGEvent ๊ฐ™์€ macOS native API ์œ„์— ์˜ฌ๋ผ๊ฐ€ ์žˆ์Šต๋‹ˆ๋‹ค. Swift ๋Š” ์ด API ๋“ค๊ณผ์˜ ์—ฐ๊ฒฐ์ด ๊ฐ€์žฅ ์ง์ ‘์ ์ด๊ณ  ์•ˆ์ •์ ์ด๋ฉฐ, Objective-C ๊ณ„์—ด ํ”„๋ ˆ์ž„์›Œํฌ์™€์˜ ์ƒํ˜ธ์šด์šฉ๋„ ์ž์—ฐ์Šค๋Ÿฝ์Šต๋‹ˆ๋‹ค. ๋ฐ˜๋Œ€๋กœ Python ์€ ๋Ÿฐํƒ€์ž„/๋ฐฐํฌ ์˜์กด์„ฑ์ด ๋Š˜์–ด๋‚˜๊ณ , Rust ๋Š” macOS framework binding ๊ณผ FFI ๊ด€๋ฆฌ ๋ณต์žก๋„๊ฐ€ ์ปค์ง€๊ธฐ ๋•Œ๋ฌธ์— ํ˜„์žฌ ๋ชฉ์ ์—๋Š” Swift ์ชฝ์ด ๊ตฌํ˜„๊ณผ ์œ ์ง€๋ณด์ˆ˜ ๋ชจ๋‘ ๋” ์‹ค์šฉ์ ์ด์—ˆ์Šต๋‹ˆ๋‹ค.

AX ๊ฐ€ ๋ญ”๊ฐ€์š”?

AX ๋Š” macOS Accessibility(์†์‰ฌ์šด ์‚ฌ์šฉ) API ๋ฅผ ๋œปํ•ฉ๋‹ˆ๋‹ค. ์•ฑ์˜ ์ฐฝ, ๋ฒ„ํŠผ, ์ž…๋ ฅ์ฐฝ, ํ…์ŠคํŠธ ๊ฐ™์€ UI ์š”์†Œ๋ฅผ AXUIElement ๋กœ ์ฝ๊ณ  ์ œ์–ดํ•  ์ˆ˜ ์žˆ๊ฒŒ ํ•ด์ฃผ๋Š” ์‹œ์Šคํ…œ ์ธํ„ฐํŽ˜์ด์Šค์ž…๋‹ˆ๋‹ค.

์™œ AX ๋ฅผ ์‚ฌ์šฉํ•˜๋‚˜์š”?

KakaoTalk ์€ ๊ณต์‹์ ์œผ๋กœ CLI ๋ฅผ ์ œ๊ณตํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค. ๊ทธ๋ž˜์„œ ์‹ค์ œ ์‚ฌ์šฉ์ž๊ฐ€ ๋ณด๊ณ  ํด๋ฆญํ•˜๋Š” UI ๋ฅผ ์‹œ์Šคํ…œ ๋ ˆ๋ฒจ์—์„œ ์ฝ๊ณ  ์กฐ์ž‘ํ•  ์ˆ˜ ์žˆ๋Š” ํ˜„์‹ค์ ์ธ ๋ฐฉ๋ฒ•์ด AX ์ž…๋‹ˆ๋‹ค. kmsg ์˜ ์ฑ„ํŒ… ๋ชฉ๋ก ์ฝ๊ธฐ, ๊ฒ€์ƒ‰, ๋ฉ”์‹œ์ง€ ์ „์†ก, ์ด๋ฏธ์ง€ ์ „์†ก, ์ฐฝ ์ œ์–ด๊ฐ€ ๋ชจ๋‘ ์ด ๋ ˆ์ด์–ด ์œ„์—์„œ ๋™์ž‘ํ•˜๊ธฐ ๋•Œ๋ฌธ์— Accessibility permission ์ด ํ•„์š”ํ•ฉ๋‹ˆ๋‹ค.

์™œ KakaoTalk ์˜ LOCO Protocol ์„ ์‚ฌ์šฉํ•˜์ง€ ์•Š๋‚˜์š”?

LOCO Protocol ์„ ์“ฐ๋ ค๋ฉด ์‚ฌ์‹ค์ƒ ๋น„๊ณต๊ฐœ ๋™์ž‘์„ ๋ฆฌ๋ฒ„์Šค ์—”์ง€๋‹ˆ์–ด๋งํ•ด์•ผ ํ•˜๋Š”๋ฐ, ์ €๋Š” ์ด ์ ‘๊ทผ์ด ๋ช…๋ฐฑํ•œ ์•ฝ๊ด€ ์œ„๋ฐ˜์œผ๋กœ ํ•ด์„๋  ์—ฌ์ง€๊ฐ€ ํฌ๋‹ค๊ณ  ๋ด…๋‹ˆ๋‹ค. ์‹ค์ œ๋กœ ์ด๋Ÿฐ ๋ฐฉ์‹์˜ ์ž๋™ํ™”๋‚˜ ๋น„๊ณต์‹ ํ”„๋กœํ† ์ฝœ ์‚ฌ์šฉ๊ณผ ๊ด€๋ จํ•ด ๊ณ„์ •์ด ์˜๊ตฌ์ •์ง€๋œ ์‚ฌ๋ก€๋„ ์ด๋ฏธ ์ ์ง€ ์•Š๊ฒŒ ์•Œ๋ ค์ ธ ์žˆ์–ด์„œ, ๊ธฐ๋Šฅ์ ์œผ๋กœ ๊ฐ€๋Šฅํ•˜๋”๋ผ๋„ ๊ฐ์ˆ˜ํ•ด์•ผ ํ•˜๋Š” ๋ฆฌ์Šคํฌ๊ฐ€ ๋„ˆ๋ฌด ํฌ๋‹ค๊ณ  ํŒ๋‹จํ–ˆ์Šต๋‹ˆ๋‹ค.

๋ฐ˜๋ฉด AX ๋ฐฉ์‹์€ ์‚ฌ์šฉ์ž๊ฐ€ ์‹ค์ œ๋กœ ๋ณด๊ณ  ์กฐ์ž‘ํ•˜๋Š” KakaoTalk UI ๋ฅผ macOS ์˜ ๊ณต์‹ Accessibility API ๋กœ ์ฝ๊ณ  ์ œ์–ดํ•˜๋Š” ์ ‘๊ทผ์ด๋ผ, ์ ์–ด๋„ LOCO ๋ฅผ ์ง์ ‘ ๊ฑด๋“œ๋ฆฌ๋Š” ๋ฐฉ์‹๋ณด๋‹ค๋Š” ์ƒ๋Œ€์ ์œผ๋กœ ์•ˆ์ „ํ•  ๊ฒƒ์ด๋ผ๊ณ  ํŒ๋‹จํ–ˆ์Šต๋‹ˆ๋‹ค. ๋‹ค๋งŒ ์ด๊ฒƒ๋„ ์–ด๋””๊นŒ์ง€๋‚˜ ์ œ ๊ฐœ์ธ์ ์ธ ํŒ๋‹จ์ผ ๋ฟ์ด๋ฉฐ, ์นด์นด์˜ค์˜ ๊ณต์‹ ์ž…์žฅ์ด๋‚˜ ์นด์นด์˜ค ๋ฒ•๋ฌดํŒ€์˜ ํ•ด์„๊ณผ๋Š” ๋‹ค๋ฅผ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์‹ค์ œ ์•ฝ๊ด€ ํ•ด์„๊ณผ ์ œ์žฌ ๊ธฐ์ค€์€ ์นด์นด์˜ค ์ธก ํŒ๋‹จ์ด ์šฐ์„ ํ•ฉ๋‹ˆ๋‹ค.

์ด ๊ธฐ๋Šฅ์„ Rust ๋กœ ์ž‘์„ฑํ•˜๋ฉด ๋” ๋นจ๋ผ์งˆ๊นŒ์š”?

์ผ๋ถ€ ๊ตฌ๊ฐ„์€ ๋นจ๋ผ์งˆ ์ˆ˜ ์žˆ์ง€๋งŒ, ์ฒด๊ฐ ์„ฑ๋Šฅ ํ–ฅ์ƒ์€ ์ œํ•œ์ ์ผ ๊ฐ€๋Šฅ์„ฑ์ด ํฝ๋‹ˆ๋‹ค. kmsg ์˜ ์ฃผ๋œ ์ง€์—ฐ์€ ์–ธ์–ด ๋Ÿฐํƒ€์ž„๋ณด๋‹ค AXUIElement ํ˜ธ์ถœ, UI ํƒ์ƒ‰, KakaoTalk ์ฐฝ ํ™œ์„ฑํ™”/๋ณต๊ตฌ, ์‹ค์ œ ์•ฑ ์‘๋‹ต ์‹œ๊ฐ„์—์„œ ๋ฐœ์ƒํ•ฉ๋‹ˆ๋‹ค. Rust ๋กœ ๋ฐ”๊พธ๋ฉด ๋‹จ๋ฐœ CLI cold start, ์ˆœ์ˆ˜ stdio ํ”„๋ ˆ์ด๋ฐ, JSON encode/decode ๊ฐ™์€ ์ข์€ ๊ตฌ๊ฐ„์€ ์œ ๋ฆฌํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ํ•˜์ง€๋งŒ MCP ์„œ๋ฒ„์ฒ˜๋Ÿผ ํ”„๋กœ์„ธ์Šค๊ฐ€ ์ด๋ฏธ ๋–  ์žˆ๋Š” ๊ฒฝ๋กœ์—์„œ๋Š” ๊ทธ ์ด๋“์ด ๋” ์ž‘์•„์ง€๊ณ , ์ „์ฒด end-to-end latency ๋Š” ์—ฌ์ „ํžˆ macOS AX ์™€ KakaoTalk UI ์‘๋‹ต ์‹œ๊ฐ„์ด ์ง€๋ฐฐํ•ฉ๋‹ˆ๋‹ค. ๋Œ€์‹  Rust ๋Š” macOS native framework binding ๊ณผ FFI ๊ด€๋ฆฌ ๋น„์šฉ์ด Swift ๋ณด๋‹ค ์ปค์งˆ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

Inspiration

This project is strongly inspired by steipete and his works.

License

kmsg is available under the MIT License.

References

About

CLI for KakaoTalk.app so your agent can send and receive k-messages.

Topics

Resources

License

Stars

230 stars

Watchers

0 watching

Forks

Contributors