# Katok

> Search local KakaoTalk keyword, BM25, and EmbeddingGemma vector indexes through the katok CLI, list rooms, export chunks or transcripts, extract attachments, and prepare guarded message sends only after explicit user confirmation.

- Skill: `nomadamas/katok` (Agent Skill)
- Install (CLI): `npx skillmds@latest add nomadamas/katok`
- Raw SKILL.md: https://api.skillmd.com/api/skills/nomadamas/katok/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: Coding & Dev Tools
- Author: nomadamas (https://skillmd.com/u/nomadamas)
- Updated: 2026-09-17
- Page: https://skillmd.com/skills/nomadamas/katok

---


# katok

Use the `katok` CLI as the only execution surface. This skill stays thin: it checks readiness, calls CLI search commands, retrieves explicit chunks when needed, and summarizes results.

## Privacy Rules

- Do not inspect local database internals from the skill.
- Do not handle auth caches or decryption material.
- Do not read KakaoTalk DB files directly. Use `katok sync --source macos --json`.
- Search commands return minimal snippets and chunk ids by default.
- Full chunk content is shown only when the user explicitly asks for an exact result, asks to open a result, or provides a chunk id.

## Commands

```bash
export PATH="$HOME/.cargo/bin:$PATH"       # use when katok is not found after cargo install
katok doctor --json
katok permissions macos                   # opens Full Disk Access settings
katok permissions macos --accessibility   # also opens Accessibility settings
katok doctor --macos-probe --json        # explicit macOS permission/app-data probe
katok sync --source macos --json          # reads live macOS KakaoTalk (needs Full Disk Access)
katok sync --json                         # uses source_adapter from config
katok index --json                        # builds local EmbeddingGemma vector index
katok index --full --json                 # full rebuild instead of incremental
katok wipe-index --yes                    # drops the local index
katok source chats --source macos --json  # lists rooms with their chat ids
katok search keyword "검색어" --json
katok search bm25 "검색어" --json
katok search semantic "지난 회의 보고서" --json
katok chunk get <chunk-id> --json
katok chunk get <chunk-id> --redact --json
katok chunk context <chunk-id> --json
katok chunk parent <chunk-id> --json
katok chunks --chat <chat-id> --json      # every chunk in one room (metadata only)
katok transcript --chat <chat-id> --json                      # exports one room to Markdown
katok transcript --chat <chat-id> --since 2026-07-20 --json   # only messages at or after a time
katok transcript --chat <chat-id> --out <dir> --json          # writes outside the data dir
katok media get --chat <chat-id> --out <dir> --json   # photos, albums, videos, and files
katok media get --chat <chat-id> --kind file --json   # only file attachments (zip, pdf, xlsx, ...)
katok media get --chat <chat-id> --out <dir> --no-cdn --json   # local tiers only
katok media backfill --dry-run --json   # what is still fetchable across every room
katok media backfill --json             # save it before the presigned links expire
katok send --chat <chat-id> --dry-run --json
```

For synthetic QA only:

```bash
katok sync --source fixture tests/fixtures/kakao/replies.jsonl --json
KATOK_EMBEDDER=local-test katok index --json
KATOK_EMBEDDER=mock katok index --json
```

Synthetic runs must be isolated with the `--data-dir <tmp>` flag. There is no `KATOK_DATA_DIR`
environment variable; setting one is silently ignored and the run writes into the real archive.

## Operating Pattern

1. If `katok` is not found after install, run `export PATH="$HOME/.cargo/bin:$PATH"` and retry.
2. If macOS permission setup is needed, run `katok permissions macos` so the user can grant Full Disk Access in System Settings.
3. Run `katok doctor --json` before search to inspect freshness without triggering macOS app-data permission prompts.
4. Inspect the `freshness` section from `doctor --json` before search.
5. Run `katok sync --source macos --json` when `freshness.recommendation.sync_before_search` is `true`, when the user asks for recent messages, or when search freshness matters.
6. Run `katok index --json` before semantic search when `freshness.recommendation.index_before_semantic_search` is `true` or after a sync that should affect vector search.
7. Use `katok search keyword ...`, `katok search bm25 ...`, and `katok search semantic ...` for discovery.
   - A search returns a global top-k across rooms (default 10). For a person or topic that may occur across many rooms, raise `--limit` (for example 100) and group the returned hits by the stable `chat_id`; a room outside the first 10 is not an engine omission.
8. Use `katok chunk get ...` only for explicit retrieval.
9. Run `katok doctor --macos-probe --json` only for setup or permission diagnostics, because it may trigger a macOS "access data from other apps" prompt.

## Message Sending Safety

Sending is an irreversible external side effect. Use the dedicated
[`katok-send`](../katok-send/SKILL.md) skill rules whenever the user explicitly
asks to send or stage a message.

- Never infer send permission from a search, summary, drafting, or setup request.
- Never pass `--accept-use-policy` until the user has explicitly confirmed the
  exact room and final message or image in the current interaction.
- Run `katok send --chat <chat-id> --dry-run --json` first.
- Prefer `--chat` over a room name; names are not unique.
- Do not automate loops, schedules, recipient lists, bulk delivery, or retries.
- Do not assist spam, impersonation, account theft, post-refusal contact,
  stalking, harassment, privacy violations, or protection-measure evasion.
- Read `ACCEPTABLE_USE_POLICY.md` and `DISCLAIMER.md`; Accessibility permission
  is not Kakao approval.
- Image paste UI failures (webp confirm miss → PNG once, etc.): see
  [`katok-send` Troubleshooting](../katok-send/SKILL.md#troubleshooting).

`--source macos` reads the live macOS KakaoTalk SQLCipher database locally in Rust; the terminal must have Full Disk Access to `~/Library/Containers/com.kakao.KakaoTalkMac/`.

Use `katok chunk context <chunk-id> --json` to inspect the immediate previous and next micro chunks in the same chat. Use `katok chunk parent <chunk-id> --json` to jump from a micro chunk to its larger 5-minute same-chat window parent chunk. Semantic search returns parent-window hits with `child_chunk_ids`; use these chunk commands to navigate from broad context back to exact messages.

`katok index` runs the local `embeddinggemma-300m-q4` embedder in-process by default. Do not ask the user to start a Python, Jina, TEI, or local HTTP embedding server. Use `KATOK_EMBEDDER=mock` only for synthetic QA and `KATOK_EMBEDDER=local-test` only when you need deterministic local vector tests without downloading the model.

The index never follows the KakaoTalk database on its own, so a search only ever reflects the last sync. Run `katok sync --source macos --json` before the first query of a session and before any question about recent messages. After sync, doctor compares the archive revision with the committed semantic generation and requests indexing when they differ; semantic search also refuses stale or orphaned generations instead of silently searching them. Freshness also depends on KakaoTalk itself running (`pgrep -x KakaoTalk`), because the source database receives new messages only while the app is up.

Sync is cheap enough to run often because only chats whose messages changed have their chunk tails recomputed. Three runs still pay the full cost: the first sync on an empty archive, the first sync after `chunk_gap_group_seconds` or `chunk_gap_direct_seconds` changes, and the first sync after an upgrade that bumps the chunker version, which includes the first run against an archive written before the version was recorded. Each of these invalidates every stored chunk once, after which sync returns to the incremental path. The payload reports `rebuilt_chats` and a `timings_ms` breakdown (`read_source`, `upsert_messages`, `rebuild_chunks`), so a slow run can be attributed to a stage instead of guessed at.

## Field Notes

- Most group rooms come back as a `chat-<id>` placeholder rather than a real title, because the source database does not always carry the room name. Grepping room names will not find them.
- To locate a room whose title is a placeholder, search for a term used inside it and group the hits by `chat_name`. The `chat_id` holding the most hits is that room. Each hit carries `chunk_id`, `chat_name`, `sender_nickname`, `started_at`, and `snippet`.
- `katok chunks --chat <chat-id>` returns chunk metadata only, never `text`. To export a whole room, collect the chunk ids and run `katok chunk get` per chunk, then assemble in timestamp order.
- `katok chunk get --redact` masks the entire `text`, not only the PII inside it. Use it for a line you must quote, not for a readable export.
- KakaoTalk system feed entries (invite, join, leave) arrive as JSON strings such as `{"inviter":...}` or `{"member":...,"feedType":N}`. Filter them out of anything a person will read.

## Deleted Messages

Sync only upserts, so a message removed upstream stays in the archive unless it
is reconciled away:

```
katok sync --prune-preview --json   # what the source no longer has; deletes nothing
katok sync --prune-deleted --json   # actually remove it
```

Only the time range the source still reports for a chat is considered. KakaoTalk
prunes its own database and outliving that is the reason this archive exists, so
messages older than the source's reach are kept, and a chat the source did not
mention at all is skipped. Preview before deleting — katok cannot undo it.

## Media Attachments

`katok media get --chat <chat-id> --out <dir>` resolves each media message through four tiers in order: the decrypted local cache, a GET of the attachment's own presigned CDN URL verified by SHA-1, the decrypted `.thm` thumbnail, and finally a metadata stub. That CDN GET is the only network access anywhere in the CLI, and `--no-cdn` disables it so resolution stays entirely local.

Photos (type 2), albums (type 27), videos (type 3), and generic file attachments (type 18) are all covered; `--kind photo|video|file` narrows a run and defaults to every kind. Photos and albums read the `.img` cache; videos read the `.vid` cache, whose filename stem uses a `v` key prefix instead of `p`. For those, the output extension is sniffed from the decoded body, so a video lands as `.mp4`.

**A file attachment is one message type covering every extension** — zip, pdf, xlsx, hwp, pptx, csv, mp3 and the rest — so nothing needs adding per format.

Two things make files behave unlike photos:

- **They have no local cache at all.** KakaoTalk writes only `.thm`, `.img`, and `.vid` into the container; a file attachment never touches disk. The CDN is its only tier, `--no-cdn` returns nothing for it, and a stub for a file says `unavailable` rather than `not-cached` because there was never a cache that could have held it.
- **The attachment `name` is authoritative for the extension**, not the body. A zip sniffs as `.bin`, so output is written as `<logId>_<original name>` with the name sanitised so it cannot escape the output directory.

Three practical notes for video:

- The local `.vid` cache only holds videos that were actually played on this Mac. Anything else must come from the CDN tier, so `--no-cdn` will miss it.
- The presigned CDN URL carries an `expires` epoch. Past that, the video is unrecoverable from the archive and only the sender can re-send it.
- KakaoTalk re-encodes video on send. The retrieved file is the compressed copy the room received, not the sender's original.

## Preserving Attachments Before They Expire

A presigned URL lasts roughly 14 days. For a video that means falling back to whatever is cached; for a file, which has no cache, expiry is permanent loss. `katok media backfill` exists for that window:

```
katok media backfill --dry-run --json    # what would be fetched, no requests at all
katok media backfill --json              # every room, live links only, kind=file by default
```

It walks every room holding media, skips anything already saved without a network call — so re-running is idempotent and resumes an interrupted run — and reports per-tier totals. `--dry-run` distinguishes `planned` (would fetch) from `stub`/`cdn-expired` (already gone), which switching the CDN tier off cannot do.

Run it on a schedule if attachments matter. A backfill started after the window has closed cannot recover anything: report the expired count honestly rather than implying the files can be retrieved.

## Related Skills

`katok send` is documented separately in `skills/katok-send/SKILL.md`. It is the
only subcommand that writes, and it does so by driving the running official
KakaoTalk app's UI rather than a Kakao remote private protocol or API.

## Platform

Assume Apple Silicon macOS. Intel macOS is not a supported target for the packaged local EmbeddingGemma path.

