@pachca/cli 2026.8.1 → 2026.8.3

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.
package/CHANGELOG.md CHANGED
@@ -1,6 +1,23 @@
1
1
  # Changelog
2
2
 
3
- ## 2026.8.1 (6 августа 2026)
3
+ ## 2026.8.3 (28 августа 2026)
4
+
5
+ - **Добавлено** (security list): Флаг `--event-key` принимает значение `user_2fa_disabled` — сотрудник отключил себе двухфакторную аутентификацию
6
+ - **Исправлено** (api): `api POST /messages --spec` и `--docs`: размер файла `size` описан 64-битным числом, у ссылки `url` указан срок действия до 7 дней, а у кнопок появились пределы — `url` до 1024 символов и не больше 32 строк в `buttons`
7
+ - **Исправлено** (api): `api POST /views/open --spec` и `--docs`: у радиокнопки блока `radio` изначальный выбор задаётся полем `checked`, как у чекбоксов, а не `selected`. У пункта списка предел `value` — 255 символов, а сам `options` стал обязательным
8
+ - **Исправлено** (api): `api POST /tasks --spec` и `api PUT /tasks/{id} --spec`: у `priority` показаны границы 1–3, которые проверяет API
9
+ - **Исправлено** (api): `api GET /tasks --spec`: у `chat_ids` и `performer_ids` показан предел в 100 идентификаторов, который проверяет API
10
+ - **Добавлено** (api): `api --spec` показывает ответ `409` у методов сотрудника, тега и закрепления сообщения и ответ `429` у `POST /messages` — суточный предел сообщений в чат
11
+
12
+ ## 2026.8.2 (12 августа 2026)
13
+
14
+ - **Добавлено** (chats list-company): Новая команда — все беседы и каналы пространства, включая закрытые, где вы не состоите. Флаг `--activity` со значением `active` или `archived` оставляет одно состояние, по умолчанию отдаются оба. Нужна роль владельца пространства, тариф «Корпорация» и право `company_chats:read`
15
+ - **Добавлено** (bots list-company): Новая команда — все боты пространства, включая недоступные вам для редактирования. У таких ботов заполнены только имя и никнейм, остальные настройки приходят пустыми. Фильтр по имени — флаг `--query`. Нужна роль владельца пространства, тариф «Корпорация» и право `company_bots:read`
16
+ - **Добавлено** (tasks list): Флаги `--status`, `--chat-ids`, `--performer-ids` и `--author-id` для фильтрации напоминаний. С `--chat-ids` выдача расширяется до всех напоминаний указанных чатов, а не только ваших
17
+ - **Добавлено** (security list): Фильтр `--event-key` принимает `company_chats_accessed` и `company_bots_accessed`
18
+ - **Исправлено**: Недостающий скоуп в ошибке `insufficient_scope` берётся из заголовка `WWW-Authenticate` — раньше его вытаскивали регуляркой из текста сообщения, и он терялся при любой смене формулировки. Разбор текста остался запасным путём
19
+
20
+ ## 2026.8.1 (7 августа 2026)
4
21
 
5
22
  - **Добавлено** (auth login): Вход через браузер — без ручной вставки токена. CLI показывает адрес и код, открывает браузер и ждёт подтверждения. Токен такого входа живёт час и продлевается сам перед каждой командой; при параллельных процессах обновляет только один, остальные ждут результат. Флаг `--no-browser` печатает адрес и код, не открывая браузер. `--token` и `PACHCA_TOKEN` работают как раньше — это путь для CI и агентов
6
23
  - **Добавлено** (auth login): Токен хранится в хранилище ключей ОС — Keychain на macOS, Secret Service на Linux, Диспетчер учётных данных на Windows; в файле конфигурации остаются только имя, почта и права. Где хранилища нет (headless-машина, контейнер), CLI сохраняет токен в файл и сообщает об этом при входе. Отказаться от хранилища: флаг `--insecure-storage` или переменная `PACHCA_SECRET_STORE=file`
package/README.md CHANGED
@@ -86,6 +86,7 @@ pachca guide # список всех сценариев
86
86
  | `pachca bots update` | Редактирование бота |
87
87
  | `pachca bots delete` | Удаление бота |
88
88
  | `pachca bots recreate-token` | Ротация токена бота |
89
+ | `pachca bots list-company` | Список ботов пространства |
89
90
  | `pachca bots list-events` | История событий |
90
91
  | `pachca bots remove-event` | Удаление события |
91
92
 
@@ -101,6 +102,7 @@ pachca guide # список всех сценариев
101
102
  | `pachca chats update` | Редактирование чата |
102
103
  | `pachca chats archive` | Архивация чата |
103
104
  | `pachca chats unarchive` | Разархивация чата |
105
+ | `pachca chats list-company` | Список чатов пространства |
104
106
 
105
107
  ### members
106
108
 
package/dist/client.js CHANGED
@@ -195,7 +195,8 @@ export async function request(opts, clientFlags) {
195
195
  if (!response.ok) {
196
196
  s?.stop('');
197
197
  const requestId = response.headers.get('x-request-id') || undefined;
198
- throw createApiError(response.status, data, requestId);
198
+ const authenticate = response.headers.get('www-authenticate') || undefined;
199
+ throw createApiError(response.status, data, requestId, authenticate);
199
200
  }
200
201
  s?.stop('');
201
202
  return { data, status: response.status, headers: response.headers };
@@ -228,12 +229,21 @@ export async function request(opts, clientFlags) {
228
229
  message: lastError?.message || 'Failed to connect to server',
229
230
  });
230
231
  }
231
- function createApiError(status, body, requestId) {
232
+ // Недостающий скоуп по RFC 6750 приходит в параметре scope заголовка
233
+ // WWW-Authenticate: Bearer error="insufficient_scope", …, scope="profile:read"
234
+ function scopeFromChallenge(header) {
235
+ if (!header)
236
+ return undefined;
237
+ return /(?:^|[\s,])scope="([^"]*)"/.exec(header)?.[1] || undefined;
238
+ }
239
+ function createApiError(status, body, requestId, authenticate) {
232
240
  // Scope refusal is a distinct case from "token is bad": the token works, it
233
241
  // just may not do this. Pull the missing scope out so the command can name it.
234
242
  if (isRecord(body) && body.error === 'insufficient_scope') {
235
243
  const description = typeof body.error_description === 'string' ? body.error_description : '';
236
- const scope = /Required:\s*([\w:]+)/.exec(description)?.[1];
244
+ // Заголовок — источник по умолчанию: там скоуп лежит отдельным параметром.
245
+ // Разбор error_description оставлен запасным путём для версий API без заголовка.
246
+ const scope = scopeFromChallenge(authenticate) || /Required:\s*([\w:]+)/.exec(description)?.[1];
237
247
  return new ApiError({
238
248
  error: description || 'Insufficient scope',
239
249
  code: status,
@@ -0,0 +1,98 @@
1
+ // Auto-generated from openapi.yaml — DO NOT EDIT
2
+ import { Flags } from '@oclif/core';
3
+ import { BaseCommand } from '../../base-command.js';
4
+ export default class BotsListCompany extends BaseCommand {
5
+ static description = "Список ботов пространства";
6
+ static examples = [
7
+ "Инвентаризация всех ботов пространства — Получи всех ботов пространства, а не только доступных для редактирования:\n $ pachca bots list-company"
8
+ ];
9
+ static scope = "company_bots:read";
10
+ static plan = "corporation";
11
+ static apiMethod = "GET";
12
+ static apiPath = "/company/bots";
13
+ static defaultColumns = ["id", "webhook"];
14
+ static args = {};
15
+ static flags = {
16
+ ...BaseCommand.baseFlags,
17
+ 'query': Flags.string({
18
+ description: "Поисковая фраза для фильтрации ботов по имени",
19
+ }),
20
+ limit: Flags.integer({
21
+ description: 'Количество результатов на страницу',
22
+ }),
23
+ cursor: Flags.string({
24
+ description: 'Курсор для следующей страницы',
25
+ }),
26
+ all: Flags.boolean({
27
+ description: 'Загрузить все страницы автоматически',
28
+ default: false,
29
+ }),
30
+ };
31
+ async run() {
32
+ const { args, flags } = await this.parse(BotsListCompany);
33
+ this.parsedFlags = flags;
34
+ if (flags.all) {
35
+ // Auto-pagination
36
+ const allData = [];
37
+ let nextCursor = undefined;
38
+ let pages = 0;
39
+ const seenCursors = new Set();
40
+ while (pages < 500) {
41
+ const query = {
42
+ query: flags['query'],
43
+ limit: flags.limit,
44
+ cursor: nextCursor,
45
+ };
46
+ const response = await this.apiRequest({ method: 'GET', path: '/company/bots', query });
47
+ const body = response.data;
48
+ const items = body.data;
49
+ if (items)
50
+ allData.push(...items);
51
+ const meta = body.meta;
52
+ const paginate = meta?.paginate;
53
+ nextCursor = paginate?.next_page;
54
+ pages++;
55
+ // Условие конца: списочные методы — has_next === false; методы поиска и /users?query= (без has_next) — пустой data
56
+ const hasNext = paginate?.has_next;
57
+ if (typeof hasNext === 'boolean') {
58
+ if (!hasNext)
59
+ break;
60
+ }
61
+ else if (!items || items.length === 0) {
62
+ break;
63
+ }
64
+ if (process.stderr.isTTY) {
65
+ const total = paginate?.total;
66
+ const progress = total ? `${allData.length} / ${total}` : String(allData.length);
67
+ process.stderr.write(`\r Загружено: ${progress}...`);
68
+ }
69
+ if (!nextCursor)
70
+ break;
71
+ if (seenCursors.has(nextCursor)) {
72
+ process.stderr.write('\n⚠ Обнаружен цикл пагинации, остановка.\n');
73
+ break;
74
+ }
75
+ seenCursors.add(nextCursor);
76
+ }
77
+ if (pages >= 500) {
78
+ process.stderr.write('\n⚠ Достигнут лимит 500 страниц.\n');
79
+ }
80
+ if (process.stderr.isTTY)
81
+ process.stderr.write('\n');
82
+ this.output(allData);
83
+ return;
84
+ }
85
+ const { data } = await this.apiRequest({
86
+ method: 'GET',
87
+ path: '/company/bots',
88
+ query: {
89
+ query: flags['query'],
90
+ limit: flags.limit,
91
+ cursor: flags.cursor,
92
+ },
93
+ });
94
+ const responseBody = (data ?? {});
95
+ const items = responseBody.data ?? responseBody;
96
+ this.output(items);
97
+ }
98
+ }
@@ -0,0 +1,99 @@
1
+ // Auto-generated from openapi.yaml — DO NOT EDIT
2
+ import { Flags } from '@oclif/core';
3
+ import { BaseCommand } from '../../base-command.js';
4
+ export default class ChatsListCompany extends BaseCommand {
5
+ static description = "Список чатов пространства";
6
+ static examples = [
7
+ "Выгрузить все чаты пространства, включая закрытые — Получи беседы и каналы всего пространства, включая закрытые, где владелец токена не состоит:\n $ pachca chats list-company"
8
+ ];
9
+ static scope = "company_chats:read";
10
+ static plan = "corporation";
11
+ static apiMethod = "GET";
12
+ static apiPath = "/company/chats";
13
+ static defaultColumns = ["id", "name", "created_at", "owner_id", "channel"];
14
+ static args = {};
15
+ static flags = {
16
+ ...BaseCommand.baseFlags,
17
+ 'activity': Flags.string({
18
+ description: "Состояние чатов. Если параметр не указан, возвращаются и активные, и архивные чаты.",
19
+ options: ["active", "archived"],
20
+ }),
21
+ limit: Flags.integer({
22
+ description: 'Количество результатов на страницу',
23
+ }),
24
+ cursor: Flags.string({
25
+ description: 'Курсор для следующей страницы',
26
+ }),
27
+ all: Flags.boolean({
28
+ description: 'Загрузить все страницы автоматически',
29
+ default: false,
30
+ }),
31
+ };
32
+ async run() {
33
+ const { args, flags } = await this.parse(ChatsListCompany);
34
+ this.parsedFlags = flags;
35
+ if (flags.all) {
36
+ // Auto-pagination
37
+ const allData = [];
38
+ let nextCursor = undefined;
39
+ let pages = 0;
40
+ const seenCursors = new Set();
41
+ while (pages < 500) {
42
+ const query = {
43
+ activity: flags['activity'],
44
+ limit: flags.limit,
45
+ cursor: nextCursor,
46
+ };
47
+ const response = await this.apiRequest({ method: 'GET', path: '/company/chats', query });
48
+ const body = response.data;
49
+ const items = body.data;
50
+ if (items)
51
+ allData.push(...items);
52
+ const meta = body.meta;
53
+ const paginate = meta?.paginate;
54
+ nextCursor = paginate?.next_page;
55
+ pages++;
56
+ // Условие конца: списочные методы — has_next === false; методы поиска и /users?query= (без has_next) — пустой data
57
+ const hasNext = paginate?.has_next;
58
+ if (typeof hasNext === 'boolean') {
59
+ if (!hasNext)
60
+ break;
61
+ }
62
+ else if (!items || items.length === 0) {
63
+ break;
64
+ }
65
+ if (process.stderr.isTTY) {
66
+ const total = paginate?.total;
67
+ const progress = total ? `${allData.length} / ${total}` : String(allData.length);
68
+ process.stderr.write(`\r Загружено: ${progress}...`);
69
+ }
70
+ if (!nextCursor)
71
+ break;
72
+ if (seenCursors.has(nextCursor)) {
73
+ process.stderr.write('\n⚠ Обнаружен цикл пагинации, остановка.\n');
74
+ break;
75
+ }
76
+ seenCursors.add(nextCursor);
77
+ }
78
+ if (pages >= 500) {
79
+ process.stderr.write('\n⚠ Достигнут лимит 500 страниц.\n');
80
+ }
81
+ if (process.stderr.isTTY)
82
+ process.stderr.write('\n');
83
+ this.output(allData);
84
+ return;
85
+ }
86
+ const { data } = await this.apiRequest({
87
+ method: 'GET',
88
+ path: '/company/chats',
89
+ query: {
90
+ activity: flags['activity'],
91
+ limit: flags.limit,
92
+ cursor: flags.cursor,
93
+ },
94
+ });
95
+ const responseBody = (data ?? {});
96
+ const items = responseBody.data ?? responseBody;
97
+ this.output(items);
98
+ }
99
+ }
@@ -6,7 +6,7 @@ export default class ChatsList extends BaseCommand {
6
6
  static examples = [
7
7
  "Найти активные чаты за период — Получи чаты с активностью после указанной даты:\n $ pachca chats list",
8
8
  "Найти и заархивировать неактивные чаты — Получи чаты без активности с нужной даты:\n $ pachca chats list",
9
- "Создать гостя в чат — Выбери активный чат, в который добавить гостя — узнай его ID:\n $ pachca chats list"
9
+ "Выгрузить все чаты пространства, включая закрытые — Если метод ответил `403` — у токена нет скоупа `company_chats:read`, либо владелец токена не владелец пространства, либо у пространства нет тарифа «Корпорация». Тогда возьми открытые чаты обычным списком:\n $ pachca chats list"
10
10
  ];
11
11
  static scope = "chats:read";
12
12
  static apiMethod = "GET";
@@ -6,7 +6,7 @@ export default class SecurityList extends BaseCommand {
6
6
  static examples = [
7
7
  "Получить журнал аудита событий — Получи журнал аудита:\n $ pachca security list",
8
8
  "Мониторинг подозрительных входов — Получи события неудачных 2FA за период:\n $ pachca security list",
9
- "Экспорт логов за период — Получи все события за период с пагинацией:\n $ pachca security list"
9
+ "Мониторинг подозрительных входов — Повтори запрос с `--event-key=user_2fa_disabled` — отключение второго фактора у сотрудника:\n $ pachca security list"
10
10
  ];
11
11
  static scope = "audit_events:read";
12
12
  static plan = "corporation";
@@ -24,7 +24,7 @@ export default class SecurityList extends BaseCommand {
24
24
  }),
25
25
  'event-key': Flags.string({
26
26
  description: "Фильтр по конкретному типу события",
27
- options: ["user_login", "user_logout", "user_2fa_fail", "user_2fa_success", "user_created", "user_deleted", "user_role_changed", "user_updated", "tag_created", "tag_deleted", "user_added_to_tag", "user_removed_from_tag", "chat_created", "chat_renamed", "chat_permission_changed", "user_chat_join", "user_chat_leave", "tag_added_to_chat", "tag_removed_from_chat", "message_updated", "message_deleted", "message_created", "reaction_created", "reaction_deleted", "thread_created", "access_token_created", "access_token_updated", "access_token_destroy", "kms_encrypt", "kms_decrypt", "audit_events_accessed", "dlp_violation_detected", "search_users_api", "search_chats_api", "search_messages_api", "bot_scopes_updated", "bot_webhook_settings_updated", "bot_token_recreated", "bot_deleted", "bot_oauth_client_updated", "oauth_authorization_granted", "oauth_authorization_revoked", "video_call_started", "video_call_finished", "video_call_recording_ready"],
27
+ options: ["user_login", "user_logout", "user_2fa_fail", "user_2fa_success", "user_2fa_disabled", "user_created", "user_deleted", "user_role_changed", "user_updated", "tag_created", "tag_deleted", "user_added_to_tag", "user_removed_from_tag", "chat_created", "chat_renamed", "chat_permission_changed", "user_chat_join", "user_chat_leave", "tag_added_to_chat", "tag_removed_from_chat", "message_updated", "message_deleted", "message_created", "reaction_created", "reaction_deleted", "thread_created", "access_token_created", "access_token_updated", "access_token_destroy", "kms_encrypt", "kms_decrypt", "audit_events_accessed", "company_chats_accessed", "company_bots_accessed", "dlp_violation_detected", "search_users_api", "search_chats_api", "search_messages_api", "bot_scopes_updated", "bot_webhook_settings_updated", "bot_token_recreated", "bot_deleted", "bot_oauth_client_updated", "oauth_authorization_granted", "oauth_authorization_revoked", "video_call_started", "video_call_finished", "video_call_recording_ready"],
28
28
  }),
29
29
  'actor-id': Flags.string({
30
30
  description: "Идентификатор пользователя, выполнившего действие",
@@ -4,7 +4,9 @@ import { BaseCommand } from '../../base-command.js';
4
4
  export default class TasksList extends BaseCommand {
5
5
  static description = "Список напоминаний";
6
6
  static examples = [
7
- "Получить список предстоящих задач — Получи все задачи, фильтруй по `status` на клиенте:\n $ pachca tasks list"
7
+ "Получить список предстоящих задач — Получи невыполненные задачи:\n $ pachca tasks list",
8
+ "Собрать задачи по чатам проекта — Запроси задачи нужных чатов:\n $ pachca tasks list",
9
+ "Собрать задачи по чатам проекта — Сузь выборку по автору, ответственным и состоянию:\n $ pachca tasks list"
8
10
  ];
9
11
  static scope = "tasks:read";
10
12
  static apiMethod = "GET";
@@ -13,6 +15,19 @@ export default class TasksList extends BaseCommand {
13
15
  static args = {};
14
16
  static flags = {
15
17
  ...BaseCommand.baseFlags,
18
+ 'status': Flags.string({
19
+ description: "Фильтр по состоянию. Если параметр не указан, возвращаются напоминания в любом состоянии.",
20
+ options: ["done", "undone"],
21
+ }),
22
+ 'chat-ids': Flags.string({
23
+ description: "Фильтр по чатам, к которым привязаны напоминания. Доступными считаются чаты, где вы состоите участником, открытые беседы и каналы пространства, а также треды, доступные вам по родительскому чату. Остальные переданные идентификаторы игнорируются без ошибки. Не более 100 идентификаторов." + " (через запятую)",
24
+ }),
25
+ 'performer-ids': Flags.string({
26
+ description: "Фильтр по ответственным за напоминание. Не более 100 идентификаторов." + " (через запятую)",
27
+ }),
28
+ 'author-id': Flags.integer({
29
+ description: "Фильтр по автору напоминания",
30
+ }),
16
31
  limit: Flags.integer({
17
32
  description: 'Количество результатов на страницу',
18
33
  }),
@@ -35,6 +50,10 @@ export default class TasksList extends BaseCommand {
35
50
  const seenCursors = new Set();
36
51
  while (pages < 500) {
37
52
  const query = {
53
+ status: flags['status'],
54
+ 'chat_ids': flags['chat-ids']?.split(','),
55
+ 'performer_ids': flags['performer-ids']?.split(','),
56
+ 'author_id': flags['author-id'],
38
57
  limit: flags.limit,
39
58
  cursor: nextCursor,
40
59
  };
@@ -81,6 +100,10 @@ export default class TasksList extends BaseCommand {
81
100
  method: 'GET',
82
101
  path: '/tasks',
83
102
  query: {
103
+ status: flags['status'],
104
+ 'chat_ids': flags['chat-ids']?.split(','),
105
+ 'performer_ids': flags['performer-ids']?.split(','),
106
+ 'author_id': flags['author-id'],
84
107
  limit: flags.limit,
85
108
  cursor: flags.cursor,
86
109
  },
@@ -23,6 +23,8 @@
23
23
  "members:remove": "Исключение пользователя",
24
24
  "members:update": "Редактирование роли",
25
25
  "chats:unarchive": "Разархивация чата",
26
+ "bots:list-company": "Список ботов пространства",
27
+ "chats:list-company": "Список чатов пространства",
26
28
  "custom-properties:list": "Список дополнительных полей",
27
29
  "files:direct-url": "Загрузка файла",
28
30
  "group-tags:create": "Новый тег",
@@ -1,7 +1,73 @@
1
1
  [
2
+ {
3
+ "version": "2026.8.3",
4
+ "date": "28 августа 2026",
5
+ "changes": [
6
+ {
7
+ "type": "+",
8
+ "command": "security list",
9
+ "description": "Флаг `--event-key` принимает значение `user_2fa_disabled` — сотрудник отключил себе двухфакторную аутентификацию"
10
+ },
11
+ {
12
+ "type": "-",
13
+ "command": "api",
14
+ "description": "`api POST /messages --spec` и `--docs`: размер файла `size` описан 64-битным числом, у ссылки `url` указан срок действия до 7 дней, а у кнопок появились пределы — `url` до 1024 символов и не больше 32 строк в `buttons`"
15
+ },
16
+ {
17
+ "type": "-",
18
+ "command": "api",
19
+ "description": "`api POST /views/open --spec` и `--docs`: у радиокнопки блока `radio` изначальный выбор задаётся полем `checked`, как у чекбоксов, а не `selected`. У пункта списка предел `value` — 255 символов, а сам `options` стал обязательным"
20
+ },
21
+ {
22
+ "type": "-",
23
+ "command": "api",
24
+ "description": "`api POST /tasks --spec` и `api PUT /tasks/{id} --spec`: у `priority` показаны границы 1–3, которые проверяет API"
25
+ },
26
+ {
27
+ "type": "-",
28
+ "command": "api",
29
+ "description": "`api GET /tasks --spec`: у `chat_ids` и `performer_ids` показан предел в 100 идентификаторов, который проверяет API"
30
+ },
31
+ {
32
+ "type": "+",
33
+ "command": "api",
34
+ "description": "`api --spec` показывает ответ `409` у методов сотрудника, тега и закрепления сообщения и ответ `429` у `POST /messages` — суточный предел сообщений в чат"
35
+ }
36
+ ]
37
+ },
38
+ {
39
+ "version": "2026.8.2",
40
+ "date": "12 августа 2026",
41
+ "changes": [
42
+ {
43
+ "type": "+",
44
+ "command": "chats list-company",
45
+ "description": "Новая команда — все беседы и каналы пространства, включая закрытые, где вы не состоите. Флаг `--activity` со значением `active` или `archived` оставляет одно состояние, по умолчанию отдаются оба. Нужна роль владельца пространства, тариф «Корпорация» и право `company_chats:read`"
46
+ },
47
+ {
48
+ "type": "+",
49
+ "command": "bots list-company",
50
+ "description": "Новая команда — все боты пространства, включая недоступные вам для редактирования. У таких ботов заполнены только имя и никнейм, остальные настройки приходят пустыми. Фильтр по имени — флаг `--query`. Нужна роль владельца пространства, тариф «Корпорация» и право `company_bots:read`"
51
+ },
52
+ {
53
+ "type": "+",
54
+ "command": "tasks list",
55
+ "description": "Флаги `--status`, `--chat-ids`, `--performer-ids` и `--author-id` для фильтрации напоминаний. С `--chat-ids` выдача расширяется до всех напоминаний указанных чатов, а не только ваших"
56
+ },
57
+ {
58
+ "type": "+",
59
+ "command": "security list",
60
+ "description": "Фильтр `--event-key` принимает `company_chats_accessed` и `company_bots_accessed`"
61
+ },
62
+ {
63
+ "type": "-",
64
+ "description": "Недостающий скоуп в ошибке `insufficient_scope` берётся из заголовка `WWW-Authenticate` — раньше его вытаскивали регуляркой из текста сообщения, и он терялся при любой смене формулировки. Разбор текста остался запасным путём"
65
+ }
66
+ ]
67
+ },
2
68
  {
3
69
  "version": "2026.8.1",
4
- "date": "6 августа 2026",
70
+ "date": "7 августа 2026",
5
71
  "changes": [
6
72
  {
7
73
  "type": "+",