@pachca/cli 2026.10.2 → 2026.10.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.
Files changed (99) hide show
  1. package/CHANGELOG.md +16 -0
  2. package/README.md +24 -24
  3. package/README.template.md +14 -23
  4. package/dist/base-command.js +1 -1
  5. package/dist/client.js +56 -32
  6. package/dist/commands/api.js +1 -1
  7. package/dist/commands/auth/logout.js +23 -12
  8. package/dist/commands/bots/create-token.js +0 -3
  9. package/dist/commands/bots/create.js +0 -4
  10. package/dist/commands/bots/delete.js +0 -3
  11. package/dist/commands/bots/get-stream.js +23 -0
  12. package/dist/commands/bots/list-company.js +0 -3
  13. package/dist/commands/bots/list-events.js +0 -3
  14. package/dist/commands/bots/list-scopes.js +0 -3
  15. package/dist/commands/bots/list.js +0 -3
  16. package/dist/commands/bots/recreate-token-self.js +0 -3
  17. package/dist/commands/bots/recreate-token.js +0 -3
  18. package/dist/commands/bots/reissue-token.js +0 -3
  19. package/dist/commands/bots/remove-event.js +0 -3
  20. package/dist/commands/bots/update-webhook.js +0 -3
  21. package/dist/commands/bots/update.js +0 -5
  22. package/dist/commands/chats/archive.js +0 -4
  23. package/dist/commands/chats/create.js +3 -6
  24. package/dist/commands/chats/download-export.js +0 -3
  25. package/dist/commands/chats/list-company.js +0 -3
  26. package/dist/commands/chats/list.js +1 -6
  27. package/dist/commands/chats/remove-avatar.js +0 -3
  28. package/dist/commands/chats/request-export.js +0 -3
  29. package/dist/commands/chats/unarchive.js +0 -3
  30. package/dist/commands/chats/update-avatar.js +0 -3
  31. package/dist/commands/chats/update.js +0 -3
  32. package/dist/commands/custom-properties/list.js +0 -3
  33. package/dist/commands/drafts/create.js +1 -4
  34. package/dist/commands/drafts/delete.js +0 -3
  35. package/dist/commands/drafts/update.js +1 -1
  36. package/dist/commands/files/uploads.js +0 -3
  37. package/dist/commands/group-tags/create.js +0 -3
  38. package/dist/commands/group-tags/list-users.js +0 -3
  39. package/dist/commands/group-tags/list.js +0 -3
  40. package/dist/commands/important-messages/create.js +64 -0
  41. package/dist/commands/important-messages/delete.js +42 -0
  42. package/dist/commands/important-messages/list.js +89 -0
  43. package/dist/commands/members/add.js +0 -5
  44. package/dist/commands/members/leave.js +0 -3
  45. package/dist/commands/members/list.js +0 -4
  46. package/dist/commands/members/remove.js +0 -3
  47. package/dist/commands/members/update.js +0 -3
  48. package/dist/commands/messages/create.js +3 -8
  49. package/dist/commands/messages/delete.js +0 -3
  50. package/dist/commands/messages/get.js +0 -5
  51. package/dist/commands/messages/list.js +1 -5
  52. package/dist/commands/messages/pin.js +0 -3
  53. package/dist/commands/messages/unfurl.js +0 -3
  54. package/dist/commands/messages/unpin.js +0 -3
  55. package/dist/commands/messages/update.js +2 -7
  56. package/dist/commands/oauth/token-info.js +0 -3
  57. package/dist/commands/profile/delete-avatar.js +0 -3
  58. package/dist/commands/profile/delete-status.js +0 -3
  59. package/dist/commands/profile/get.js +0 -4
  60. package/dist/commands/profile/update-avatar.js +0 -3
  61. package/dist/commands/profile/update-status.js +1 -4
  62. package/dist/commands/reactions/add.js +3 -6
  63. package/dist/commands/reactions/remove.js +3 -6
  64. package/dist/commands/read-member/list-readers.js +0 -3
  65. package/dist/commands/search/list-chats.js +0 -5
  66. package/dist/commands/search/list-messages.js +0 -3
  67. package/dist/commands/search/list-users.js +0 -5
  68. package/dist/commands/security/list.js +0 -5
  69. package/dist/commands/tasks/create.js +1 -6
  70. package/dist/commands/tasks/delete.js +0 -3
  71. package/dist/commands/tasks/get.js +0 -3
  72. package/dist/commands/tasks/list.js +0 -5
  73. package/dist/commands/tasks/update.js +2 -6
  74. package/dist/commands/threads/add.js +0 -4
  75. package/dist/commands/threads/list.js +0 -4
  76. package/dist/commands/users/create.js +0 -5
  77. package/dist/commands/users/delete.js +0 -3
  78. package/dist/commands/users/get-status.js +0 -3
  79. package/dist/commands/users/get.js +0 -3
  80. package/dist/commands/users/list.js +0 -5
  81. package/dist/commands/users/remove-avatar.js +0 -3
  82. package/dist/commands/users/remove-status.js +0 -3
  83. package/dist/commands/users/update-avatar.js +0 -3
  84. package/dist/commands/users/update-status.js +1 -4
  85. package/dist/commands/users/update.js +4 -4
  86. package/dist/commands/views/open.js +0 -5
  87. package/dist/commands/views/submit-response.js +0 -3
  88. package/dist/data/alternatives.json +5 -9
  89. package/dist/data/changelog.json +66 -0
  90. package/dist/data/endpoints.json +1 -1
  91. package/dist/help.js +0 -1
  92. package/dist/hooks/command-not-found.js +0 -1
  93. package/dist/hooks/init.js +5 -1
  94. package/dist/oauth.js +9 -4
  95. package/dist/token-refresh.js +61 -14
  96. package/oclif.manifest.json +1 -1
  97. package/package.json +1 -1
  98. package/dist/commands/guide.js +0 -90
  99. package/dist/data/workflows.json +0 -1058
package/CHANGELOG.md CHANGED
@@ -1,5 +1,21 @@
1
1
  # Changelog
2
2
 
3
+ ## 2026.10.3 (28 октября 2026)
4
+
5
+ - **Изменено** (guide): Команда убрана вместе со сценариями: как решать задачи в Пачке, агенту рассказывают скиллы (https://dev.pachca.com/guides/mcp/plugin), а флаги каждой команды показывает `pachca <команда> --help`
6
+ - **Изменено**: Из `--help` команд убран блок примеров со сценариями
7
+ - **Изменено** (api): `api ls` и справка `--describe`, `--spec`, `--docs` работают без токена: они читают встроенную спецификацию и в API не обращаются
8
+ - **Добавлено** (important-messages list, important-messages create, important-messages delete): Новые команды — раздел «Важное» владельца токена: список отмеченных сообщений, добавление сообщения и удаление из раздела
9
+ - **Добавлено** (bots get-stream): Новая команда — ссылка на поток событий бота
10
+ - **Добавлено** (users update): Значение `guest` у `--role` вместе с флагом `--chat-ids` переводит сотрудника в гости
11
+ - **Добавлено** (chats create): Команда предупреждает в stderr об идентификаторах из `--group-tag-ids`, которых нет среди тегов пространства
12
+ - **Добавлено**: В выводе команд с сообщениями появилось поле `important`: сообщение в разделе «Важное» владельца токена
13
+ - **Изменено** (auth logout): После входа через браузер команда закрывает вход на сервере и при истёкшем токене: сначала обновляет его, потом отзывает свежий
14
+ - **Изменено** (auth logout): `token_revoked: true` значит, что вход на сервере закрыт, в том числе если его закрыли раньше, а при `false` закрыть вход не удалось, и его нужно отозвать в списке «Токены CLI»
15
+ - **Изменено**: Ответы `500`, `502`, `503`, `504` и сетевую ошибку CLI повторяет до трёх раз только у запросов GET, PUT и DELETE, POST и PATCH после них не повторяются, а `504` с кодом `timeout` (поиск не уложился по времени) приходит ошибкой сразу
16
+ - **Изменено**: Подсказки при опечатке в команде предлагают только существующие команды, без `pachca skill …`
17
+ - **Исправлено**: Описание флага `--no-retry` уточнено: он отключает повторы после `429`, `5xx` и сетевой ошибки
18
+
3
19
  ## 2026.10.2 (21 октября 2026)
4
20
 
5
21
  - **Добавлено** (security list): Флаг `--event-key` принимает события входа по QR-коду: `login_grant_created`, `login_grant_approved` и `login_grant_denied`
package/README.md CHANGED
@@ -14,31 +14,33 @@ npx -y @pachca/cli users list
14
14
  ## Быстрый старт
15
15
 
16
16
  ```
17
- pachca auth login # сохранить токен
17
+ pachca auth login # вход через браузер
18
18
  pachca users list # список сотрудников
19
19
  pachca messages create --entity-id 123 --content "Привет!"
20
20
  ```
21
21
 
22
- Получить токен: https://dev.pachca.com/guides/authorization
22
+ Какой токен выбрать: https://dev.pachca.com/api/authorization
23
23
 
24
24
  ## Использование в агентах (Claude, Cursor и др.)
25
25
 
26
- Если агент устанавливает CLI по просьбе пользователя и токена ещё нет — агент должен запросить его у пользователя:
26
+ Вход запускает сотрудник в своём терминале командой `pachca auth login` и подтверждает в браузере. Агент проверяет вход командой `pachca auth status`. Токен в разговоре агенту не передают.
27
27
 
28
28
  ```
29
- # Агент спрашивает: "Нужен API-токен Пачки."
30
- # "Получите его на https://dev.pachca.com/guides/authorization"
31
- pachca auth login --token <токен от пользователя>
29
+ pachca auth status # есть ли вход и под кем
30
+ pachca api ls # справка по API без входа и без токена
31
+ pachca api POST /messages --describe
32
+ ```
32
33
 
33
- # Дальше команды работают без токена в каждой строке
34
- pachca users list
35
- pachca messages create --entity-id 123 --content "Привет!"
34
+ В CI и у агентов без сотрудника токен приходит из переменной окружения, `auth login` не нужен:
35
+
36
+ ```
37
+ PACHCA_TOKEN=$TOKEN pachca users list --no-input
36
38
  ```
37
39
 
38
- Если токен уже есть в окружении — `auth login` не нужен:
40
+ Готовый токен сохраняют через stdin, а не аргументом: аргумент остаётся в истории оболочки и в списке процессов.
39
41
 
40
42
  ```
41
- PACHCA_TOKEN=$TOKEN pachca users list
43
+ pbpaste | pachca auth login --token -
42
44
  ```
43
45
 
44
46
  ## Профили (несколько токенов)
@@ -52,18 +54,7 @@ pachca auth list
52
54
  pachca auth switch bot-notify
53
55
  ```
54
56
 
55
- Токены хранятся в `~/.config/pachca/config.toml` (chmod 600).
56
- В CI — через переменную окружения PACHCA_TOKEN.
57
-
58
- ## Поиск по сценариям
59
-
60
- Не знаете какую команду использовать? Поищите по задаче:
61
-
62
- ```
63
- pachca guide "отправить файл"
64
- pachca guide "создать беседу и добавить участников"
65
- pachca guide # список всех сценариев
66
- ```
57
+ Токен хранится в хранилище ключей ОС, в `~/.config/pachca/config.toml` только имя, почта и права. Где хранилища нет, токен лежит в этом файле, CLI говорит об этом при входе.
67
58
 
68
59
  <!-- AUTO:COMMANDS -->
69
60
  ## Команды
@@ -97,6 +88,7 @@ pachca guide # список всех сценариев
97
88
  | `pachca bots reissue-token` | Перевыпуск токена бота |
98
89
  | `pachca bots list-company` | Список ботов пространства |
99
90
  | `pachca bots list-events` | История событий |
91
+ | `pachca bots get-stream` | Поток событий |
100
92
  | `pachca bots remove-event` | Удаление события истории |
101
93
 
102
94
  ### chats
@@ -163,6 +155,14 @@ pachca guide # список всех сценариев
163
155
  | `pachca group-tags delete` | Удаление тега |
164
156
  | `pachca group-tags list-users` | Список сотрудников тега |
165
157
 
158
+ ### important-messages
159
+
160
+ | Команда | Описание |
161
+ |---------|---------|
162
+ | `pachca important-messages list` | Список важных сообщений |
163
+ | `pachca important-messages create` | Добавление сообщения в важные |
164
+ | `pachca important-messages delete` | Удаление сообщения из важных |
165
+
166
166
  ### messages
167
167
 
168
168
  | Команда | Описание |
@@ -275,7 +275,7 @@ pachca guide # список всех сценариев
275
275
  | `--no-input` | | Отключить интерактивные промпты |
276
276
  | `--dry-run` | | Показать запрос без отправки |
277
277
  | `--timeout <value>` | | Таймаут запроса в секундах (по умолчанию 30) |
278
- | `--no-retry` | | Отключить авто-retry при 429/503 |
278
+ | `--no-retry` | | Отключить автоповтор при 429, 5xx и сетевых ошибках |
279
279
  | `--plain` | | Плоский вывод: TSV без заголовка, ID первым, без цвета (для скриптов) |
280
280
 
281
281
  <!-- AUTO:FLAGS:END -->
@@ -14,31 +14,33 @@ npx -y @pachca/cli users list
14
14
  ## Быстрый старт
15
15
 
16
16
  ```
17
- pachca auth login # сохранить токен
17
+ pachca auth login # вход через браузер
18
18
  pachca users list # список сотрудников
19
19
  pachca messages create --entity-id 123 --content "Привет!"
20
20
  ```
21
21
 
22
- Получить токен: https://dev.pachca.com/guides/authorization
22
+ Какой токен выбрать: https://dev.pachca.com/api/authorization
23
23
 
24
24
  ## Использование в агентах (Claude, Cursor и др.)
25
25
 
26
- Если агент устанавливает CLI по просьбе пользователя и токена ещё нет — агент должен запросить его у пользователя:
26
+ Вход запускает сотрудник в своём терминале командой `pachca auth login` и подтверждает в браузере. Агент проверяет вход командой `pachca auth status`. Токен в разговоре агенту не передают.
27
27
 
28
28
  ```
29
- # Агент спрашивает: "Нужен API-токен Пачки."
30
- # "Получите его на https://dev.pachca.com/guides/authorization"
31
- pachca auth login --token <токен от пользователя>
29
+ pachca auth status # есть ли вход и под кем
30
+ pachca api ls # справка по API без входа и без токена
31
+ pachca api POST /messages --describe
32
+ ```
33
+
34
+ В CI и у агентов без сотрудника токен приходит из переменной окружения, `auth login` не нужен:
32
35
 
33
- # Дальше команды работают без токена в каждой строке
34
- pachca users list
35
- pachca messages create --entity-id 123 --content "Привет!"
36
+ ```
37
+ PACHCA_TOKEN=$TOKEN pachca users list --no-input
36
38
  ```
37
39
 
38
- Если токен уже есть в окружении — `auth login` не нужен:
40
+ Готовый токен сохраняют через stdin, а не аргументом: аргумент остаётся в истории оболочки и в списке процессов.
39
41
 
40
42
  ```
41
- PACHCA_TOKEN=$TOKEN pachca users list
43
+ pbpaste | pachca auth login --token -
42
44
  ```
43
45
 
44
46
  ## Профили (несколько токенов)
@@ -52,18 +54,7 @@ pachca auth list
52
54
  pachca auth switch bot-notify
53
55
  ```
54
56
 
55
- Токены хранятся в `~/.config/pachca/config.toml` (chmod 600).
56
- В CI — через переменную окружения PACHCA_TOKEN.
57
-
58
- ## Поиск по сценариям
59
-
60
- Не знаете какую команду использовать? Поищите по задаче:
61
-
62
- ```
63
- pachca guide "отправить файл"
64
- pachca guide "создать беседу и добавить участников"
65
- pachca guide # список всех сценариев
66
- ```
57
+ Токен хранится в хранилище ключей ОС, в `~/.config/pachca/config.toml` только имя, почта и права. Где хранилища нет, токен лежит в этом файле, CLI говорит об этом при входе.
67
58
 
68
59
  <!-- AUTO:COMMANDS -->
69
60
  <!-- AUTO:COMMANDS:END -->
@@ -57,7 +57,7 @@ export class BaseCommand extends Command {
57
57
  min: 1,
58
58
  }),
59
59
  'no-retry': Flags.boolean({
60
- description: 'Disable auto-retry on 429/503',
60
+ description: 'Disable auto-retry on 429, 5xx and network errors',
61
61
  default: false,
62
62
  }),
63
63
  json: Flags.boolean({
package/dist/client.js CHANGED
@@ -24,6 +24,11 @@ export function getBaseUrl() {
24
24
  }
25
25
  const MAX_RETRIES = 3;
26
26
  const DEFAULT_TIMEOUT = 30;
27
+ const RETRYABLE_5XX = new Set([500, 502, 503, 504]);
28
+ // A 5xx says nothing about whether the server already applied the request, and
29
+ // the API has no idempotency key: a retried POST /messages sends the message
30
+ // twice. These methods are safe to repeat, the same set as in the SDKs.
31
+ const IDEMPOTENT_METHODS = new Set(['GET', 'HEAD', 'OPTIONS', 'PUT', 'DELETE']);
27
32
  function buildUrl(apiPath, query) {
28
33
  const base = apiPath.startsWith('http://') || apiPath.startsWith('https://')
29
34
  ? apiPath
@@ -101,9 +106,11 @@ export async function request(opts, clientFlags) {
101
106
  // means by `--timeout 0`. The env var already ignored 0; make the flag agree.
102
107
  const timeoutSeconds = requestedTimeout > 0 ? requestedTimeout : DEFAULT_TIMEOUT;
103
108
  const noRetry = opts.noRetry ?? clientFlags?.['no-retry'] ?? false;
104
- // Retrying a request the server may already have committed duplicates it.
105
- // Only these methods are safe to repeat blindly.
106
- const safeMethod = ['GET', 'HEAD', 'OPTIONS', 'PUT'].includes(opts.method);
109
+ const method = opts.method.toUpperCase();
110
+ // The same set is retried after a 5xx and after a network error. A repeated
111
+ // DELETE whose first attempt went through answers 404, which the caller sees
112
+ // as an error, but it never deletes anything twice.
113
+ const idempotent = IDEMPOTENT_METHODS.has(method);
107
114
  const fetchHeaders = {
108
115
  ...(opts.noAuth ? {} : { Authorization: `Bearer ${opts.token}` }),
109
116
  Accept: 'application/json',
@@ -178,35 +185,32 @@ export async function request(opts, clientFlags) {
178
185
  continue;
179
186
  }
180
187
  }
181
- // Handle 503 with backoff. Unlike 429, a 503 from a proxy or a draining
182
- // load balancer says nothing about whether the backend already committed
183
- // the write — retrying a POST there duplicates the message.
184
- if (response.status === 503 && safeMethod && !noRetry && attempt < MAX_RETRIES) {
185
- const baseMs = 1000;
186
- const waitMs = baseMs * Math.pow(2, attempt) + Math.random() * baseMs;
187
- if (clientFlags?.verbose) {
188
- process.stderr.write(`${ansis.dim(`⏳ Service unavailable, retrying in ${Math.round(waitMs)}ms...`)}\n`);
189
- }
190
- await sleep(waitMs);
191
- continue;
192
- }
193
- // Parse response body
194
- const contentType = response.headers.get('content-type') || '';
188
+ // Handle 5xx with backoff, for idempotent methods only. Unlike 429, a 5xx
189
+ // from a proxy or a draining load balancer says nothing about whether the
190
+ // backend already committed the write — retrying a POST there duplicates
191
+ // the message.
195
192
  let data;
196
- if (response.status === 204 || response.headers.get('content-length') === '0') {
197
- data = null;
198
- }
199
- else if (opts.isBinary && response.ok) {
200
- // Тело файла нельзя читать как текст: любой байт вне UTF-8 портится
201
- // при декодировании, и сохранённый файл оказывается битым.
202
- data = Buffer.from(await response.arrayBuffer());
203
- }
204
- else if (contentType.includes('application/json')) {
205
- data = await response.json();
206
- }
207
- else {
208
- data = await response.text();
193
+ let bodyRead = false;
194
+ if (RETRYABLE_5XX.has(response.status) && idempotent && !noRetry && attempt < MAX_RETRIES) {
195
+ // A 504 with the code `timeout` is a search the server stopped because
196
+ // the query is too heavy: the same query would time out again, so it
197
+ // goes back to the caller at once. A 504 from a proxy has no such code.
198
+ if (response.status === 504) {
199
+ data = await readBody(response, opts.isBinary).catch(() => null);
200
+ bodyRead = true;
201
+ }
202
+ if (!isQueryTimeout(response.status, data)) {
203
+ const baseMs = 1000;
204
+ const waitMs = baseMs * Math.pow(2, attempt) + Math.random() * baseMs;
205
+ if (clientFlags?.verbose) {
206
+ process.stderr.write(`${ansis.dim(`⏳ Server error ${response.status}, retrying in ${Math.round(waitMs)}ms...`)}\n`);
207
+ }
208
+ await sleep(waitMs);
209
+ continue;
210
+ }
209
211
  }
212
+ if (!bodyRead)
213
+ data = await readBody(response, opts.isBinary);
210
214
  // Handle errors
211
215
  if (!response.ok) {
212
216
  s?.stop('');
@@ -231,8 +235,7 @@ export async function request(opts, clientFlags) {
231
235
  });
232
236
  }
233
237
  lastError = error;
234
- // Only retry network errors for idempotent methods (not POST/DELETE)
235
- if (!safeMethod || noRetry || attempt >= MAX_RETRIES) {
238
+ if (!idempotent || noRetry || attempt >= MAX_RETRIES) {
236
239
  break;
237
240
  }
238
241
  }
@@ -245,6 +248,27 @@ export async function request(opts, clientFlags) {
245
248
  message: lastError?.message || 'Failed to connect to server',
246
249
  });
247
250
  }
251
+ async function readBody(response, isBinary) {
252
+ const contentType = response.headers.get('content-type') || '';
253
+ if (response.status === 204 || response.headers.get('content-length') === '0') {
254
+ return null;
255
+ }
256
+ if (isBinary && response.ok) {
257
+ // Тело файла нельзя читать как текст: любой байт вне UTF-8 портится
258
+ // при декодировании, и сохранённый файл оказывается битым.
259
+ return Buffer.from(await response.arrayBuffer());
260
+ }
261
+ if (contentType.includes('application/json')) {
262
+ return response.json();
263
+ }
264
+ return response.text();
265
+ }
266
+ function isQueryTimeout(status, body) {
267
+ return (status === 504 &&
268
+ isRecord(body) &&
269
+ Array.isArray(body.errors) &&
270
+ body.errors.some((error) => isRecord(error) && error.code === 'timeout'));
271
+ }
248
272
  // Недостающий скоуп по RFC 6750 приходит в параметре scope заголовка
249
273
  // WWW-Authenticate: Bearer error="insufficient_scope", …, scope="profile:read"
250
274
  function scopeFromChallenge(header) {
@@ -160,7 +160,7 @@ export default class Api extends BaseCommand {
160
160
  const { outputData } = await import('../output.js');
161
161
  outputData(data, { ...this.getOutputOptions(), format: outFormat });
162
162
  }
163
- /** Load the build-time endpoint index (mirrors guide.ts workflows.json loading). */
163
+ /** Load the build-time endpoint index. */
164
164
  async loadEndpoints() {
165
165
  try {
166
166
  const data = await import('../data/endpoints.json', { with: { type: 'json' } });
@@ -2,7 +2,7 @@ import { Args } from '@oclif/core';
2
2
  import * as clack from '@clack/prompts';
3
3
  import { BaseCommand } from '../../base-command.js';
4
4
  import { listProfiles, deleteProfile, getAuthMethod, getSecretStorage, getProfile } from '../../profiles.js';
5
- import { revokeToken } from '../../oauth.js';
5
+ import { revokeSession } from '../../token-refresh.js';
6
6
  import { outputError } from '../../output.js';
7
7
  export default class AuthLogout extends BaseCommand {
8
8
  static description = 'Удаление сохранённого профиля';
@@ -72,14 +72,14 @@ export default class AuthLogout extends BaseCommand {
72
72
  }
73
73
  const authMethod = getAuthMethod(profiles[profileName]);
74
74
  const storage = getSecretStorage(profiles[profileName]);
75
- // Put the token out server-side before dropping the profile — afterwards the
76
- // secret is gone and there is nothing left to revoke with. Only tokens this
77
- // CLI issued can revoke themselves; a pasted one is not ours to cancel.
78
- let revoked = false;
75
+ // Close the login server-side before dropping the profile — afterwards the
76
+ // secrets are gone and there is nothing left to revoke with. Only logins this
77
+ // CLI made can revoke themselves; a pasted token is not ours to cancel.
78
+ let session = 'open';
79
79
  if (authMethod === 'oauth') {
80
- const secret = getProfile(profileName)?.token;
81
- if (secret)
82
- revoked = await revokeToken(secret);
80
+ const profile = getProfile(profileName);
81
+ if (profile)
82
+ session = await revokeSession(profileName, profile);
83
83
  }
84
84
  deleteProfile(profileName);
85
85
  if (format === 'json') {
@@ -87,7 +87,11 @@ export default class AuthLogout extends BaseCommand {
87
87
  deleted_profile: profileName,
88
88
  auth: authMethod,
89
89
  storage,
90
- token_revoked: revoked,
90
+ // True when the server confirms the login is closed. The revoke endpoint
91
+ // answers 200 to a login closed earlier too, so the field reports the
92
+ // state on the server, not which call closed it. False means nothing
93
+ // confirms it: the login may still be alive in the «Токены CLI» list.
94
+ token_revoked: session !== 'open',
91
95
  });
92
96
  return;
93
97
  }
@@ -96,9 +100,16 @@ export default class AuthLogout extends BaseCommand {
96
100
  // token when it did not is worse than an extra line here.
97
101
  if (!flags.quiet) {
98
102
  if (authMethod === 'oauth') {
99
- process.stderr.write(revoked
100
- ? ` Токен отозван на сервере и удалён с этой машины.\n`
101
- : ` Токен удалён с этой машины. Отозвать его не удалось — перестанет действовать по истечении срока.\n`);
103
+ if (session === 'revoked') {
104
+ process.stderr.write(` Вход закрыт на сервере, токен удалён с этой машины.\n`);
105
+ }
106
+ else if (session === 'closed') {
107
+ process.stderr.write(` Токен удалён с этой машины. Вход на сервере уже был закрыт.\n`);
108
+ }
109
+ else {
110
+ process.stderr.write(` Токен удалён с этой машины, но закрыть вход на сервере не удалось.\n`);
111
+ process.stderr.write(` Отзовите его в Пачке: Интеграции > API, список «Токены CLI».\n`);
112
+ }
102
113
  }
103
114
  else {
104
115
  process.stderr.write(` Токен удалён с этой машины, но продолжает действовать.\n`);
@@ -4,9 +4,6 @@ import { BaseCommand } from '../../base-command.js';
4
4
  import * as clack from '@clack/prompts';
5
5
  export default class BotsCreateToken extends BaseCommand {
6
6
  static description = "Новый токен бота";
7
- static examples = [
8
- "Выпустить боту отдельный токен — Выпусти токен с именем и правами из каталога. Без `--scopes` токен выпускается без прав, право не из каталога отклоняется с `400`:\n $ pachca bots create-token"
9
- ];
10
7
  static scope = "bots:write";
11
8
  static apiMethod = "POST";
12
9
  static apiPath = "/bots/{id}/tokens";
@@ -4,10 +4,6 @@ import { BaseCommand } from '../../base-command.js';
4
4
  import * as clack from '@clack/prompts';
5
5
  export default class BotsCreate extends BaseCommand {
6
6
  static description = "Новый бот";
7
- static examples = [
8
- "Создать бота через API и получить токен — Создай бота. Только пользовательским токеном (не токеном бота); `nickname` обязан заканчиваться на `_bot`. Параметры вебхука (Webhook URL, события, команды) можно задать сразу или позже. Скоупы токена бота можно ограничить флагом `--scopes` (если не указать — бот получит набор по умолчанию):\n $ pachca bots create",
9
- "Настроить бота с исходящим вебхуком — Создай бота, сразу указав Webhook URL и события в одном вызове (детали создания и работы с токеном — в сценарии «Создать бота через API и получить токен»):\n $ pachca bots create"
10
- ];
11
7
  static scope = "bots:write";
12
8
  static apiMethod = "POST";
13
9
  static apiPath = "/bots";
@@ -4,9 +4,6 @@ import { BaseCommand } from '../../base-command.js';
4
4
  import * as clack from '@clack/prompts';
5
5
  export default class BotsDelete extends BaseCommand {
6
6
  static description = "Удаление бота";
7
- static examples = [
8
- "Найти и удалить бота — Возьми `id` нужного бота из списка и удали его (скоуп `bots:write`). Доступно создателю бота и администратору, если бот открыт администраторам, — владельцы чатов удалять бота не могут. Все токены бота инвалидируются сразу, бот исключается из всех чатов, его вебхуки удаляются:\n $ pachca bots delete"
9
- ];
10
7
  static scope = "bots:write";
11
8
  static apiMethod = "DELETE";
12
9
  static apiPath = "/bots/{id}";
@@ -0,0 +1,23 @@
1
+ import { BaseCommand } from '../../base-command.js';
2
+ export default class BotsGetStream extends BaseCommand {
3
+ static description = "Поток событий";
4
+ static scope = "webhooks:events:read";
5
+ static apiMethod = "GET";
6
+ static apiPath = "/webhooks/events/stream";
7
+ static defaultColumns = ["url", "expires_at"];
8
+ static args = {};
9
+ static flags = {
10
+ ...BaseCommand.baseFlags,
11
+ };
12
+ async run() {
13
+ const { args, flags } = await this.parse(BotsGetStream);
14
+ this.parsedFlags = flags;
15
+ const { data } = await this.apiRequest({
16
+ method: 'GET',
17
+ path: '/webhooks/events/stream',
18
+ });
19
+ const responseBody = (data ?? {});
20
+ const result = responseBody.data ?? responseBody;
21
+ this.output(result);
22
+ }
23
+ }
@@ -3,9 +3,6 @@ import { Flags } from '@oclif/core';
3
3
  import { BaseCommand } from '../../base-command.js';
4
4
  export default class BotsListCompany extends BaseCommand {
5
5
  static description = "Список ботов пространства";
6
- static examples = [
7
- "Инвентаризация всех ботов пространства — Получи всех ботов пространства, а не только доступных для редактирования:\n $ pachca bots list-company"
8
- ];
9
6
  static scope = "company_bots:read";
10
7
  static plan = "corporation";
11
8
  static apiMethod = "GET";
@@ -3,9 +3,6 @@ import { Flags } from '@oclif/core';
3
3
  import { BaseCommand } from '../../base-command.js';
4
4
  export default class BotsListEvents extends BaseCommand {
5
5
  static description = "История событий";
6
- static examples = [
7
- "Обработка событий через историю (polling) — Получи накопленные события:\n $ pachca bots list-events"
8
- ];
9
6
  static scope = "webhooks:events:read";
10
7
  static apiMethod = "GET";
11
8
  static apiPath = "/webhooks/events";
@@ -3,9 +3,6 @@ import { Args } from '@oclif/core';
3
3
  import { BaseCommand } from '../../base-command.js';
4
4
  export default class BotsListScopes extends BaseCommand {
5
5
  static description = "Каталог прав бота";
6
- static examples = [
7
- "Выпустить боту отдельный токен — Пользовательским токеном (создатель бота или администратор, если бот открыт администраторам) получи каталог прав, которые можно выдать этому боту:\n $ pachca bots list-scopes"
8
- ];
9
6
  static scope = "bots:read";
10
7
  static apiMethod = "GET";
11
8
  static apiPath = "/bots/{id}/scopes";
@@ -3,9 +3,6 @@ import { Flags } from '@oclif/core';
3
3
  import { BaseCommand } from '../../base-command.js';
4
4
  export default class BotsList extends BaseCommand {
5
5
  static description = "Список ботов";
6
- static examples = [
7
- "Найти и удалить бота — Пользовательским токеном (скоуп `bots:read`) получи список ботов, доступных тебе для редактирования: созданных тобой и тех, чьи настройки открывают тебе доступ. Фильтруй по имени параметром `query`. Пока `meta.paginate.has_next` равен `true`, следующую страницу бери из `meta.paginate.next_page`:\n $ pachca bots list"
8
- ];
9
6
  static scope = "bots:read";
10
7
  static apiMethod = "GET";
11
8
  static apiPath = "/bots";
@@ -1,9 +1,6 @@
1
1
  import { BaseCommand } from '../../base-command.js';
2
2
  export default class BotsRecreateTokenSelf extends BaseCommand {
3
3
  static description = "Ротация собственного токена бота";
4
- static examples = [
5
- "Ротация токена бота — Или: бот перевыпускает свой основной токен сам (скоуп `bot_self:write`). Если запрос сделан основным токеном, он инвалидируется сразу — обязательно сохрани новый `access_token` из ответа, иначе бот потеряет доступ к API:\n $ pachca bots recreate-token-self"
6
- ];
7
4
  static scope = "bot_self:write";
8
5
  static apiMethod = "POST";
9
6
  static apiPath = "/bot/recreate_token";
@@ -3,9 +3,6 @@ import { Args } from '@oclif/core';
3
3
  import { BaseCommand } from '../../base-command.js';
4
4
  export default class BotsRecreateToken extends BaseCommand {
5
5
  static description = "Ротация токена бота";
6
- static examples = [
7
- "Ротация токена бота — Пользовательским токеном (создатель бота или администратор, если бот открыт администраторам) — перевыпусти основной токен по `id` бота: тот, что без имени, а если такого нет, самый старый. Прежнее значение инвалидируется сразу:\n $ pachca bots recreate-token"
8
- ];
9
6
  static scope = "bots:write";
10
7
  static apiMethod = "POST";
11
8
  static apiPath = "/bots/{id}/recreate_token";
@@ -3,9 +3,6 @@ import { Args } from '@oclif/core';
3
3
  import { BaseCommand } from '../../base-command.js';
4
4
  export default class BotsReissueToken extends BaseCommand {
5
5
  static description = "Перевыпуск токена бота";
6
- static examples = [
7
- "Ротация токена бота — Отдельный токен бота перевыпускай по его `id` из `pachca bots list-tokens`: значение меняется, имя и права остаются:\n $ pachca bots reissue-token"
8
- ];
9
6
  static scope = "bots:write";
10
7
  static apiMethod = "POST";
11
8
  static apiPath = "/bots/{id}/tokens/{token_id}/reissue";
@@ -4,9 +4,6 @@ import { BaseCommand } from '../../base-command.js';
4
4
  import * as clack from '@clack/prompts';
5
5
  export default class BotsRemoveEvent extends BaseCommand {
6
6
  static description = "Удаление события истории";
7
- static examples = [
8
- "Обработка событий через историю (polling) — Удали обработанное событие:\n $ pachca bots remove-event"
9
- ];
10
7
  static scope = "webhooks:events:delete";
11
8
  static apiMethod = "DELETE";
12
9
  static apiPath = "/webhooks/events/{id}";
@@ -4,9 +4,6 @@ import { BaseCommand } from '../../base-command.js';
4
4
  import * as clack from '@clack/prompts';
5
5
  export default class BotsUpdateWebhook extends BaseCommand {
6
6
  static description = "Саморегистрация вебхука бота";
7
- static examples = [
8
- "Обновить Webhook URL бота — Или: бот сам обновляет свой webhook своим же токеном — без `id` и без участия администратора (нужен скоуп `bot_self:webhook:write`):\n $ pachca bots update-webhook"
9
- ];
10
7
  static scope = "bot_self:webhook:write";
11
8
  static apiMethod = "PUT";
12
9
  static apiPath = "/bot/webhook";
@@ -3,11 +3,6 @@ import { Args, Flags } from '@oclif/core';
3
3
  import { BaseCommand } from '../../base-command.js';
4
4
  export default class BotsUpdate extends BaseCommand {
5
5
  static description = "Редактирование бота";
6
- static examples = [
7
- "Обновить Webhook URL бота — Пользовательским токеном (с правом редактировать бота) — обнови URL по `id` бота. Пустая строка отключает вебхук:\n $ pachca bots update",
8
- "Включить боту авторизацию от имени сотрудника — Пользовательским токеном (создатель бота или администратор, если бот открыт администраторам) включи авторизацию: где хранится секрет, адреса возврата и права, которые бот попросит у сотрудника. Права бери из `pachca bots list-scopes`:\n $ pachca bots update",
9
- "Включить боту авторизацию от имени сотрудника — Чтобы бот появился в витрине, добавь описание и опубликуй страницу:\n $ pachca bots update"
10
- ];
11
6
  static scope = "bots:write";
12
7
  static apiMethod = "PUT";
13
8
  static apiPath = "/bots/{id}";
@@ -3,10 +3,6 @@ import { Args } from '@oclif/core';
3
3
  import { BaseCommand } from '../../base-command.js';
4
4
  export default class ChatsArchive extends BaseCommand {
5
5
  static description = "Архивация чата";
6
- static examples = [
7
- "Архивация и управление чатом — Архивировать чат:\n $ pachca chats archive",
8
- "Найти и заархивировать неактивные чаты — Для каждого чата: архивируй:\n $ pachca chats archive"
9
- ];
10
6
  static scope = "chats:archive";
11
7
  static apiMethod = "PUT";
12
8
  static apiPath = "/chats/{id}/archive";