@leemour/max-cli 0.7.0 → 0.8.0

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 (160) hide show
  1. package/README.md +198 -79
  2. package/dist/cache/schema.d.ts +13 -8
  3. package/dist/cache/schema.d.ts.map +1 -1
  4. package/dist/cache/schema.js +84 -23
  5. package/dist/cache/schema.js.map +1 -1
  6. package/dist/cache/store.d.ts +15 -0
  7. package/dist/cache/store.d.ts.map +1 -1
  8. package/dist/cache/store.js +32 -3
  9. package/dist/cache/store.js.map +1 -1
  10. package/dist/client.d.ts +28 -1
  11. package/dist/client.d.ts.map +1 -1
  12. package/dist/client.js +110 -5
  13. package/dist/client.js.map +1 -1
  14. package/dist/commands/chats.d.ts.map +1 -1
  15. package/dist/commands/chats.js +8 -0
  16. package/dist/commands/chats.js.map +1 -1
  17. package/dist/commands/config.d.ts.map +1 -1
  18. package/dist/commands/config.js +6 -2
  19. package/dist/commands/config.js.map +1 -1
  20. package/dist/commands/context.d.ts +14 -2
  21. package/dist/commands/context.d.ts.map +1 -1
  22. package/dist/commands/context.js +18 -19
  23. package/dist/commands/context.js.map +1 -1
  24. package/dist/commands/export.d.ts +3 -0
  25. package/dist/commands/export.d.ts.map +1 -0
  26. package/dist/commands/export.js +83 -0
  27. package/dist/commands/export.js.map +1 -0
  28. package/dist/commands/mcp.d.ts.map +1 -1
  29. package/dist/commands/mcp.js +9 -2
  30. package/dist/commands/mcp.js.map +1 -1
  31. package/dist/commands/messages.d.ts.map +1 -1
  32. package/dist/commands/messages.js +126 -3
  33. package/dist/commands/messages.js.map +1 -1
  34. package/dist/commands/models.d.ts +7 -0
  35. package/dist/commands/models.d.ts.map +1 -0
  36. package/dist/commands/models.js +47 -0
  37. package/dist/commands/models.js.map +1 -0
  38. package/dist/commands/serve.d.ts.map +1 -1
  39. package/dist/commands/serve.js +50 -2
  40. package/dist/commands/serve.js.map +1 -1
  41. package/dist/commands/session.d.ts.map +1 -1
  42. package/dist/commands/session.js +13 -3
  43. package/dist/commands/session.js.map +1 -1
  44. package/dist/commands/watch.d.ts.map +1 -1
  45. package/dist/commands/watch.js +12 -16
  46. package/dist/commands/watch.js.map +1 -1
  47. package/dist/config.d.ts +18 -2
  48. package/dist/config.d.ts.map +1 -1
  49. package/dist/config.js +58 -4
  50. package/dist/config.js.map +1 -1
  51. package/dist/domain/map.d.ts.map +1 -1
  52. package/dist/domain/map.js +5 -0
  53. package/dist/domain/map.js.map +1 -1
  54. package/dist/domain/models.d.ts +14 -0
  55. package/dist/domain/models.d.ts.map +1 -1
  56. package/dist/domain/models.js.map +1 -1
  57. package/dist/download.d.ts +2 -0
  58. package/dist/download.d.ts.map +1 -1
  59. package/dist/download.js +11 -0
  60. package/dist/download.js.map +1 -1
  61. package/dist/export.d.ts +26 -0
  62. package/dist/export.d.ts.map +1 -0
  63. package/dist/export.js +75 -0
  64. package/dist/export.js.map +1 -0
  65. package/dist/generated/client.generated.d.ts +2 -0
  66. package/dist/generated/client.generated.d.ts.map +1 -1
  67. package/dist/generated/client.generated.js +2 -0
  68. package/dist/generated/client.generated.js.map +1 -1
  69. package/dist/generated/opcodes.generated.d.ts +0 -9
  70. package/dist/generated/opcodes.generated.d.ts.map +1 -1
  71. package/dist/generated/opcodes.generated.js +0 -9
  72. package/dist/generated/opcodes.generated.js.map +1 -1
  73. package/dist/generated/operations.generated.d.ts +18 -1
  74. package/dist/generated/operations.generated.d.ts.map +1 -1
  75. package/dist/generated/operations.generated.js +4 -2
  76. package/dist/generated/operations.generated.js.map +1 -1
  77. package/dist/mcp/confirm.d.ts +7 -0
  78. package/dist/mcp/confirm.d.ts.map +1 -1
  79. package/dist/mcp/confirm.js +16 -7
  80. package/dist/mcp/confirm.js.map +1 -1
  81. package/dist/mcp/instructions.d.ts +5 -1
  82. package/dist/mcp/instructions.d.ts.map +1 -1
  83. package/dist/mcp/instructions.js +16 -2
  84. package/dist/mcp/instructions.js.map +1 -1
  85. package/dist/mcp/server.d.ts +3 -1
  86. package/dist/mcp/server.d.ts.map +1 -1
  87. package/dist/mcp/server.js +22 -3
  88. package/dist/mcp/server.js.map +1 -1
  89. package/dist/mcp/session.d.ts +1 -1
  90. package/dist/mcp/session.d.ts.map +1 -1
  91. package/dist/mcp/session.js +1 -1
  92. package/dist/mcp/session.js.map +1 -1
  93. package/dist/mcp/tools.d.ts +8 -1
  94. package/dist/mcp/tools.d.ts.map +1 -1
  95. package/dist/mcp/tools.js +87 -10
  96. package/dist/mcp/tools.js.map +1 -1
  97. package/dist/program.d.ts.map +1 -1
  98. package/dist/program.js +6 -2
  99. package/dist/program.js.map +1 -1
  100. package/dist/sends/guard.d.ts +7 -3
  101. package/dist/sends/guard.d.ts.map +1 -1
  102. package/dist/sends/guard.js +32 -11
  103. package/dist/sends/guard.js.map +1 -1
  104. package/dist/sends/journal.d.ts +6 -1
  105. package/dist/sends/journal.d.ts.map +1 -1
  106. package/dist/sends/journal.js.map +1 -1
  107. package/dist/sends/permissions.d.ts +11 -0
  108. package/dist/sends/permissions.d.ts.map +1 -0
  109. package/dist/sends/permissions.js +47 -0
  110. package/dist/sends/permissions.js.map +1 -0
  111. package/dist/server/server-connection.d.ts +25 -15
  112. package/dist/server/server-connection.d.ts.map +1 -1
  113. package/dist/server/server-connection.js +69 -63
  114. package/dist/server/server-connection.js.map +1 -1
  115. package/dist/server/server.d.ts +7 -0
  116. package/dist/server/server.d.ts.map +1 -1
  117. package/dist/server/server.js +82 -19
  118. package/dist/server/server.js.map +1 -1
  119. package/dist/server/start.d.ts +17 -1
  120. package/dist/server/start.d.ts.map +1 -1
  121. package/dist/server/start.js +46 -7
  122. package/dist/server/start.js.map +1 -1
  123. package/dist/session/adopt.d.ts.map +1 -1
  124. package/dist/session/adopt.js +4 -0
  125. package/dist/session/adopt.js.map +1 -1
  126. package/dist/session/handshake.d.ts +1 -1
  127. package/dist/spec/index.js +4 -4
  128. package/dist/spec/index.js.map +1 -1
  129. package/dist/spec/operations/chats.d.ts +12 -2
  130. package/dist/spec/operations/chats.d.ts.map +1 -1
  131. package/dist/spec/operations/chats.js +32 -5
  132. package/dist/spec/operations/chats.js.map +1 -1
  133. package/dist/spec/operations/messages.d.ts +13 -1
  134. package/dist/spec/operations/messages.d.ts.map +1 -1
  135. package/dist/spec/operations/messages.js +18 -4
  136. package/dist/spec/operations/messages.js.map +1 -1
  137. package/dist/transcribe/index.d.ts +34 -0
  138. package/dist/transcribe/index.d.ts.map +1 -0
  139. package/dist/transcribe/index.js +43 -0
  140. package/dist/transcribe/index.js.map +1 -0
  141. package/dist/transcribe/install.d.ts +18 -0
  142. package/dist/transcribe/install.d.ts.map +1 -0
  143. package/dist/transcribe/install.js +67 -0
  144. package/dist/transcribe/install.js.map +1 -0
  145. package/dist/transcribe/models.d.ts +32 -0
  146. package/dist/transcribe/models.d.ts.map +1 -0
  147. package/dist/transcribe/models.js +124 -0
  148. package/dist/transcribe/models.js.map +1 -0
  149. package/dist/transcribe/speech.d.ts +25 -0
  150. package/dist/transcribe/speech.d.ts.map +1 -0
  151. package/dist/transcribe/speech.js +105 -0
  152. package/dist/transcribe/speech.js.map +1 -0
  153. package/dist/update.d.ts +1 -11
  154. package/dist/update.d.ts.map +1 -1
  155. package/dist/update.js +14 -42
  156. package/dist/update.js.map +1 -1
  157. package/dist/version.d.ts +1 -1
  158. package/dist/version.js +1 -1
  159. package/package.json +8 -3
  160. package/skills/max-cli/SKILL.md +19 -3
package/README.md CHANGED
@@ -1,48 +1,138 @@
1
+ <p align="center">
2
+ <img src="https://raw.githubusercontent.com/leemour/max-cli/main/docs/design/logo.png" alt="max-cli" width="160">
3
+ </p>
4
+
1
5
  # max-cli
2
6
 
3
- Читать и писать в свой личный аккаунт [MAX Messenger](https://max.ru) из терминала или из
4
- скрипта — чаты, сообщения, контакты, — не открывая браузер.
7
+ Ваш личный аккаунт [MAX](https://max.ru) в терминале и в ИИ-агентах. `max` — командная строка и
8
+ MCP-сервер: читайте и пишите в чаты сами или поручите это Claude, Codex, Cursor и другим агентам.
5
9
 
6
- Сделано **в первую очередь для агентов и скриптов**: любой результат доступен как одно
7
- детерминированное значение JSON, у любого отказа есть код, на который можно ветвиться, а stdout
8
- никогда не несёт ничего, кроме данных. Та же дисциплина делает его приятным для человека: в
9
- терминале таблицы и цвет, и ничего не происходит само.
10
+ > ⚠️ **max-cli использует неофициальный внутренний API MAX.** API может измениться без
11
+ > предупреждения, а использование инструмента может нарушать условия сервиса. Вы используете
12
+ > max-cli на свой риск; авторы и контрибьюторы не несут ответственности за блокировки аккаунтов,
13
+ > потерю данных или другие последствия.
10
14
 
11
15
  ```sh
12
- max session start qr # один раз: токен уходит в ключницу
13
16
  max chats list --limit 5
14
17
  max messages list "Иван Петров"
18
+ max messages send "Иван Петров" "Опаздываю на 15 минут"
15
19
  ```
16
20
 
17
- ## Что это даёт
18
-
19
- - **Один запуск — одна операция.** Соединиться, сделать, напечатать, отключиться. Ничего не висит
20
- фоном, не держит сокет и не слушает события: команда, которая напечатала ответ и не вышла, — это
21
- дефект, а не особенность.
22
- - **Чтение ничего не помечает прочитанным.** В протоколе «получить историю» и «отметить
23
- прочитанным» — разные операции; вторая не отправляется никогда, и это проверяется тестом, а не
24
- обещается в README.
25
- - **Токен не касается ни файла, ни истории оболочки.** Он спрашивается без эха или читается из
26
- трубы и уходит в ключницу операционной системы. В схеме настроек нет поля, куда его можно было
27
- бы положить.
28
- - **Отправка, которая не врёт об исходе.** Если ответ не пришёл, это `outcome_unknown`, а не
29
- «ошибка» и не «отправлено»: сообщение могло уйти. Повтор возможен только с тем же `--cid`, и
30
- MAX схлопывает дубль — это измерено на реальном аккаунте, а не предположено.
31
- - **Имя чата не угадывается.** Часть названия, подходящая к двум чатам, — это отказ со списком
32
- кандидатов. Отправить не в тот разговор нельзя отменить.
33
- - **Диагностика, в которой нет содержимого.** `--trace` показывает по строке на запрос,
34
- `--record` кладёт их в каталог запуска на 30 дней. Операция, опкод, идентификаторы, байты,
35
- длительности — да. Название чата, имя, текст, телефон, токен — никогда, ни обрезанными, ни
36
- хэшем.
37
- - **По умолчанию не записывается ничего.** Мессенджер, который сам собирает каталог с историей
38
- того, кого вы читали, — это чужая жизнь в чужом логе; запись включается флагом.
39
- - **Каждый вход спрашивает только то, что изменилось.** Метка времени прошлого входа уходит
40
- обратно, и MAX присылает дельту, а не весь список заново — так что контакты остаются свежими,
41
- не стоя ничего.
42
- - **Два рантайма, и это проверяется.** Node 22+ и Bun; под обоими в CI выполняется собранная
43
- команда, а не только проверка типов.
44
- - **Мы выглядим как официальный клиент.** На проводе нет ни нашего имени, ни своего user-agent:
45
- поля, которыми клиент представляется, копируют веб-клиент MAX.
21
+ ## Как это использовать
22
+
23
+ Самое полезное — поручить переписку агенту: Claude, Codex или другому. Он читает чаты через `max`
24
+ и делает то, что вы попросили словами.
25
+
26
+ - **Проверка по расписанию.** Утром и вечером агент смотрит, что пришло, и присылает короткую
27
+ сводку: кто ждёт ответа, что срочно, что можно не читать.
28
+ - **Отчёты.** Итог недели по рабочему чату: что решили, кто что взял на себя, какие вопросы
29
+ остались без ответа.
30
+ - **Кто кому должен.** Агент находит в переписке обещания, долги и договорённости и собирает их в
31
+ список: кто, что, когда и в каком чате.
32
+ - **Напоминания.** Кому вы обещали ответить и не ответили; чей вопрос висит третий день.
33
+ - **Черновики ответов.** Агент предлагает текст, а отправляете вы — или он сам, если вы разрешили
34
+ писать в этот чат.
35
+ - **Поиск.** «Когда мы договорились о встрече с Иваном?» — ответ с датой и самим сообщением.
36
+
37
+ Как настроить агента под каждую задачу — готовые запросы, расписание и ограничения:
38
+ [docs/recipes.md](docs/recipes.md).
39
+
40
+ ## Как это работает
41
+
42
+ `max` работает от вашего имени, как ещё одно ваше устройство, а не как бот. Вы входите один раз
43
+ по QR-коду, и дальше доступно то же, что в приложении: чаты, сообщения, файлы, группы, контакты и
44
+ профиль.
45
+
46
+ Агенту `max` подключается одним из двух способов:
47
+
48
+ - **Агент с терминалом** — Claude Code, Codex, Gemini CLI. Он сам вызывает команды `max`. Дайте
49
+ ему навык (skill) — инструкцию, как работать с `max`: одна команда, см.
50
+ [ниже](#для-скриптов-и-агентов).
51
+ - **Агент без терминала** — Claude Desktop, Cursor и другие клиенты MCP. Подключите `max mcp`.
52
+ По умолчанию агент только читает. Отправку можно разрешить, в том числе с вашим подтверждением
53
+ каждого сообщения.
54
+
55
+ [![npm](https://img.shields.io/npm/v/@leemour/max-cli)](https://www.npmjs.com/package/@leemour/max-cli)
56
+ [![CI](https://github.com/leemour/max-cli/actions/workflows/ci.yml/badge.svg)](https://github.com/leemour/max-cli/actions/workflows/ci.yml)
57
+ [![Node](https://img.shields.io/node/v/@leemour/max-cli)](https://nodejs.org/)
58
+ [![Bun](https://img.shields.io/badge/bun-1.3%2B-f9f1e1)](https://bun.sh/)
59
+ [![npm downloads](https://img.shields.io/npm/dm/@leemour/max-cli)](https://www.npmjs.com/package/@leemour/max-cli)
60
+ [![License: MIT](https://img.shields.io/npm/l/@leemour/max-cli)](LICENSE)
61
+
62
+ ## Что умеет
63
+
64
+ - **Читать.** Чаты, история, одно сообщение вместе с соседними, непрочитанное во всех чатах
65
+ сразу (`max inbox`), новые сообщения по мере прихода (`max watch`), скачивание вложений,
66
+ расшифровка голосовых в текст на вашем компьютере, выгрузка переписки в JSONL или Markdown.
67
+ - **Искать.** Сообщения — по тексту всего прочитанного, без подключения к MAX. Чаты и контакты —
68
+ по части названия или имени.
69
+ - **Писать.** Текст и разметка, ответы, фото и файлы, правка, пересылка, закрепление, реакции,
70
+ отложенная отправка, удаление — у себя или у всех.
71
+ - **Вести группы и каналы.** Создать группу, вступить по ссылке, выйти, участники и админы,
72
+ название и настройки, заявки на вступление, ссылка-приглашение, папки.
73
+ - **Контакты и аккаунт.** Найти человека по номеру, добавить, удалить, импортировать из файла;
74
+ изменить профиль; посмотреть и завершить другие сеансы.
75
+ - **Входить удобным способом.** QR-код прямо в терминале, QR-код или SMS через web.max.ru в
76
+ браузере, или готовый токен.
77
+
78
+ ## Чем он хорош
79
+
80
+ - **Читает незаметно.** Чтение не ставит отметку «прочитано»: собеседник не видит, что вы открыли
81
+ чат. Отметить прочитанным можно, когда нужно: `max chats read` или `--mark-read`.
82
+ - **Агент не напишет лишнего.** Есть профиль только для чтения, список чатов, в которые можно
83
+ писать, и лимит сообщений в час. MCP-сервер может показывать вам каждое сообщение до отправки.
84
+ Если имя подходит к двум чатам, `max` не выбирает сам, а показывает оба.
85
+ - **Сообщение не уйдёт дважды.** Если связь оборвалась во время отправки, `max` прямо говорит, что
86
+ не знает, дошло ли сообщение, и даёт команду для повтора. MAX узнаёт повтор и второго сообщения не
87
+ создаёт.
88
+ - **Своя копия переписки.** Всё прочитанное сохраняется на вашем компьютере. По ней работает
89
+ быстрый поиск по тексту сообщений, и её можно читать без интернета (`--offline`). При входе MAX
90
+ присылает только то, что изменилось. Стереть копию — `max cache clear`.
91
+ - **Пароль от аккаунта не лежит в файле.** Токен входа хранится в системном хранилище паролей:
92
+ Keychain в macOS, Secret Service (GNOME Keyring, KWallet) в Linux, диспетчер учётных данных в
93
+ Windows. В историю команд он не попадает.
94
+ - **Скрипту и агенту легко разобрать ответ.** С `--json` команда отдаёт только данные, в одной и
95
+ той же форме, без текста для человека вокруг. Ошибка приходит отдельно и с номером, поэтому
96
+ скрипт сразу понимает, что случилось: нет входа, чат не найден или MAX не ответил вовремя.
97
+ - **Разбор проблем без вашей переписки.** `--trace` показывает каждый запрос к MAX, чтобы понять,
98
+ почему команда не сработала. В этой записи нет текстов, имён, телефонов и токена, поэтому её
99
+ можно приложить к сообщению об ошибке. Без флага ничего не записывается.
100
+
101
+ ## Под заказ
102
+
103
+ Делаем автоматизацию процессов и инструменты под вашу задачу: интеграции с MAX и другими
104
+ мессенджерами, ИИ-агентов, внутренние сервисы. Пишите на [info@neirox.ai](mailto:info@neirox.ai).
105
+
106
+ ## Чем отличается от других
107
+
108
+ Для MAX уже есть хорошие проекты, и max-cli многому у них научился.
109
+ [PyMax](https://github.com/MaxApiTeam/PyMax) — самая полная библиотека для личного аккаунта; её код
110
+ помог разобрать многие операции протокола. Выбирайте то, что подходит под задачу.
111
+
112
+ | | max-cli | [PyMax](https://github.com/MaxApiTeam/PyMax) | [max-mcp](https://github.com/renosaza/max-mcp) |
113
+ |---|:-:|:-:|:-:|
114
+ | что это | командная строка и MCP-сервер | библиотека на Python | MCP-сервер на PyMax |
115
+ | MCP-сервер и навык для агентов | ✅ | — | MCP |
116
+ | ограничения для агента: только чтение, разрешённые действия, список чатов, лимит в час, подтверждение отправки | ✅ | — | — |
117
+ | расшифровка голосовых в текст на вашем компьютере | ✅ | — | — |
118
+ | готовые команды — не нужно писать свою программу | ✅ | — (библиотека для своего кода на Python) | — |
119
+ | локальная копия с поиском и чтением без интернета | ✅ | — | — |
120
+ | новые сообщения в реальном времени | ✅ `max watch` | ✅ обработчики событий | — |
121
+ | отложенная отправка — уходит, даже если компьютер выключен | ✅ | ✅ | — |
122
+ | выгрузка переписки в JSONL или Markdown | ✅ | — | — |
123
+ | чтение, отправка текста, фото и файлов | ✅ | ✅ | ✅ |
124
+ | реакции, правка, пересылка, закрепление | ✅ | ✅ | — |
125
+ | удаление сообщений — у себя или у всех, с лимитом | ✅ | ✅ | — |
126
+ | отметка «прочитано» по запросу | ✅ | ✅ | — |
127
+ | группы и каналы: создать, вступить, участники, админы | ✅ | ✅ | чтение каналов |
128
+ | контакты, профиль, другие сеансы | ✅ | ✅ | — |
129
+ | отправка видео и кружков | — | ✅ | — |
130
+ | отправка голосовых | — | ⚠️ в 2.4.1 не проходит ([#102](https://github.com/MaxApiTeam/PyMax/issues/102), [#103](https://github.com/MaxApiTeam/PyMax/issues/103)) | — |
131
+ | опросы | — | ✅ | — |
132
+ | пароль и двухфакторный вход, фото профиля | — | ✅ | — |
133
+
134
+ Если нужен бот со своим аккаунтом, а не ваш личный аккаунт, — это официальный
135
+ [Bot API MAX](https://dev.max.ru/docs).
46
136
 
47
137
  ## Содержание
48
138
 
@@ -52,7 +142,9 @@ max messages list "Иван Петров"
52
142
  - [Для скриптов и агентов](#для-скриптов-и-агентов)
53
143
  - [Документация](#документация)
54
144
  - [Разработка](#разработка)
145
+ - [Дорожная карта](#дорожная-карта)
55
146
  - [Лицензия](#лицензия)
147
+ - [Участие](#участие)
56
148
 
57
149
  ## Установка
58
150
 
@@ -69,7 +161,7 @@ npx @leemour/max-cli --help
69
161
 
70
162
  ```sh
71
163
  npm install -g @leemour/max-cli
72
- max --version # 0.1.0
164
+ max --version
73
165
  ```
74
166
 
75
167
  Нужен **Node 22 или новее**, либо **Bun 1.3+**. Подробности, переменные окружения и то, куда
@@ -77,8 +169,8 @@ max --version # 0.1.0
77
169
 
78
170
  ## Вход
79
171
 
80
- `max session start qr` рисует QR-код в терминале: вы сканируете его приложением MAX, и токен уходит
81
- в ключницу. Ещё способы — `qr-chrome`, `sms` (оба через web.max.ru в браузере) и `token`
172
+ `max session start qr` рисует QR-код в терминале: вы сканируете его приложением MAX, и токен входа
173
+ сохраняется в системном хранилище паролей. Ещё способы — `qr-chrome`, `sms` (оба через web.max.ru в браузере) и `token`
82
174
  ([docs/sessions.md](docs/sessions.md)).
83
175
 
84
176
  ```sh
@@ -136,27 +228,12 @@ max cache clear
136
228
 
137
229
  ## Для скриптов и агентов
138
230
 
139
- ```sh
140
- max chats list --json
141
- ```
142
-
143
- `--json` — это **ровно одно значение JSON на stdout и больше ничего**: ни спиннера, ни галочки, ни
144
- предупреждения. То же самое включается само, когда stdout не терминал. Любой список отвечает одним
145
- объектом — `{ "items": […], "page": 1, "limit": 20, "hasMore": true }` — и `--all` с `--offline`
146
- отвечают **тем же**, чтобы разбирающему ответ не приходилось ветвиться на две формы.
231
+ ### Навык для агентов с терминалом
147
232
 
148
- Ошибка уходит на stderr, а stdout остаётся пустым, поэтому отказ невозможно принять за результат:
149
-
150
- ```json
151
- {"error":{"code":"authentication_error","message":"no session for profile \"default\" — run `max session start`"}}
152
- ```
153
-
154
- Ветвиться надо по коду возврата: `4` — нет сессии, `6` — не найдено, `9` — таймаут, `14` — исход
155
- неизвестен, `130` — прервано. Вся таблица — в [docs/commands.md](docs/commands.md).
156
-
157
- **Инструкция для агента** — не список флагов, а ловушки и границы: что нельзя делать без просьбы,
158
- почему id — строки, как повторять отправку. Ставится одной строкой, той же версии, что и `max`.
159
- Файл один и тот же, различается только папка:
233
+ Навык (skill) — файл с инструкцией, который агент читает перед работой. В навыке `max` описано,
234
+ какие команды есть, что агент делает только по вашей просьбе и как безопасно повторить отправку.
235
+ Его понимают **Claude Code**, **Codex** и **Gemini CLI**. Навык ставится одной командой и всегда
236
+ той же версии, что и `max`:
160
237
 
161
238
  ```sh
162
239
  # Claude Code
@@ -165,33 +242,59 @@ mkdir -p ~/.claude/skills/max-cli && max skill show > ~/.claude/skills/max-cli/S
165
242
  mkdir -p ~/.agents/skills/max-cli && max skill show > ~/.agents/skills/max-cli/SKILL.md
166
243
  ```
167
244
 
168
- Где какая среда ищет навыки: [Codex](https://learn.chatgpt.com/docs/build-skills),
169
- [Gemini CLI](https://geminicli.com/docs/cli/skills/) (сверено 2026-09-24).
245
+ Как эти агенты находят навыки: [Codex](https://learn.chatgpt.com/docs/build-skills),
246
+ [Gemini CLI](https://geminicli.com/docs/cli/skills/).
170
247
 
171
- **Для клиентов без терминала** — Claude Desktop, Cursor — тот же профиль по MCP. Без `--allow-send`
172
- сервер только читает. Подробно — [docs/mcp.md](docs/mcp.md).
248
+ ### MCP-сервер для агентов без терминала
249
+
250
+ Claude Desktop, Cursor и другие клиенты MCP подключаются к `max mcp` и работают с тем же
251
+ аккаунтом. Без `--allow-send` агент только читает. С `--confirm-send` перед каждой отправкой вы
252
+ видите чат и текст и отвечаете «да» или «нет». Подробно — [docs/mcp.md](docs/mcp.md).
173
253
 
174
254
  ```sh
175
255
  claude mcp add max -- max mcp
176
256
  ```
177
257
 
258
+ ### Ответы в JSON
259
+
260
+ ```sh
261
+ max chats list --json
262
+ ```
263
+
264
+ С `--json` команда печатает только данные в JSON, без таблиц, цвета и подсказок. Так же она
265
+ работает, когда её вывод читает другая программа, — например, в конвейере `max … | jq`. Любой
266
+ список приходит в одной и той же форме:
267
+
268
+ ```json
269
+ { "items": [ … ], "page": 1, "limit": 20, "hasMore": true }
270
+ ```
271
+
272
+ Ошибка приходит отдельно от данных, поэтому её нельзя принять за пустой результат:
273
+
274
+ ```json
275
+ {"error":{"code":"authentication_error","message":"no session for profile \"default\" — run `max session start`"}}
276
+ ```
277
+
278
+ У каждой ошибки есть номер — код завершения программы. По нему скрипт решает, что делать дальше:
279
+ `4` — нужно войти, `6` — чат или сообщение не найдены, `9` — MAX не ответил вовремя, `14` —
280
+ неизвестно, ушло ли сообщение. Все коды — в [docs/commands.md](docs/commands.md).
281
+
178
282
  ## Документация
179
283
 
180
- | | |
181
- |---|---|
182
- | [docs/installation.md](docs/installation.md) | установка, обновление, куда что ложится |
183
- | [docs/usage.md](docs/usage.md) | вход, чтение, отправка, машинный режим |
184
- | [docs/sessions.md](docs/sessions.md) | токен, ключница, профили |
185
- | [docs/configuration.md](docs/configuration.md) | настройки, переменные, порядок разрешения |
186
- | [docs/mcp.md](docs/mcp.md) | MCP-сервер: подключение, отправка, соединение с MAX |
187
- | [docs/diagnostics.md](docs/diagnostics.md) | `--trace`, `--record`, `max runs` |
188
- | [docs/security.md](docs/security.md) | что пишется на диск, а что никогда |
189
- | [docs/troubleshooting.md](docs/troubleshooting.md) | по симптому: что делать, когда не работает |
190
- | [docs/commands.md](docs/commands.md) | каждая команда и опция — **генерируется** из программы |
191
- | [docs/protocol.md](docs/protocol.md) | каждый опкод и откуда известна его форма — **генерируется** |
192
- | [docs/ARCHITECTURE.md](docs/ARCHITECTURE.md) | как это устроено и какие швы нельзя пересекать |
193
- | [docs/DECISIONS.md](docs/DECISIONS.md) | что и почему решено — читать прежде, чем «чинить» странное |
194
- | [docs/BACKLOG.md](docs/BACKLOG.md) | что осталось |
284
+ - [docs/installation.md](docs/installation.md) — установка, обновление, куда что ложится
285
+ - [docs/usage.md](docs/usage.md) — вход, чтение, отправка, машинный режим
286
+ - [docs/sessions.md](docs/sessions.md) — токен, хранилище паролей, профили
287
+ - [docs/configuration.md](docs/configuration.md) — настройки, переменные, порядок разрешения
288
+ - [docs/mcp.md](docs/mcp.md) — MCP-сервер: подключение, отправка, соединение с MAX
289
+ - [docs/recipes.md](docs/recipes.md) — рецепты для агентов: сводки, отчёты, долги, напоминания, расписание
290
+ - [docs/diagnostics.md](docs/diagnostics.md) — `--trace`, `--record`, `max runs`
291
+ - [docs/ROADMAP.md](docs/ROADMAP.md) — что планируется
292
+ - [docs/security.md](docs/security.md) — что пишется на диск, а что никогда
293
+ - [docs/troubleshooting.md](docs/troubleshooting.md) — по симптому: что делать, когда не работает
294
+ - [docs/commands.md](docs/commands.md) — каждая команда и опция — **генерируется** из программы
295
+ - [docs/protocol.md](docs/protocol.md) — каждый опкод и откуда известна его форма — **генерируется**
296
+ - [docs/dev/ARCHITECTURE.md](docs/dev/ARCHITECTURE.md) — как это устроено и какие швы нельзя пересекать
297
+ - [docs/dev/BACKLOG.md](docs/dev/BACKLOG.md) — что осталось
195
298
 
196
299
  Оглавление целиком — [docs/README.md](docs/README.md).
197
300
 
@@ -208,9 +311,25 @@ pnpm generate # переписать сгенериро
208
311
  каждого сообщения объявлены один раз в `src/spec/`, а реестр, типизированный клиент и
209
312
  [docs/protocol.md](docs/protocol.md) из них генерируются — и CI падает, если дерево устарело.
210
313
 
211
- Половина, не имеющая отношения к MAX — потоки вывода, рендерер, коды ошибок, ключница, часы, —
314
+ Половина, не имеющая отношения к MAX — потоки вывода, рендерер, коды ошибок, хранилище паролей, часы, —
212
315
  вынесена в [`@leemour/cli-core`](https://github.com/leemour/cli-core) и общая с `braze-cli`.
213
316
 
317
+ ## Дорожная карта
318
+
319
+ Ближайшее:
320
+
321
+ - голосовые сообщения и видео;
322
+ - опросы: показывать при чтении и голосовать;
323
+ - локальная копия по желанию — можно работать совсем без неё.
324
+
325
+ Весь список — [docs/ROADMAP.md](docs/ROADMAP.md).
326
+
214
327
  ## Лицензия
215
328
 
216
329
  MIT — см. [LICENSE](LICENSE).
330
+
331
+ ## Участие
332
+
333
+ Пулл-реквесты, сообщения об ошибках и предложения приветствуются —
334
+ [issues](https://github.com/leemour/max-cli/issues). Как устроен код и как его проверять —
335
+ [docs/dev/ARCHITECTURE.md](docs/dev/ARCHITECTURE.md) и [docs/dev/TESTING.md](docs/dev/TESTING.md).
@@ -3,21 +3,26 @@ import type { CacheDatabase } from "./driver.js";
3
3
  * **Raise this on every change to the statements below.** The second schema change is the one that
4
4
  * corrupts somebody's file, because the first is always made while the only copy is your own.
5
5
  */
6
- export declare const SCHEMA_VERSION = 4;
6
+ export declare const SCHEMA_VERSION = 5;
7
7
  /**
8
8
  * Brings a file up to date, and **refuses a file from the future** rather than writing to it.
9
9
  *
10
10
  * A newer `max` may have added a column this one does not know about. Reading it is survivable;
11
11
  * writing to it is how one version quietly destroys what another stored.
12
12
  *
13
- * **An older file is rebuilt, not altered.** Everything in here comes back from MAX, so a
14
- * column-by-column migration would be code that runs once, is tested never, and is how the second
15
- * schema change corrupts somebody's file. What a rebuild costs is one full login — which is what
16
- * every command did before the delta sync existed.
13
+ * **An older file is rebuilt, except for the history.** Chats, people and memberships come back
14
+ * from MAX with one login, so they are dropped and refilled rather than altered. Messages do not:
15
+ * getting a chat's history back costs a request per page, and a backup (`CLI-34`) is exactly what
16
+ * cannot be allowed to vanish on the next upgrade (`MAX-44`). `messages` and `ranges` therefore
17
+ * keep their rows — copied, by the columns both versions share, into the tables this version
18
+ * creates.
17
19
  *
18
- * ⚠ **This stops being the right answer the day the database holds something MAX cannot re-send.**
19
- * Contacts that are in no chat would be exactly that (`RES-7`), and the day they arrive this
20
- * becomes `ALTER TABLE … ADD COLUMN` and this paragraph gets rewritten.
20
+ * ⚠ **A column added to `messages` or `ranges` must be nullable or have a default.** The copy
21
+ * leaves it out, and a `NOT NULL` column without one fails it. A test holds every shipped shape of
22
+ * those tables to that.
23
+ *
24
+ * Contacts that are in no chat would be another thing MAX cannot re-send (`RES-7`); the day they
25
+ * arrive, their table joins `KEPT`.
21
26
  */
22
27
  /** Ours, so its message is known to hold no path and can be shown as it is. */
23
28
  export declare class NewerCacheError extends Error {
@@ -1 +1 @@
1
- {"version":3,"file":"schema.d.ts","sourceRoot":"","sources":["../../src/cache/schema.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,aAAa,CAAA;AAEhD;;;GAGG;AACH,eAAO,MAAM,cAAc,IAAI,CAAA;AAqM/B;;;;;;;;;;;;;;GAcG;AACH,+EAA+E;AAC/E,qBAAa,eAAgB,SAAQ,KAAK;CAAG;AAE7C,eAAO,MAAM,OAAO,GAAI,UAAU,aAAa,KAAG,IAgBjD,CAAA"}
1
+ {"version":3,"file":"schema.d.ts","sourceRoot":"","sources":["../../src/cache/schema.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,aAAa,CAAA;AAEhD;;;GAGG;AACH,eAAO,MAAM,cAAc,IAAI,CAAA;AAuM/B;;;;;;;;;;;;;;;;;;;GAmBG;AACH,+EAA+E;AAC/E,qBAAa,eAAgB,SAAQ,KAAK;CAAG;AAO7C,eAAO,MAAM,OAAO,GAAI,UAAU,aAAa,KAAG,IAgCjD,CAAA"}
@@ -2,7 +2,7 @@
2
2
  * **Raise this on every change to the statements below.** The second schema change is the one that
3
3
  * corrupts somebody's file, because the first is always made while the only copy is your own.
4
4
  */
5
- export const SCHEMA_VERSION = 4;
5
+ export const SCHEMA_VERSION = 5;
6
6
  /**
7
7
  * Everything the cache holds, and the indexes are part of it rather than an afterthought — each
8
8
  * one exists for a query that is actually made.
@@ -80,6 +80,8 @@ const STATEMENTS = [
80
80
  attachments TEXT NOT NULL,
81
81
  link TEXT,
82
82
  fetched_at INTEGER NOT NULL,
83
+ transcript TEXT,
84
+ transcript_model TEXT,
83
85
  PRIMARY KEY (chat_id, id)
84
86
  )`,
85
87
  `CREATE INDEX IF NOT EXISTS messages_by_time ON messages (chat_id, time DESC)`,
@@ -192,26 +194,53 @@ const STATEMENTS = [
192
194
  * A newer `max` may have added a column this one does not know about. Reading it is survivable;
193
195
  * writing to it is how one version quietly destroys what another stored.
194
196
  *
195
- * **An older file is rebuilt, not altered.** Everything in here comes back from MAX, so a
196
- * column-by-column migration would be code that runs once, is tested never, and is how the second
197
- * schema change corrupts somebody's file. What a rebuild costs is one full login — which is what
198
- * every command did before the delta sync existed.
197
+ * **An older file is rebuilt, except for the history.** Chats, people and memberships come back
198
+ * from MAX with one login, so they are dropped and refilled rather than altered. Messages do not:
199
+ * getting a chat's history back costs a request per page, and a backup (`CLI-34`) is exactly what
200
+ * cannot be allowed to vanish on the next upgrade (`MAX-44`). `messages` and `ranges` therefore
201
+ * keep their rows — copied, by the columns both versions share, into the tables this version
202
+ * creates.
199
203
  *
200
- * ⚠ **This stops being the right answer the day the database holds something MAX cannot re-send.**
201
- * Contacts that are in no chat would be exactly that (`RES-7`), and the day they arrive this
202
- * becomes `ALTER TABLE … ADD COLUMN` and this paragraph gets rewritten.
204
+ * ⚠ **A column added to `messages` or `ranges` must be nullable or have a default.** The copy
205
+ * leaves it out, and a `NOT NULL` column without one fails it. A test holds every shipped shape of
206
+ * those tables to that.
207
+ *
208
+ * Contacts that are in no chat would be another thing MAX cannot re-send (`RES-7`); the day they
209
+ * arrive, their table joins `KEPT`.
203
210
  */
204
211
  /** Ours, so its message is known to hold no path and can be shown as it is. */
205
212
  export class NewerCacheError extends Error {
206
213
  }
214
+ const KEPT = ["messages", "ranges"];
215
+ const versionOf = (database) => Number(database.prepare("PRAGMA user_version").get()?.user_version ?? 0);
207
216
  export const migrate = (database) => {
208
- const current = Number(database.prepare("PRAGMA user_version").get()?.user_version ?? 0);
217
+ const current = versionOf(database);
209
218
  if (current > SCHEMA_VERSION) {
210
219
  throw new NewerCacheError(`this cache was written by a newer max (schema ${current}, this one speaks ${SCHEMA_VERSION}) — ` +
211
220
  "run `max cache clear`, or use the newer version");
212
221
  }
213
- if (current > 0 && current < SCHEMA_VERSION)
214
- rebuild(database);
222
+ if (current > 0 && current < SCHEMA_VERSION) {
223
+ database.exec("BEGIN IMMEDIATE");
224
+ try {
225
+ // Another max may have waited on the same lock and upgraded the file while we did.
226
+ if (versionOf(database) === SCHEMA_VERSION) {
227
+ database.exec("COMMIT");
228
+ return;
229
+ }
230
+ const kept = rebuild(database);
231
+ for (const statement of STATEMENTS)
232
+ database.exec(statement);
233
+ for (const table of kept)
234
+ restore(database, table);
235
+ database.exec(`PRAGMA user_version = ${SCHEMA_VERSION}`);
236
+ database.exec("COMMIT");
237
+ }
238
+ catch (error) {
239
+ database.exec("ROLLBACK");
240
+ throw error;
241
+ }
242
+ return;
243
+ }
215
244
  for (const statement of STATEMENTS)
216
245
  database.exec(statement);
217
246
  database.exec(`PRAGMA user_version = ${SCHEMA_VERSION}`);
@@ -219,28 +248,60 @@ export const migrate = (database) => {
219
248
  /**
220
249
  * Drops what is there by asking the file rather than by listing what we think a previous version
221
250
  * wrote. A version that added a table we have since forgotten would otherwise survive the rebuild
222
- * and collide with a later name.
251
+ * and collide with a later name. The tables in `KEPT` are set aside under another name instead, and
252
+ * returned so `restore` can bring them back.
223
253
  *
224
254
  * The `fetched` table goes with the rest on purpose: it records that a collection was complete,
225
255
  * and keeping it would claim a sweep whose rows have just been thrown away.
226
256
  */
227
257
  const rebuild = (database) => {
228
- // ⚠ **Virtual tables first.** An FTS5 index keeps four shadow tables of its own, and
258
+ const names = (sql) => database
259
+ .prepare(sql)
260
+ .all()
261
+ .map(({ name }) => String(name));
262
+ // Triggers and indexes first: a kept table takes both along when it is renamed, and then the new
263
+ // table's `CREATE … IF NOT EXISTS` finds the name taken and quietly creates nothing. A trigger
264
+ // that writes to a search index about to be dropped also makes the rename itself fail.
265
+ for (const name of names("SELECT name FROM sqlite_master WHERE type = 'trigger'")) {
266
+ database.exec(`DROP TRIGGER IF EXISTS "${name}"`);
267
+ }
268
+ for (const name of names("SELECT name FROM sqlite_master WHERE type = 'index' AND sql IS NOT NULL")) {
269
+ database.exec(`DROP INDEX IF EXISTS "${name}"`);
270
+ }
271
+ // ⚠ **Virtual tables before ordinary ones.** An FTS5 index keeps four shadow tables of its own, and
229
272
  // `sqlite_master` lists them as ordinary tables. Dropping one of those out from under a live
230
273
  // index is how a rebuild leaves a corrupt file behind. Dropping the virtual table takes its
231
274
  // shadows with it, and the second pass then finds only real tables.
232
275
  //
233
276
  // It happens to work without this today, because `sqlite_master` returns them in creation order
234
277
  // and `IF EXISTS` swallows the leftovers. That is an accident of ordering, not a guarantee.
235
- const virtual = database
236
- .prepare("SELECT name FROM sqlite_master WHERE type = 'table' AND sql LIKE 'CREATE VIRTUAL TABLE%'")
237
- .all();
238
- for (const { name } of virtual)
239
- database.exec(`DROP TABLE IF EXISTS "${String(name)}"`);
240
- const tables = database
241
- .prepare("SELECT name FROM sqlite_master WHERE type = 'table' AND name NOT LIKE 'sqlite_%'")
242
- .all();
243
- for (const { name } of tables)
244
- database.exec(`DROP TABLE IF EXISTS "${String(name)}"`);
278
+ for (const name of names("SELECT name FROM sqlite_master WHERE type = 'table' AND sql LIKE 'CREATE VIRTUAL TABLE%'")) {
279
+ database.exec(`DROP TABLE IF EXISTS "${name}"`);
280
+ }
281
+ const kept = [];
282
+ for (const name of names("SELECT name FROM sqlite_master WHERE type = 'table' AND name NOT LIKE 'sqlite_%'")) {
283
+ if (KEPT.includes(name)) {
284
+ database.exec(`ALTER TABLE "${name}" RENAME TO "${name}_previous"`);
285
+ kept.push(name);
286
+ }
287
+ else {
288
+ database.exec(`DROP TABLE IF EXISTS "${name}"`);
289
+ }
290
+ }
291
+ return kept;
292
+ };
293
+ /** The search index fills itself: the insert trigger runs for every copied message. */
294
+ const restore = (database, table) => {
295
+ const columns = (name) => database
296
+ .prepare(`PRAGMA table_info("${name}")`)
297
+ .all()
298
+ .map((row) => String(row.name));
299
+ const now = new Set(columns(table));
300
+ const shared = columns(`${table}_previous`)
301
+ .filter((column) => now.has(column))
302
+ .map((column) => `"${column}"`)
303
+ .join(", ");
304
+ database.exec(`INSERT OR IGNORE INTO "${table}" (${shared}) SELECT ${shared} FROM "${table}_previous"`);
305
+ database.exec(`DROP TABLE "${table}_previous"`);
245
306
  };
246
307
  //# sourceMappingURL=schema.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"schema.js","sourceRoot":"","sources":["../../src/cache/schema.ts"],"names":[],"mappings":"AAEA;;;GAGG;AACH,MAAM,CAAC,MAAM,cAAc,GAAG,CAAC,CAAA;AAE/B;;;;;;;;;GASG;AACH,MAAM,UAAU,GAAG;IACjB;;;;;;;;;KASG;IACH,6EAA6E;IAE7E;;;;;;;;;OASG;IACH;;;;;;;;KAQG;IACH,gFAAgF;IAEhF;;;;OAIG;IACH;;;;KAIG;IACH,0EAA0E;IAE1E;;;;;;;OAOG;IACH;;;KAGG;IAEH;;;;;;;;;;;;;KAaG;IACH,8EAA8E;IAC9E,kFAAkF;IAClF,8FAA8F;IAC9F,4DAA4D;IAE5D;;;;OAIG;IACH;;;;;KAKG;IACH,0EAA0E;IAE1E;;;;OAIG;IACH;;;KAGG;IAEH;;;;;;;;;;;;;;;;;;;;OAoBG;IACH,qGAAqG;IACrG,gHAAgH;IAChH,0GAA0G;IAE1G;;;;;;;;;;;;;;;;OAgBG;IACH;;OAEK;IACL;;OAEK;IACL;;;OAGK;IAEL;;OAEK;IACL;;OAEK;IACL;;;OAGK;IAEL;;OAEK;IACL;;OAEK;IACL;;;OAGK;IAEL;;;;;;KAMG;CACJ,CAAA;AAED;;;;;;;;;;;;;;GAcG;AACH,+EAA+E;AAC/E,MAAM,OAAO,eAAgB,SAAQ,KAAK;CAAG;AAE7C,MAAM,CAAC,MAAM,OAAO,GAAG,CAAC,QAAuB,EAAQ,EAAE;IACvD,MAAM,OAAO,GAAG,MAAM,CACnB,QAAQ,CAAC,OAAO,CAAC,qBAAqB,CAAC,CAAC,GAAG,EAAgC,EAAE,YAAY,IAAI,CAAC,CAChG,CAAA;IAED,IAAI,OAAO,GAAG,cAAc,EAAE,CAAC;QAC7B,MAAM,IAAI,eAAe,CACvB,iDAAiD,OAAO,qBAAqB,cAAc,MAAM;YAC/F,iDAAiD,CACpD,CAAA;IACH,CAAC;IAED,IAAI,OAAO,GAAG,CAAC,IAAI,OAAO,GAAG,cAAc;QAAE,OAAO,CAAC,QAAQ,CAAC,CAAA;IAE9D,KAAK,MAAM,SAAS,IAAI,UAAU;QAAE,QAAQ,CAAC,IAAI,CAAC,SAAS,CAAC,CAAA;IAC5D,QAAQ,CAAC,IAAI,CAAC,yBAAyB,cAAc,EAAE,CAAC,CAAA;AAC1D,CAAC,CAAA;AAED;;;;;;;GAOG;AACH,MAAM,OAAO,GAAG,CAAC,QAAuB,EAAQ,EAAE;IAChD,qFAAqF;IACrF,6FAA6F;IAC7F,4FAA4F;IAC5F,oEAAoE;IACpE,EAAE;IACF,gGAAgG;IAChG,4FAA4F;IAC5F,MAAM,OAAO,GAAG,QAAQ;SACrB,OAAO,CAAC,0FAA0F,CAAC;SACnG,GAAG,EAAE,CAAA;IACR,KAAK,MAAM,EAAE,IAAI,EAAE,IAAI,OAAO;QAAE,QAAQ,CAAC,IAAI,CAAC,yBAAyB,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAA;IAEvF,MAAM,MAAM,GAAG,QAAQ;SACpB,OAAO,CAAC,kFAAkF,CAAC;SAC3F,GAAG,EAAE,CAAA;IAER,KAAK,MAAM,EAAE,IAAI,EAAE,IAAI,MAAM;QAAE,QAAQ,CAAC,IAAI,CAAC,yBAAyB,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAA;AACxF,CAAC,CAAA"}
1
+ {"version":3,"file":"schema.js","sourceRoot":"","sources":["../../src/cache/schema.ts"],"names":[],"mappings":"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"}
@@ -121,8 +121,23 @@ export interface CacheStore {
121
121
  write(chatId: Id, messages: Message[]): void;
122
122
  /** After a send, what we hold for that chat is missing the message we just added. */
123
123
  invalidate(chatId: Id): void;
124
+ /** Messages deleted in MAX: gone from reading and from search, not merely stale. */
125
+ forget(chatId: Id, messageIds: Id[]): void;
126
+ /** What `max messages transcribe` heard in a voice message, and which model heard it. */
127
+ transcript(chatId: Id, messageId: Id): {
128
+ text: string;
129
+ model: string;
130
+ } | undefined;
131
+ keepTranscript(chatId: Id, messageId: Id, text: string, model: string): void;
124
132
  /** `before` messages up to and including `time`, and `after` messages later than it, oldest first. */
125
133
  window(chatId: Id, time: number, before: number, after: number): Message[];
134
+ /** Everything held for a chat from `since` on, oldest first. */
135
+ all(chatId: Id, since?: number): Message[];
136
+ /** The windows read completely, in epoch ms, oldest first. Neighbouring windows are not merged. */
137
+ ranges(chatId: Id): {
138
+ from: number;
139
+ to: number;
140
+ }[];
126
141
  /**
127
142
  * Messages whose text contains `query`, newest first, across every chat we hold or one.
128
143
  *