@rikaidev/yomi 0.2.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (119) hide show
  1. package/LICENSE +21 -0
  2. package/NOTICE +81 -0
  3. package/PRIVACY.md +39 -0
  4. package/README.md +659 -0
  5. package/dist/auth/credential-store.js +395 -0
  6. package/dist/auth/keychain.js +256 -0
  7. package/dist/cli/login.js +142 -0
  8. package/dist/line/auth/protocol/e2ee-bootstrap.js +87 -0
  9. package/dist/line/auth/protocol/index.js +3 -0
  10. package/dist/line/auth/protocol/login-metadata.js +159 -0
  11. package/dist/line/auth/protocol/pwless-login-flow.js +83 -0
  12. package/dist/line/auth/pwless/index.js +163 -0
  13. package/dist/line/auth/pwless/steps.js +213 -0
  14. package/dist/line/client/auth-service/client.js +240 -0
  15. package/dist/line/client/e2ee-service/client.js +69 -0
  16. package/dist/line/client/e2ee-service/requests.js +92 -0
  17. package/dist/line/client/e2ee-service/shared-key-log.js +24 -0
  18. package/dist/line/client/e2ee-service/shared-key-payload.js +35 -0
  19. package/dist/line/client/index.js +213 -0
  20. package/dist/line/client/obs-media-client.js +140 -0
  21. package/dist/line/client/parsers.js +69 -0
  22. package/dist/line/client/shop-service/client.js +63 -0
  23. package/dist/line/client/sticker-meta.js +46 -0
  24. package/dist/line/client/sync-service/client.js +317 -0
  25. package/dist/line/client/talk-service/chat-query.js +122 -0
  26. package/dist/line/client/talk-service/command-client.js +242 -0
  27. package/dist/line/client/talk-service/contact-query.js +77 -0
  28. package/dist/line/client/talk-service/message-box-query.js +137 -0
  29. package/dist/line/client/talk-service/message-id-query.js +241 -0
  30. package/dist/line/client/talk-service/query-client.js +219 -0
  31. package/dist/line/client/talk-service/requests.js +383 -0
  32. package/dist/line/client/transport.js +138 -0
  33. package/dist/line/core/auth-session-logout.js +55 -0
  34. package/dist/line/core/auth-session-runtime.js +231 -0
  35. package/dist/line/core/auth-session-service.js +85 -0
  36. package/dist/line/core/chat-runtime-service.js +343 -0
  37. package/dist/line/core/client-runtime.js +17 -0
  38. package/dist/line/core/config.js +67 -0
  39. package/dist/line/core/constants.js +56 -0
  40. package/dist/line/core/contact-query-service.js +20 -0
  41. package/dist/line/core/directory-runtime-service.js +83 -0
  42. package/dist/line/core/e2ee/bootstrap/keychain.js +41 -0
  43. package/dist/line/core/e2ee/crypto/crypto-primitives.js +98 -0
  44. package/dist/line/core/e2ee/index.js +6 -0
  45. package/dist/line/core/e2ee/keys/decrypt.js +110 -0
  46. package/dist/line/core/e2ee/keys/encrypt.js +117 -0
  47. package/dist/line/core/e2ee/keys/group-key.js +260 -0
  48. package/dist/line/core/e2ee/keys/key-manager.js +231 -0
  49. package/dist/line/core/e2ee/keys/key-payload.js +130 -0
  50. package/dist/line/core/e2ee/keys/key-types.js +1 -0
  51. package/dist/line/core/e2ee/keys/message-crypto.js +146 -0
  52. package/dist/line/core/e2ee/keys/message-decrypt.js +390 -0
  53. package/dist/line/core/e2ee/keys/peer-key.js +191 -0
  54. package/dist/line/core/e2ee/recovery/bootstrap-keys.js +161 -0
  55. package/dist/line/core/e2ee/types.js +4 -0
  56. package/dist/line/core/group-query-service.js +20 -0
  57. package/dist/line/core/media-decrypt.js +78 -0
  58. package/dist/line/core/media-encrypt.js +97 -0
  59. package/dist/line/core/mention.js +89 -0
  60. package/dist/line/core/message-command-service.js +454 -0
  61. package/dist/line/core/message-query-service.js +161 -0
  62. package/dist/line/core/service.js +134 -0
  63. package/dist/line/core/session-state/index.js +243 -0
  64. package/dist/line/core/session-state/persist.js +192 -0
  65. package/dist/line/core/store.js +8 -0
  66. package/dist/line/core/thrift/binary/binary-reader.js +151 -0
  67. package/dist/line/core/thrift/binary/binary-writer.js +157 -0
  68. package/dist/line/core/thrift/binary/type-codec-dsl.js +42 -0
  69. package/dist/line/core/thrift/compact/compact-reader.js +180 -0
  70. package/dist/line/core/thrift/compact/compact-writer.js +161 -0
  71. package/dist/line/core/thrift/compact/type-codec-dsl.js +66 -0
  72. package/dist/line/core/thrift/fields/builders.js +124 -0
  73. package/dist/line/core/thrift/index.js +12 -0
  74. package/dist/line/core/thrift/message/decoder.js +55 -0
  75. package/dist/line/core/thrift/message/encoder.js +25 -0
  76. package/dist/line/core/thrift/primitives/varint.js +64 -0
  77. package/dist/line/core/thrift/struct/reader.js +172 -0
  78. package/dist/line/core/thrift/struct/writer.js +189 -0
  79. package/dist/line/core/thrift/types.js +23 -0
  80. package/dist/line/core/video-thumbnail.js +104 -0
  81. package/dist/mcp/handlers/contacts.js +166 -0
  82. package/dist/mcp/handlers/conversations.js +193 -0
  83. package/dist/mcp/handlers/groups.js +123 -0
  84. package/dist/mcp/handlers/index.js +15 -0
  85. package/dist/mcp/handlers/login.js +473 -0
  86. package/dist/mcp/handlers/media.js +69 -0
  87. package/dist/mcp/handlers/message-actions.js +62 -0
  88. package/dist/mcp/handlers/scope.js +120 -0
  89. package/dist/mcp/handlers/search.js +196 -0
  90. package/dist/mcp/handlers/send.js +523 -0
  91. package/dist/mcp/handlers/shared.js +84 -0
  92. package/dist/mcp/handlers/stickers.js +87 -0
  93. package/dist/mcp/media.js +228 -0
  94. package/dist/mcp/names.js +133 -0
  95. package/dist/mcp/phi-guard.js +105 -0
  96. package/dist/mcp/policy.js +34 -0
  97. package/dist/mcp/server.js +301 -0
  98. package/dist/mcp/tools.js +761 -0
  99. package/dist/mcp/ui/capability.js +26 -0
  100. package/dist/mcp/ui/login-app-protocol.js +118 -0
  101. package/dist/mcp/ui/login-app-styles.js +142 -0
  102. package/dist/mcp/ui/login-app-view.js +361 -0
  103. package/dist/mcp/ui/login-app.js +72 -0
  104. package/dist/mcp/ui/login-structured-content.js +1 -0
  105. package/dist/mcp/ui/resource.js +35 -0
  106. package/dist/mcp/ui/tools-with-ui.js +40 -0
  107. package/dist/search/bigram.js +86 -0
  108. package/dist/search/capture.js +140 -0
  109. package/dist/search/collector.js +176 -0
  110. package/dist/search/default-embedder.js +101 -0
  111. package/dist/search/embedder.js +13 -0
  112. package/dist/search/scope.js +142 -0
  113. package/dist/search/sqlite.js +141 -0
  114. package/dist/search/store.js +469 -0
  115. package/dist/util/data-dir.js +55 -0
  116. package/dist/util/log.js +86 -0
  117. package/dist/version.js +7 -0
  118. package/package.json +63 -0
  119. package/run.mjs +71 -0
package/README.md ADDED
@@ -0,0 +1,659 @@
1
+ <!-- rikai-logo -->
2
+ <p align="center">
3
+ <picture>
4
+ <source media="(prefers-color-scheme: dark)" srcset=".github/assets/logo-dark.svg">
5
+ <img src=".github/assets/logo.svg" alt="yomi" width="96" height="96">
6
+ </picture>
7
+ </p>
8
+
9
+ # Yomi (読み)
10
+
11
+ **Read your LINE from an AI agent — and reply, send images, and search across every conversation — without a browser and without LINE's own client.**
12
+
13
+ *The name reads more than one way. **読み** (yomi) — a reading: not just parsing
14
+ your messages, but reading the situation, the way you do. **詠み** (yomi) — to
15
+ recite: it doesn't read in silence, it reads things back to you. And **黄泉** (yomi)
16
+ — the realm of what's buried and out of reach; the very yomi in **黄泉帰り**
17
+ (yomigaeri), "the return from it," the root of the Japanese word for revival. Yomi
18
+ reaches into what is sealed or forgotten and brings it back to light.*
19
+
20
+ Yomi speaks LINE's TCompact-over-HTTPS protocol directly, decrypts Letter-Sealing
21
+ (E2EE) messages and media, and exposes the result to any AI agent through a small
22
+ stdio [MCP](https://modelcontextprotocol.io) server. Point Claude Desktop (or any
23
+ MCP client) at it and the agent can catch up on your LINE the way you do — read,
24
+ reply, send an image, mention someone, and search your whole history locally.
25
+
26
+ No official API, no bot account, no webhook. Yomi logs in as a secondary device
27
+ on your own account.
28
+
29
+ ![license](https://img.shields.io/badge/license-MIT-blue) ![runtime](https://img.shields.io/badge/runtime-node%20%7C%20bun-black) ![protocol](https://img.shields.io/badge/MCP-stdio-green) ![npm](https://img.shields.io/badge/npm-@rikaidev%2Fyomi-red)
30
+
31
+ > **Unofficial.** Yomi is an independent personal project, not affiliated with or
32
+ > endorsed by LINE. Using it may be against LINE's Terms of Service, and running an
33
+ > additional client on your account carries a risk of rate-limiting or suspension.
34
+ > It is intended for reading *your own* account. Use it at your own risk. See
35
+ > [Disclaimer](#disclaimer).
36
+
37
+ ---
38
+
39
+ ## Getting started
40
+
41
+ You need [Node.js](https://nodejs.org) and a LINE account. Yomi runs locally
42
+ through `npx`; you do not need to clone this repository, install Bun, or build
43
+ anything. Run `node --version` first, and install the current LTS if the command
44
+ is missing or the version is unsupported.
45
+
46
+ Yomi's local search index uses Node's built-in `node:sqlite`, so it needs
47
+ **v22.13 or newer — except v23.0–v23.3**, which are newer than v22.13 yet still
48
+ lack that module ([it was unflagged in both 22.13.0 and
49
+ 23.4.0](https://github.com/nodejs/node/pull/55890)). Any current LTS is fine.
50
+ This is `engines.node` in `package.json`; the rest of Yomi runs on older Node,
51
+ but search, scope and capture do not.
52
+
53
+ > **⚠️ Yomi needs a client that runs it on your own machine.**
54
+ > Cloud-only tools (ChatGPT, Claude.ai web) cannot run Yomi. Configure Yomi in
55
+ > Claude Desktop or Claude Code and it works in both chat and Cowork, because
56
+ > Desktop starts Yomi on your machine and Cowork's local sessions load it. Do not
57
+ > ask Cowork to *install* Yomi for you: Cowork's shell runs inside a throwaway VM,
58
+ > not on your machine, so anything it installs there is gone when the session ends.
59
+ > Follow the steps below yourself, in your own terminal.
60
+
61
+ Choose the client you actually use and follow only that section. Claude Code and
62
+ Claude Desktop have separate MCP settings; configuring one does not configure the
63
+ other.
64
+
65
+ <details open>
66
+ <summary><strong>Claude Desktop — one-click install (no terminal, no Node)</strong></summary>
67
+
68
+ The easiest path, and the only one that needs no command line at all. Claude
69
+ Desktop ships its own Node runtime, so nothing else has to be installed.
70
+
71
+ 1. Download the bundle for your machine from the
72
+ [latest release](https://github.com/RikaiDev/yomi/releases/latest):
73
+
74
+ | Machine | File |
75
+ | --- | --- |
76
+ | Windows (Intel/AMD) | `yomi-win32-x64.mcpb` |
77
+ | Mac (Apple Silicon) | `yomi-darwin-arm64.mcpb` |
78
+ | Linux (x64) | `yomi-linux-x64.mcpb` |
79
+
80
+ 2. In Claude Desktop, open **Settings → Extensions**, and drag the downloaded
81
+ file onto that page (or just double-click the file).
82
+
83
+ 3. Review what it asks for, click **Install**.
84
+
85
+ 4. Start a conversation and say *"log in to LINE"*. Yomi shows a form, you enter
86
+ your phone number, and you confirm on your phone. No terminal at any point.
87
+
88
+ This skips the config file entirely, which also sidesteps the Windows MSIX bug
89
+ described below.
90
+
91
+ > **Note for Cowork users:** do not ask Cowork to install Yomi for you. Cowork's
92
+ > shell runs inside a throwaway VM — not on your machine — and it will not type
93
+ > into your real terminal. Install the bundle yourself with the three clicks
94
+ > above; once installed, Cowork's local sessions can use Yomi like any other tool.
95
+
96
+ </details>
97
+
98
+ <details>
99
+ <summary><strong>Claude Desktop — manual config (npx)</strong></summary>
100
+
101
+ 1. Find the full path to `npx`:
102
+
103
+ ```text
104
+ macOS / Linux: which npx
105
+ Windows: where npx
106
+ ```
107
+
108
+ 2. Open the Claude Desktop config file:
109
+
110
+ ```text
111
+ macOS: ~/Library/Application Support/Claude/claude_desktop_config.json
112
+ Windows: %APPDATA%\Claude\claude_desktop_config.json
113
+ ```
114
+
115
+ > **⚠️ Windows: the documented path may not be the one Claude actually reads.**
116
+ > Claude Desktop ships as an MSIX package, whose filesystem is virtualized. The
117
+ > app reads its config from
118
+ >
119
+ > ```text
120
+ > %LOCALAPPDATA%\Packages\Claude_pzs8sxrjxfjjc\LocalCache\Roaming\Claude\claude_desktop_config.json
121
+ > ```
122
+ >
123
+ > while **Settings → Developer → Edit Config** opens the *un*virtualized
124
+ > `%APPDATA%\Claude\` file. They are two different files that never sync, so a
125
+ > correct Yomi config written to the documented path is **silently ignored** — no
126
+ > error, no log, Yomi simply never appears. This is
127
+ > [claude-code#26073](https://github.com/anthropics/claude-code/issues/26073),
128
+ > still open. If Yomi does not show up after a restart, write the same config to
129
+ > the `LocalCache` path above as well. (Not applicable to Claude Code, or to
130
+ > Desktop installed outside MSIX.)
131
+
132
+ 3. Add Yomi under `mcpServers`, replacing the example `command` with the full
133
+ path printed in step 1. On Windows, JSON requires each `\` in the path to be
134
+ written as `\\`:
135
+
136
+ ```json
137
+ {
138
+ "mcpServers": {
139
+ "yomi": {
140
+ "command": "/opt/homebrew/bin/npx",
141
+ "args": ["@rikaidev/yomi"]
142
+ }
143
+ }
144
+ }
145
+ ```
146
+
147
+ For example, a Windows path may look like
148
+ `"C:\\Program Files\\nodejs\\npx.cmd"`. Use the path reported on your own
149
+ machine rather than copying either example blindly.
150
+
151
+ > **⚠️ The `npx` you name here does not decide which Node runs Yomi.**
152
+ > On macOS and Linux `npx` is a script beginning with `#!/usr/bin/env node`,
153
+ > so it runs whichever `node` comes first on **Claude Desktop's** `PATH` —
154
+ > not the Node installed beside the `npx` you just pointed at. Desktop
155
+ > inherits its `PATH` from the desktop session, which is often not your
156
+ > terminal's, and version managers (nvm, fnm, asdf, volta, …) put their own
157
+ > `node` ahead of everything else. So `which npx` can report a perfectly
158
+ > modern install while Yomi still starts on an old Node.
159
+ >
160
+ > The symptom is specific: logging in, reading and sending all work, but
161
+ > search, scope and capture fail, and the log says
162
+ > `No such built-in module: node:sqlite`. Yomi prints the runtime it actually
163
+ > got in that error — read it rather than assuming.
164
+ >
165
+ > The fix is to give the server a `PATH` whose first entry holds a supported
166
+ > `node`:
167
+ >
168
+ > ```json
169
+ > "yomi": {
170
+ > "command": "<the npx path from step 1>",
171
+ > "args": ["-y", "@rikaidev/yomi"],
172
+ > "env": { "PATH": "<dir holding a supported node><sep><a base PATH>" }
173
+ > }
174
+ > ```
175
+ >
176
+ > Separator: `:` on macOS/Linux, `;` on Windows. Keep a usable base after it
177
+ > (`/usr/local/bin:/usr/bin:/bin` or your platform's equivalent) — this
178
+ > replaces the server's `PATH` rather than prepending to it, and `npx` needs
179
+ > the ordinary tools.
180
+ >
181
+ > Naming an absolute `node` in `command` does **not** work, however plausible
182
+ > it looks: `npx` spawns the package's own bin as a child process and resolves
183
+ > `node` from `PATH` again for it, so `command` only picks who runs `npx`, not
184
+ > who runs Yomi. Setting `PATH` is what actually reaches the server.
185
+
186
+ **Configuring this with an AI agent?** MCP configs are mostly written by agents
187
+ now rather than by hand, so the steps above are stated for a human reading their
188
+ own machine. An agent should resolve the values instead of emitting placeholders,
189
+ and this setup has one trap worth stating outright: naming `npx` in `command`
190
+ does **not** pin the runtime. Procedure:
191
+
192
+ 1. Find a Node that actually has the module Yomi's index needs — do not infer it
193
+ from a version number:
194
+
195
+ ```bash
196
+ node -e 'require("node:sqlite")' && command -v node # POSIX
197
+ node -e "require('node:sqlite')" && where node # Windows
198
+ ```
199
+
200
+ If it throws, that Node is unusable for search/scope/capture. Try the other
201
+ installs (`which -a node`, `nvm ls`, `fnm list`, …) until one passes; only
202
+ then continue.
203
+
204
+ 2. Write that Node's **directory** into `env.PATH`, per the JSON above, and keep
205
+ a base `PATH` after it. Resolve the directory on the target machine
206
+ (`dirname` of the path from step 1); do not copy an example out of this
207
+ README — they are Apple Silicon Homebrew paths and wrong nearly everywhere.
208
+
209
+ Do not instead put an absolute `node` in `command`. It reads like the
210
+ stronger fix and is not one: `npx` spawns the package's bin as a child and
211
+ re-resolves `node` from `PATH` for it, so `command` only decides who runs
212
+ `npx`. This was measured, not assumed — with a Node 20 first on `PATH`,
213
+ `command`-pinning still gave the server Node 20.
214
+
215
+ `command -v` may also hand back a wrapper rather than a real binary — heap
216
+ shims, local-first `npx` wrappers and version-manager stubs are all common,
217
+ and some behave differently depending on the client's working directory. Do
218
+ not try to reason your way to the "real" one. Step 3 settles it.
219
+
220
+ 3. Verify with a tool that touches the index. **`yomi version` is not a
221
+ verification** — it prints a string without opening SQLite, so it succeeds on
222
+ a Node that cannot run search. Call `get_scope_policy`: it needs SQLite and no
223
+ LINE login. If it returns the policy text, the runtime is right. If it fails,
224
+ the error names the Node that actually ran and what it needed.
225
+
226
+ 4. Fully quit and reopen Claude Desktop. Confirm Yomi is loaded under **Settings →
227
+ Developer** before anything else: if Yomi is not listed there, Claude never read
228
+ your config — on Windows, see the MSIX warning in step 2. Once it is listed, the
229
+ tools are available in chat and in Cowork's local sessions alike.
230
+
231
+ You do **not** need to install Claude Code for this setup.
232
+
233
+ </details>
234
+
235
+ <details>
236
+ <summary><strong>Claude Code</strong></summary>
237
+
238
+ ```bash
239
+ claude mcp add yomi -- npx @rikaidev/yomi
240
+ ```
241
+
242
+ Start a new `claude` session. The Yomi tools should appear automatically. This
243
+ command configures Claude Code only; it does not configure Claude Desktop.
244
+
245
+ This form leaves the runtime to `PATH`, which is fine as long as the `node` your
246
+ `claude` session resolves is a supported one — it is the same `PATH` you can see,
247
+ unlike Claude Desktop's. Check with `node -e 'require("node:sqlite")'`. If it
248
+ throws, or you would rather not depend on `PATH` at all, name the Node
249
+ explicitly:
250
+
251
+ ```bash
252
+ claude mcp add yomi -e PATH="$(dirname "$(command -v node)"):$PATH" -- npx -y @rikaidev/yomi
253
+ ```
254
+
255
+ Either way, verify with `get_scope_policy` rather than `yomi version`: the
256
+ version command prints a string without opening SQLite, so it passes on a Node
257
+ that cannot run search.
258
+
259
+ > **Running Claude Code inside a clone of this repo?** Spell the spec
260
+ > `@rikaidev/yomi@latest`. Without a tag, `npx` looks for a local bin first, and
261
+ > in this repo `package.json` declares `"bin": {"yomi": ...}` while nothing links
262
+ > it into `node_modules/.bin` — so `npx` skips the install and dies with
263
+ > `sh: yomi: command not found`. An explicit tag makes it fetch the published
264
+ > package. Only affects working copies of Yomi itself; everywhere else the
265
+ > untagged form is fine.
266
+
267
+ </details>
268
+
269
+ <details>
270
+ <summary><strong>Other MCP clients</strong></summary>
271
+
272
+ This standard config works in most MCP clients:
273
+
274
+ ```json
275
+ {
276
+ "mcpServers": {
277
+ "yomi": {
278
+ "command": "npx",
279
+ "args": ["@rikaidev/yomi"]
280
+ }
281
+ }
282
+ }
283
+ ```
284
+
285
+ <details>
286
+ <summary>Cursor</summary>
287
+
288
+ `Cursor Settings` → `MCP` → `Add new MCP Server` → name it `yomi`, command type, value: `npx @rikaidev/yomi`
289
+
290
+ Or add to `.cursor/mcp.json` in your project root.
291
+ </details>
292
+
293
+ <details>
294
+ <summary>VS Code (GitHub Copilot)</summary>
295
+
296
+ Add to `.vscode/mcp.json`:
297
+
298
+ ```json
299
+ {
300
+ "servers": {
301
+ "yomi": {
302
+ "command": "npx",
303
+ "args": ["@rikaidev/yomi"]
304
+ }
305
+ }
306
+ }
307
+ ```
308
+ </details>
309
+
310
+ <details>
311
+ <summary>opencode</summary>
312
+
313
+ Add to `~/.config/opencode/opencode.json`:
314
+
315
+ ```json
316
+ {
317
+ "mcp": {
318
+ "yomi": {
319
+ "type": "local",
320
+ "command": ["npx", "@rikaidev/yomi"],
321
+ "enabled": true
322
+ }
323
+ }
324
+ }
325
+ ```
326
+ </details>
327
+
328
+ <details>
329
+ <summary>Codex</summary>
330
+
331
+ ```bash
332
+ codex mcp add yomi npx @rikaidev/yomi
333
+ ```
334
+
335
+ Or add to `~/.codex/config.toml`:
336
+
337
+ ```toml
338
+ [mcp_servers.yomi]
339
+ command = "npx"
340
+ args = ["@rikaidev/yomi"]
341
+ ```
342
+ </details>
343
+
344
+ <details>
345
+ <summary>Cline</summary>
346
+
347
+ Add to `cline_mcp_settings.json`:
348
+
349
+ ```json
350
+ {
351
+ "mcpServers": {
352
+ "yomi": {
353
+ "type": "stdio",
354
+ "command": "npx",
355
+ "args": ["-y", "@rikaidev/yomi"]
356
+ }
357
+ }
358
+ }
359
+ ```
360
+ </details>
361
+
362
+ <details>
363
+ <summary>Windsurf</summary>
364
+
365
+ Add to `~/.codeium/windsurf/mcp_config.json` — same JSON as standard config.
366
+ </details>
367
+
368
+ <details>
369
+ <summary>Amp</summary>
370
+
371
+ ```bash
372
+ amp mcp add yomi -- npx @rikaidev/yomi
373
+ ```
374
+ </details>
375
+
376
+ <details>
377
+ <summary>Goose</summary>
378
+
379
+ `Advanced settings` → `Extensions` → `Add custom extension` → name `yomi`, type `STDIO`, command: `npx @rikaidev/yomi`
380
+ </details>
381
+
382
+ <details>
383
+ <summary>Grok</summary>
384
+
385
+ ```bash
386
+ grok mcp add yomi -- npx @rikaidev/yomi
387
+ ```
388
+ </details>
389
+
390
+ </details>
391
+
392
+ ### First login
393
+
394
+ Once connected, tell the agent:
395
+
396
+ > *"Log into LINE — my number is +8869XXXXXXXX."*
397
+
398
+ Approve the device on your phone (see [Logging in](#logging-in)), and:
399
+
400
+ > *"Summarize my unread LINE and tell me who's waiting on a reply."*
401
+
402
+ ---
403
+
404
+ ## What you can do
405
+
406
+ Yomi exposes **19 tools** over MCP. All return JSON. "Honest error" below means an
407
+ explicit failure naming the problem (e.g. `missing_decrypt_material`) — Yomi never
408
+ fabricates a fallback, a placeholder, or a fake success.
409
+
410
+ ### Reading
411
+
412
+ | Tool | Does |
413
+ | --- | --- |
414
+ | `get_unread_digest` | One-shot: every conversation with unread messages, each with its latest messages, E2EE-decrypted, sender names resolved. Built for "summarize my unread and suggest next steps." Read-only — never marks anything read. |
415
+ | `list_conversations` | Chats/groups/rooms with unread counts and a decrypted last-message preview. Newest-active first. |
416
+ | `get_chat_messages` | One conversation, decrypted. Paginate deeper with a `before` cursor. Each message carries any raw `MENTION` metadata so you can see who was @-mentioned (a literal `@name` in the text is *not* a mention). |
417
+ | `get_message_media` / `get_message_image` | Any decrypted attachment (image/video/audio/file). Honest error on non-media. |
418
+ | `find_contact` / `list_contacts` | Friend-list lookup by name substring, or the full list. Raw LINE data — no fuzzy scoring, no affinity ranking. |
419
+ | `get_group_members` | Members of a persistent group. Ad-hoc rooms without a group record fail honestly rather than returning a fake empty list. |
420
+
421
+ ### Writing (these really send — not drafts)
422
+
423
+ | Tool | Does |
424
+ | --- | --- |
425
+ | `send_message` | Sends an E2EE text message now (pairwise key for 1:1, group key for groups/rooms). Optional `mentions` attach real @-mentions that LINE highlights and notifies; omit them and a literal `@name` is just text that notifies no one. One send per call, no retries. |
426
+ | `send_image` | Encrypts, uploads to LINE OBS, and sends an E2EE image now. Works for **1:1, groups, and rooms**. One send per call. Honest failure if the key can't be resolved or the upload is rejected. |
427
+ | `mark_read` | Sends a read receipt the other party can see. Explicit only — reading messages and background capture never mark anything read. |
428
+
429
+ ### Search (local, cross-conversation — LINE has no such primitive)
430
+
431
+ | Tool | Does |
432
+ | --- | --- |
433
+ | `search_messages` | Hybrid search across all indexed conversations. Auto-collects on first use. Returns `mode` (`hybrid`/`semantic`/`keyword`) so nothing is hidden. |
434
+ | `collect_messages` | Explicit bulk-index into the local DB + embed for semantic search. The only tool allowed to bulk-fetch; runs once per call, never on a timer. |
435
+
436
+ ### Scope & privacy (all work offline, no LINE session needed)
437
+
438
+ | Tool | Does |
439
+ | --- | --- |
440
+ | `exclude_chats` | Denylist a conversation **and purge** its already-indexed data. Not just future capture — a denylist that left old data in the index would be fake privacy. |
441
+ | `include_chats` | Re-allow a conversation (does not restore purged data). |
442
+ | `list_excluded_chats` / `get_scope_policy` | Show the denylist / the full privacy policy (read from [`PRIVACY.md`](PRIVACY.md)). |
443
+
444
+ ### Session
445
+
446
+ | Tool | Does |
447
+ | --- | --- |
448
+ | `login` / `login_complete` | Passwordless secondary-device login. See below. `login` is the only tool callable without an existing session. |
449
+
450
+ ---
451
+
452
+ ## Logging in
453
+
454
+ **Prerequisite:** on your primary phone, enable **設定 › 我的帳號 › 允許自其他裝置登入**
455
+ (*Settings › Account › Allow login on other devices*). Without it LINE never offers
456
+ this device a sign-in prompt — this is the single most common reason a first login
457
+ appears to hang.
458
+
459
+ You only need to give the agent your phone number in E.164 form — it supplies the
460
+ region itself (e.g. `TW` for a `+886` number) when it calls the tool.
461
+
462
+ Yomi drives LINE's **passwordless (secondary-device)** flow. How the login surfaces
463
+ depends on your MCP client:
464
+
465
+ - **Clients with the MCP `elicitation` capability** — `login` prompts you for your
466
+ phone/region, shows the PIN in a dialog, and blocks to completion. One call, no
467
+ PIN relayed through the model. Your phone number never enters the transcript.
468
+
469
+ - **Clients without it (e.g. Claude Desktop today)** — two calls. `login` (with your
470
+ phone + region) returns the PIN in its result; enter it in LINE on your primary
471
+ phone and approve the device; then call `login_complete`, which blocks until the
472
+ phone confirms. The agent should call `login_complete` immediately — it does the
473
+ waiting, so there is nothing for you to report back.
474
+
475
+ - **From a terminal** — `npx @rikaidev/yomi login` runs the whole flow on stdout, PIN and
476
+ all. Always available as a fallback.
477
+
478
+ **The one deadline that matters is LINE's:** you have about **3 minutes** from when
479
+ the PIN is shown to enter it and approve the device. Yomi's own client keeps
480
+ listening for many minutes beyond that, so a slow phone is never the failure — only
481
+ LINE's 3-minute code lifetime is.
482
+
483
+ Once you've logged in, the session — including a login *certificate* — is persisted
484
+ (see [below](#sessions-and-credentials)), and future logins **skip the PIN
485
+ entirely**.
486
+
487
+ > There is also an experimental [MCP Apps](https://modelcontextprotocol.io) UI (a
488
+ > `ui://yomi/login` card) for clients that render interactive views. It is
489
+ > spec-correct and renders under the MCP Inspector, but some hosts fetch the resource
490
+ > without completing the view handshake, so it is **display-only and never on the
491
+ > critical path** — the flows above always work regardless.
492
+
493
+ ### Sessions and credentials
494
+
495
+ Yomi owns its own login. On startup it calls `resumeSession()` once, reading the
496
+ LINE session from the macOS Keychain (service `com.yomi.credentials`, account
497
+ `line`) and silently refreshing the token if needed.
498
+
499
+ - **First-party credentials.** The passwordless login persists the auth token,
500
+ refresh token, certificate, MID, and the E2EE keypair itself — then reads them
501
+ back to verify the write actually landed. A login that can't be persisted fails
502
+ loudly at login, not silently at the next restart.
503
+ - **Backward compatibility.** If no session is found under `com.yomi.credentials`,
504
+ Yomi reads the legacy `com.inboxd.credentials` entry once, migrates it forward,
505
+ and never deletes it. An existing session keeps working with no re-login.
506
+ - **Platform note.** On macOS the session lives in the login Keychain. On **Linux
507
+ and Windows** Yomi currently falls back to a local JSON file — functional, but
508
+ less protected than an OS secret store, and less exercised than the macOS path.
509
+ Native secure-storage backends (libsecret / DPAPI) are planned; until then, treat
510
+ a non-macOS install accordingly.
511
+
512
+ Every tool except `login` and the offline scope/search tools returns an honest error
513
+ when there is no session. Yomi is otherwise a pure query server — it never polls,
514
+ never backfills in the background; each tool call makes exactly the LINE requests it
515
+ needs, and `collect_messages` is the only path that fetches across many chats at once.
516
+
517
+ ---
518
+
519
+ ## Search
520
+
521
+ LINE has no cross-conversation search; Yomi builds one locally. The index is a
522
+ gitignored SQLite database (`data/search-index.db`) in the repo — **nothing leaves
523
+ your machine**.
524
+
525
+ Search is **hybrid** by design. It always runs FTS5 keyword search (bm25 over
526
+ bigram-preprocessed text, so CJK substrings with no word boundaries are covered) and,
527
+ when embeddings exist, semantic search, then fuses the two ranked lists by
528
+ **Reciprocal Rank Fusion**. Pure semantic search silently drops exact matches that
529
+ live in un-embedded messages; pure keyword misses paraphrases; fusing both surfaces
530
+ an exact term and a meaning-match together. The response's `mode` field always
531
+ reports which methods contributed.
532
+
533
+ Semantic ranking uses **`Xenova/bge-small-zh-v1.5`** (BAAI general embedding, small,
534
+ Chinese-primary but multilingual) via **transformers.js**. Embedding **inference runs
535
+ fully in-process on CPU — your message text is never sent anywhere.**
536
+
537
+ The one caveat is a **one-time model download**: on the first `collect_messages` or
538
+ `search_messages`, transformers.js fetches the model (~90 MB) from **`huggingface.co`**
539
+ and caches it locally; every run afterwards is fully offline. That first fetch is an
540
+ outbound HTTPS request to HuggingFace for the model weights — it carries your IP and
541
+ which model is being downloaded, **but no message content and no LINE data**. If you
542
+ need Yomi fully air-gapped, pre-populate the transformers.js cache (or point it at a
543
+ local model directory) before the first search so no network call is ever made.
544
+
545
+ ---
546
+
547
+ ## The secondary-device reality (read before trusting "I can't get old messages")
548
+
549
+ Yomi runs as a **secondary device** on your account. That shapes what it can see:
550
+
551
+ - **Group chats *without* Letter Sealing are plaintext at the LINE server** — full
552
+ history and media, no restriction.
553
+ - **Group chats *with* Letter Sealing are E2EE, and there the epoch matters.** Such a
554
+ group is encrypted with a shared *group key* that rotates (on membership changes, or
555
+ when a client provisions a new one). LINE only ever hands a device the **current**
556
+ group key — there is no API to fetch a superseded one. So a secondary device decrypts
557
+ messages from the epoch whose key it holds onward; messages sent under an **older
558
+ epoch — before Yomi obtained the current key — can come back undecryptable**, even in
559
+ a group whose earlier history it *can* read. This is inherent to Letter Sealing's
560
+ per-epoch group keys, not a bug here. It can also cut sideways: the epoch your phone
561
+ holds and the epoch Yomi holds need not be the same, so the **two devices can each
562
+ read a different slice** of the very same group.
563
+ - **1:1 media** uses the account-level E2EE keychain, which a secondary device fully
564
+ possesses (LINE syncs it during pairing) — Yomi **can** decrypt 1:1 images/files it
565
+ can see.
566
+ - **1:1 history *backfill* is the one real limitation.** LINE does not hand a
567
+ secondary device the *past* 1:1 message history the way it does for groups. Messages
568
+ received while Yomi is connected decrypt normally; deep-scrolling into 1:1 history
569
+ that predates pairing may come back empty. This is a LINE server-side restriction,
570
+ not a bug here.
571
+
572
+ When decryption genuinely fails, Yomi returns an explicit `missing_decrypt_material`
573
+ error — never a fake card or placeholder. Silence is honest; a fabricated result is not.
574
+
575
+ ---
576
+
577
+ ## Development
578
+
579
+ For contributors working on the Yomi source code (requires [bun](https://bun.sh)
580
+ or Node.js 24+ — `.nvmrc` pins the same 24 that CI installs, so `nvm use` in this
581
+ directory picks it up; note nvm does not read `engines`, which is why the two are
582
+ stated separately and guarded by a test):
583
+
584
+ ```bash
585
+ bun install # install dependencies (or npm install)
586
+ bun run.mjs # run the stdio MCP server
587
+ bun run.mjs login # run the login flow in a terminal
588
+ npm run build # tsc --noEmit — type-check only (Yomi ships & runs from src/)
589
+ npm test # bun test
590
+ ```
591
+
592
+ The build only type-checks and compiles; this repo does not talk to live LINE
593
+ servers as part of its own build. Connecting an MCP client and calling `login` is
594
+ what actually starts a session.
595
+
596
+ ```
597
+ src/
598
+ line/ LINE protocol core: TCompact/Thrift codec, E2EE (Letter-Sealing,
599
+ group keys, media), Talk/Auth/Sync service clients, session state,
600
+ passwordless login flow.
601
+ auth/ Credential store (macOS Keychain, JSON-file fallback off-darwin).
602
+ search/ Local cross-conversation index (SQLite + FTS5) and the offline
603
+ embedding pipeline (transformers.js).
604
+ mcp/ The stdio server: tool schemas, handlers, the privacy-policy loader,
605
+ and the experimental MCP Apps login view (mcp/ui/).
606
+ util/ [TAG]-prefixed logger (stderr only — stdout is the MCP JSON-RPC stream).
607
+ ```
608
+
609
+ Everything Yomi writes for humans goes to **stderr**; **stdout is reserved for the
610
+ MCP JSON-RPC stream**. A stray `console.log` corrupts the protocol — don't add one on
611
+ any path the server can reach.
612
+
613
+ ---
614
+
615
+ ## Privacy
616
+
617
+ By default Yomi indexes **all** your conversations into the local, on-device search
618
+ index so an agent can search across them — a capture-all default, opt-out per chat.
619
+ Nothing is ever uploaded; the only data that leaves your device is whatever the agent
620
+ itself surfaces in its replies. (The one non-LINE network call Yomi itself makes is the
621
+ **one-time embedding-model download from HuggingFace** on the first search — model
622
+ weights in, no message content out; see [Search](#search).) [`PRIVACY.md`](PRIVACY.md)
623
+ is the canonical policy,
624
+ and Yomi surfaces that same text to the agent on connect (and via `get_scope_policy`)
625
+ so it can disclose the default before any bulk read.
626
+
627
+ ---
628
+
629
+ ## Disclaimer
630
+
631
+ Yomi is an independent, unofficial personal project, built for learning and for
632
+ accessing one's own LINE account. It is **not affiliated with, authorized, or
633
+ endorsed by LINE Corporation**. "LINE" is a trademark of its respective owner.
634
+
635
+ Running Yomi may violate LINE's Terms of Service, and operating an additional client
636
+ on an account can result in rate-limiting or suspension of that account. Yomi is
637
+ intended for accessing **your own** account and data. You are solely responsible for
638
+ how you use it. The software is provided "as is", without warranty of any kind — see
639
+ [`LICENSE`](LICENSE).
640
+
641
+ ## Acknowledgments
642
+
643
+ Yomi's LINE protocol implementation was written with reference to three open-source
644
+ projects, whose field layouts, E2EE chunk ordering, request shapes, and Thrift
645
+ definitions informed this independent implementation:
646
+
647
+ - **[evex-dev/linejs](https://github.com/evex-dev/linejs)** (MIT) — request shapes
648
+ and the Letter-Sealing E2EE payload layout.
649
+ - **[DeachSword/CHRLINE](https://github.com/DeachSword/CHRLINE)** (BSD-3-Clause) —
650
+ protocol field layouts and the passwordless login flow.
651
+ - **[er1ce/LINE-Protocol](https://github.com/er1ce/LINE-Protocol)** (Apache-2.0) —
652
+ Thrift enum definitions (ContentType, MessageRelationType, ServiceCode).
653
+
654
+ Their copyright notices and full license texts are reproduced in [`NOTICE`](NOTICE),
655
+ as their licenses require.
656
+
657
+ ## License
658
+
659
+ MIT