@pachca/cli 2026.7.0 → 2026.7.2

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 (88) hide show
  1. package/CHANGELOG.md +17 -0
  2. package/README.md +2 -1
  3. package/README.template.md +1 -1
  4. package/dist/base-command.js +2 -0
  5. package/dist/client.js +51 -17
  6. package/dist/commands/bots/create.js +17 -8
  7. package/dist/commands/bots/delete.js +1 -2
  8. package/dist/commands/bots/get.js +1 -5
  9. package/dist/commands/bots/list-events.js +2 -2
  10. package/dist/commands/bots/list.js +2 -4
  11. package/dist/commands/bots/recreate-token-self.js +2 -2
  12. package/dist/commands/bots/recreate-token.js +2 -2
  13. package/dist/commands/bots/remove-event.js +1 -1
  14. package/dist/commands/bots/update-webhook.js +2 -2
  15. package/dist/commands/bots/update.js +16 -7
  16. package/dist/commands/chats/archive.js +3 -3
  17. package/dist/commands/chats/create.js +11 -5
  18. package/dist/commands/chats/download-export.js +2 -2
  19. package/dist/commands/chats/get.js +1 -4
  20. package/dist/commands/chats/list.js +4 -4
  21. package/dist/commands/chats/request-export.js +2 -2
  22. package/dist/commands/chats/unarchive.js +2 -2
  23. package/dist/commands/chats/update.js +10 -3
  24. package/dist/commands/custom-properties/list.js +2 -2
  25. package/dist/commands/files/direct-url.js +12 -12
  26. package/dist/commands/files/uploads.js +2 -2
  27. package/dist/commands/group-tags/create.js +10 -4
  28. package/dist/commands/group-tags/get.js +1 -1
  29. package/dist/commands/group-tags/list-users.js +2 -2
  30. package/dist/commands/group-tags/list.js +2 -3
  31. package/dist/commands/group-tags/update.js +9 -2
  32. package/dist/commands/members/add-group-tags.js +1 -1
  33. package/dist/commands/members/add.js +4 -4
  34. package/dist/commands/members/leave.js +1 -1
  35. package/dist/commands/members/list.js +3 -4
  36. package/dist/commands/members/remove.js +1 -2
  37. package/dist/commands/members/update.js +2 -3
  38. package/dist/commands/messages/create.js +4 -4
  39. package/dist/commands/messages/delete.js +1 -3
  40. package/dist/commands/messages/get.js +4 -4
  41. package/dist/commands/messages/list.js +2 -4
  42. package/dist/commands/messages/pin.js +2 -3
  43. package/dist/commands/messages/unfurl.js +4 -4
  44. package/dist/commands/messages/unpin.js +1 -2
  45. package/dist/commands/messages/update.js +4 -4
  46. package/dist/commands/oauth/token-info.js +2 -2
  47. package/dist/commands/profile/delete-avatar.js +1 -2
  48. package/dist/commands/profile/delete-status.js +1 -2
  49. package/dist/commands/profile/get-status.js +1 -5
  50. package/dist/commands/profile/get.js +3 -3
  51. package/dist/commands/profile/update-avatar.js +2 -3
  52. package/dist/commands/profile/update-status.js +6 -4
  53. package/dist/commands/reactions/add.js +2 -3
  54. package/dist/commands/reactions/list.js +1 -5
  55. package/dist/commands/reactions/remove.js +1 -2
  56. package/dist/commands/read-member/list-readers.js +2 -2
  57. package/dist/commands/search/list-chats.js +3 -3
  58. package/dist/commands/search/list-messages.js +2 -2
  59. package/dist/commands/search/list-users.js +4 -4
  60. package/dist/commands/security/list.js +6 -6
  61. package/dist/commands/tasks/create.js +5 -5
  62. package/dist/commands/tasks/delete.js +1 -3
  63. package/dist/commands/tasks/get.js +2 -4
  64. package/dist/commands/tasks/list.js +2 -4
  65. package/dist/commands/tasks/update.js +4 -5
  66. package/dist/commands/threads/add.js +3 -3
  67. package/dist/commands/threads/create.js +23 -0
  68. package/dist/commands/threads/get.js +1 -1
  69. package/dist/commands/threads/list.js +3 -2
  70. package/dist/commands/users/create.js +32 -10
  71. package/dist/commands/users/delete.js +1 -3
  72. package/dist/commands/users/get-status.js +2 -4
  73. package/dist/commands/users/get.js +2 -4
  74. package/dist/commands/users/list.js +4 -4
  75. package/dist/commands/users/remove-avatar.js +1 -2
  76. package/dist/commands/users/remove-status.js +1 -3
  77. package/dist/commands/users/update-avatar.js +2 -3
  78. package/dist/commands/users/update-status.js +6 -5
  79. package/dist/commands/users/update.js +31 -10
  80. package/dist/commands/views/open.js +4 -4
  81. package/dist/data/alternatives.json +1 -0
  82. package/dist/data/changelog.json +67 -0
  83. package/dist/data/endpoints.json +1 -1
  84. package/dist/data/workflows.json +2 -2
  85. package/dist/output.js +10 -2
  86. package/dist/profiles.js +7 -3
  87. package/oclif.manifest.json +1 -1
  88. package/package.json +1 -1
package/CHANGELOG.md CHANGED
@@ -1,5 +1,22 @@
1
1
  # Changelog
2
2
 
3
+ ## 2026.7.2 (27 июля 2026)
4
+
5
+ - **Удалено** (files direct-url): Файл теперь добавляется в форму последним — S3 игнорирует поля после файла, из-за чего загрузка отклонялась с 400
6
+ - **Удалено**: Явный `--profile` больше не перебивается переменной `PACHCA_TOKEN` — команда с указанным профилем не уйдёт в чужое пространство
7
+ - **Удалено**: Запись (`POST`/`DELETE`/`PATCH`) больше не повторяется автоматически при 503 — повтор мог создать дубль сообщения
8
+ - **Удалено**: `Retry-After` в формате даты больше не игнорируется, значение ограничено разумным пределом
9
+ - **Удалено** (chats download-export): Имя файла из `Content-Disposition` больше не может увести запись за пределы указанной папки
10
+ - **Удалено**: Вывод `-o csv`: ячейки, начинающиеся с `=`, `+`, `-` или `@`, экранируются; объекты и массивы выводятся как JSON, а не `[object Object]`
11
+ - **Удалено**: `--timeout` меньше 1 отклоняется, а не приводит к мгновенной отмене запроса
12
+ - **Удалено**: Примеры в `--help` подписаны шагом сценария, а не общим заголовком (`reactions remove` больше не подписан «Добавить реакцию»)
13
+ - **Удалено** (files direct-url): Команда больше не падает с TypeError при успешной загрузке: S3 отвечает 204 с пустым телом, а команда пыталась прочитать из него поле
14
+
15
+ ## 2026.7.1 (24 июля 2026)
16
+
17
+ - **Добавлено** (threads create): Новая команда — создание самостоятельного треда без привязки к сообщению
18
+ - **Удалено**: Примеры в `--help` больше не смешиваются между командами с общим путём (например, create и list одного ресурса показывали примеры друг друга)
19
+
3
20
  ## 2026.7.0 (7 июля 2026)
4
21
 
5
22
  - **Добавлено** (bots list): Новая команда — список ботов с поиском по имени (`--query`)
package/README.md CHANGED
@@ -170,6 +170,7 @@ pachca guide # список всех сценариев
170
170
  | Команда | Описание |
171
171
  |---------|---------|
172
172
  | `pachca threads add` | Новый тред |
173
+ | `pachca threads create` | Новый самостоятельный тред |
173
174
  | `pachca threads list` | Список тредов |
174
175
  | `pachca threads get` | Информация о треде |
175
176
 
@@ -391,7 +392,7 @@ pachca autocomplete powershell # PowerShell completion
391
392
 
392
393
  | Переменная | Описание |
393
394
  |-----------|---------|
394
- | `PACHCA_TOKEN` | Bearer-токен (высший приоритет, удобно для CI) |
395
+ | `PACHCA_TOKEN` | Bearer-токен, удобно для CI. Приоритетнее профиля, но флаги `--token` и `--profile` его перебивают |
395
396
  | `PACHCA_PROFILE` | Активный профиль для команды |
396
397
  | `PACHCA_TIMEOUT` | Таймаут запроса в секундах (по умолчанию 30) |
397
398
  | `PACHCA_PROMPT_DISABLED` | Отключить интерактивные промпты |
@@ -209,7 +209,7 @@ pachca autocomplete powershell # PowerShell completion
209
209
 
210
210
  | Переменная | Описание |
211
211
  |-----------|---------|
212
- | `PACHCA_TOKEN` | Bearer-токен (высший приоритет, удобно для CI) |
212
+ | `PACHCA_TOKEN` | Bearer-токен, удобно для CI. Приоритетнее профиля, но флаги `--token` и `--profile` его перебивают |
213
213
  | `PACHCA_PROFILE` | Активный профиль для команды |
214
214
  | `PACHCA_TIMEOUT` | Таймаут запроса в секундах (по умолчанию 30) |
215
215
  | `PACHCA_PROMPT_DISABLED` | Отключить интерактивные промпты |
@@ -53,6 +53,8 @@ export class BaseCommand extends Command {
53
53
  }),
54
54
  timeout: Flags.integer({
55
55
  description: 'Request timeout in seconds',
56
+ // 0 or negative would abort on the next tick; fail loudly instead.
57
+ min: 1,
56
58
  }),
57
59
  'no-retry': Flags.boolean({
58
60
  description: 'Disable auto-retry on 429/503',
package/dist/client.js CHANGED
@@ -74,12 +74,35 @@ export function formatDryRun(opts, isJson) {
74
74
  }
75
75
  return lines.join('\n');
76
76
  }
77
+ /** Longest we honour a server-supplied `Retry-After` before falling back. */
78
+ const MAX_RETRY_AFTER_MS = 60_000;
79
+ /**
80
+ * `Retry-After` in milliseconds. RFC 9110 allows both delta-seconds and an
81
+ * HTTP-date; parsing only the former turned a date into NaN and silently
82
+ * discarded the server's backoff. Negative and absurd values are clamped so a
83
+ * hostile or buggy header can neither hot-loop nor park the CLI for a day.
84
+ */
85
+ function parseRetryAfter(header) {
86
+ if (!header)
87
+ return 1000;
88
+ const seconds = Number.parseInt(header, 10);
89
+ const ms = Number.isNaN(seconds) ? Date.parse(header) - Date.now() : seconds * 1000;
90
+ if (Number.isNaN(ms))
91
+ return 1000;
92
+ return Math.min(Math.max(ms, 0), MAX_RETRY_AFTER_MS);
93
+ }
77
94
  export async function request(opts, clientFlags) {
78
95
  const url = buildUrl(opts.path, opts.query);
79
96
  const rawEnvTimeout = process.env.PACHCA_TIMEOUT ? Number(process.env.PACHCA_TIMEOUT) : undefined;
80
97
  const envTimeout = rawEnvTimeout && !Number.isNaN(rawEnvTimeout) ? rawEnvTimeout : undefined;
81
- const timeoutSeconds = opts.timeout ?? clientFlags?.timeout ?? envTimeout ?? DEFAULT_TIMEOUT;
98
+ const requestedTimeout = opts.timeout ?? clientFlags?.timeout ?? envTimeout ?? DEFAULT_TIMEOUT;
99
+ // A non-positive timeout aborts on the next tick, which is never what anyone
100
+ // means by `--timeout 0`. The env var already ignored 0; make the flag agree.
101
+ const timeoutSeconds = requestedTimeout > 0 ? requestedTimeout : DEFAULT_TIMEOUT;
82
102
  const noRetry = opts.noRetry ?? clientFlags?.['no-retry'] ?? false;
103
+ // Retrying a request the server may already have committed duplicates it.
104
+ // Only these methods are safe to repeat blindly.
105
+ const safeMethod = ['GET', 'HEAD', 'OPTIONS', 'PUT'].includes(opts.method);
83
106
  const fetchHeaders = {
84
107
  ...(opts.noAuth ? {} : { Authorization: `Bearer ${opts.token}` }),
85
108
  Accept: 'application/json',
@@ -106,14 +129,21 @@ export async function request(opts, clientFlags) {
106
129
  try {
107
130
  const controller = new AbortController();
108
131
  const timer = setTimeout(() => controller.abort(), timeoutSeconds * 1000);
109
- const response = await fetch(url, {
110
- method: opts.method,
111
- headers: fetchHeaders,
112
- body: fetchBody,
113
- signal: controller.signal,
114
- redirect: opts.isRedirect ? 'manual' : 'follow',
115
- });
116
- clearTimeout(timer);
132
+ let response;
133
+ try {
134
+ response = await fetch(url, {
135
+ method: opts.method,
136
+ headers: fetchHeaders,
137
+ body: fetchBody,
138
+ signal: controller.signal,
139
+ redirect: opts.isRedirect ? 'manual' : 'follow',
140
+ });
141
+ }
142
+ finally {
143
+ // Must run on reject too: a leaked timer keeps the event loop alive and
144
+ // the CLI hangs for the full timeout after an otherwise successful retry.
145
+ clearTimeout(timer);
146
+ }
117
147
  if (clientFlags?.verbose) {
118
148
  process.stderr.write(`${ansis.dim(`← ${response.status} ${response.statusText}`)}\n`);
119
149
  }
@@ -127,19 +157,20 @@ export async function request(opts, clientFlags) {
127
157
  headers: response.headers,
128
158
  };
129
159
  }
130
- // Handle rate limiting (429)
160
+ // Handle rate limiting (429). Safe for any method: a throttled request
161
+ // was rejected before it was processed, so it cannot have been committed.
131
162
  if (response.status === 429 && !noRetry && attempt < MAX_RETRIES) {
132
- const retryAfter = response.headers.get('retry-after');
133
- const parsed = retryAfter ? Number.parseInt(retryAfter, 10) : NaN;
134
- const waitMs = Number.isNaN(parsed) ? 1000 : parsed * 1000;
163
+ const waitMs = parseRetryAfter(response.headers.get('retry-after'));
135
164
  if (clientFlags?.verbose) {
136
165
  process.stderr.write(`${ansis.dim(`⏳ Rate limited, retrying in ${waitMs}ms...`)}\n`);
137
166
  }
138
167
  await sleep(waitMs);
139
168
  continue;
140
169
  }
141
- // Handle 503 with backoff
142
- if (response.status === 503 && !noRetry && attempt < MAX_RETRIES) {
170
+ // Handle 503 with backoff. Unlike 429, a 503 from a proxy or a draining
171
+ // load balancer says nothing about whether the backend already committed
172
+ // the write — retrying a POST there duplicates the message.
173
+ if (response.status === 503 && safeMethod && !noRetry && attempt < MAX_RETRIES) {
143
174
  const baseMs = 1000;
144
175
  const waitMs = baseMs * Math.pow(2, attempt) + Math.random() * baseMs;
145
176
  if (clientFlags?.verbose) {
@@ -184,7 +215,6 @@ export async function request(opts, clientFlags) {
184
215
  }
185
216
  lastError = error;
186
217
  // Only retry network errors for idempotent methods (not POST/DELETE)
187
- const safeMethod = ['GET', 'HEAD', 'OPTIONS', 'PUT'].includes(opts.method);
188
218
  if (!safeMethod || noRetry || attempt >= MAX_RETRIES) {
189
219
  break;
190
220
  }
@@ -292,7 +322,11 @@ export async function downloadFile(url, savePath) {
292
322
  const urlPath = new URL(url).pathname;
293
323
  filename = path.basename(urlPath.split('?')[0]);
294
324
  }
295
- if (!filename)
325
+ // Strip any directory part: the name comes from a Content-Disposition on
326
+ // a redirect target outside our trust boundary, and `../../..` in it would
327
+ // otherwise let the response choose where on disk to write.
328
+ filename = path.basename(filename);
329
+ if (!filename || filename === '.' || filename === '..')
296
330
  filename = 'download';
297
331
  if (!fs.existsSync(savePath)) {
298
332
  throw new Error(`Directory does not exist: ${savePath}`);
@@ -5,9 +5,8 @@ import * as clack from '@clack/prompts';
5
5
  export default class BotsCreate extends BaseCommand {
6
6
  static description = "Новый бот";
7
7
  static examples = [
8
- "Создать бота через API и получить токен:\n $ pachca bots create",
9
- "Настроить бота с исходящим вебхуком:\n $ pachca bots create",
10
- "Найти и удалить бота:\n $ pachca bots list"
8
+ "Создать бота через API и получить токен — Создай бота. Только пользовательским токеном (не токеном бота); `nickname` обязан заканчиваться на `_bot`. Параметры вебхука (Webhook URL, события, команды) можно задать сразу или позже. Скоупы токена бота можно ограничить флагом `--scopes` (если не указать — бот получит набор по умолчанию):\n $ pachca bots create",
9
+ "Настроить бота с исходящим вебхуком — Создай бота, сразу указав Webhook URL и события в одном вызове (детали создания и работы с токеном — в сценарии «Создать бота через API и получить токен»):\n $ pachca bots create"
11
10
  ];
12
11
  static scope = "bots:write";
13
12
  static apiMethod = "POST";
@@ -18,10 +17,10 @@ export default class BotsCreate extends BaseCommand {
18
17
  static flags = {
19
18
  ...BaseCommand.baseFlags,
20
19
  'name': Flags.string({
21
- description: "Имя бота",
20
+ description: "Имя бота (макс. 255 символов)",
22
21
  }),
23
22
  'nickname': Flags.string({
24
- description: "Никнейм бота. Должен заканчиваться на `_bot`.",
23
+ description: "Никнейм бота. Должен заканчиваться на `_bot`. (макс. 255 символов)",
25
24
  }),
26
25
  'outgoing-url': Flags.string({
27
26
  description: "URL исходящего вебхука",
@@ -33,10 +32,10 @@ export default class BotsCreate extends BaseCommand {
33
32
  description: "Условие срабатывания исходящего вебхука",
34
33
  }),
35
34
  'commands': Flags.string({
36
- description: "Команды бота (триггер-слова), на которые он реагирует при trigger_on = commands",
35
+ description: "Команды бота (триггер-слова), на которые он реагирует при trigger_on = commands. Суммарная длина команд, объединённых через запятую, не должна превышать 255 символов.",
37
36
  }),
38
37
  'scopes': Flags.string({
39
- description: "Скоупы (права доступа) токена бота. Если не указано, бот получает набор по умолчанию.",
38
+ description: "Скоупы (права доступа) токена бота. Если не указано, бот получает набор по умолчанию. Боту доступны не все скоупы: часть из них разрешена только пользовательским ролям, и попытка назначить такой скоуп возвращает `400`. Служебные значения `bot` и `all` назначать нельзя.",
40
39
  }),
41
40
  'template': Flags.string({
42
41
  description: "Шаблон форматирования входящего вебхука",
@@ -99,6 +98,16 @@ export default class BotsCreate extends BaseCommand {
99
98
  this.validationError(missingRequired.map((f) => ({ message: `Обязательный флаг --${f.flag} не передан`, flag: f.flag })), { hint: "Обязательные: --name <string>. pachca introspect bots create" });
100
99
  }
101
100
  }
101
+ const validationErrors = [];
102
+ if (flags['name'] && String(flags['name']).length > 255) {
103
+ validationErrors.push({ message: `--name: максимум 255 символов (передано: ${String(flags['name']).length})`, flag: 'name' });
104
+ }
105
+ if (flags['nickname'] && String(flags['nickname']).length > 255) {
106
+ validationErrors.push({ message: `--nickname: максимум 255 символов (передано: ${String(flags['nickname']).length})`, flag: 'nickname' });
107
+ }
108
+ if (validationErrors.length > 0) {
109
+ this.validationError(validationErrors);
110
+ }
102
111
  const body = { webhook: {
103
112
  name: flags['name'],
104
113
  nickname: flags['nickname'],
@@ -128,7 +137,7 @@ export default class BotsCreate extends BaseCommand {
128
137
  path: '/bots',
129
138
  body,
130
139
  });
131
- const responseBody = data;
140
+ const responseBody = (data ?? {});
132
141
  const result = responseBody.data ?? responseBody;
133
142
  this.output(result);
134
143
  }
@@ -5,8 +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
- "Обновить Webhook URL бота:\n $ pachca bots update",
9
- "Найти и удалить бота:\n $ pachca bots delete"
8
+ "Найти и удалить бота — Возьми `id` нужного бота из списка и удали его (скоуп `bots:write`). Доступно администратору, владельцу компании или создателю бота — владельцы чатов удалять бота не могут. Прежний токен инвалидируется сразу, бот исключается из всех чатов, его исходящий вебхук удаляется:\n $ pachca bots delete"
10
9
  ];
11
10
  static scope = "bots:write";
12
11
  static apiMethod = "DELETE";
@@ -3,10 +3,6 @@ import { Args } from '@oclif/core';
3
3
  import { BaseCommand } from '../../base-command.js';
4
4
  export default class BotsGet extends BaseCommand {
5
5
  static description = "Информация о боте";
6
- static examples = [
7
- "Обновить Webhook URL бота:\n $ pachca bots update",
8
- "Найти и удалить бота:\n $ pachca bots delete"
9
- ];
10
6
  static scope = "bots:read";
11
7
  static apiMethod = "GET";
12
8
  static apiPath = "/bots/{id}";
@@ -27,7 +23,7 @@ export default class BotsGet extends BaseCommand {
27
23
  method: 'GET',
28
24
  path: `/bots/${args.id}`,
29
25
  });
30
- const responseBody = data;
26
+ const responseBody = (data ?? {});
31
27
  const result = responseBody.data ?? responseBody;
32
28
  this.output(result);
33
29
  }
@@ -4,7 +4,7 @@ import { BaseCommand } from '../../base-command.js';
4
4
  export default class BotsListEvents extends BaseCommand {
5
5
  static description = "История событий";
6
6
  static examples = [
7
- "Обработка событий через историю (polling):\n $ pachca bots list-events"
7
+ "Обработка событий через историю (polling) — Получи накопленные события:\n $ pachca bots list-events"
8
8
  ];
9
9
  static scope = "webhooks:events:read";
10
10
  static apiMethod = "GET";
@@ -85,7 +85,7 @@ export default class BotsListEvents extends BaseCommand {
85
85
  cursor: flags.cursor,
86
86
  },
87
87
  });
88
- const responseBody = data;
88
+ const responseBody = (data ?? {});
89
89
  const items = responseBody.data ?? responseBody;
90
90
  this.output(items);
91
91
  }
@@ -4,9 +4,7 @@ import { BaseCommand } from '../../base-command.js';
4
4
  export default class BotsList extends BaseCommand {
5
5
  static description = "Список ботов";
6
6
  static examples = [
7
- "Создать бота через API и получить токен:\n $ pachca bots create",
8
- "Настроить бота с исходящим вебхуком:\n $ pachca bots create",
9
- "Найти и удалить бота:\n $ pachca bots list"
7
+ "Найти и удалить бота — Пользовательским токеном (скоуп `bots:read`) получи список ботов, доступных тебе для редактирования: созданных тобой и тех, чьи настройки открывают тебе доступ. Фильтруй по имени параметром `query`, следующую страницу бери из `meta.paginate.next_page`:\n $ pachca bots list"
10
8
  ];
11
9
  static scope = "bots:read";
12
10
  static apiMethod = "GET";
@@ -92,7 +90,7 @@ export default class BotsList extends BaseCommand {
92
90
  cursor: flags.cursor,
93
91
  },
94
92
  });
95
- const responseBody = data;
93
+ const responseBody = (data ?? {});
96
94
  const items = responseBody.data ?? responseBody;
97
95
  this.output(items);
98
96
  }
@@ -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
- "Ротация токена бота:\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";
@@ -19,7 +19,7 @@ export default class BotsRecreateTokenSelf extends BaseCommand {
19
19
  method: 'POST',
20
20
  path: '/bot/recreate_token',
21
21
  });
22
- const responseBody = data;
22
+ const responseBody = (data ?? {});
23
23
  const result = responseBody.data ?? responseBody;
24
24
  this.output(result);
25
25
  }
@@ -4,7 +4,7 @@ import { BaseCommand } from '../../base-command.js';
4
4
  export default class BotsRecreateToken extends BaseCommand {
5
5
  static description = "Ротация токена бота";
6
6
  static examples = [
7
- "Ротация токена бота:\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";
@@ -26,7 +26,7 @@ export default class BotsRecreateToken extends BaseCommand {
26
26
  method: 'POST',
27
27
  path: `/bots/${args.id}/recreate_token`,
28
28
  });
29
- const responseBody = data;
29
+ const responseBody = (data ?? {});
30
30
  const result = responseBody.data ?? responseBody;
31
31
  this.output(result);
32
32
  }
@@ -5,7 +5,7 @@ import * as clack from '@clack/prompts';
5
5
  export default class BotsRemoveEvent extends BaseCommand {
6
6
  static description = "Удаление события";
7
7
  static examples = [
8
- "Обработка событий через историю (polling):\n $ pachca bots remove-event"
8
+ "Обработка событий через историю (polling) — Удали обработанное событие:\n $ pachca bots remove-event"
9
9
  ];
10
10
  static scope = "webhooks:events:delete";
11
11
  static apiMethod = "DELETE";
@@ -5,7 +5,7 @@ import * as clack from '@clack/prompts';
5
5
  export default class BotsUpdateWebhook extends BaseCommand {
6
6
  static description = "Саморегистрация вебхука бота";
7
7
  static examples = [
8
- "Обновить Webhook URL бота:\n $ pachca bots update-webhook"
8
+ "Обновить Webhook URL бота — Или: бот сам обновляет свой webhook своим же токеном — без `id` и без участия администратора (нужен скоуп `bot_self:webhook:write`):\n $ pachca bots update-webhook"
9
9
  ];
10
10
  static scope = "bot_self:webhook:write";
11
11
  static apiMethod = "PUT";
@@ -65,7 +65,7 @@ export default class BotsUpdateWebhook extends BaseCommand {
65
65
  path: '/bot/webhook',
66
66
  body,
67
67
  });
68
- const responseBody = data;
68
+ const responseBody = (data ?? {});
69
69
  const result = responseBody.data ?? responseBody;
70
70
  this.output(result);
71
71
  }
@@ -4,8 +4,7 @@ import { BaseCommand } from '../../base-command.js';
4
4
  export default class BotsUpdate extends BaseCommand {
5
5
  static description = "Редактирование бота";
6
6
  static examples = [
7
- "Обновить Webhook URL бота:\n $ pachca bots update",
8
- "Найти и удалить бота:\n $ pachca bots delete"
7
+ "Обновить Webhook URL бота — Пользовательским токеном (с правом редактировать бота) — обнови URL по `id` бота. Пустая строка отключает вебхук:\n $ pachca bots update"
9
8
  ];
10
9
  static scope = "bots:write";
11
10
  static apiMethod = "PUT";
@@ -20,10 +19,10 @@ export default class BotsUpdate extends BaseCommand {
20
19
  static flags = {
21
20
  ...BaseCommand.baseFlags,
22
21
  'name': Flags.string({
23
- description: "Имя бота",
22
+ description: "Имя бота (макс. 255 символов)",
24
23
  }),
25
24
  'nickname': Flags.string({
26
- description: "Никнейм бота. Должен заканчиваться на `_bot`.",
25
+ description: "Никнейм бота. Должен заканчиваться на `_bot`. (макс. 255 символов)",
27
26
  }),
28
27
  'outgoing-url': Flags.string({
29
28
  description: "URL исходящего вебхука",
@@ -35,10 +34,10 @@ export default class BotsUpdate extends BaseCommand {
35
34
  description: "Условие срабатывания исходящего вебхука",
36
35
  }),
37
36
  'commands': Flags.string({
38
- description: "Команды бота (триггер-слова), на которые он реагирует при trigger_on = commands",
37
+ description: "Команды бота (триггер-слова), на которые он реагирует при trigger_on = commands. Суммарная длина команд, объединённых через запятую, не должна превышать 255 символов.",
39
38
  }),
40
39
  'scopes': Flags.string({
41
- description: "Скоупы (права доступа) токена бота. Если не указано, бот получает набор по умолчанию.",
40
+ description: "Скоупы (права доступа) токена бота. Если не указано, бот получает набор по умолчанию. Боту доступны не все скоупы: часть из них разрешена только пользовательским ролям, и попытка назначить такой скоуп возвращает `400`. Служебные значения `bot` и `all` назначать нельзя.",
42
41
  }),
43
42
  'template': Flags.string({
44
43
  description: "Шаблон форматирования входящего вебхука",
@@ -71,6 +70,16 @@ export default class BotsUpdate extends BaseCommand {
71
70
  async run() {
72
71
  const { args, flags } = await this.parse(BotsUpdate);
73
72
  this.parsedFlags = flags;
73
+ const validationErrors = [];
74
+ if (flags['name'] && String(flags['name']).length > 255) {
75
+ validationErrors.push({ message: `--name: максимум 255 символов (передано: ${String(flags['name']).length})`, flag: 'name' });
76
+ }
77
+ if (flags['nickname'] && String(flags['nickname']).length > 255) {
78
+ validationErrors.push({ message: `--nickname: максимум 255 символов (передано: ${String(flags['nickname']).length})`, flag: 'nickname' });
79
+ }
80
+ if (validationErrors.length > 0) {
81
+ this.validationError(validationErrors);
82
+ }
74
83
  const body = { webhook: {
75
84
  name: flags['name'],
76
85
  nickname: flags['nickname'],
@@ -102,7 +111,7 @@ export default class BotsUpdate extends BaseCommand {
102
111
  path: `/bots/${args.id}`,
103
112
  body,
104
113
  });
105
- const responseBody = data;
114
+ const responseBody = (data ?? {});
106
115
  const result = responseBody.data ?? responseBody;
107
116
  this.output(result);
108
117
  }
@@ -4,8 +4,8 @@ import { BaseCommand } from '../../base-command.js';
4
4
  export default class ChatsArchive extends BaseCommand {
5
5
  static description = "Архивация чата";
6
6
  static examples = [
7
- "Архивация и управление чатом:\n $ pachca chats archive",
8
- "Найти и заархивировать неактивные чаты:\n $ pachca chats archive"
7
+ "Архивация и управление чатом — Архивировать чат:\n $ pachca chats archive",
8
+ "Найти и заархивировать неактивные чаты — Для каждого чата: архивируй:\n $ pachca chats archive"
9
9
  ];
10
10
  static scope = "chats:archive";
11
11
  static apiMethod = "PUT";
@@ -26,7 +26,7 @@ export default class ChatsArchive extends BaseCommand {
26
26
  method: 'PUT',
27
27
  path: `/chats/${args.id}/archive`,
28
28
  });
29
- const responseBody = data;
29
+ const responseBody = (data ?? {});
30
30
  const result = responseBody.data ?? responseBody;
31
31
  this.output(result);
32
32
  }
@@ -5,9 +5,8 @@ import * as clack from '@clack/prompts';
5
5
  export default class ChatsCreate extends BaseCommand {
6
6
  static description = "Новый чат";
7
7
  static examples = [
8
- "Создать канал и пригласить участников:\n $ pachca chats create",
9
- "Создать проектную беседу из шаблона:\n $ pachca chats create",
10
- "Найти активные чаты за период:\n $ pachca chats list"
8
+ "Создать канал и пригласить участников — Создай канал с участниками:\n $ pachca chats create",
9
+ "Создать проектную беседу из шаблона — Создай беседу с участниками из тега:\n $ pachca chats create"
11
10
  ];
12
11
  static scope = "chats:create";
13
12
  static apiMethod = "POST";
@@ -18,7 +17,7 @@ export default class ChatsCreate extends BaseCommand {
18
17
  static flags = {
19
18
  ...BaseCommand.baseFlags,
20
19
  'name': Flags.string({
21
- description: "Название",
20
+ description: "Название (макс. 255 символов)",
22
21
  }),
23
22
  'member-ids': Flags.string({
24
23
  description: "Массив идентификаторов пользователей, которые станут участниками",
@@ -64,6 +63,13 @@ export default class ChatsCreate extends BaseCommand {
64
63
  this.validationError(missingRequired.map((f) => ({ message: `Обязательный флаг --${f.flag} не передан`, flag: f.flag })), { hint: "Обязательные: --name <string>. pachca introspect chats create" });
65
64
  }
66
65
  }
66
+ const validationErrors = [];
67
+ if (flags['name'] && String(flags['name']).length > 255) {
68
+ validationErrors.push({ message: `--name: максимум 255 символов (передано: ${String(flags['name']).length})`, flag: 'name' });
69
+ }
70
+ if (validationErrors.length > 0) {
71
+ this.validationError(validationErrors);
72
+ }
67
73
  const body = { chat: {
68
74
  name: flags['name'],
69
75
  member_ids: flags['member-ids'] ? this.parseJSON(flags['member-ids'], 'member-ids') : undefined,
@@ -82,7 +88,7 @@ export default class ChatsCreate extends BaseCommand {
82
88
  path: '/chats',
83
89
  body,
84
90
  });
85
- const responseBody = data;
91
+ const responseBody = (data ?? {});
86
92
  const result = responseBody.data ?? responseBody;
87
93
  this.output(result);
88
94
  }
@@ -6,7 +6,7 @@ import { formatSize } from '../../utils.js';
6
6
  export default class ChatsDownloadExport extends BaseCommand {
7
7
  static description = "Скачать архив экспорта";
8
8
  static examples = [
9
- "Экспорт истории чата:\n $ pachca chats download-export"
9
+ "Экспорт истории чата — Скачай файл экспорта:\n $ pachca chats download-export"
10
10
  ];
11
11
  static hiddenAliases = ["common:get-exports"];
12
12
  static scope = "chat_exports:read";
@@ -39,7 +39,7 @@ export default class ChatsDownloadExport extends BaseCommand {
39
39
  this.success(`Сохранено: ${flags.save} (${formatSize(result.size)})`);
40
40
  return;
41
41
  }
42
- const responseBody = data;
42
+ const responseBody = (data ?? {});
43
43
  const result = responseBody.data ?? responseBody;
44
44
  this.output(result);
45
45
  }
@@ -3,9 +3,6 @@ import { Args } from '@oclif/core';
3
3
  import { BaseCommand } from '../../base-command.js';
4
4
  export default class ChatsGet extends BaseCommand {
5
5
  static description = "Информация о чате";
6
- static examples = [
7
- "Переименовать или обновить чат:\n $ pachca chats update"
8
- ];
9
6
  static scope = "chats:read";
10
7
  static apiMethod = "GET";
11
8
  static apiPath = "/chats/{id}";
@@ -26,7 +23,7 @@ export default class ChatsGet extends BaseCommand {
26
23
  method: 'GET',
27
24
  path: `/chats/${args.id}`,
28
25
  });
29
- const responseBody = data;
26
+ const responseBody = (data ?? {});
30
27
  const result = responseBody.data ?? responseBody;
31
28
  this.output(result);
32
29
  }
@@ -4,9 +4,9 @@ import { BaseCommand } from '../../base-command.js';
4
4
  export default class ChatsList extends BaseCommand {
5
5
  static description = "Список чатов";
6
6
  static examples = [
7
- "Создать канал и пригласить участников:\n $ pachca chats create",
8
- "Создать проектную беседу из шаблона:\n $ pachca chats create",
9
- "Найти активные чаты за период:\n $ pachca chats list"
7
+ "Найти активные чаты за период — Получи чаты с активностью после указанной даты:\n $ pachca chats list",
8
+ "Найти и заархивировать неактивные чаты — Получи чаты без активности с нужной даты:\n $ pachca chats list",
9
+ "Создать гостя в чат — Выбери активный чат, в который добавить гостя — узнай его ID:\n $ pachca chats list"
10
10
  ];
11
11
  static scope = "chats:read";
12
12
  static apiMethod = "GET";
@@ -124,7 +124,7 @@ export default class ChatsList extends BaseCommand {
124
124
  cursor: flags.cursor,
125
125
  },
126
126
  });
127
- const responseBody = data;
127
+ const responseBody = (data ?? {});
128
128
  const items = responseBody.data ?? responseBody;
129
129
  this.output(items);
130
130
  }
@@ -5,7 +5,7 @@ import * as clack from '@clack/prompts';
5
5
  export default class ChatsRequestExport extends BaseCommand {
6
6
  static description = "Экспорт сообщений";
7
7
  static examples = [
8
- "Экспорт истории чата:\n $ pachca chats request-export"
8
+ "Экспорт истории чата — Запроси экспорт:\n $ pachca chats request-export"
9
9
  ];
10
10
  static hiddenAliases = ["common:request-export"];
11
11
  static scope = "chat_exports:write";
@@ -81,7 +81,7 @@ export default class ChatsRequestExport extends BaseCommand {
81
81
  path: '/chats/exports',
82
82
  body,
83
83
  });
84
- const responseBody = data;
84
+ const responseBody = (data ?? {});
85
85
  const result = responseBody.data ?? responseBody;
86
86
  this.output(result);
87
87
  }
@@ -4,7 +4,7 @@ import { BaseCommand } from '../../base-command.js';
4
4
  export default class ChatsUnarchive extends BaseCommand {
5
5
  static description = "Разархивация чата";
6
6
  static examples = [
7
- "Архивация и управление чатом:\n $ pachca chats unarchive"
7
+ "Архивация и управление чатом — Разархивировать чат:\n $ pachca chats unarchive"
8
8
  ];
9
9
  static scope = "chats:archive";
10
10
  static apiMethod = "PUT";
@@ -25,7 +25,7 @@ export default class ChatsUnarchive extends BaseCommand {
25
25
  method: 'PUT',
26
26
  path: `/chats/${args.id}/unarchive`,
27
27
  });
28
- const responseBody = data;
28
+ const responseBody = (data ?? {});
29
29
  const result = responseBody.data ?? responseBody;
30
30
  this.output(result);
31
31
  }