From e5d1e4ccc58c87c2c2163823513403269e35b67c Mon Sep 17 00:00:00 2001 From: chann Date: Mon, 24 Aug 2026 05:47:01 +0900 Subject: [PATCH 1/8] fix(site): unblock Pages builds after releases --- site/app/content/document-content.json | 8 ++++---- site/test/build.test.mjs | 5 +++-- 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/site/app/content/document-content.json b/site/app/content/document-content.json index 5edb320..39b22ea 100644 --- a/site/app/content/document-content.json +++ b/site/app/content/document-content.json @@ -5,7 +5,7 @@ "title": "kmsg 사용법", "source": "site/content/ko/usage.md", "sourceLabel": "site/content/ko/usage.md", - "lastModified": "2026-08-19T22:08:18+09:00", + "lastModified": "2026-08-19T22:23:45+09:00", "html": "

이 문서는 설치부터 메시지 읽기·전송, MCP 서버, 문제 해결까지 자주 쓰는 흐름을 한국어로 정리합니다. 모든 옵션과 최신 계약은 영문 전체 레퍼런스에서 확인할 수 있습니다.

\n

설치

\n

요구 사항

\n\n

Homebrew

\n
brew install channprj/tap/kmsg\n
\n

업데이트는 다음 명령으로 진행합니다.

\n
kmsg update\n
\n

kmsg update는 필요하면 Homebrew를 설치하고, 포뮬러를 설치하거나 업그레이드한 뒤, 직접 설치한 바이너리를 Homebrew 명령으로 연결합니다.

\n

직접 다운로드

\n

릴리스에는 Apple Silicon과 Intel Mac을 모두 지원하는 kmsg-macos-universal 바이너리가 포함됩니다.

\n
mkdir -p ~/.local/bin\ncurl -fL \\\n  https://github.com/channprj/kmsg/releases/latest/download/kmsg-macos-universal \\\n  -o ~/.local/bin/kmsg\nchmod +x ~/.local/bin/kmsg\nkmsg --version\n
\n

직접 받은 바이너리도 kmsg update를 실행하면 Homebrew 설치본으로 전환됩니다.

\n

소스에서 빌드

\n
git clone https://github.com/channprj/kmsg.git\ncd kmsg\nswift build -c release\ninstall -m 755 .build/release/kmsg ~/.local/bin/kmsg\n
\n

빠른 시작

\n

먼저 권한과 KakaoTalk 상태를 확인합니다.

\n
kmsg status --verbose\n
\n

채팅 목록을 확인하고 최근 메시지를 읽습니다.

\n
kmsg chats --limit 20\nkmsg read \"채팅방 이름\" --limit 20\n
\n

실제 전송 전에는 반드시 dry run으로 대상과 메시지를 확인할 수 있습니다.

\n
kmsg send \"채팅방 이름\" \"안녕하세요\" --dry-run\n
\n

--dry-run은 KakaoTalk UI에 접근하기 전에 종료하므로 실제 메시지를 전송하지 않습니다.

\n

로그인과 권한

\n

대화형 명령은 KakaoTalk 로그인 상태를 확인합니다. 새 자격 증명을 입력하거나 저장된 자격 증명을 재사용할 수 있습니다.

\n
kmsg auth login\nkmsg auth login --auto\n
\n

비밀번호는 AES-GCM으로 암호화하며 자격 증명과 키를 소유자 전용 권한으로 분리 저장합니다. 이 파일은 공유하거나 이슈에 첨부하지 마세요.

\n
~/.config/kmsg/credentials.json\n~/.config/kmsg/credentials/primary.key\n
\n

잠금 모드

\n

KakaoTalk 잠금 화면이 떠 있으면 명령을 실행하기 전에 잠금을 해제하고, 원래 요청을 그대로 이어서 수행합니다.

\n

비밀번호는 이미 저장된 값에서 가져옵니다. 이전 해제에서 기억해 둔 잠금 비밀번호를 먼저 쓰고, 없으면 kmsg auth login으로 저장한 계정 비밀번호를 씁니다. 둘 다 없을 때만 입력을 요청하며, 성공한 값은 기억해 둡니다.

\n

잠금 해제는 명령마다 한 번만 시도합니다. 비밀번호를 여러 번 틀리면 KakaoTalk이 계정을 로그아웃시키기 때문입니다. 저장된 계정 비밀번호가 거부되면 다시 시도하지 않으므로, kmsg auth login으로 현재 비밀번호를 저장하세요.

\n

터미널이 없는 호출자(kmsg mcp-server, kmsg watch, cron)는 입력을 받을 수 없으므로 저장된 자격 증명에 의존합니다.

\n

명령 레퍼런스

\n
\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n
명령용도
kmsg status손쉬운 사용 권한, KakaoTalk 실행·로그인, 준비 상태 확인
kmsg auth login자격 증명 입력 또는 저장된 자격 증명 재사용
kmsg chats채팅 목록과 로컬 chat_id 조회
kmsg read최근 메시지 읽기
kmsg watch새 메시지를 지속적으로 감시
kmsg send텍스트 메시지 전송
kmsg send-image이미지 전송
kmsg inspectKakaoTalk AX 계층 구조 조사
kmsg cacheself-healing AX 경로 캐시 관리
kmsg mcp-server네이티브 stdio MCP 서버 실행
kmsg updateHomebrew 릴리스로 kmsg 업데이트
\n

안전한 읽기

\n

포그라운드 작업을 방해하면 안 되는 자동화에서는 --background-safe를 사용합니다.

\n
kmsg read \"채팅방 이름\" --json --background-safe\n
\n

이 모드는 KakaoTalk을 실행하거나 활성화하지 않고, 로그인·검색·창 열기·크기 조절·닫기도 수행하지 않습니다. 일치하는 채팅 창이 이미 열려 있지 않으면 실패합니다.

\n

--background-safe가 보이지 않을 때

\n

이 CLI 플래그는 kmsg v1.260618.0부터 제공되며 kmsg read 명령에서만 사용할 수 있습니다. 현재 셸이 실행하는 바이너리를 먼저 확인하세요.

\n
kmsg --version\nkmsg read --help\n
\n

도움말에 플래그가 없다면 Homebrew 설치본을 업데이트한 뒤 다시 확인합니다.

\n
brew update\nbrew upgrade kmsg\nkmsg read --help\n
\n

MCP 클라이언트에서는 CLI 표기인 --background-safe 대신 JSON 인자 background_safe: true를 사용합니다.

\n

텍스트 전송

\n
kmsg send <recipient> <message> [options]\nkmsg send --chat-id <chat-id> <message> [options]\n
\n

주요 옵션은 다음과 같습니다.

\n
\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n
옵션동작
--dry-runUI에 접근하지 않고 대상과 메시지만 출력
--chat-id IDkmsg chats에서 생성한 로컬 ID 사용
--keep-window명령이 연 채팅 창을 유지
--no-cache관련 AX 캐시를 지우고 다시 탐색
--layout MODEpreserve, left, right, split-left, split-right 중 선택
\n

이미지 전송

\n
kmsg send-image \"채팅방 이름\" /absolute/path/image.png --dry-run\n
\n

이미지 경로는 읽을 수 있는 로컬 파일이어야 합니다. dry run으로 확인한 뒤에만 실제 전송을 실행하세요.

\n

JSON 출력과 MCP

\n

--json을 지원하는 명령은 구조화된 결과를 stdout에 기록하고 AX 추적 로그는 stderr에 분리합니다.

\n
kmsg chats --json\nkmsg read \"채팅방 이름\" --json --limit 20\nkmsg watch \"채팅방 이름\" --json\n
\n

네이티브 MCP 서버는 다음 도구를 제공합니다.

\n
\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n
도구용도
kmsg_read채팅 이름으로 최근 메시지 읽기
kmsg_send채팅 이름으로 텍스트 전송
kmsg_send_image채팅 이름으로 로컬 이미지 전송
\n

전송 도구에서 confirm=false 또는 생략된 confirm은 즉시 전송합니다. confirm=true는 전송하지 않고 CONFIRMATION_REQUIRED를 반환합니다.

\n

환경 변수

\n
\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n
변수기본값설명
KMSG_MCP_KMSG_PATH현재 실행 파일MCP 서버가 호출할 kmsg 경로
KMSG_MCP_TIMEOUT_SECONDS30하위 프로세스 제한 시간
KMSG_DEFAULT_LAYOUTpreserve기본 창 배치
KMSG_DEFAULT_BACKGROUND_SAFEfalsekmsg_read의 기본 안전 읽기 모드
KMSG_DEFAULT_DEEP_RECOVERYfalse더 깊은 창 복구 기본값
KMSG_MCP_STARTUP_STATUS_CHECKfalseMCP 시작 시 상태 확인 여부
\n

문제 해결

\n

손쉬운 사용 권한이 없습니다

\n
kmsg status\n
\n

시스템 설정에서 실제로 실행하는 바이너리를 허용한 뒤 다시 시도합니다. Homebrew 경로와 직접 빌드 경로는 서로 다른 바이너리로 인식될 수 있습니다.

\n

채팅을 찾지 못합니다

\n
kmsg chats --verbose --limit 50\nkmsg cache clear\nkmsg read \"정확한 채팅방 이름\" --deep-recovery\n
\n

반복 자동화에서는 kmsg chats로 레지스트리를 갱신하고 chat_id를 사용하는 편이 안정적입니다.

\n

메시지 파싱이나 AX 경로가 불안정합니다

\n
kmsg read \"채팅방 이름\" --debug --trace-ax\nkmsg inspect --depth 5\nkmsg cache stats\n
\n

KakaoTalk 업데이트 후에는 캐시를 지우고 다시 탐색하세요.

\n

개발과 릴리스

\n
swift build\nswift build -c release\npython3 -m pytest -q\n
\n

릴리스 버전은 루트의 VERSION을 기준으로 하며 수동 편집 대신 프로젝트의 릴리스 명령을 사용합니다. 자세한 규칙은 버전 관리 문서를 참고하세요.

\n", "headings": [ { @@ -116,7 +116,7 @@ "title": "kmsg Usage", "source": "USAGE.md", "sourceLabel": "USAGE.md", - "lastModified": "2026-08-19T22:08:18+09:00", + "lastModified": "2026-08-19T22:23:45+09:00", "html": "

This guide covers installation, first-run setup, every public CLI command,\nconfiguration, output formats, MCP operation, troubleshooting, and release\nmaintenance.

\n

Installation

\n

Requirements

\n\n

Homebrew

\n
brew install channprj/tap/kmsg\n
\n

The tap publishes kmsg for the latest release and retains exact-version\nformulae for the ten most recent published releases.

\n

Update an existing installation with:

\n
kmsg update\n
\n

kmsg update installs Homebrew when it is missing, installs or upgrades the\nformula, and links a directly installed binary to the Homebrew-managed command.\nThe plain Homebrew equivalent is brew update followed by brew upgrade kmsg.

\n

Direct download

\n

The release workflow publishes a universal macOS binary named\nkmsg-macos-universal.

\n
mkdir -p ~/.local/bin\ncurl -fL \\\n  https://github.com/channprj/kmsg/releases/latest/download/kmsg-macos-universal \\\n  -o ~/.local/bin/kmsg\nchmod +x ~/.local/bin/kmsg\n
\n

Ensure ~/.local/bin is in PATH, then verify the binary:

\n
kmsg --version\n
\n

Run kmsg update later to move this binary onto the Homebrew-managed release.

\n

Build from source

\n

Source builds require Swift 6.

\n
git clone https://github.com/channprj/kmsg.git\ncd kmsg\nswift build -c release\ninstall -m 755 .build/release/kmsg ~/.local/bin/kmsg\n
\n

Quick start

\n

Run the environment check first:

\n
kmsg status\n
\n

macOS may open the Accessibility settings pane. Enable the binary you actually\nrun, then retry kmsg status.

\n

List chats and read recent messages:

\n
kmsg chats\nkmsg read \"Chat name\" --limit 20\n
\n

Preview a send without changing KakaoTalk:

\n
kmsg send \"Chat name\" \"Hello\" --dry-run\n
\n

After verifying the target and message, omit --dry-run to send.

\n

Authentication

\n

Commands that require interactive KakaoTalk access automatically check the\nlogin state. If saved credentials are unavailable, kmsg prompts in the\nterminal and stores them locally.

\n

Enter fresh credentials explicitly:

\n
kmsg auth login\n
\n

Reuse stored credentials when available:

\n
kmsg auth login --auto\n
\n

Add --trace-ax to print Accessibility traversal and retry diagnostics.

\n

The password is encrypted with AES-GCM. The credential document and encryption\nkey are stored separately with owner-only filesystem permissions:

\n
~/.config/kmsg/credentials.json\n~/.config/kmsg/credentials/primary.key\n
\n

Lock mode

\n

When KakaoTalk's lock screen is showing, commands unlock it before continuing,\nthen carry on with the original request.

\n

The passcode comes from what is already stored: the passcode remembered from an\nearlier unlock, otherwise the account password saved by kmsg auth login. kmsg\nprompts only when neither is available, and remembers whatever worked.

\n

Only one unlock attempt is made per command, because KakaoTalk signs the account\nout after repeated wrong passcodes. A saved account password the lock screen\nrefuses is not tried again — run kmsg auth login to save the current one.

\n

Callers without a terminal — kmsg mcp-server, kmsg watch, cron jobs — cannot\nprompt, so they depend on those stored credentials.

\n

Command reference

\n

Global behavior

\n
kmsg\nkmsg --help\nkmsg --version\nkmsg -v\n
\n

Running kmsg without a subcommand runs status.

\n

status

\n
kmsg status [--verbose]\n
\n

Checks Accessibility permission, launches KakaoTalk if needed, verifies\nauthentication, and prints readiness information.

\n
\n\n\n\n\n\n\n\n\n\n\n
OptionDescription
--verboseInclude window titles, indexes, positions, and sizes.
\n

auth login

\n
kmsg auth login [--auto] [--trace-ax]\n
\n
\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n
OptionDescription
--autoUse stored credentials when available; otherwise prompt and save.
--trace-axPrint AX traversal and retry details to stderr.
\n

Without --auto, the command prompts for fresh credentials and replaces the\nstored values.

\n

chats

\n
kmsg chats [options]\n
\n
\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n
OptionDescription
-v, --verboseInclude the last visible message preview.
-l N, --limit NReturn at most N chats. Default: 20.
--trace-axPrint AX traversal and retry details.
--jsonReturn one structured JSON document.
-k, --keep-windowKeep a chat-list window that the command opened.
\n

Each result includes a local synthetic chat_id. The registry is refreshed by\nrunning kmsg chats and stored at ~/.kmsg/chat-registry.json.

\n

read

\n
kmsg read <chat> [options]\nkmsg read --chat-id <chat-id> [options]\n
\n

A chat name and --chat-id are mutually exclusive.

\n
\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n
OptionDescription
--chat-id IDResolve a synthetic ID produced by kmsg chats.
-l N, --limit NMaximum messages to return. Default: 20.
--debugShow compact raw parsing information in text output.
--trace-axPrint AX traversal and retry details.
-k, --keep-windowKeep a chat window opened by the command.
--background-safeRead only an already exposed matching window without launching, activating, logging in, searching, opening, resizing, or closing UI.
--deep-recoveryEnable deeper window recovery after the fast path fails.
--layout MODEUse preserve, left, right, split-left, or split-right. Default: preserve.
--jsonReturn one structured JSON document.
\n

The name form supports partial matching. For repeated automation, use a\nchat_id after refreshing the registry with kmsg chats.

\n

--background-safe is suitable for automation that must not disturb foreground\nwork, but it fails unless the matching chat window is already exposed.

\n

If --background-safe is missing

\n

The CLI flag is available in kmsg v1.260618.0 and later, and belongs only to\nthe kmsg read command. Check the binary that your shell actually resolves:

\n
kmsg --version\nkmsg read --help\n
\n

If the help output does not list the flag, update the Homebrew installation and\ncheck again:

\n
brew update\nbrew upgrade kmsg\nkmsg read --help\n
\n

MCP clients use the JSON argument background_safe: true instead of the CLI\nspelling --background-safe.

\n

Messages whose author cannot be resolved are represented as \"(me)\".

\n

watch

\n
kmsg watch <chat> [options]\n
\n
\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n
OptionDescription
--poll-interval SPoll interval in seconds. Default: 0.2; values are clamped to 0.2...10.0.
--trace-axPrint AX traversal and recovery details.
-k, --keep-windowKeep a chat window opened by the command.
--deep-recoveryEnable deeper recovery when fast window detection fails.
--jsonEmit each event as a separate pretty-printed JSON object.
--include-systemInclude system rows such as date separators.
\n

At startup, watch stabilizes a transcript baseline for up to two seconds and\ndoes not replay the existing history. Rows that cannot be assigned a safe\ntimestamp may be suppressed during startup.

\n

Stop the process with Ctrl-C or SIGTERM.

\n

send

\n
kmsg send <recipient> <message> [options]\nkmsg send --chat-id <chat-id> <message> [options]\n
\n
\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n
OptionDescription
--chat-id IDResolve a synthetic ID produced by kmsg chats.
--dry-runPrint the target and message without accessing or changing KakaoTalk.
--trace-axPrint AX traversal and retry details.
--no-cacheDisable AX path cache use for this invocation.
--refresh-cacheRemove relevant cached paths before sending.
-k, --keep-windowKeep chat and list windows opened by the command.
--deep-recoveryEnable deeper recovery after the fast path fails.
\n

Without --keep-window, windows opened transiently by the command are closed\nafter the send attempt.

\n

send-image

\n
kmsg send-image <recipient> <image-path> [options]\n
\n
\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n
OptionDescription
--trace-axPrint AX traversal and retry details.
--no-cacheDisable AX path cache use for this invocation.
-k, --keep-windowKeep chat and list windows opened by the command.
--deep-recoveryEnable deeper recovery after the fast path fails.
\n

The path must point to a readable image. kmsg loads the image into the macOS\npasteboard and completes KakaoTalk's image-send UI.

\n

inspect

\n
kmsg inspect [options]\n
\n
\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n
OptionDescription
-d N, --depth NMaximum hierarchy depth. Default: 4.
-w N, --window NInspect a window by zero-based index.
--show-attributesPrint available AX attributes.
--show-pathPrint the hierarchy path.
--show-framePrint element frames.
--show-indexPrint sibling indexes.
--show-flagsPrint enabled, focused, selected, and editable state.
--show-actionsPrint supported AX actions.
--debug-layoutEnable path, frame, index, and state output together.
--row-summaryPrint message-row parsing diagnostics.
--row-range A:BLimit row summaries to an inclusive zero-based range.
\n

--row-range applies only with --row-summary.

\n

cache

\n
kmsg cache\nkmsg cache status\nkmsg cache clear\nkmsg cache export <output-path>\nkmsg cache import <input-path>\nkmsg cache warmup [options]\n
\n

Running kmsg cache without a subcommand runs cache status.

\n
\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n
SubcommandDescription
statusShow path, schema, KakaoTalk fingerprint, entry count, and update time.
clearDelete the local AX path cache.
export PATHExport the current cache document as JSON.
import PATHImport JSON after schema and KakaoTalk fingerprint validation.
warmupDiscover and store common chat-list, search, and input paths.
\n

cache warmup accepts:

\n
\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n
OptionDescription
--recipient NAMEAlso warm the chat-open and message-input path for a recipient.
--trace-axPrint AX traversal and retry details.
-k, --keep-windowKeep a chat window opened by warmup.
\n

mcp-server

\n
kmsg mcp-server\n
\n

Starts the native stdio MCP server. It accepts both Content-Length framing and\none JSON-RPC request per line, and responds using the request's transport style.

\n

The server exposes:

\n
\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n
ToolPurpose
kmsg_readRead by chat or chat_id, with limit, layout, recovery, background-safe, window, and trace controls.
kmsg_sendSend a text message by chat name.
kmsg_send_imageSend a local image by chat name.
\n

For send tools, confirm=false or an omitted confirm sends immediately.\nconfirm=true does not send; it returns CONFIRMATION_REQUIRED so a supervisor\ncan request approval and call again.

\n

See docs/openclaw.md for complete integration examples.

\n

update

\n
kmsg update\n
\n

Moves the current installation onto the Homebrew-managed release:

\n
    \n
  1. Finds brew on PATH, at /opt/homebrew/bin/brew, or at\n/usr/local/bin/brew, and runs Homebrew's official installer when none of\nthem exists.
  2. \n
  3. Runs brew install or brew upgrade for channprj/tap/kmsg.
  4. \n
  5. Verifies the resulting binary by running it with --version.
  6. \n
  7. Replaces a directly installed kmsg with a link to the verified binary, so\nthe path the shell already resolved keeps working. A build product under\n.build is never replaced.
  8. \n
\n

Progress goes to stderr and the final summary to stdout. Homebrew or sudo\nmay ask for confirmation. The command needs no Accessibility permission and\nnever edits shell startup files.

\n

JSON output

\n

Chats

\n
kmsg chats --json\n
\n
{\n  \"count\": 1,\n  \"chats\": [\n    {\n      \"title\": \"홍길동\",\n      \"chat_id\": \"chat_7f42c5e1d9ab\",\n      \"last_message\": \"곧 도착해요\"\n    }\n  ]\n}\n
\n

Read

\n
kmsg read \"홍길동\" --limit 20 --json\n
\n
{\n  \"chat\": \"홍길동\",\n  \"fetched_at\": \"2026-02-26T01:23:45.678Z\",\n  \"count\": 1,\n  \"messages\": [\n    {\n      \"author\": \"홍길동\",\n      \"time_raw\": \"00:27\",\n      \"date\": \"2026-02-26\",\n      \"body\": \"밤이 깊었네\",\n      \"has_image\": false,\n      \"image_count\": 0,\n      \"link_count\": 0,\n      \"has_attachment\": false,\n      \"attachment_count\": 0\n    }\n  ]\n}\n
\n

date and time_raw may be absent when KakaoTalk does not expose the\ncorresponding metadata.

\n

Watch

\n
kmsg watch \"홍길동\" --json\n
\n
{\n  \"chat\": \"홍길동\",\n  \"detected_at\": \"2026-03-25T10:20:30.123Z\",\n  \"event\": \"message\",\n  \"message\": {\n    \"author\": \"홍길동\",\n    \"time_raw\": \"10:20\",\n    \"body\": \"새 메시지\",\n    \"has_image\": false,\n    \"image_count\": 0,\n    \"link_count\": 0,\n    \"has_attachment\": false,\n    \"attachment_count\": 0\n  }\n}\n
\n

With --include-system, event may be \"system\".

\n

JSON payloads are written to stdout. --trace-ax diagnostics remain on\nstderr.

\n

Configuration

\n

Environment variables

\n
\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n
VariableDefaultBehavior
KMSG_AX_TIMEOUT0.25AX messaging timeout in seconds. Accepted range: 0.05...5.0; invalid values fall back to the default.
KMSG_DEFAULT_DEEP_RECOVERYfalseDefault deep_recovery value for MCP tools.
KMSG_DEFAULT_BACKGROUND_SAFEfalseDefault background_safe value for kmsg_read.
KMSG_TRACE_DEFAULTfalseDefault trace_ax value for MCP tools.
KMSG_DEFAULT_READ_LAYOUTpreserveDefault MCP read layout: preserve, left, right, split-left, or split-right.
KMSG_MCP_STARTUP_STATUS_CHECKfalseWhen true, MCP initialization also runs kmsg status; otherwise it checks only the binary version.
KMSG_MCP_VERSIONbuild versionOverrides MCP server version metadata.
\n

Example:

\n
KMSG_DEFAULT_BACKGROUND_SAFE=true \\\nKMSG_DEFAULT_READ_LAYOUT=split-right \\\nkmsg mcp-server\n
\n

Local state

\n
\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n
PathContents
~/.config/kmsg/credentials.jsonEncrypted credential document.
~/.config/kmsg/credentials/primary.keyLocal AES-GCM key.
~/.kmsg/chat-registry.jsonSynthetic chat identities.
~/.kmsg/ax-cache.jsonSelf-healing AX paths.
\n

Do not publish these files or include their contents in issue reports.

\n

Examples

\n

Read without disturbing foreground windows:

\n
kmsg read \"홍길동\" --json --background-safe\n
\n

Read by synthetic ID and arrange the window on the right:

\n
kmsg chats --json\nkmsg read --chat-id \"chat_7f42c5e1d9ab\" \\\n  --layout split-right \\\n  --limit 50 \\\n  --json\n
\n

Watch for new messages:

\n
kmsg watch \"홍길동\" --json --poll-interval 0.5\n
\n

Preview and then send:

\n
kmsg send \"홍길동\" \"안녕하세요\" --dry-run\nkmsg send \"홍길동\" \"안녕하세요\"\n
\n

Warm and inspect the AX cache:

\n
kmsg cache warmup --recipient \"홍길동\" --trace-ax\nkmsg cache status\n
\n

Collect parsing diagnostics:

\n
kmsg inspect --window 0 --depth 20 --debug-layout\nkmsg inspect --window 0 --depth 20 --row-summary --row-range 10:30\nkmsg read \"홍길동\" --limit 20 --trace-ax\n
\n

Troubleshooting

\n

Accessibility permission is not granted

\n
    \n
  1. Run kmsg status.
  2. \n
  3. Open System Settings → Privacy & Security → Accessibility.
  4. \n
  5. Add or enable the exact kmsg binary you run.
  6. \n
  7. Retry kmsg status.
  8. \n
\n

A rebuilt or moved binary may need to be added again.

\n

KakaoTalk cannot be launched or has no usable window

\n

Open KakaoTalk manually, complete login if necessary, and run:

\n
kmsg status --verbose\n
\n

Use --deep-recovery only when the normal fast path cannot recover a window.

\n

A chat name or chat_id is not found

\n

Refresh local identities:

\n
kmsg chats --json\n
\n

A renamed room receives a new local identity. If duplicate rooms have the same\nname, prefer the chat_id values produced from the current chat list.

\n

Background-safe read fails

\n

--background-safe never opens or searches for a chat. Expose the target chat\nwindow first, or omit the flag when foreground automation is acceptable.

\n

Messages cannot be parsed

\n

Collect both hierarchy and trace evidence:

\n
kmsg inspect --window 0 --depth 20 --row-summary\nkmsg read \"Chat name\" --trace-ax\n
\n

Attach the relevant output to an issue after removing private message content.

\n

Cached paths appear stale

\n
kmsg cache status\nkmsg cache clear\n
\n

Verify target and message parsing without touching KakaoTalk:

\n
kmsg send \"Chat name\" \"Test\" --dry-run\n
\n

--dry-run exits before KakaoTalk access. If a real test send is acceptable,\nretry that send with --no-cache to compare the uncached UI path:

\n
kmsg send \"Chat name\" \"Test\" --no-cache\n
\n

MCP initialization fails

\n

Verify the binary directly:

\n
kmsg --version\nkmsg status\nkmsg mcp-server\n
\n

The MCP server skips the interactive status check by default. Set\nKMSG_MCP_STARTUP_STATUS_CHECK=true only when startup should validate full\nKakaoTalk readiness.

\n

Development and releases

\n

Build debug and release variants:

\n
swift build\nswift build -c release\n.build/debug/kmsg --version\n
\n

Useful manual checks:

\n
.build/debug/kmsg status --verbose\n.build/debug/kmsg inspect --depth 5\n.build/debug/kmsg chats --verbose --limit 20\n.build/debug/kmsg send \"Name\" \"Message\" --dry-run\n.build/debug/kmsg read \"Chat\" --limit 50\n
\n

The root VERSION file is managed through Headatever-backed make targets:

\n
make version\nmake release\nmake release-major\nmake release-push\n
\n

Do not hand-edit VERSION. See VERSIONING.md for the\nMAJOR.YYMMDD.PATCH_COUNT rules.

\n

Pushing a matching v* tag triggers the release workflow, which:

\n
    \n
  1. Builds arm64 and x86_64 release binaries.
  2. \n
  3. combines them as kmsg-macos-universal;
  4. \n
  5. verifies the embedded version against the tag;
  6. \n
  7. creates or updates the GitHub Release; and
  8. \n
  9. synchronizes the required Homebrew tap metadata.
  10. \n
\n", "headings": [ { @@ -322,7 +322,7 @@ "title": "kmsgの使い方", "source": "site/content/jp/usage.md", "sourceLabel": "site/content/jp/usage.md", - "lastModified": "2026-08-19T22:08:18+09:00", + "lastModified": "2026-08-19T22:23:45+09:00", "html": "

このページでは、インストールからメッセージの読み取り・送信、MCPサーバー、トラブルシューティングまでの主要な操作を説明します。全オプションは英語版リファレンスで確認できます。

\n

インストール

\n

必要な環境

\n\n

Homebrew

\n
brew install channprj/tap/kmsg\n
\n

アップデート:

\n
kmsg update\n
\n

kmsg updateは必要に応じてHomebrewをインストールし、フォーミュラをインストールまたはアップグレードし、直接インストールしたバイナリをHomebrew管理のコマンドにリンクします。

\n

ソースからビルド

\n
git clone https://github.com/channprj/kmsg.git\ncd kmsg\nswift build -c release\ninstall -m 755 .build/release/kmsg ~/.local/bin/kmsg\n
\n

クイックスタート

\n

最初に権限とKakaoTalkの状態を確認します。

\n
kmsg status --verbose\n
\n

チャット一覧を取得し、最近のメッセージを読みます。

\n
kmsg chats --limit 20\nkmsg read \"チャット名\" --limit 20\n
\n

送信前に対象と内容を確認します。

\n
kmsg send \"チャット名\" \"こんにちは\" --dry-run\n
\n

--dry-runはKakaoTalk UIへアクセスする前に終了するため、メッセージを送信しません。

\n

ログインと権限

\n
kmsg auth login\nkmsg auth login --auto\n
\n

パスワードはAES-GCMで暗号化され、認証情報と鍵は所有者だけが読める別々のファイルに保存されます。これらのファイルを公開しないでください。

\n
~/.config/kmsg/credentials.json\n~/.config/kmsg/credentials/primary.key\n
\n

ロックモード

\n

KakaoTalkのロック画面が表示されている場合は、コマンドの実行前にロックを解除し、元の要求をそのまま続行します。

\n

パスワードは保存済みの値から取得します。前回の解除で記憶したロックパスワードを優先し、無ければkmsg auth loginで保存したアカウントのパスワードを使います。どちらも無い場合のみ入力を求め、成功した値を記憶します。

\n

ロック解除はコマンドごとに一度だけ試行します。パスワードを何度も間違えるとKakaoTalkがアカウントをログアウトさせるためです。保存済みのアカウントパスワードが拒否された場合は再試行しないので、kmsg auth loginで現在のパスワードを保存してください。

\n

ターミナルを持たない呼び出し元(kmsg mcp-server、kmsg watch、cron)は入力を受け取れないため、保存済みの認証情報に依存します。

\n

コマンド一覧

\n
\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n
コマンド用途
kmsg status権限、KakaoTalk、ログイン、準備状態を確認
kmsg auth login認証情報を入力または再利用
kmsg chatsチャット一覧とローカルchat_idを取得
kmsg read最近のメッセージを読み取る
kmsg watch新着メッセージを継続監視
kmsg sendテキストメッセージを送信
kmsg send-image画像を送信
kmsg inspectAX階層を調査
kmsg cacheAXパスキャッシュを管理
kmsg mcp-serverネイティブstdio MCPサーバーを起動
kmsg updateHomebrewのリリースへkmsgを更新
\n

安全な読み取り

\n

前面での作業を妨げたくない場合は--background-safeを使います。

\n
kmsg read \"チャット名\" --json --background-safe\n
\n

このモードはKakaoTalkの起動・アクティブ化・ログイン・検索・ウィンドウ操作を行いません。対象のチャットウィンドウがすでに表示されていなければ失敗します。

\n

--background-safeが表示されない場合

\n

このCLIフラグはkmsg v1.260618.0以降で提供され、kmsg readコマンドでのみ使用できます。現在のシェルが実行するバイナリを確認してください。

\n
kmsg --version\nkmsg read --help\n
\n

ヘルプにフラグが表示されない場合は、Homebrew版を更新して再確認します。

\n
brew update\nbrew upgrade kmsg\nkmsg read --help\n
\n

MCPクライアントでは、CLI表記の--background-safeではなくJSON引数background_safe: trueを使用します。

\n

送信

\n
kmsg send <recipient> <message> [options]\nkmsg send --chat-id <chat-id> <message> [options]\n
\n
\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n
オプション動作
--dry-runUIを操作せず、対象と内容だけを表示
--chat-id IDkmsg chatsが生成したローカルIDを利用
--keep-windowコマンドが開いたチャットを維持
--no-cache関連AXキャッシュを破棄して再探索
--layout MODEウィンドウ配置を指定
\n

画像送信:

\n
kmsg send-image \"チャット名\" /absolute/path/image.png --dry-run\n
\n

JSONとMCP

\n
kmsg chats --json\nkmsg read \"チャット名\" --json\nkmsg watch \"チャット名\" --json\n
\n

構造化結果はstdout、AX診断はstderrに出力されます。

\n

MCPサーバーは次のツールを公開します。

\n
\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n
ツール用途
kmsg_read最近のメッセージを読み取る
kmsg_sendテキストを送信
kmsg_send_imageローカル画像を送信
\n

送信ツールでは、confirm=trueは送信せずCONFIRMATION_REQUIREDを返します。confirm=falseまたは省略時は即時送信です。

\n

主な環境変数

\n
\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n
変数既定値説明
KMSG_MCP_KMSG_PATH現在の実行ファイルMCPサーバーが呼び出すパス
KMSG_MCP_TIMEOUT_SECONDS30サブプロセスの制限時間
KMSG_DEFAULT_LAYOUTpreserve既定のウィンドウ配置
KMSG_DEFAULT_BACKGROUND_SAFEfalse安全な読み取りの既定値
KMSG_DEFAULT_DEEP_RECOVERYfalse深い復旧の既定値
\n

トラブルシューティング

\n

アクセシビリティ権限がない

\n
kmsg status\n
\n

システム設定で、実際に実行しているバイナリを許可します。Homebrew版とローカルビルドは別のバイナリとして扱われる場合があります。

\n

チャットが見つからない

\n
kmsg chats --verbose --limit 50\nkmsg cache clear\nkmsg read \"正確なチャット名\" --deep-recovery\n
\n

繰り返し実行する自動化では、kmsg chatsでレジストリを更新しchat_idを利用すると安定します。

\n

UI構造が変わった

\n
kmsg read \"チャット名\" --debug --trace-ax\nkmsg inspect --depth 5\nkmsg cache stats\n
\n

KakaoTalk更新後はキャッシュを消去して再探索してください。

\n", "headings": [ { @@ -418,7 +418,7 @@ "title": "kmsg使用指南", "source": "site/content/cn/usage.md", "sourceLabel": "site/content/cn/usage.md", - "lastModified": "2026-08-19T22:08:18+09:00", + "lastModified": "2026-08-19T22:23:45+09:00", "html": "

本页介绍安装、消息读取与发送、MCP服务器以及常见故障处理。全部参数与最新契约请参阅英文完整参考。

\n

安装

\n

环境要求

\n\n

Homebrew

\n
brew install channprj/tap/kmsg\n
\n

更新:

\n
kmsg update\n
\n

kmsg update会在需要时安装Homebrew,安装或升级formula,并将直接安装的二进制文件链接到由Homebrew管理的命令。

\n

从源码构建

\n
git clone https://github.com/channprj/kmsg.git\ncd kmsg\nswift build -c release\ninstall -m 755 .build/release/kmsg ~/.local/bin/kmsg\n
\n

快速开始

\n

首先检查权限和KakaoTalk状态。

\n
kmsg status --verbose\n
\n

列出聊天并读取最近消息。

\n
kmsg chats --limit 20\nkmsg read \"聊天名称\" --limit 20\n
\n

发送前先检查目标与内容。

\n
kmsg send \"聊天名称\" \"你好\" --dry-run\n
\n

--dry-run会在访问KakaoTalk UI之前结束,因此不会发送消息。

\n

登录与权限

\n
kmsg auth login\nkmsg auth login --auto\n
\n

密码使用AES-GCM加密,凭据和密钥分别存放在仅所有者可访问的文件中。请勿公开或上传这些文件。

\n
~/.config/kmsg/credentials.json\n~/.config/kmsg/credentials/primary.key\n
\n

锁定模式

\n

如果KakaoTalk显示锁定界面,命令会先解锁再继续,并接着完成原本的请求。

\n

密码取自已保存的值:优先使用上次解锁时记住的锁定密码,否则使用kmsg auth login保存的账号密码。只有两者都不可用时才会提示输入,并记住成功的值。

\n

每条命令只尝试解锁一次,因为多次输错密码会导致KakaoTalk将账号登出。若已保存的账号密码被拒绝,则不会再次尝试,请运行kmsg auth login保存当前密码。

\n

没有终端的调用方(kmsg mcp-server、kmsg watch、cron)无法接收输入,因此依赖这些已保存的凭据。

\n

命令概览

\n
\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n
命令用途
kmsg status检查权限、KakaoTalk、登录和就绪状态
kmsg auth login输入或复用凭据
kmsg chats获取聊天列表与本地chat_id
kmsg read读取最近消息
kmsg watch持续监控新消息
kmsg send发送文本消息
kmsg send-image发送图片
kmsg inspect检查AX层级
kmsg cache管理AX路径缓存
kmsg mcp-server启动原生stdio MCP服务器
kmsg update将kmsg更新到Homebrew发布版本
\n

安全读取

\n

不希望打断前台工作时,可使用--background-safe。

\n
kmsg read \"聊天名称\" --json --background-safe\n
\n

此模式不会启动或激活KakaoTalk,也不会登录、搜索、打开、调整或关闭窗口。如果匹配的聊天窗口尚未显示,读取会失败。

\n

找不到--background-safe时

\n

此CLI标志从kmsg v1.260618.0开始提供,并且仅适用于kmsg read命令。请先确认当前Shell实际执行的二进制文件:

\n
kmsg --version\nkmsg read --help\n
\n

如果帮助中没有此标志,请更新Homebrew安装后再次检查:

\n
brew update\nbrew upgrade kmsg\nkmsg read --help\n
\n

MCP客户端不使用CLI写法--background-safe,而是使用JSON参数background_safe: true。

\n

发送

\n
kmsg send <recipient> <message> [options]\nkmsg send --chat-id <chat-id> <message> [options]\n
\n
\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n
选项行为
--dry-run不操作UI,只显示目标与内容
--chat-id ID使用kmsg chats生成的本地ID
--keep-window保留命令打开的聊天窗口
--no-cache清除相关AX缓存并重新查找
--layout MODE指定窗口布局
\n

发送图片:

\n
kmsg send-image \"聊天名称\" /absolute/path/image.png --dry-run\n
\n

JSON与MCP

\n
kmsg chats --json\nkmsg read \"聊天名称\" --json\nkmsg watch \"聊天名称\" --json\n
\n

结构化结果写入stdout,AX诊断写入stderr。

\n

MCP服务器提供以下工具。

\n
\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n
工具用途
kmsg_read读取最近消息
kmsg_send发送文本
kmsg_send_image发送本地图片
\n

对于发送工具,confirm=true不会发送,而是返回CONFIRMATION_REQUIRED。confirm=false或省略时会立即发送。

\n

主要环境变量

\n
\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n
变量默认值说明
KMSG_MCP_KMSG_PATH当前可执行文件MCP服务器调用的kmsg路径
KMSG_MCP_TIMEOUT_SECONDS30子进程超时时间
KMSG_DEFAULT_LAYOUTpreserve默认窗口布局
KMSG_DEFAULT_BACKGROUND_SAFEfalse安全读取默认值
KMSG_DEFAULT_DEEP_RECOVERYfalse深度恢复默认值
\n

故障排除

\n

没有辅助功能权限

\n
kmsg status\n
\n

请在系统设置中允许实际运行的二进制。Homebrew版本和本地构建可能会被视为不同的程序。

\n

找不到聊天

\n
kmsg chats --verbose --limit 50\nkmsg cache clear\nkmsg read \"准确的聊天名称\" --deep-recovery\n
\n

重复自动化时,建议先用kmsg chats刷新注册表,再使用chat_id。

\n

UI结构发生变化

\n
kmsg read \"聊天名称\" --debug --trace-ax\nkmsg inspect --depth 5\nkmsg cache stats\n
\n

KakaoTalk更新后,可清除缓存并重新发现路径。

\n", "headings": [ { diff --git a/site/test/build.test.mjs b/site/test/build.test.mjs index e90da80..36d1b74 100644 --- a/site/test/build.test.mjs +++ b/site/test/build.test.mjs @@ -91,7 +91,7 @@ test("hashed application assets referenced by HTML exist", async () => { }) test("redirects, 404, and discovery files preserve public contracts", async () => { - const [koRedirect, openclawRedirect, notFound, sitemap, llm, manifest] = + const [koRedirect, openclawRedirect, notFound, sitemap, llm, manifest, version] = await Promise.all([ readOutput("ko/usage/index.html"), readOutput("cn/openclaw/index.html"), @@ -99,6 +99,7 @@ test("redirects, 404, and discovery files preserve public contracts", async () = readOutput("sitemap.xml"), readOutput("llm.txt"), readOutput("site.webmanifest"), + readFile(resolve(siteDir, "../VERSION"), "utf8"), ]) assert.match(koRedirect, /url=https:\/\/channprj\.github\.io\/kmsg\/usage\//) @@ -106,7 +107,7 @@ test("redirects, 404, and discovery files preserve public contracts", async () = assert.match(notFound, //g) ?? []).length, 32) - assert.match(llm, /Current version: 1\.260729\.0/) + assert.ok(llm.includes(`Current version: ${version.trim()}`)) assert.match(llm, /https:\/\/channprj\.github\.io\/kmsg\/cn\/terms\//) assert.equal(JSON.parse(manifest).start_url, "/kmsg/") }) From 565d3a0566d7de0dd181a5b4ef13a8119ea01807 Mon Sep 17 00:00:00 2001 From: chann Date: Mon, 24 Aug 2026 05:49:33 +0900 Subject: [PATCH 2/8] feat(site): serve agent-readable static entry points --- site/app/root.tsx | 2 +- site/scripts/build-static.mjs | 28 ++++++++++------- site/test/build.test.mjs | 58 ++++++++++++++++++++++++++--------- 3 files changed, 61 insertions(+), 27 deletions(-) diff --git a/site/app/root.tsx b/site/app/root.tsx index 1861cff..3629810 100644 --- a/site/app/root.tsx +++ b/site/app/root.tsx @@ -22,7 +22,7 @@ import "./app.css" export const links: Route.LinksFunction = () => [ { rel: "icon", href: "/kmsg/assets/favicon.svg", type: "image/svg+xml" }, { rel: "manifest", href: "/kmsg/site.webmanifest" }, - { rel: "alternate", href: "/kmsg/llm.txt", type: "text/plain", title: "LLM-readable site index" }, + { rel: "describedby", href: "/kmsg/llms.txt", title: "LLM-readable site index" }, ] export function Layout({ children }: { children: ReactNode }) { diff --git a/site/scripts/build-static.mjs b/site/scripts/build-static.mjs index 8d19c75..9560710 100644 --- a/site/scripts/build-static.mjs +++ b/site/scripts/build-static.mjs @@ -19,7 +19,6 @@ if (basename(distDir) !== "dist" || dirname(distDir) !== siteDir) { } const localePrefixes = { ko: "", en: "en/", jp: "jp/", cn: "cn/" } -const hrefLang = { ko: "ko", en: "en", jp: "ja", cn: "zh-CN" } const pageSlugs = { home: "", usage: "usage/", @@ -47,16 +46,18 @@ function lastModified(source) { } } -function redirectDocument(target, language = "en") { - return `

Continue to kmsg

` -} - async function write(relativePath, content) { const path = join(distDir, relativePath) await mkdir(dirname(path), { recursive: true }) await writeFile(path, content) } +async function copyOutput(sourceRelativePath, targetRelativePath) { + const target = join(distDir, targetRelativePath) + await mkdir(dirname(target), { recursive: true }) + await cp(join(distDir, sourceRelativePath), target) +} + await rm(distDir, { recursive: true, force: true }) await mkdir(distDir, { recursive: true }) await cp(prerenderDir, distDir, { recursive: true }) @@ -68,17 +69,15 @@ await Promise.all([ ]) for (const page of canonicalPages.filter(({ locale }) => locale === "ko")) { - const target = `${baseUrl}${page.publicPath}` const legacyPath = `ko/${pageSlugs[page.pageKey]}index.html` - await write(legacyPath, redirectDocument(target, "ko")) + await copyOutput(`${page.publicPath}index.html`, legacyPath) } for (const locale of Object.keys(localePrefixes)) { const prefix = localePrefixes[locale] - const target = `${baseUrl}${prefix}mcp/` - await write(`${prefix}openclaw/index.html`, redirectDocument(target, hrefLang[locale])) + await copyOutput(`${prefix}mcp/index.html`, `${prefix}openclaw/index.html`) if (locale === "ko") { - await write("ko/openclaw/index.html", redirectDocument(target, "ko")) + await copyOutput("mcp/index.html", "ko/openclaw/index.html") } } @@ -90,7 +89,14 @@ const localeLinks = Object.entries(localePrefixes) .join("") await write( "404.html", - `Page not found - kmsg

404

Page not found

The requested page does not exist. Choose a language to return to kmsg.

`, + `Page not found - kmsg

404

Page not found

The requested page does not exist. Choose a language to return to kmsg.

# kmsg 404
+
+The requested path does not exist. Continue with one of these resources:
+
+- [Home](${baseUrl})
+- [Agent index](${baseUrl}llms.txt)
+- [Sitemap](${baseUrl}sitemap.xml)
+- [MCP documentation](${baseUrl}mcp/)
`, ) const sitemapEntries = canonicalPages diff --git a/site/test/build.test.mjs b/site/test/build.test.mjs index 36d1b74..d90d2dc 100644 --- a/site/test/build.test.mjs +++ b/site/test/build.test.mjs @@ -13,22 +13,34 @@ const pageSlugs = ["", "usage/", "architecture/", "mcp/", "skill/", "versioning/ const canonicalFiles = localePrefixes.flatMap((prefix) => pageSlugs.map((slug) => `${prefix}${slug}index.html`), ) +const legacyAliasFiles = [ + "ko/index.html", + "ko/usage/index.html", + "ko/architecture/index.html", + "ko/mcp/index.html", + "ko/openclaw/index.html", + "ko/skill/index.html", + "ko/versioning/index.html", + "ko/privacy/index.html", + "ko/terms/index.html", + "openclaw/index.html", + "en/openclaw/index.html", + "jp/openclaw/index.html", + "cn/openclaw/index.html", +] + +const visibleText = (html) => + html + .replace(/]*>[\s\S]*?<\/script>/gi, " ") + .replace(/]*>[\s\S]*?<\/style>/gi, " ") + .replace(/<[^>]+>/g, " ") + .replace(/&(?:amp|lt|gt|quot|#39|#x27);/gi, " ") + .replace(/\s+/g, " ") + .trim() test("React build emits every canonical page and compatibility artifact", async () => { const compatibilityFiles = [ - "ko/index.html", - "ko/usage/index.html", - "ko/architecture/index.html", - "ko/mcp/index.html", - "ko/openclaw/index.html", - "ko/skill/index.html", - "ko/versioning/index.html", - "ko/privacy/index.html", - "ko/terms/index.html", - "openclaw/index.html", - "en/openclaw/index.html", - "jp/openclaw/index.html", - "cn/openclaw/index.html", + ...legacyAliasFiles, "404.html", "robots.txt", "sitemap.xml", @@ -63,6 +75,9 @@ test("canonical HTML is the localized React and Shadcn artifact", async () => { assert.match(home, /data-slot="dropdown-menu-trigger"/) assert.match(home, /data-footer-wordmark="true"/) assert.doesNotMatch(home, /= 500) + assert.doesNotMatch(home, /http-equiv="refresh"/i) assert.match(home, / { assert.match(chinese, / { + const aliases = await Promise.all(legacyAliasFiles.map(readOutput)) + + for (const html of aliases) { + assert.doesNotMatch(html, /http-equiv="refresh"/i) + assert.match(html, /= 200) + } +}) + test("hashed application assets referenced by HTML exist", async () => { const html = await readOutput("index.html") const references = [...html.matchAll(/(?:href|src)="\/kmsg\/(assets\/[^"]+)"/g)].map( @@ -102,10 +127,13 @@ test("redirects, 404, and discovery files preserve public contracts", async () = readFile(resolve(siteDir, "../VERSION"), "utf8"), ]) - assert.match(koRedirect, /url=https:\/\/channprj\.github\.io\/kmsg\/usage\//) - assert.match(openclawRedirect, /url=https:\/\/channprj\.github\.io\/kmsg\/cn\/mcp\//) + assert.match(koRedirect, /]*data-agent-recovery>/) + assert.match(notFound, /\[Agent index]\(https:\/\/channprj\.github\.io\/kmsg\/llms\.txt\)/) + assert.match(notFound, /\[Sitemap]\(https:\/\/channprj\.github\.io\/kmsg\/sitemap\.xml\)/) assert.equal((sitemap.match(//g) ?? []).length, 32) assert.ok(llm.includes(`Current version: ${version.trim()}`)) assert.match(llm, /https:\/\/channprj\.github\.io\/kmsg\/cn\/terms\//) From ce2ae664a1bbc28c15fce00adc3ab5af56a0def6 Mon Sep 17 00:00:00 2001 From: chann Date: Mon, 24 Aug 2026 05:53:08 +0900 Subject: [PATCH 3/8] feat(site): publish agent discovery resources --- site/app/root.tsx | 6 ++++- site/scripts/build-static.mjs | 46 +++++++++++++++++++++++++++++++++-- site/test/build.test.mjs | 39 +++++++++++++++++++++++++---- 3 files changed, 83 insertions(+), 8 deletions(-) diff --git a/site/app/root.tsx b/site/app/root.tsx index 3629810..50e1e69 100644 --- a/site/app/root.tsx +++ b/site/app/root.tsx @@ -11,7 +11,7 @@ import { import type { Route } from "./+types/root" import { LOCALES } from "./content/locales" -import { routeFromPath } from "./content/routes" +import { publicRouteFor, routeFromPath } from "./content/routes" import { THEME_BOOTSTRAP, readThemeFromDocument, @@ -28,6 +28,9 @@ export const links: Route.LinksFunction = () => [ export function Layout({ children }: { children: ReactNode }) { const route = routeFromPath(useLocation().pathname) const lang = LOCALES[route?.locale ?? "ko"].lang + const markdownHref = route + ? `${publicRouteFor(route.locale, route.pageKey)}index.md` + : "/kmsg/index.md" const theme = typeof document === "undefined" ? "dark" : readThemeFromDocument() @@ -43,6 +46,7 @@ export function Layout({ children }: { children: ReactNode }) {