@pachca/cli 2026.9.1 → 2026.10.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (38) hide show
  1. package/CHANGELOG.md +23 -0
  2. package/README.md +10 -0
  3. package/dist/base-command.js +35 -0
  4. package/dist/commands/auth/login.js +4 -4
  5. package/dist/commands/bots/create-token.js +84 -0
  6. package/dist/commands/bots/create.js +32 -5
  7. package/dist/commands/bots/delete-token.js +46 -0
  8. package/dist/commands/bots/delete.js +1 -1
  9. package/dist/commands/bots/get.js +1 -1
  10. package/dist/commands/bots/list-scopes.js +32 -0
  11. package/dist/commands/bots/list-tokens.js +94 -0
  12. package/dist/commands/bots/list.js +1 -1
  13. package/dist/commands/bots/recreate-token-self.js +1 -1
  14. package/dist/commands/bots/recreate-token.js +2 -2
  15. package/dist/commands/bots/reissue-token.js +37 -0
  16. package/dist/commands/bots/rotate-client-secret.js +30 -0
  17. package/dist/commands/bots/update-token.js +60 -0
  18. package/dist/commands/bots/update.js +35 -6
  19. package/dist/commands/chats/mark-unread.js +44 -0
  20. package/dist/commands/chats/remove-avatar.js +45 -0
  21. package/dist/commands/chats/update-avatar.js +54 -0
  22. package/dist/commands/drafts/create.js +1 -1
  23. package/dist/commands/drafts/update.js +1 -1
  24. package/dist/commands/messages/create.js +3 -3
  25. package/dist/commands/messages/update.js +1 -1
  26. package/dist/commands/security/list.js +1 -1
  27. package/dist/commands/tasks/create.js +1 -1
  28. package/dist/commands/tasks/list.js +1 -1
  29. package/dist/commands/tasks/update.js +1 -1
  30. package/dist/commands/users/create.js +1 -1
  31. package/dist/commands/users/update.js +1 -1
  32. package/dist/data/alternatives.json +10 -0
  33. package/dist/data/changelog.json +105 -0
  34. package/dist/data/endpoints.json +1 -1
  35. package/dist/data/workflows.json +54 -3
  36. package/dist/oauth.js +28 -3
  37. package/oclif.manifest.json +1 -1
  38. package/package.json +1 -1
package/CHANGELOG.md CHANGED
@@ -1,5 +1,28 @@
1
1
  # Changelog
2
2
 
3
+ ## 2026.10.0 (13 октября 2026)
4
+
5
+ - **Добавлено** (bots list-tokens, bots create-token, bots update-token, bots reissue-token, bots delete-token): Новые команды — токены бота: у бота может быть несколько токенов, у каждого своё имя и права. Значение токена показывается один раз, при выпуске и перевыпуске.
6
+ - **Добавлено** (bots list-scopes): Новая команда — каталог прав, которые можно выдать боту: его токенам и авторизации от имени сотрудника
7
+ - **Добавлено** (bots rotate-client-secret): Новая команда — новый `client_secret` для авторизации от имени сотрудника. Прежний секрет перестаёт работать сразу.
8
+ - **Добавлено** (bots create, bots update): Флаги `--oauth-client` и `--promo` включают авторизацию от имени сотрудника и заполняют страницу бота в витрине, `--kind` выбирает источник входящего вебхука, а `--unfurl-domains` задаёт домены для разворачивания ссылок. У `bots create` появился `--empty` для бота без токена, у `bots update` — `--single-chat`.
9
+ - **Добавлено** (chats mark-unread): Новая команда — отметка чата непрочитанным для владельца токена
10
+ - **Добавлено** (chats update-avatar, chats remove-avatar): Новые команды — аватар беседы или канала: загрузка файла флагом `--file` и удаление. В выводе команд с чатами появилось поле `avatar_url`.
11
+ - **Добавлено** (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`
12
+ - **Исправлено** (search users, search chats, search messages, users list): В справке описан ответ `504` с кодом `timeout`, если поиск не уложился по времени
13
+ - **Исправлено** (messages create, messages update, drafts create, drafts update): У файлов в справке указан предел голосового сообщения в 30 минут
14
+ - **Добавлено** (auth login): Вход через браузер передаёт серверу имя устройства, по которому вход узнаётся в списке токенов CLI: имя машины, а у профиля не по умолчанию ещё и имя профиля в скобках, например `macbook (ci)`
15
+ - **Изменено**: После ответа `401` CLI подсказывает, что делать дальше: войти заново, если вход через браузер отозван или завершён, или сохранить новый токен. В подсказку подставлен профиль, с которым запускалась команда, например `pachca auth login --profile ci`
16
+ - **Исправлено** (views open): В справке у блоков `radio` и `checkbox` до 20 вариантов
17
+ - **Исправлено** (bots create, bots update): Справка к `--ignore-self-messages` говорит, что настройка не присылает боту и его собственные реакции
18
+ - **Исправлено** (guide): Сценарии разворачивания ссылок и поллинга описывают раздел «Интеграции»: событие ссылок с доменами вместо отдельного типа бота и история событий без Webhook URL флагом `--events-history-enabled`, который попал и в примеры `bots update`
19
+ - **Исправлено** (messages create, messages update, tasks list): Подсказки к `--content` и `--chat-ids` описывают, что меняется для мульти-гостя
20
+ - **Исправлено** (messages create, messages update, drafts create, drafts update, tasks create, tasks update): В справке к `--content` указан предел длины текста: 40 000 байт, а в пространстве с продвинутым шифрованием 27 000
21
+ - **Исправлено** (messages create, threads add, threads create, threads get, threads list): Подсказка к `--parent-message-id` описывает ответ с цитатой в любом чате, в том числе в треде. В описаниях тредов сказано, какой идентификатор куда передавать: `id` — чтобы написать в тред, `chat_id` — чтобы прочитать его сообщения.
22
+ - **Исправлено** (bots list-events): В справке у события отправки формы сказано, что `chat_id` фиксируется при открытии формы, а не при её отправке
23
+ - **Исправлено** (chats create, members add, users update): В справке описаны отказы для бота с настройкой «Ограничить одним чатом» — `single_chat_bot_occupied`, если он уже состоит в другой беседе или канале, и `single_chat_bot_tag_denied` при добавлении в тег. У `users update` сказано, что у бота меняются только имя, ник, теги и `suspended`.
24
+ - **Исправлено** (tasks get): В справке сказано, что напоминание видят все, кому доступен его чат, по тому же правилу, что у фильтра `--chat-ids` в `tasks list`
25
+
3
26
  ## 2026.9.1 (16 сентября 2026)
4
27
 
5
28
  - **Изменено**: Повтор после `429` ждёт не меньше срока из `Retry-After`: прежний потолок в 60 секунд убран. Пауза дольше минуты не повторяется — команда сразу возвращает ошибку с заголовком, потому что повтор внутри паузы её удваивает
package/README.md CHANGED
@@ -86,6 +86,13 @@ pachca guide # список всех сценариев
86
86
  | `pachca bots update` | Редактирование бота |
87
87
  | `pachca bots delete` | Удаление бота |
88
88
  | `pachca bots recreate-token` | Ротация токена бота |
89
+ | `pachca bots rotate-client-secret` | Ротация секрета клиента |
90
+ | `pachca bots list-scopes` | Каталог прав бота |
91
+ | `pachca bots list-tokens` | Список токенов бота |
92
+ | `pachca bots create-token` | Новый токен бота |
93
+ | `pachca bots update-token` | Изменение токена бота |
94
+ | `pachca bots delete-token` | Удаление токена бота |
95
+ | `pachca bots reissue-token` | Перевыпуск токена бота |
89
96
  | `pachca bots list-company` | Список ботов пространства |
90
97
  | `pachca bots list-events` | История событий |
91
98
  | `pachca bots remove-event` | Удаление события |
@@ -101,7 +108,10 @@ pachca guide # список всех сценариев
101
108
  | `pachca chats get` | Информация о чате |
102
109
  | `pachca chats update` | Редактирование чата |
103
110
  | `pachca chats archive` | Архивация чата |
111
+ | `pachca chats update-avatar` | Загрузка аватара чата |
112
+ | `pachca chats remove-avatar` | Удаление аватара чата |
104
113
  | `pachca chats unarchive` | Разархивация чата |
114
+ | `pachca chats mark-unread` | Отметка чата непрочитанным |
105
115
  | `pachca chats list-company` | Список чатов пространства |
106
116
 
107
117
  ### members
@@ -272,6 +272,38 @@ export class BaseCommand extends Command {
272
272
  }
273
273
  this.exit(3);
274
274
  }
275
+ /**
276
+ * What to do after a 401: the login was revoked, or the saved token no longer works.
277
+ *
278
+ * A browser login is fixed by logging in again, a saved token by saving a new one.
279
+ * The command carries the profile the call ran with, so it can be pasted as is.
280
+ * JSON output gets the bare command, like the other hints there.
281
+ */
282
+ reloginHint() {
283
+ const format = this.getOutputFormat();
284
+ const human = format !== 'json' && process.stderr.isTTY;
285
+ let resolved;
286
+ try {
287
+ resolved = resolveToken({ token: this.parsedFlags.token, profile: this.parsedFlags.profile });
288
+ }
289
+ catch {
290
+ // no profile resolved — the token came in from a flag or the environment
291
+ }
292
+ if (!resolved?.profile || !resolved.profileName) {
293
+ return human
294
+ ? 'Токен из --token или PACHCA_TOKEN недействителен или отозван.'
295
+ : 'token from --token or PACHCA_TOKEN is invalid or revoked';
296
+ }
297
+ const flag = resolved.profileName === 'default' ? '' : ` --profile ${resolved.profileName}`;
298
+ if (getAuthMethod(resolved.profile) === 'oauth') {
299
+ return human
300
+ ? `Вход завершён или отозван. Войдите заново: pachca auth login${flag}`
301
+ : `pachca auth login${flag}`;
302
+ }
303
+ return human
304
+ ? `Токен недействителен или отозван. Сохраните новый: pachca auth login${flag} --token <токен>`
305
+ : `pachca auth login${flag} --token <token>`;
306
+ }
275
307
  /**
276
308
  * Handle errors with structured output.
277
309
  */
@@ -280,6 +312,9 @@ export class BaseCommand extends Command {
280
312
  this.reportScopeError(err);
281
313
  }
282
314
  if (err instanceof ApiError) {
315
+ if (err.details.code === 401 && !err.details.hint) {
316
+ err.details.hint = this.reloginHint();
317
+ }
283
318
  outputError(err.details, this.getOutputFormat());
284
319
  this.exit(getExitCode(err));
285
320
  }
@@ -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 = {
@@ -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)",
@@ -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
+ // Условие конца: списочные методы — has_next === false; методы поиска и /users?query= (без 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
+ }
@@ -9,7 +9,7 @@ export default class BotsList extends BaseCommand {
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,
@@ -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";
@@ -4,12 +4,12 @@ import { BaseCommand } from '../../base-command.js';
4
4
  export default class BotsRecreateToken extends BaseCommand {
5
5
  static description = "Ротация токена бота";
6
6
  static examples = [
7
- "Ротация токена бота — Пользовательским токеном (администратор, владелец компании или создатель бота) — перевыпусти токен по `id` бота. Прежний токен инвалидируется сразу:\n $ pachca bots recreate-token"
7
+ "Ротация токена бота — Пользовательским токеном (создатель бота или администратор, если бот открыт администраторам) — перевыпусти основной токен по `id` бота: тот, что без имени, а если такого нет, самый старый. Прежнее значение инвалидируется сразу:\n $ pachca bots recreate-token"
8
8
  ];
9
9
  static scope = "bots:write";
10
10
  static apiMethod = "POST";
11
11
  static apiPath = "/bots/{id}/recreate_token";
12
- static defaultColumns = ["id", "webhook", "access_token"];
12
+ static defaultColumns = ["id", "name", "created_at", "nickname", "avatar_url"];
13
13
  static args = {
14
14
  id: Args.integer({
15
15
  description: "Идентификатор бота (pachca bots list)",
@@ -0,0 +1,37 @@
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 BotsReissueToken extends BaseCommand {
5
+ static description = "Перевыпуск токена бота";
6
+ static examples = [
7
+ "Ротация токена бота — Отдельный токен бота перевыпускай по его `id` из `pachca bots list-tokens`: значение меняется, имя и права остаются:\n $ pachca bots reissue-token"
8
+ ];
9
+ static scope = "bots:write";
10
+ static apiMethod = "POST";
11
+ static apiPath = "/bots/{id}/tokens/{token_id}/reissue";
12
+ static defaultColumns = ["id", "name", "created_at", "token", "user_id"];
13
+ static args = {
14
+ id: Args.integer({
15
+ description: "Идентификатор бота (pachca bots list)",
16
+ required: true,
17
+ }),
18
+ token_id: Args.integer({
19
+ description: "Идентификатор токена",
20
+ required: true,
21
+ }),
22
+ };
23
+ static flags = {
24
+ ...BaseCommand.baseFlags,
25
+ };
26
+ async run() {
27
+ const { args, flags } = await this.parse(BotsReissueToken);
28
+ this.parsedFlags = flags;
29
+ const { data } = await this.apiRequest({
30
+ method: 'POST',
31
+ path: `/bots/${args.id}/tokens/${args.token_id}/reissue`,
32
+ });
33
+ const responseBody = (data ?? {});
34
+ const result = responseBody.data ?? responseBody;
35
+ this.output(result);
36
+ }
37
+ }
@@ -0,0 +1,30 @@
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 BotsRotateClientSecret extends BaseCommand {
5
+ static description = "Ротация секрета клиента";
6
+ static scope = "bots:write";
7
+ static apiMethod = "POST";
8
+ static apiPath = "/bots/{id}/rotate_client_secret";
9
+ static defaultColumns = ["id", "name", "created_at", "nickname", "avatar_url"];
10
+ static args = {
11
+ id: Args.integer({
12
+ description: "Идентификатор бота (pachca bots list)",
13
+ required: true,
14
+ }),
15
+ };
16
+ static flags = {
17
+ ...BaseCommand.baseFlags,
18
+ };
19
+ async run() {
20
+ const { args, flags } = await this.parse(BotsRotateClientSecret);
21
+ this.parsedFlags = flags;
22
+ const { data } = await this.apiRequest({
23
+ method: 'POST',
24
+ path: `/bots/${args.id}/rotate_client_secret`,
25
+ });
26
+ const responseBody = (data ?? {});
27
+ const result = responseBody.data ?? responseBody;
28
+ this.output(result);
29
+ }
30
+ }