@pachca/cli 2026.9.1 → 2026.10.1

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 (76) hide show
  1. package/CHANGELOG.md +33 -0
  2. package/README.md +17 -5
  3. package/dist/base-command.js +47 -0
  4. package/dist/client.js +2 -1
  5. package/dist/commands/auth/login.js +4 -4
  6. package/dist/commands/bots/create-token.js +84 -0
  7. package/dist/commands/bots/create.js +33 -6
  8. package/dist/commands/bots/delete-token.js +46 -0
  9. package/dist/commands/bots/delete.js +1 -1
  10. package/dist/commands/bots/get.js +1 -1
  11. package/dist/commands/bots/list-company.js +1 -1
  12. package/dist/commands/bots/list-events.js +1 -1
  13. package/dist/commands/bots/list-scopes.js +32 -0
  14. package/dist/commands/bots/list-tokens.js +94 -0
  15. package/dist/commands/bots/list.js +3 -3
  16. package/dist/commands/bots/recreate-token-self.js +1 -1
  17. package/dist/commands/bots/recreate-token.js +2 -2
  18. package/dist/commands/bots/reissue-token.js +37 -0
  19. package/dist/commands/bots/remove-avatar.js +42 -0
  20. package/dist/commands/bots/remove-event.js +1 -1
  21. package/dist/commands/bots/rotate-client-secret.js +30 -0
  22. package/dist/commands/bots/update-avatar.js +51 -0
  23. package/dist/commands/bots/update-token.js +60 -0
  24. package/dist/commands/bots/update.js +37 -8
  25. package/dist/commands/chats/create.js +1 -0
  26. package/dist/commands/chats/download-export.js +1 -1
  27. package/dist/commands/chats/get.js +1 -1
  28. package/dist/commands/chats/list-company.js +1 -1
  29. package/dist/commands/chats/list.js +1 -1
  30. package/dist/commands/chats/mark-unread.js +44 -0
  31. package/dist/commands/chats/remove-avatar.js +45 -0
  32. package/dist/commands/chats/request-export.js +4 -2
  33. package/dist/commands/chats/update-avatar.js +54 -0
  34. package/dist/commands/drafts/create.js +1 -1
  35. package/dist/commands/drafts/list.js +1 -1
  36. package/dist/commands/drafts/update.js +1 -1
  37. package/dist/commands/files/direct-url.js +8 -4
  38. package/dist/commands/group-tags/get.js +1 -1
  39. package/dist/commands/group-tags/list-users.js +2 -2
  40. package/dist/commands/group-tags/list.js +3 -3
  41. package/dist/commands/members/add.js +2 -2
  42. package/dist/commands/members/list.js +2 -2
  43. package/dist/commands/members/remove-group-tag.js +1 -1
  44. package/dist/commands/members/remove.js +1 -1
  45. package/dist/commands/members/update.js +1 -1
  46. package/dist/commands/messages/create.js +9 -10
  47. package/dist/commands/messages/get.js +1 -1
  48. package/dist/commands/messages/list.js +1 -1
  49. package/dist/commands/messages/update.js +2 -2
  50. package/dist/commands/profile/update-status.js +3 -32
  51. package/dist/commands/reactions/list.js +1 -1
  52. package/dist/commands/read-member/list-readers.js +1 -1
  53. package/dist/commands/search/list-chats.js +2 -2
  54. package/dist/commands/search/list-messages.js +6 -6
  55. package/dist/commands/search/list-users.js +4 -4
  56. package/dist/commands/security/list.js +2 -2
  57. package/dist/commands/tasks/create.js +1 -1
  58. package/dist/commands/tasks/get.js +1 -1
  59. package/dist/commands/tasks/list.js +6 -6
  60. package/dist/commands/tasks/update.js +1 -1
  61. package/dist/commands/threads/get.js +1 -1
  62. package/dist/commands/threads/list.js +2 -2
  63. package/dist/commands/upload.js +5 -2
  64. package/dist/commands/users/create.js +1 -1
  65. package/dist/commands/users/get.js +1 -1
  66. package/dist/commands/users/list.js +1 -1
  67. package/dist/commands/users/update-status.js +3 -32
  68. package/dist/commands/users/update.js +2 -2
  69. package/dist/commands/views/open.js +2 -2
  70. package/dist/data/alternatives.json +17 -5
  71. package/dist/data/changelog.json +146 -0
  72. package/dist/data/endpoints.json +1 -1
  73. package/dist/data/workflows.json +60 -9
  74. package/dist/oauth.js +28 -3
  75. package/oclif.manifest.json +1 -1
  76. package/package.json +1 -1
package/CHANGELOG.md CHANGED
@@ -1,5 +1,38 @@
1
1
  # Changelog
2
2
 
3
+ ## 2026.10.1 (20 октября 2026)
4
+
5
+ - **Изменено** (chats request-export): Команда выводит номер выгрузки `id`: с ним `pachca chats download-export` забирает архив, когда тот соберётся
6
+ - **Добавлено** (chats create): Команда предупреждает в stderr об идентификаторах из `--member-ids`, которых не удалось добавить в чат
7
+ - **Добавлено** (bots update-avatar, bots remove-avatar): Новые команды — аватар бота: загрузка файла флагом `--file` и удаление
8
+ - **Добавлено** (bots get): В `webhook` приходят адрес входящего вебхука `url`, секрет подписи `secret_token` и тело последнего запроса `last_request_body`, если у токена есть скоуп `bots:write` и вы можете управлять токенами бота
9
+ - **Изменено** (messages create): Сообщение уходит без `--content`, если к нему приложены файлы
10
+ - **Изменено** (profile update-status, users update-status): `--emoji` и `--title` нужны, только когда статуса ещё нет — продлить статус можно одним `--expires-at`
11
+ - **Изменено** (upload, files direct-url): `pachca upload` передаёт в форме загрузки `acl` и `bucket`, только если они пришли, а у `pachca files direct-url` флаг `--acl` необязателен и есть флаг `--bucket`: в части пространств метод `Получение подписи, ключа и других параметров` отдаёт `bucket` вместо `acl`
12
+
13
+ ## 2026.10.0 (13 октября 2026)
14
+
15
+ - **Добавлено** (bots list-tokens, bots create-token, bots update-token, bots reissue-token, bots delete-token): Новые команды — токены бота: у бота может быть несколько токенов, у каждого своё имя и права. Значение токена показывается один раз, при выпуске и перевыпуске.
16
+ - **Добавлено** (bots list-scopes): Новая команда — каталог прав, которые можно выдать боту: его токенам и авторизации от имени сотрудника
17
+ - **Добавлено** (bots rotate-client-secret): Новая команда — новый `client_secret` для авторизации от имени сотрудника. Прежний секрет перестаёт работать сразу.
18
+ - **Добавлено** (bots create, bots update): Флаги `--oauth-client` и `--promo` включают авторизацию от имени сотрудника и заполняют страницу бота в витрине, `--kind` выбирает источник входящего вебхука, а `--unfurl-domains` задаёт домены для разворачивания ссылок. У `bots create` появился `--empty` для бота без токена, у `bots update` — `--single-chat`.
19
+ - **Добавлено** (chats mark-unread): Новая команда — отметка чата непрочитанным для владельца токена
20
+ - **Добавлено** (chats update-avatar, chats remove-avatar): Новые команды — аватар беседы или канала: загрузка файла флагом `--file` и удаление. В выводе команд с чатами появилось поле `avatar_url`.
21
+ - **Добавлено** (security list): Флаг `--event-key` принимает значения `bot_token_created`, `bot_token_updated`, `bot_token_reissued`, `bot_token_deleted`, `bot_oauth_client_secret_issued`, `bot_oauth_client_secret_rotated`, `bot_oauth_client_disabled` и `oauth_token_revoked`
22
+ - **Исправлено** (search users, search chats, search messages, users list): В справке описан ответ `504` с кодом `timeout`, если поиск не уложился по времени
23
+ - **Исправлено** (messages create, messages update, drafts create, drafts update): У файлов в справке указан предел голосового сообщения в 30 минут
24
+ - **Добавлено** (auth login): Вход через браузер передаёт серверу имя устройства, по которому вход узнаётся в списке токенов CLI: имя машины, а у профиля не по умолчанию ещё и имя профиля в скобках, например `macbook (ci)`
25
+ - **Изменено**: После ответа `401` CLI подсказывает, что делать дальше: войти заново, если вход через браузер отозван или завершён, или сохранить новый токен. В подсказку подставлен профиль, с которым запускалась команда, например `pachca auth login --profile ci`
26
+ - **Исправлено** (views open): В справке у блоков `radio` и `checkbox` до 20 вариантов
27
+ - **Исправлено** (bots create, bots update): Справка к `--ignore-self-messages` говорит, что настройка не присылает боту и его собственные реакции
28
+ - **Исправлено** (guide): Сценарии разворачивания ссылок и поллинга описывают раздел «Интеграции»: событие ссылок с доменами вместо отдельного типа бота и история событий без Webhook URL флагом `--events-history-enabled`, который попал и в примеры `bots update`
29
+ - **Исправлено** (messages create, messages update, tasks list): Подсказки к `--content` и `--chat-ids` описывают, что меняется для мульти-гостя
30
+ - **Исправлено** (messages create, messages update, drafts create, drafts update, tasks create, tasks update): В справке к `--content` указан предел длины текста: 40 000 байт, а в пространстве с продвинутым шифрованием 27 000
31
+ - **Исправлено** (messages create, threads add, threads create, threads get, threads list): Подсказка к `--parent-message-id` описывает ответ с цитатой в любом чате, в том числе в треде. В описаниях тредов сказано, какой идентификатор куда передавать: `id` — чтобы написать в тред, `chat_id` — чтобы прочитать его сообщения.
32
+ - **Исправлено** (bots list-events): В справке у события отправки формы сказано, что `chat_id` фиксируется при открытии формы, а не при её отправке
33
+ - **Исправлено** (chats create, members add, users update): В справке описаны отказы для бота с настройкой «Ограничить одним чатом» — `single_chat_bot_occupied`, если он уже состоит в другой беседе или канале, и `single_chat_bot_tag_denied` при добавлении в тег. У `users update` сказано, что у бота меняются только имя, ник, теги и `suspended`.
34
+ - **Исправлено** (tasks get): В справке сказано, что напоминание видят все, кому доступен его чат, по тому же правилу, что у фильтра `--chat-ids` в `tasks list`
35
+
3
36
  ## 2026.9.1 (16 сентября 2026)
4
37
 
5
38
  - **Изменено**: Повтор после `429` ждёт не меньше срока из `Retry-After`: прежний потолок в 60 секунд убран. Пауза дольше минуты не повторяется — команда сразу возвращает ошибку с заголовком, потому что повтор внутри паузы её удваивает
package/README.md CHANGED
@@ -85,10 +85,19 @@ pachca guide # список всех сценариев
85
85
  | `pachca bots get` | Информация о боте |
86
86
  | `pachca bots update` | Редактирование бота |
87
87
  | `pachca bots delete` | Удаление бота |
88
+ | `pachca bots update-avatar` | Загрузка аватара бота |
89
+ | `pachca bots remove-avatar` | Удаление аватара бота |
88
90
  | `pachca bots recreate-token` | Ротация токена бота |
91
+ | `pachca bots rotate-client-secret` | Ротация секрета клиента |
92
+ | `pachca bots list-scopes` | Каталог прав бота |
93
+ | `pachca bots list-tokens` | Список токенов бота |
94
+ | `pachca bots create-token` | Новый токен бота |
95
+ | `pachca bots update-token` | Изменение токена бота |
96
+ | `pachca bots delete-token` | Удаление токена бота |
97
+ | `pachca bots reissue-token` | Перевыпуск токена бота |
89
98
  | `pachca bots list-company` | Список ботов пространства |
90
99
  | `pachca bots list-events` | История событий |
91
- | `pachca bots remove-event` | Удаление события |
100
+ | `pachca bots remove-event` | Удаление события истории |
92
101
 
93
102
  ### chats
94
103
 
@@ -101,7 +110,10 @@ pachca guide # список всех сценариев
101
110
  | `pachca chats get` | Информация о чате |
102
111
  | `pachca chats update` | Редактирование чата |
103
112
  | `pachca chats archive` | Архивация чата |
113
+ | `pachca chats update-avatar` | Загрузка аватара чата |
114
+ | `pachca chats remove-avatar` | Удаление аватара чата |
104
115
  | `pachca chats unarchive` | Разархивация чата |
116
+ | `pachca chats mark-unread` | Отметка чата непрочитанным |
105
117
  | `pachca chats list-company` | Список чатов пространства |
106
118
 
107
119
  ### members
@@ -109,12 +121,12 @@ pachca guide # список всех сценариев
109
121
  | Команда | Описание |
110
122
  |---------|---------|
111
123
  | `pachca members add-group-tags` | Добавление тегов |
112
- | `pachca members remove-group-tag` | Исключение тега |
124
+ | `pachca members remove-group-tag` | Исключение тега из чата |
113
125
  | `pachca members leave` | Выход из чата |
114
126
  | `pachca members list` | Список участников чата |
115
- | `pachca members add` | Добавление пользователей |
116
- | `pachca members remove` | Исключение пользователя |
117
- | `pachca members update` | Редактирование роли |
127
+ | `pachca members add` | Добавление участников |
128
+ | `pachca members remove` | Исключение участника |
129
+ | `pachca members update` | Редактирование роли участника |
118
130
 
119
131
  ### custom-properties
120
132
 
@@ -209,6 +209,18 @@ export class BaseCommand extends Command {
209
209
  }
210
210
  outputData(data, opts);
211
211
  }
212
+ /**
213
+ * Warn about a non-empty list from the response `meta` (e.g. `not_found_member_ids`).
214
+ *
215
+ * stdout keeps printing only `data`, so `-o json` output keeps its shape. The list
216
+ * goes to stderr in every output mode, like the other `⚠` notices.
217
+ */
218
+ warnMetaList(meta, field, message, hint) {
219
+ const value = meta && typeof meta === 'object' ? meta[field] : undefined;
220
+ if (!Array.isArray(value) || value.length === 0)
221
+ return;
222
+ process.stderr.write(`⚠ ${message}: ${value.join(', ')}.\n ${hint}\n`);
223
+ }
212
224
  /**
213
225
  * Output a success message to stderr.
214
226
  */
@@ -272,6 +284,38 @@ export class BaseCommand extends Command {
272
284
  }
273
285
  this.exit(3);
274
286
  }
287
+ /**
288
+ * What to do after a 401: the login was revoked, or the saved token no longer works.
289
+ *
290
+ * A browser login is fixed by logging in again, a saved token by saving a new one.
291
+ * The command carries the profile the call ran with, so it can be pasted as is.
292
+ * JSON output gets the bare command, like the other hints there.
293
+ */
294
+ reloginHint() {
295
+ const format = this.getOutputFormat();
296
+ const human = format !== 'json' && process.stderr.isTTY;
297
+ let resolved;
298
+ try {
299
+ resolved = resolveToken({ token: this.parsedFlags.token, profile: this.parsedFlags.profile });
300
+ }
301
+ catch {
302
+ // no profile resolved — the token came in from a flag or the environment
303
+ }
304
+ if (!resolved?.profile || !resolved.profileName) {
305
+ return human
306
+ ? 'Токен из --token или PACHCA_TOKEN недействителен или отозван.'
307
+ : 'token from --token or PACHCA_TOKEN is invalid or revoked';
308
+ }
309
+ const flag = resolved.profileName === 'default' ? '' : ` --profile ${resolved.profileName}`;
310
+ if (getAuthMethod(resolved.profile) === 'oauth') {
311
+ return human
312
+ ? `Вход завершён или отозван. Войдите заново: pachca auth login${flag}`
313
+ : `pachca auth login${flag}`;
314
+ }
315
+ return human
316
+ ? `Токен недействителен или отозван. Сохраните новый: pachca auth login${flag} --token <токен>`
317
+ : `pachca auth login${flag} --token <token>`;
318
+ }
275
319
  /**
276
320
  * Handle errors with structured output.
277
321
  */
@@ -280,6 +324,9 @@ export class BaseCommand extends Command {
280
324
  this.reportScopeError(err);
281
325
  }
282
326
  if (err instanceof ApiError) {
327
+ if (err.details.code === 401 && !err.details.hint) {
328
+ err.details.hint = this.reloginHint();
329
+ }
283
330
  outputError(err.details, this.getOutputFormat());
284
331
  this.exit(getExitCode(err));
285
332
  }
package/dist/client.js CHANGED
@@ -33,8 +33,9 @@ function buildUrl(apiPath, query) {
33
33
  for (const [key, value] of Object.entries(query)) {
34
34
  if (value !== undefined) {
35
35
  if (Array.isArray(value)) {
36
+ // The key already carries the form the spec declares, `name[]` for a list.
36
37
  for (const v of value) {
37
- url.searchParams.append(`${key}[]`, String(v));
38
+ url.searchParams.append(key, String(v));
38
39
  }
39
40
  }
40
41
  else {
@@ -4,7 +4,7 @@ import { setProfile, setActiveProfile, getActiveProfile, getProfile, getSecretSt
4
4
  import { isSecretStoreAvailable } from '../../secret-store.js';
5
5
  import { ApiError, request } from '../../client.js';
6
6
  import { outputError } from '../../output.js';
7
- import { OAuthError, copyToClipboard, openBrowser, pollForToken, requestDeviceCode, } from '../../oauth.js';
7
+ import { OAuthError, copyToClipboard, deviceName, openBrowser, pollForToken, requestDeviceCode, } from '../../oauth.js';
8
8
  export default class AuthLogin extends BaseCommand {
9
9
  static description = 'Вход в аккаунт и сохранение профиля';
10
10
  static examples = [
@@ -66,7 +66,7 @@ export default class AuthLogin extends BaseCommand {
66
66
  }
67
67
  const grant = flags.token
68
68
  ? { access_token: flags.token }
69
- : await this.deviceLogin(!flags['no-browser'], flags.quiet);
69
+ : await this.deviceLogin(profileName, !flags['no-browser'], flags.quiet);
70
70
  const tokenInfo = await this.verifyToken(grant.access_token);
71
71
  const identity = await this.fetchIdentity(grant.access_token, tokenInfo.user_id);
72
72
  const scopes = grant.scope ? grant.scope.split(' ').filter(Boolean) : tokenInfo.scopes;
@@ -105,10 +105,10 @@ export default class AuthLogin extends BaseCommand {
105
105
  * the code baked in is never shown — a link like that works as phishing when it
106
106
  * is forwarded to someone else's device.
107
107
  */
108
- async deviceLogin(useBrowser, quiet) {
108
+ async deviceLogin(profileName, useBrowser, quiet) {
109
109
  let grant;
110
110
  try {
111
- grant = await requestDeviceCode();
111
+ grant = await requestDeviceCode(deviceName(profileName));
112
112
  }
113
113
  catch (error) {
114
114
  this.failOAuth(error, 'Не удалось начать вход');
@@ -0,0 +1,84 @@
1
+ // Auto-generated from openapi.yaml — DO NOT EDIT
2
+ import { Args, Flags } from '@oclif/core';
3
+ import { BaseCommand } from '../../base-command.js';
4
+ import * as clack from '@clack/prompts';
5
+ export default class BotsCreateToken extends BaseCommand {
6
+ static description = "Новый токен бота";
7
+ static examples = [
8
+ "Выпустить боту отдельный токен — Выпусти токен с именем и правами из каталога. Без `--scopes` токен выпускается без прав, право не из каталога отклоняется с `400`:\n $ pachca bots create-token"
9
+ ];
10
+ static scope = "bots:write";
11
+ static apiMethod = "POST";
12
+ static apiPath = "/bots/{id}/tokens";
13
+ static defaultColumns = ["id", "name", "created_at", "token", "user_id"];
14
+ static requiredFlags = ["name"];
15
+ static args = {
16
+ id: Args.integer({
17
+ description: "Идентификатор бота (pachca bots list)",
18
+ required: true,
19
+ }),
20
+ };
21
+ static flags = {
22
+ ...BaseCommand.baseFlags,
23
+ 'name': Flags.string({
24
+ description: "Имя токена: по нему токены различают в списке (макс. 255 символов)",
25
+ }),
26
+ 'scopes': Flags.string({
27
+ description: "Права токена. Доступны только права, которые можно выдать боту. Если не передать, токен выпускается без прав.",
28
+ }),
29
+ };
30
+ async run() {
31
+ const { args, flags } = await this.parse(BotsCreateToken);
32
+ this.parsedFlags = flags;
33
+ const missingRequired = [
34
+ { flag: 'name', label: "Имя токена: по нему токены различают в списке", type: 'string' },
35
+ ].filter((f) => flags[f.flag] === undefined || flags[f.flag] === null);
36
+ if (missingRequired.length > 0) {
37
+ if (this.isInteractive()) {
38
+ for (const field of missingRequired) {
39
+ const value = await clack.text({ message: field.label, validate: (v) => v.length === 0 ? 'Обязательное поле' : undefined });
40
+ if (clack.isCancel(value)) {
41
+ process.stderr.write('Отменено.\n');
42
+ this.exit(0);
43
+ }
44
+ if (field.type === 'integer') {
45
+ flags[field.flag] = Number.parseInt(value, 10);
46
+ }
47
+ else if (field.type === 'boolean') {
48
+ flags[field.flag] = value === 'true';
49
+ }
50
+ else {
51
+ flags[field.flag] = value;
52
+ }
53
+ }
54
+ }
55
+ else {
56
+ this.validationError(missingRequired.map((f) => ({ message: `Обязательный флаг --${f.flag} не передан`, flag: f.flag })), { hint: "Обязательные: --name <string>. pachca introspect bots create-token" });
57
+ }
58
+ }
59
+ const validationErrors = [];
60
+ if (flags['name'] && String(flags['name']).length > 255) {
61
+ validationErrors.push({ message: `--name: максимум 255 символов (передано: ${String(flags['name']).length})`, flag: 'name' });
62
+ }
63
+ if (validationErrors.length > 0) {
64
+ this.validationError(validationErrors);
65
+ }
66
+ const body = {
67
+ name: flags['name'],
68
+ scopes: flags['scopes'] ? this.parseJSON(flags['scopes'], 'scopes') : undefined,
69
+ };
70
+ // Clean undefined fields
71
+ for (const [k, v] of Object.entries(body)) {
72
+ if (v === undefined)
73
+ delete body[k];
74
+ }
75
+ const { data } = await this.apiRequest({
76
+ method: 'POST',
77
+ path: `/bots/${args.id}/tokens`,
78
+ body,
79
+ });
80
+ const responseBody = (data ?? {});
81
+ const result = responseBody.data ?? responseBody;
82
+ this.output(result);
83
+ }
84
+ }
@@ -11,7 +11,7 @@ export default class BotsCreate extends BaseCommand {
11
11
  static scope = "bots:write";
12
12
  static apiMethod = "POST";
13
13
  static apiPath = "/bots";
14
- static defaultColumns = ["id", "webhook", "access_token"];
14
+ static defaultColumns = ["id", "name", "created_at", "nickname", "avatar_url"];
15
15
  static requiredFlags = ["name"];
16
16
  static args = {};
17
17
  static flags = {
@@ -41,7 +41,7 @@ export default class BotsCreate extends BaseCommand {
41
41
  description: "Шаблон форматирования входящего вебхука",
42
42
  }),
43
43
  'template-engine': Flags.string({
44
- description: "Шаблонизатор для обработки шаблона входящего вебхука",
44
+ description: "Шаблонизатор для обработки шаблона входящего вебхука. Без этого поля бот получает `liquid`, а пустой бот (`empty: true`) — `mustache`.",
45
45
  }),
46
46
  'challenge-key': Flags.string({
47
47
  description: "Название поля проверки для верификации входящего вебхука",
@@ -51,7 +51,7 @@ export default class BotsCreate extends BaseCommand {
51
51
  allowNo: true,
52
52
  }),
53
53
  'ignore-self-messages': Flags.boolean({
54
- description: "Игнорировать входящие сообщения, отправленные самим ботом",
54
+ description: "Не присылать боту события о его собственных сообщениях и реакциях",
55
55
  allowNo: true,
56
56
  }),
57
57
  'events-history-enabled': Flags.boolean({
@@ -65,9 +65,25 @@ export default class BotsCreate extends BaseCommand {
65
65
  description: "Роли, которым, помимо создателя, разрешено редактировать настройки бота. Создатель может редактировать всегда. Пустой массив — редактировать может только создатель.",
66
66
  }),
67
67
  'single-chat': Flags.boolean({
68
- description: "Ограничивает бота одной беседой или каналом: `true` — бота можно добавить только в один такой чат, `false` — в несколько. Личные чаты и треды в ограничение не входят. Задаётся только при создании, при редактировании не меняется.",
68
+ description: "Ограничивает бота одной беседой или каналом: `true` — бота можно добавить только в один такой чат, `false` — в несколько. Личные чаты и треды в ограничение не входят.",
69
69
  allowNo: true,
70
70
  }),
71
+ 'kind': Flags.string({
72
+ description: "Источник входящего вебхука. Без `empty` по умолчанию `simple`, а у пустого бота входящий вебхук включается только этим полем.",
73
+ }),
74
+ 'unfurl-domains': Flags.string({
75
+ description: "Домены, ссылки на которые бот разворачивает, не больше 5. Работают вместе с событием `message_link_shared`.",
76
+ }),
77
+ 'empty': Flags.boolean({
78
+ description: "Создать пустого бота: без токена и без входящего вебхука. Без этого поля бот создаётся с входящим вебхуком в своём формате, а его токен приходит в `access_token`.",
79
+ allowNo: true,
80
+ }),
81
+ 'oauth-client': Flags.string({
82
+ description: "Включить авторизацию от имени сотрудника сразу при создании",
83
+ }),
84
+ 'promo': Flags.string({
85
+ description: "Страница бота в витрине. Задаётся только вместе с `oauth_client`: без авторизации страницы у бота нет.",
86
+ }),
71
87
  };
72
88
  async run() {
73
89
  const { args, flags } = await this.parse(BotsCreate);
@@ -108,7 +124,8 @@ export default class BotsCreate extends BaseCommand {
108
124
  if (validationErrors.length > 0) {
109
125
  this.validationError(validationErrors);
110
126
  }
111
- const body = { webhook: {
127
+ const body = {
128
+ webhook: {
112
129
  name: flags['name'],
113
130
  nickname: flags['nickname'],
114
131
  outgoing_url: flags['outgoing-url'],
@@ -125,13 +142,23 @@ export default class BotsCreate extends BaseCommand {
125
142
  who_can_add: flags['who-can-add'],
126
143
  can_edit: flags['can-edit'] ? this.parseJSON(flags['can-edit'], 'can-edit') : undefined,
127
144
  single_chat: flags['single-chat'],
128
- } };
145
+ kind: flags['kind'],
146
+ unfurl_domains: flags['unfurl-domains'] ? this.parseJSON(flags['unfurl-domains'], 'unfurl-domains') : undefined,
147
+ },
148
+ empty: flags['empty'],
149
+ oauth_client: flags['oauth-client'] ? this.parseJSON(flags['oauth-client'], 'oauth-client') : undefined,
150
+ promo: flags['promo'] ? this.parseJSON(flags['promo'], 'promo') : undefined,
151
+ };
129
152
  // Clean undefined fields
130
153
  const inner = body['webhook'];
131
154
  for (const [k, v] of Object.entries(inner)) {
132
155
  if (v === undefined)
133
156
  delete inner[k];
134
157
  }
158
+ for (const [k, v] of Object.entries(body)) {
159
+ if (k !== 'webhook' && v === undefined)
160
+ delete body[k];
161
+ }
135
162
  const { data } = await this.apiRequest({
136
163
  method: 'POST',
137
164
  path: '/bots',
@@ -0,0 +1,46 @@
1
+ // Auto-generated from openapi.yaml — DO NOT EDIT
2
+ import { Args, Flags } from '@oclif/core';
3
+ import { BaseCommand } from '../../base-command.js';
4
+ import * as clack from '@clack/prompts';
5
+ export default class BotsDeleteToken extends BaseCommand {
6
+ static description = "Удаление токена бота";
7
+ static scope = "bots:write";
8
+ static apiMethod = "DELETE";
9
+ static apiPath = "/bots/{id}/tokens/{token_id}";
10
+ static args = {
11
+ id: Args.integer({
12
+ description: "Идентификатор бота (pachca bots list)",
13
+ required: true,
14
+ }),
15
+ token_id: Args.integer({
16
+ description: "Идентификатор токена",
17
+ required: true,
18
+ }),
19
+ };
20
+ static flags = {
21
+ ...BaseCommand.baseFlags,
22
+ force: Flags.boolean({
23
+ description: 'Пропустить подтверждение',
24
+ default: false,
25
+ }),
26
+ };
27
+ async run() {
28
+ const { args, flags } = await this.parse(BotsDeleteToken);
29
+ this.parsedFlags = flags;
30
+ if (!flags.force) {
31
+ if (!this.isInteractive()) {
32
+ this.validationError([{ message: 'Деструктивная операция требует флага --force', flag: 'force' }], { type: 'PACHCA_DESTRUCTIVE_OP_ERROR', hint: "pachca bots delete-token <id> <token_id> --force" });
33
+ }
34
+ const confirm = await clack.confirm({ message: 'Вы уверены?' });
35
+ if (clack.isCancel(confirm) || !confirm) {
36
+ process.stderr.write('Отменено.\n');
37
+ this.exit(0);
38
+ }
39
+ }
40
+ const { data } = await this.apiRequest({
41
+ method: 'DELETE',
42
+ path: `/bots/${args.id}/tokens/${args.token_id}`,
43
+ });
44
+ this.success('Удалено');
45
+ }
46
+ }
@@ -5,7 +5,7 @@ import * as clack from '@clack/prompts';
5
5
  export default class BotsDelete extends BaseCommand {
6
6
  static description = "Удаление бота";
7
7
  static examples = [
8
- "Найти и удалить бота — Возьми `id` нужного бота из списка и удали его (скоуп `bots:write`). Доступно администратору, владельцу компании или создателю бота — владельцы чатов удалять бота не могут. Прежний токен инвалидируется сразу, бот исключается из всех чатов, его исходящий вебхук удаляется:\n $ pachca bots delete"
8
+ "Найти и удалить бота — Возьми `id` нужного бота из списка и удали его (скоуп `bots:write`). Доступно создателю бота и администратору, если бот открыт администраторам, — владельцы чатов удалять бота не могут. Все токены бота инвалидируются сразу, бот исключается из всех чатов, его вебхуки удаляются:\n $ pachca bots delete"
9
9
  ];
10
10
  static scope = "bots:write";
11
11
  static apiMethod = "DELETE";
@@ -6,7 +6,7 @@ export default class BotsGet extends BaseCommand {
6
6
  static scope = "bots:read";
7
7
  static apiMethod = "GET";
8
8
  static apiPath = "/bots/{id}";
9
- static defaultColumns = ["id", "webhook"];
9
+ static defaultColumns = ["id", "name", "created_at", "nickname", "avatar_url"];
10
10
  static args = {
11
11
  id: Args.integer({
12
12
  description: "Идентификатор бота (pachca bots list)",
@@ -52,7 +52,7 @@ export default class BotsListCompany extends BaseCommand {
52
52
  const paginate = meta?.paginate;
53
53
  nextCursor = paginate?.next_page;
54
54
  pages++;
55
- // Условие конца: списочные методы — has_next === false; методы поиска и /users?query= (без has_next) — пустой data
55
+ // Условие конца: списочные методы и /users?query= — has_next === false; методы поиска (без has_next) — пустой data
56
56
  const hasNext = paginate?.has_next;
57
57
  if (typeof hasNext === 'boolean') {
58
58
  if (!hasNext)
@@ -47,7 +47,7 @@ export default class BotsListEvents extends BaseCommand {
47
47
  const paginate = meta?.paginate;
48
48
  nextCursor = paginate?.next_page;
49
49
  pages++;
50
- // Условие конца: списочные методы — has_next === false; методы поиска и /users?query= (без has_next) — пустой data
50
+ // Условие конца: списочные методы и /users?query= — has_next === false; методы поиска (без has_next) — пустой data
51
51
  const hasNext = paginate?.has_next;
52
52
  if (typeof hasNext === 'boolean') {
53
53
  if (!hasNext)
@@ -0,0 +1,32 @@
1
+ // Auto-generated from openapi.yaml — DO NOT EDIT
2
+ import { Args } from '@oclif/core';
3
+ import { BaseCommand } from '../../base-command.js';
4
+ export default class BotsListScopes extends BaseCommand {
5
+ static description = "Каталог прав бота";
6
+ static examples = [
7
+ "Выпустить боту отдельный токен — Пользовательским токеном (создатель бота или администратор, если бот открыт администраторам) получи каталог прав, которые можно выдать этому боту:\n $ pachca bots list-scopes"
8
+ ];
9
+ static scope = "bots:read";
10
+ static apiMethod = "GET";
11
+ static apiPath = "/bots/{id}/scopes";
12
+ static args = {
13
+ id: Args.integer({
14
+ description: "Идентификатор бота (pachca bots list)",
15
+ required: true,
16
+ }),
17
+ };
18
+ static flags = {
19
+ ...BaseCommand.baseFlags,
20
+ };
21
+ async run() {
22
+ const { args, flags } = await this.parse(BotsListScopes);
23
+ this.parsedFlags = flags;
24
+ const { data } = await this.apiRequest({
25
+ method: 'GET',
26
+ path: `/bots/${args.id}/scopes`,
27
+ });
28
+ const responseBody = (data ?? {});
29
+ const result = responseBody.data ?? responseBody;
30
+ this.output(result);
31
+ }
32
+ }
@@ -0,0 +1,94 @@
1
+ // Auto-generated from openapi.yaml — DO NOT EDIT
2
+ import { Args, Flags } from '@oclif/core';
3
+ import { BaseCommand } from '../../base-command.js';
4
+ export default class BotsListTokens extends BaseCommand {
5
+ static description = "Список токенов бота";
6
+ static scope = "bots:read";
7
+ static apiMethod = "GET";
8
+ static apiPath = "/bots/{id}/tokens";
9
+ static defaultColumns = ["id", "name", "created_at", "token", "user_id"];
10
+ static args = {
11
+ id: Args.integer({
12
+ description: "Идентификатор бота (pachca bots list)",
13
+ required: true,
14
+ }),
15
+ };
16
+ static flags = {
17
+ ...BaseCommand.baseFlags,
18
+ limit: Flags.integer({
19
+ description: 'Количество результатов на страницу',
20
+ }),
21
+ cursor: Flags.string({
22
+ description: 'Курсор для следующей страницы',
23
+ }),
24
+ all: Flags.boolean({
25
+ description: 'Загрузить все страницы автоматически',
26
+ default: false,
27
+ }),
28
+ };
29
+ async run() {
30
+ const { args, flags } = await this.parse(BotsListTokens);
31
+ this.parsedFlags = flags;
32
+ if (flags.all) {
33
+ // Auto-pagination
34
+ const allData = [];
35
+ let nextCursor = undefined;
36
+ let pages = 0;
37
+ const seenCursors = new Set();
38
+ while (pages < 500) {
39
+ const query = {
40
+ limit: flags.limit,
41
+ cursor: nextCursor,
42
+ };
43
+ const response = await this.apiRequest({ method: 'GET', path: `/bots/${args.id}/tokens`, query });
44
+ const body = response.data;
45
+ const items = body.data;
46
+ if (items)
47
+ allData.push(...items);
48
+ const meta = body.meta;
49
+ const paginate = meta?.paginate;
50
+ nextCursor = paginate?.next_page;
51
+ pages++;
52
+ // Условие конца: списочные методы и /users?query= — has_next === false; методы поиска (без has_next) — пустой data
53
+ const hasNext = paginate?.has_next;
54
+ if (typeof hasNext === 'boolean') {
55
+ if (!hasNext)
56
+ break;
57
+ }
58
+ else if (!items || items.length === 0) {
59
+ break;
60
+ }
61
+ if (process.stderr.isTTY) {
62
+ const total = paginate?.total;
63
+ const progress = total ? `${allData.length} / ${total}` : String(allData.length);
64
+ process.stderr.write(`\r Загружено: ${progress}...`);
65
+ }
66
+ if (!nextCursor)
67
+ break;
68
+ if (seenCursors.has(nextCursor)) {
69
+ process.stderr.write('\n⚠ Обнаружен цикл пагинации, остановка.\n');
70
+ break;
71
+ }
72
+ seenCursors.add(nextCursor);
73
+ }
74
+ if (pages >= 500) {
75
+ process.stderr.write('\n⚠ Достигнут лимит 500 страниц.\n');
76
+ }
77
+ if (process.stderr.isTTY)
78
+ process.stderr.write('\n');
79
+ this.output(allData);
80
+ return;
81
+ }
82
+ const { data } = await this.apiRequest({
83
+ method: 'GET',
84
+ path: `/bots/${args.id}/tokens`,
85
+ query: {
86
+ limit: flags.limit,
87
+ cursor: flags.cursor,
88
+ },
89
+ });
90
+ const responseBody = (data ?? {});
91
+ const items = responseBody.data ?? responseBody;
92
+ this.output(items);
93
+ }
94
+ }
@@ -4,12 +4,12 @@ import { BaseCommand } from '../../base-command.js';
4
4
  export default class BotsList extends BaseCommand {
5
5
  static description = "Список ботов";
6
6
  static examples = [
7
- "Найти и удалить бота — Пользовательским токеном (скоуп `bots:read`) получи список ботов, доступных тебе для редактирования: созданных тобой и тех, чьи настройки открывают тебе доступ. Фильтруй по имени параметром `query`, следующую страницу бери из `meta.paginate.next_page`:\n $ pachca bots list"
7
+ "Найти и удалить бота — Пользовательским токеном (скоуп `bots:read`) получи список ботов, доступных тебе для редактирования: созданных тобой и тех, чьи настройки открывают тебе доступ. Фильтруй по имени параметром `query`. Пока `meta.paginate.has_next` равен `true`, следующую страницу бери из `meta.paginate.next_page`:\n $ pachca bots list"
8
8
  ];
9
9
  static scope = "bots:read";
10
10
  static apiMethod = "GET";
11
11
  static apiPath = "/bots";
12
- static defaultColumns = ["id", "webhook"];
12
+ static defaultColumns = ["id", "name", "created_at", "nickname", "avatar_url"];
13
13
  static args = {};
14
14
  static flags = {
15
15
  ...BaseCommand.baseFlags,
@@ -51,7 +51,7 @@ export default class BotsList extends BaseCommand {
51
51
  const paginate = meta?.paginate;
52
52
  nextCursor = paginate?.next_page;
53
53
  pages++;
54
- // Условие конца: списочные методы — has_next === false; методы поиска и /users?query= (без has_next) — пустой data
54
+ // Условие конца: списочные методы и /users?query= — has_next === false; методы поиска (без has_next) — пустой data
55
55
  const hasNext = paginate?.has_next;
56
56
  if (typeof hasNext === 'boolean') {
57
57
  if (!hasNext)
@@ -2,7 +2,7 @@ import { BaseCommand } from '../../base-command.js';
2
2
  export default class BotsRecreateTokenSelf extends BaseCommand {
3
3
  static description = "Ротация собственного токена бота";
4
4
  static examples = [
5
- "Ротация токена бота — Или: бот перевыпускает собственный токен своим же токеном (скоуп `bot_self:write`). Токен, которым выполнен запрос, инвалидируется сразу — обязательно сохрани новый `access_token` из ответа, иначе бот потеряет доступ к API:\n $ pachca bots recreate-token-self"
5
+ "Ротация токена бота — Или: бот перевыпускает свой основной токен сам (скоуп `bot_self:write`). Если запрос сделан основным токеном, он инвалидируется сразу — обязательно сохрани новый `access_token` из ответа, иначе бот потеряет доступ к API:\n $ pachca bots recreate-token-self"
6
6
  ];
7
7
  static scope = "bot_self:write";
8
8
  static apiMethod = "POST";