@pachca/cli 2026.10.2 → 2026.10.4

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 (107) hide show
  1. package/CHANGELOG.md +41 -0
  2. package/README.md +29 -29
  3. package/README.template.md +19 -28
  4. package/dist/base-command.js +69 -37
  5. package/dist/client.js +58 -33
  6. package/dist/commands/api.js +4 -4
  7. package/dist/commands/auth/login.js +12 -9
  8. package/dist/commands/auth/logout.js +23 -12
  9. package/dist/commands/bots/create-token.js +0 -3
  10. package/dist/commands/bots/create.js +1 -5
  11. package/dist/commands/bots/delete.js +0 -3
  12. package/dist/commands/bots/get-stream.js +23 -0
  13. package/dist/commands/bots/list-company.js +4 -5
  14. package/dist/commands/bots/list-events.js +4 -5
  15. package/dist/commands/bots/list-scopes.js +0 -3
  16. package/dist/commands/bots/list-tokens.js +4 -2
  17. package/dist/commands/bots/list.js +4 -5
  18. package/dist/commands/bots/recreate-token-self.js +0 -3
  19. package/dist/commands/bots/recreate-token.js +0 -3
  20. package/dist/commands/bots/reissue-token.js +0 -3
  21. package/dist/commands/bots/remove-event.js +0 -3
  22. package/dist/commands/bots/update-webhook.js +0 -3
  23. package/dist/commands/bots/update.js +4 -8
  24. package/dist/commands/chats/archive.js +0 -4
  25. package/dist/commands/chats/create.js +3 -6
  26. package/dist/commands/chats/download-export.js +0 -3
  27. package/dist/commands/chats/list-company.js +4 -5
  28. package/dist/commands/chats/list.js +5 -8
  29. package/dist/commands/chats/remove-avatar.js +0 -3
  30. package/dist/commands/chats/request-export.js +0 -3
  31. package/dist/commands/chats/unarchive.js +0 -3
  32. package/dist/commands/chats/update-avatar.js +0 -3
  33. package/dist/commands/chats/update.js +0 -3
  34. package/dist/commands/custom-properties/list.js +0 -3
  35. package/dist/commands/doctor.js +60 -35
  36. package/dist/commands/drafts/create.js +6 -6
  37. package/dist/commands/drafts/delete.js +0 -3
  38. package/dist/commands/drafts/list.js +4 -2
  39. package/dist/commands/drafts/update.js +7 -4
  40. package/dist/commands/files/uploads.js +0 -3
  41. package/dist/commands/group-tags/create.js +0 -3
  42. package/dist/commands/group-tags/list-users.js +4 -5
  43. package/dist/commands/group-tags/list.js +4 -5
  44. package/dist/commands/important-messages/create.js +64 -0
  45. package/dist/commands/important-messages/delete.js +42 -0
  46. package/dist/commands/important-messages/list.js +91 -0
  47. package/dist/commands/members/add.js +1 -6
  48. package/dist/commands/members/leave.js +0 -3
  49. package/dist/commands/members/list.js +4 -6
  50. package/dist/commands/members/remove.js +0 -3
  51. package/dist/commands/members/update.js +0 -3
  52. package/dist/commands/messages/create.js +6 -9
  53. package/dist/commands/messages/delete.js +0 -3
  54. package/dist/commands/messages/get.js +0 -5
  55. package/dist/commands/messages/list.js +5 -7
  56. package/dist/commands/messages/pin.js +0 -3
  57. package/dist/commands/messages/unfurl.js +0 -3
  58. package/dist/commands/messages/unpin.js +0 -3
  59. package/dist/commands/messages/update.js +5 -8
  60. package/dist/commands/oauth/token-info.js +0 -3
  61. package/dist/commands/profile/delete-avatar.js +0 -3
  62. package/dist/commands/profile/delete-status.js +0 -3
  63. package/dist/commands/profile/get.js +0 -4
  64. package/dist/commands/profile/update-avatar.js +0 -3
  65. package/dist/commands/profile/update-status.js +1 -4
  66. package/dist/commands/reactions/add.js +3 -6
  67. package/dist/commands/reactions/list.js +4 -2
  68. package/dist/commands/reactions/remove.js +3 -6
  69. package/dist/commands/read-member/list-readers.js +4 -5
  70. package/dist/commands/search/list-chats.js +4 -7
  71. package/dist/commands/search/list-messages.js +4 -5
  72. package/dist/commands/search/list-users.js +4 -7
  73. package/dist/commands/security/list.js +4 -7
  74. package/dist/commands/tasks/create.js +4 -7
  75. package/dist/commands/tasks/delete.js +0 -3
  76. package/dist/commands/tasks/get.js +0 -3
  77. package/dist/commands/tasks/list.js +4 -7
  78. package/dist/commands/tasks/update.js +5 -7
  79. package/dist/commands/threads/add.js +0 -4
  80. package/dist/commands/threads/list.js +4 -6
  81. package/dist/commands/upload.js +27 -8
  82. package/dist/commands/users/create.js +0 -5
  83. package/dist/commands/users/delete.js +0 -3
  84. package/dist/commands/users/get-status.js +0 -3
  85. package/dist/commands/users/get.js +0 -3
  86. package/dist/commands/users/list.js +4 -7
  87. package/dist/commands/users/remove-avatar.js +0 -3
  88. package/dist/commands/users/remove-status.js +0 -3
  89. package/dist/commands/users/update-avatar.js +0 -3
  90. package/dist/commands/users/update-status.js +1 -4
  91. package/dist/commands/users/update.js +5 -5
  92. package/dist/commands/views/open.js +1 -5
  93. package/dist/commands/views/submit-response.js +0 -3
  94. package/dist/data/alternatives.json +5 -9
  95. package/dist/data/changelog.json +177 -0
  96. package/dist/data/endpoints.json +1 -1
  97. package/dist/help.js +0 -1
  98. package/dist/hooks/command-not-found.js +0 -1
  99. package/dist/hooks/init.js +13 -5
  100. package/dist/lib/flags.js +25 -0
  101. package/dist/oauth.js +11 -5
  102. package/dist/profiles.js +22 -4
  103. package/dist/token-refresh.js +61 -14
  104. package/oclif.manifest.json +1 -1
  105. package/package.json +2 -2
  106. package/dist/commands/guide.js +0 -90
  107. package/dist/data/workflows.json +0 -1058
@@ -10,7 +10,7 @@ export default class AuthLogin extends BaseCommand {
10
10
  static examples = [
11
11
  '<%= config.bin %> auth login',
12
12
  '<%= config.bin %> auth login --profile personal',
13
- '<%= config.bin %> auth login --profile ci --token $PACHCA_TOKEN',
13
+ 'pbpaste | <%= config.bin %> auth login --profile bot-notify --token -',
14
14
  ];
15
15
  static flags = {
16
16
  ...BaseCommand.baseFlags,
@@ -42,7 +42,7 @@ export default class AuthLogin extends BaseCommand {
42
42
  // the person guesses to press Ctrl-D. Say what was expected instead.
43
43
  if (flags.token === '-' && process.stdin.isTTY) {
44
44
  outputError({
45
- error: 'Option --token - reads the token from stdin. Pipe it in: echo "$TOKEN" | pachca auth login --token -',
45
+ error: 'Option --token - reads the token from stdin. Pipe it in, for example from the clipboard: pbpaste | pachca auth login --token -',
46
46
  type: 'PACHCA_USAGE_ERROR',
47
47
  code: null,
48
48
  }, this.getOutputFormat());
@@ -104,6 +104,10 @@ export default class AuthLogin extends BaseCommand {
104
104
  * The address and the code are printed separately and the ready-made link with
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
+ *
108
+ * They are printed even under `--quiet`: the browser opens either way, and a
109
+ * confirmation page with no code to type leaves the person stuck. `--quiet`
110
+ * drops only the waiting line and the final report.
107
111
  */
108
112
  async deviceLogin(profileName, useBrowser, quiet) {
109
113
  let grant;
@@ -113,14 +117,13 @@ export default class AuthLogin extends BaseCommand {
113
117
  catch (error) {
114
118
  this.failOAuth(error, 'Не удалось начать вход');
115
119
  }
116
- if (!quiet) {
117
- const copied = copyToClipboard(grant.user_code);
118
- process.stderr.write(`\n Откройте ${grant.verification_uri}\n`);
119
- process.stderr.write(` и введите код: ${grant.user_code}\n`);
120
- if (copied)
121
- process.stderr.write(` (код скопирован в буфер обмена)\n`);
120
+ const copied = copyToClipboard(grant.user_code);
121
+ process.stderr.write(`\n Откройте ${grant.verification_uri}\n`);
122
+ process.stderr.write(` и введите код: ${grant.user_code}\n`);
123
+ if (copied)
124
+ process.stderr.write(` (код скопирован в буфер обмена)\n`);
125
+ if (!quiet)
122
126
  process.stderr.write(`\n Ожидаю подтверждения…\n`);
123
- }
124
127
  if (useBrowser)
125
128
  openBrowser(grant.verification_uri);
126
129
  try {
@@ -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";
@@ -32,7 +28,7 @@ export default class BotsCreate extends BaseCommand {
32
28
  description: "Условие срабатывания исходящего вебхука",
33
29
  }),
34
30
  'commands': Flags.string({
35
- description: "Команды бота (триггер-слова), на которые он реагирует при trigger_on = commands. Суммарная длина команд, объединённых через запятую, не должна превышать 255 символов.",
31
+ description: "Команды бота (триггер-слова), на которые он реагирует при trigger_on = commands. Событие о сообщении приходит боту, когда первое слово текста совпадает с одной из команд целиком, с учётом регистра. Команду можно задать и без слэша в начале. Суммарная длина команд, объединённых через запятую, не должна превышать 255 символов.",
36
32
  }),
37
33
  'scopes': Flags.string({
38
34
  description: "Скоупы (права доступа) токена бота. Если не указано, бот получает набор по умолчанию. Боту доступны не все скоупы: часть из них разрешена только пользовательским ролям, и попытка назначить такой скоуп возвращает `400`. Служебные значения `bot` и `all` назначать нельзя.",
@@ -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";
@@ -37,6 +34,7 @@ export default class BotsListCompany extends BaseCommand {
37
34
  let nextCursor = undefined;
38
35
  let pages = 0;
39
36
  const seenCursors = new Set();
37
+ let total;
40
38
  while (pages < 500) {
41
39
  const query = {
42
40
  query: flags['query'],
@@ -52,7 +50,7 @@ export default class BotsListCompany extends BaseCommand {
52
50
  const paginate = meta?.paginate;
53
51
  nextCursor = paginate?.next_page;
54
52
  pages++;
55
- // Условие конца: списочные методы и /users?query= — has_next === false; методы поиска (без has_next) — пустой data
53
+ // Условие конца: has_next === false, а в ответе без has_next — пустой data
56
54
  const hasNext = paginate?.has_next;
57
55
  if (typeof hasNext === 'boolean') {
58
56
  if (!hasNext)
@@ -62,7 +60,8 @@ export default class BotsListCompany extends BaseCommand {
62
60
  break;
63
61
  }
64
62
  if (process.stderr.isTTY) {
65
- const total = paginate?.total;
63
+ // total лежит в meta у методов поиска. Берём его с первой страницы: при поиске сообщений по дате следующие страницы считают только остаток
64
+ total ??= meta?.total;
66
65
  const progress = total ? `${allData.length} / ${total}` : String(allData.length);
67
66
  process.stderr.write(`\r Загружено: ${progress}...`);
68
67
  }
@@ -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";
@@ -33,6 +30,7 @@ export default class BotsListEvents extends BaseCommand {
33
30
  let nextCursor = undefined;
34
31
  let pages = 0;
35
32
  const seenCursors = new Set();
33
+ let total;
36
34
  while (pages < 500) {
37
35
  const query = {
38
36
  limit: flags.limit,
@@ -47,7 +45,7 @@ export default class BotsListEvents extends BaseCommand {
47
45
  const paginate = meta?.paginate;
48
46
  nextCursor = paginate?.next_page;
49
47
  pages++;
50
- // Условие конца: списочные методы и /users?query= — has_next === false; методы поиска (без has_next) — пустой data
48
+ // Условие конца: has_next === false, а в ответе без has_next — пустой data
51
49
  const hasNext = paginate?.has_next;
52
50
  if (typeof hasNext === 'boolean') {
53
51
  if (!hasNext)
@@ -57,7 +55,8 @@ export default class BotsListEvents extends BaseCommand {
57
55
  break;
58
56
  }
59
57
  if (process.stderr.isTTY) {
60
- const total = paginate?.total;
58
+ // total лежит в meta у методов поиска. Берём его с первой страницы: при поиске сообщений по дате следующие страницы считают только остаток
59
+ total ??= meta?.total;
61
60
  const progress = total ? `${allData.length} / ${total}` : String(allData.length);
62
61
  process.stderr.write(`\r Загружено: ${progress}...`);
63
62
  }
@@ -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";
@@ -35,6 +35,7 @@ export default class BotsListTokens extends BaseCommand {
35
35
  let nextCursor = undefined;
36
36
  let pages = 0;
37
37
  const seenCursors = new Set();
38
+ let total;
38
39
  while (pages < 500) {
39
40
  const query = {
40
41
  limit: flags.limit,
@@ -49,7 +50,7 @@ export default class BotsListTokens extends BaseCommand {
49
50
  const paginate = meta?.paginate;
50
51
  nextCursor = paginate?.next_page;
51
52
  pages++;
52
- // Условие конца: списочные методы и /users?query= — has_next === false; методы поиска (без has_next) — пустой data
53
+ // Условие конца: has_next === false, а в ответе без has_next — пустой data
53
54
  const hasNext = paginate?.has_next;
54
55
  if (typeof hasNext === 'boolean') {
55
56
  if (!hasNext)
@@ -59,7 +60,8 @@ export default class BotsListTokens extends BaseCommand {
59
60
  break;
60
61
  }
61
62
  if (process.stderr.isTTY) {
62
- const total = paginate?.total;
63
+ // total лежит в meta у методов поиска. Берём его с первой страницы: при поиске сообщений по дате следующие страницы считают только остаток
64
+ total ??= meta?.total;
63
65
  const progress = total ? `${allData.length} / ${total}` : String(allData.length);
64
66
  process.stderr.write(`\r Загружено: ${progress}...`);
65
67
  }
@@ -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";
@@ -36,6 +33,7 @@ export default class BotsList extends BaseCommand {
36
33
  let nextCursor = undefined;
37
34
  let pages = 0;
38
35
  const seenCursors = new Set();
36
+ let total;
39
37
  while (pages < 500) {
40
38
  const query = {
41
39
  query: flags['query'],
@@ -51,7 +49,7 @@ export default class BotsList extends BaseCommand {
51
49
  const paginate = meta?.paginate;
52
50
  nextCursor = paginate?.next_page;
53
51
  pages++;
54
- // Условие конца: списочные методы и /users?query= — has_next === false; методы поиска (без has_next) — пустой data
52
+ // Условие конца: has_next === false, а в ответе без has_next — пустой data
55
53
  const hasNext = paginate?.has_next;
56
54
  if (typeof hasNext === 'boolean') {
57
55
  if (!hasNext)
@@ -61,7 +59,8 @@ export default class BotsList extends BaseCommand {
61
59
  break;
62
60
  }
63
61
  if (process.stderr.isTTY) {
64
- const total = paginate?.total;
62
+ // total лежит в meta у методов поиска. Берём его с первой страницы: при поиске сообщений по дате следующие страницы считают только остаток
63
+ total ??= meta?.total;
65
64
  const progress = total ? `${allData.length} / ${total}` : String(allData.length);
66
65
  process.stderr.write(`\r Загружено: ${progress}...`);
67
66
  }
@@ -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";
@@ -1,13 +1,9 @@
1
1
  // Auto-generated from openapi.yaml — DO NOT EDIT
2
2
  import { Args, Flags } from '@oclif/core';
3
3
  import { BaseCommand } from '../../base-command.js';
4
+ import { nullableString } from '../../lib/flags.js';
4
5
  export default class BotsUpdate extends BaseCommand {
5
6
  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
7
  static scope = "bots:write";
12
8
  static apiMethod = "PUT";
13
9
  static apiPath = "/bots/{id}";
@@ -36,7 +32,7 @@ export default class BotsUpdate extends BaseCommand {
36
32
  description: "Условие срабатывания исходящего вебхука",
37
33
  }),
38
34
  'commands': Flags.string({
39
- description: "Команды бота (триггер-слова), на которые он реагирует при trigger_on = commands. Суммарная длина команд, объединённых через запятую, не должна превышать 255 символов.",
35
+ description: "Команды бота (триггер-слова), на которые он реагирует при trigger_on = commands. Событие о сообщении приходит боту, когда первое слово текста совпадает с одной из команд целиком, с учётом регистра. Команду можно задать и без слэша в начале. Суммарная длина команд, объединённых через запятую, не должна превышать 255 символов.",
40
36
  }),
41
37
  'scopes': Flags.string({
42
38
  description: "Скоупы (права доступа) токена бота. Если не указано, бот получает набор по умолчанию. Боту доступны не все скоупы: часть из них разрешена только пользовательским ролям, и попытка назначить такой скоуп возвращает `400`. Служебные значения `bot` и `all` назначать нельзя.",
@@ -72,8 +68,8 @@ export default class BotsUpdate extends BaseCommand {
72
68
  description: "Ограничивает бота одной беседой или каналом: `true` — бота можно добавить только в один такой чат, `false` — в несколько. Личные чаты и треды в ограничение не входят.",
73
69
  allowNo: true,
74
70
  }),
75
- 'kind': Flags.string({
76
- description: "Источник входящего вебхука. Если у бота входящего вебхука не было, он включается.",
71
+ 'kind': nullableString({
72
+ description: "Источник входящего вебхука. Если у бота входящего вебхука не было, он включается. `null` удаляет входящий вебхук: адрес перестаёт работать, шаблон и настройки входящего вебхука сбрасываются, а при следующем включении бот получает новый адрес. Чтобы передать null, укажите `--kind null`.",
77
73
  }),
78
74
  'unfurl-domains': Flags.string({
79
75
  description: "Домены, ссылки на которые бот разворачивает, не больше 5. Работают вместе с событием `message_link_shared`. Список заменяется целиком.",
@@ -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";
@@ -4,10 +4,6 @@ import { BaseCommand } from '../../base-command.js';
4
4
  import * as clack from '@clack/prompts';
5
5
  export default class ChatsCreate extends BaseCommand {
6
6
  static description = "Новый чат";
7
- static examples = [
8
- "Создать канал и пригласить участников — Создай канал с участниками:\n $ pachca chats create",
9
- "Создать проектную беседу из шаблона — Создай беседу с участниками из тега:\n $ pachca chats create"
10
- ];
11
7
  static scope = "chats:create";
12
8
  static apiMethod = "POST";
13
9
  static apiPath = "/chats";
@@ -17,7 +13,7 @@ export default class ChatsCreate extends BaseCommand {
17
13
  static flags = {
18
14
  ...BaseCommand.baseFlags,
19
15
  'name': Flags.string({
20
- description: "Название. Пустое название заменяется именем и фамилией владельца токена (у бота — его именем), а если их нет — никнеймом. (макс. 255 символов)",
16
+ description: "Название беседы или канала. Пустое название или название из одних пробелов отвечает `422` с кодом `blank`. (макс. 255 символов)",
21
17
  }),
22
18
  'member-ids': Flags.string({
23
19
  description: "Массив идентификаторов пользователей, которые станут участниками",
@@ -38,7 +34,7 @@ export default class ChatsCreate extends BaseCommand {
38
34
  const { args, flags } = await this.parse(ChatsCreate);
39
35
  this.parsedFlags = flags;
40
36
  const missingRequired = [
41
- { flag: 'name', label: "Название. Пустое название заменяется именем и фамилией владельца токена (у бота — его именем), а если их нет — никнеймом.", type: 'string' },
37
+ { flag: 'name', label: "Название беседы или канала. Пустое название или название из одних пробелов отвечает `422` с кодом `blank`.", type: 'string' },
42
38
  ].filter((f) => flags[f.flag] === undefined || flags[f.flag] === null);
43
39
  if (missingRequired.length > 0) {
44
40
  if (this.isInteractive()) {
@@ -92,5 +88,6 @@ export default class ChatsCreate extends BaseCommand {
92
88
  const result = responseBody.data ?? responseBody;
93
89
  this.output(result);
94
90
  this.warnMetaList(responseBody.meta, "not_found_member_ids", "Не удалось добавить в чат", "Таких сотрудников нет в пространстве, либо они приостановлены или удалены.");
91
+ this.warnMetaList(responseBody.meta, "not_found_group_tag_ids", "Не удалось подключить теги", "Таких тегов нет в пространстве.");
95
92
  }
96
93
  }
@@ -5,9 +5,6 @@ import { downloadFile } from '../../client.js';
5
5
  import { formatSize } from '../../utils.js';
6
6
  export default class ChatsDownloadExport extends BaseCommand {
7
7
  static description = "Скачать архив экспорта";
8
- static examples = [
9
- "Экспорт истории чата — Скачай файл экспорта:\n $ pachca chats download-export"
10
- ];
11
8
  static hiddenAliases = ["common:get-exports"];
12
9
  static scope = "chat_exports:read";
13
10
  static plan = "corporation";
@@ -3,9 +3,6 @@ import { Flags } from '@oclif/core';
3
3
  import { BaseCommand } from '../../base-command.js';
4
4
  export default class ChatsListCompany extends BaseCommand {
5
5
  static description = "Список чатов пространства";
6
- static examples = [
7
- "Выгрузить все чаты пространства, включая закрытые — Получи беседы и каналы всего пространства, включая закрытые, где владелец токена не состоит:\n $ pachca chats list-company"
8
- ];
9
6
  static scope = "company_chats:read";
10
7
  static plan = "corporation";
11
8
  static apiMethod = "GET";
@@ -38,6 +35,7 @@ export default class ChatsListCompany extends BaseCommand {
38
35
  let nextCursor = undefined;
39
36
  let pages = 0;
40
37
  const seenCursors = new Set();
38
+ let total;
41
39
  while (pages < 500) {
42
40
  const query = {
43
41
  activity: flags['activity'],
@@ -53,7 +51,7 @@ export default class ChatsListCompany extends BaseCommand {
53
51
  const paginate = meta?.paginate;
54
52
  nextCursor = paginate?.next_page;
55
53
  pages++;
56
- // Условие конца: списочные методы и /users?query= — has_next === false; методы поиска (без has_next) — пустой data
54
+ // Условие конца: has_next === false, а в ответе без has_next — пустой data
57
55
  const hasNext = paginate?.has_next;
58
56
  if (typeof hasNext === 'boolean') {
59
57
  if (!hasNext)
@@ -63,7 +61,8 @@ export default class ChatsListCompany extends BaseCommand {
63
61
  break;
64
62
  }
65
63
  if (process.stderr.isTTY) {
66
- const total = paginate?.total;
64
+ // total лежит в meta у методов поиска. Берём его с первой страницы: при поиске сообщений по дате следующие страницы считают только остаток
65
+ total ??= meta?.total;
67
66
  const progress = total ? `${allData.length} / ${total}` : String(allData.length);
68
67
  process.stderr.write(`\r Загружено: ${progress}...`);
69
68
  }
@@ -3,11 +3,6 @@ import { Flags } from '@oclif/core';
3
3
  import { BaseCommand } from '../../base-command.js';
4
4
  export default class ChatsList extends BaseCommand {
5
5
  static description = "Список чатов";
6
- static examples = [
7
- "Найти активные чаты за период — Получи чаты с активностью после указанной даты:\n $ pachca chats list",
8
- "Найти и заархивировать неактивные чаты — Получи чаты без активности с нужной даты:\n $ pachca chats list",
9
- "Выгрузить все чаты пространства, включая закрытые — Если метод ответил `403` — у токена нет скоупа `company_chats:read`, либо владелец токена не владелец пространства, либо у пространства нет тарифа «Корпорация». Тогда возьми открытые чаты обычным списком:\n $ pachca chats list"
10
- ];
11
6
  static scope = "chats:read";
12
7
  static apiMethod = "GET";
13
8
  static apiPath = "/chats";
@@ -21,7 +16,7 @@ export default class ChatsList extends BaseCommand {
21
16
  default: "id",
22
17
  }),
23
18
  'order': Flags.string({
24
- description: "Направление сортировки",
19
+ description: "Направление сортировки. Применяется вместе с `sort`, например `sort=last_message_at&order=desc`. Без `sort` чаты идут по убыванию `id` при любом `order`.",
25
20
  options: ["asc", "desc"],
26
21
  default: "desc",
27
22
  }),
@@ -65,6 +60,7 @@ export default class ChatsList extends BaseCommand {
65
60
  let nextCursor = undefined;
66
61
  let pages = 0;
67
62
  const seenCursors = new Set();
63
+ let total;
68
64
  while (pages < 500) {
69
65
  const query = {
70
66
  sort: flags['sort'],
@@ -86,7 +82,7 @@ export default class ChatsList extends BaseCommand {
86
82
  const paginate = meta?.paginate;
87
83
  nextCursor = paginate?.next_page;
88
84
  pages++;
89
- // Условие конца: списочные методы и /users?query= — has_next === false; методы поиска (без has_next) — пустой data
85
+ // Условие конца: has_next === false, а в ответе без has_next — пустой data
90
86
  const hasNext = paginate?.has_next;
91
87
  if (typeof hasNext === 'boolean') {
92
88
  if (!hasNext)
@@ -96,7 +92,8 @@ export default class ChatsList extends BaseCommand {
96
92
  break;
97
93
  }
98
94
  if (process.stderr.isTTY) {
99
- const total = paginate?.total;
95
+ // total лежит в meta у методов поиска. Берём его с первой страницы: при поиске сообщений по дате следующие страницы считают только остаток
96
+ total ??= meta?.total;
100
97
  const progress = total ? `${allData.length} / ${total}` : String(allData.length);
101
98
  process.stderr.write(`\r Загружено: ${progress}...`);
102
99
  }
@@ -4,9 +4,6 @@ import { BaseCommand } from '../../base-command.js';
4
4
  import * as clack from '@clack/prompts';
5
5
  export default class ChatsRemoveAvatar extends BaseCommand {
6
6
  static description = "Удаление аватара чата";
7
- static examples = [
8
- "Поставить или убрать аватар чата — Чтобы убрать аватар, удали его:\n $ pachca chats remove-avatar"
9
- ];
10
7
  static scope = "chats:update";
11
8
  static apiMethod = "DELETE";
12
9
  static apiPath = "/chats/{id}/avatar";
@@ -4,9 +4,6 @@ import { BaseCommand } from '../../base-command.js';
4
4
  import * as clack from '@clack/prompts';
5
5
  export default class ChatsRequestExport extends BaseCommand {
6
6
  static description = "Экспорт сообщений";
7
- static examples = [
8
- "Экспорт истории чата — Запроси экспорт и возьми номер выгрузки из `data.id`:\n $ pachca chats request-export"
9
- ];
10
7
  static hiddenAliases = ["common:request-export"];
11
8
  static scope = "chat_exports:write";
12
9
  static plan = "corporation";
@@ -3,9 +3,6 @@ import { Args } from '@oclif/core';
3
3
  import { BaseCommand } from '../../base-command.js';
4
4
  export default class ChatsUnarchive extends BaseCommand {
5
5
  static description = "Разархивация чата";
6
- static examples = [
7
- "Архивация и управление чатом — Разархивировать чат:\n $ pachca chats unarchive"
8
- ];
9
6
  static scope = "chats:archive";
10
7
  static apiMethod = "PUT";
11
8
  static apiPath = "/chats/{id}/unarchive";