@pachca/cli 2026.7.1 → 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 (87) hide show
  1. package/CHANGELOG.md +12 -0
  2. package/README.md +1 -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 +4 -4
  7. package/dist/commands/bots/delete.js +1 -1
  8. package/dist/commands/bots/get.js +1 -1
  9. package/dist/commands/bots/list-events.js +2 -2
  10. package/dist/commands/bots/list.js +2 -2
  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 +3 -3
  16. package/dist/commands/chats/archive.js +3 -3
  17. package/dist/commands/chats/create.js +3 -3
  18. package/dist/commands/chats/download-export.js +2 -2
  19. package/dist/commands/chats/get.js +1 -1
  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 +2 -2
  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 +2 -2
  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 -2
  31. package/dist/commands/group-tags/update.js +1 -1
  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 -2
  36. package/dist/commands/members/remove.js +1 -1
  37. package/dist/commands/members/update.js +2 -2
  38. package/dist/commands/messages/create.js +4 -4
  39. package/dist/commands/messages/delete.js +1 -1
  40. package/dist/commands/messages/get.js +4 -4
  41. package/dist/commands/messages/list.js +2 -2
  42. package/dist/commands/messages/pin.js +2 -2
  43. package/dist/commands/messages/unfurl.js +4 -4
  44. package/dist/commands/messages/unpin.js +1 -1
  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 -1
  48. package/dist/commands/profile/delete-status.js +1 -1
  49. package/dist/commands/profile/get-status.js +1 -1
  50. package/dist/commands/profile/get.js +3 -3
  51. package/dist/commands/profile/update-avatar.js +2 -2
  52. package/dist/commands/profile/update-status.js +2 -2
  53. package/dist/commands/reactions/add.js +2 -2
  54. package/dist/commands/reactions/list.js +1 -1
  55. package/dist/commands/reactions/remove.js +1 -1
  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 +5 -5
  61. package/dist/commands/tasks/create.js +5 -5
  62. package/dist/commands/tasks/delete.js +1 -1
  63. package/dist/commands/tasks/get.js +2 -2
  64. package/dist/commands/tasks/list.js +2 -2
  65. package/dist/commands/tasks/update.js +4 -4
  66. package/dist/commands/threads/add.js +3 -3
  67. package/dist/commands/threads/create.js +1 -1
  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 +4 -4
  71. package/dist/commands/users/delete.js +1 -1
  72. package/dist/commands/users/get-status.js +2 -2
  73. package/dist/commands/users/get.js +2 -2
  74. package/dist/commands/users/list.js +4 -4
  75. package/dist/commands/users/remove-avatar.js +1 -1
  76. package/dist/commands/users/remove-status.js +1 -1
  77. package/dist/commands/users/update-avatar.js +2 -2
  78. package/dist/commands/users/update-status.js +2 -2
  79. package/dist/commands/users/update.js +3 -3
  80. package/dist/commands/views/open.js +4 -4
  81. package/dist/data/changelog.json +51 -0
  82. package/dist/data/endpoints.json +1 -1
  83. package/dist/data/workflows.json +2 -2
  84. package/dist/output.js +10 -2
  85. package/dist/profiles.js +7 -3
  86. package/oclif.manifest.json +1 -1
  87. package/package.json +1 -1
package/CHANGELOG.md CHANGED
@@ -1,5 +1,17 @@
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
+
3
15
  ## 2026.7.1 (24 июля 2026)
4
16
 
5
17
  - **Добавлено** (threads create): Новая команда — создание самостоятельного треда без привязки к сообщению
package/README.md CHANGED
@@ -392,7 +392,7 @@ pachca autocomplete powershell # PowerShell completion
392
392
 
393
393
  | Переменная | Описание |
394
394
  |-----------|---------|
395
- | `PACHCA_TOKEN` | Bearer-токен (высший приоритет, удобно для CI) |
395
+ | `PACHCA_TOKEN` | Bearer-токен, удобно для CI. Приоритетнее профиля, но флаги `--token` и `--profile` его перебивают |
396
396
  | `PACHCA_PROFILE` | Активный профиль для команды |
397
397
  | `PACHCA_TIMEOUT` | Таймаут запроса в секундах (по умолчанию 30) |
398
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,8 +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"
8
+ "Создать бота через API и получить токен — Создай бота. Только пользовательским токеном (не токеном бота); `nickname` обязан заканчиваться на `_bot`. Параметры вебхука (Webhook URL, события, команды) можно задать сразу или позже. Скоупы токена бота можно ограничить флагом `--scopes` (если не указать — бот получит набор по умолчанию):\n $ pachca bots create",
9
+ "Настроить бота с исходящим вебхуком — Создай бота, сразу указав Webhook URL и события в одном вызове (детали создания и работы с токеном — в сценарии «Создать бота через API и получить токен»):\n $ pachca bots create"
10
10
  ];
11
11
  static scope = "bots:write";
12
12
  static apiMethod = "POST";
@@ -35,7 +35,7 @@ export default class BotsCreate extends BaseCommand {
35
35
  description: "Команды бота (триггер-слова), на которые он реагирует при trigger_on = commands. Суммарная длина команд, объединённых через запятую, не должна превышать 255 символов.",
36
36
  }),
37
37
  'scopes': Flags.string({
38
- description: "Скоупы (права доступа) токена бота. Если не указано, бот получает набор по умолчанию.",
38
+ description: "Скоупы (права доступа) токена бота. Если не указано, бот получает набор по умолчанию. Боту доступны не все скоупы: часть из них разрешена только пользовательским ролям, и попытка назначить такой скоуп возвращает `400`. Служебные значения `bot` и `all` назначать нельзя.",
39
39
  }),
40
40
  'template': Flags.string({
41
41
  description: "Шаблон форматирования входящего вебхука",
@@ -137,7 +137,7 @@ export default class BotsCreate extends BaseCommand {
137
137
  path: '/bots',
138
138
  body,
139
139
  });
140
- const responseBody = data;
140
+ const responseBody = (data ?? {});
141
141
  const result = responseBody.data ?? responseBody;
142
142
  this.output(result);
143
143
  }
@@ -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
- "Найти и удалить бота:\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";
@@ -23,7 +23,7 @@ export default class BotsGet extends BaseCommand {
23
23
  method: 'GET',
24
24
  path: `/bots/${args.id}`,
25
25
  });
26
- const responseBody = data;
26
+ const responseBody = (data ?? {});
27
27
  const result = responseBody.data ?? responseBody;
28
28
  this.output(result);
29
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,7 +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
- "Найти и удалить бота:\n $ pachca bots list"
7
+ "Найти и удалить бота — Пользовательским токеном (скоуп `bots:read`) получи список ботов, доступных тебе для редактирования: созданных тобой и тех, чьи настройки открывают тебе доступ. Фильтруй по имени параметром `query`, следующую страницу бери из `meta.paginate.next_page`:\n $ pachca bots list"
8
8
  ];
9
9
  static scope = "bots:read";
10
10
  static apiMethod = "GET";
@@ -90,7 +90,7 @@ export default class BotsList extends BaseCommand {
90
90
  cursor: flags.cursor,
91
91
  },
92
92
  });
93
- const responseBody = data;
93
+ const responseBody = (data ?? {});
94
94
  const items = responseBody.data ?? responseBody;
95
95
  this.output(items);
96
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,7 +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"
7
+ "Обновить Webhook URL бота — Пользовательским токеном (с правом редактировать бота) — обнови URL по `id` бота. Пустая строка отключает вебхук:\n $ pachca bots update"
8
8
  ];
9
9
  static scope = "bots:write";
10
10
  static apiMethod = "PUT";
@@ -37,7 +37,7 @@ export default class BotsUpdate extends BaseCommand {
37
37
  description: "Команды бота (триггер-слова), на которые он реагирует при trigger_on = commands. Суммарная длина команд, объединённых через запятую, не должна превышать 255 символов.",
38
38
  }),
39
39
  'scopes': Flags.string({
40
- description: "Скоупы (права доступа) токена бота. Если не указано, бот получает набор по умолчанию.",
40
+ description: "Скоупы (права доступа) токена бота. Если не указано, бот получает набор по умолчанию. Боту доступны не все скоупы: часть из них разрешена только пользовательским ролям, и попытка назначить такой скоуп возвращает `400`. Служебные значения `bot` и `all` назначать нельзя.",
41
41
  }),
42
42
  'template': Flags.string({
43
43
  description: "Шаблон форматирования входящего вебхука",
@@ -111,7 +111,7 @@ export default class BotsUpdate extends BaseCommand {
111
111
  path: `/bots/${args.id}`,
112
112
  body,
113
113
  });
114
- const responseBody = data;
114
+ const responseBody = (data ?? {});
115
115
  const result = responseBody.data ?? responseBody;
116
116
  this.output(result);
117
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,8 +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"
8
+ "Создать канал и пригласить участников — Создай канал с участниками:\n $ pachca chats create",
9
+ "Создать проектную беседу из шаблона — Создай беседу с участниками из тега:\n $ pachca chats create"
10
10
  ];
11
11
  static scope = "chats:create";
12
12
  static apiMethod = "POST";
@@ -88,7 +88,7 @@ export default class ChatsCreate extends BaseCommand {
88
88
  path: '/chats',
89
89
  body,
90
90
  });
91
- const responseBody = data;
91
+ const responseBody = (data ?? {});
92
92
  const result = responseBody.data ?? responseBody;
93
93
  this.output(result);
94
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
  }
@@ -23,7 +23,7 @@ export default class ChatsGet extends BaseCommand {
23
23
  method: 'GET',
24
24
  path: `/chats/${args.id}`,
25
25
  });
26
- const responseBody = data;
26
+ const responseBody = (data ?? {});
27
27
  const result = responseBody.data ?? responseBody;
28
28
  this.output(result);
29
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 list",
8
- "Найти и заархивировать неактивные чаты:\n $ pachca chats list",
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
  }
@@ -4,7 +4,7 @@ import { BaseCommand } from '../../base-command.js';
4
4
  export default class ChatsUpdate extends BaseCommand {
5
5
  static description = "Редактирование чата";
6
6
  static examples = [
7
- "Переименовать или обновить чат:\n $ pachca chats update"
7
+ "Переименовать или обновить чат — Обнови чат:\n $ pachca chats update"
8
8
  ];
9
9
  static scope = "chats:update";
10
10
  static apiMethod = "PUT";
@@ -54,7 +54,7 @@ export default class ChatsUpdate extends BaseCommand {
54
54
  path: `/chats/${args.id}`,
55
55
  body,
56
56
  });
57
- const responseBody = data;
57
+ const responseBody = (data ?? {});
58
58
  const result = responseBody.data ?? responseBody;
59
59
  this.output(result);
60
60
  }
@@ -5,7 +5,7 @@ import * as clack from '@clack/prompts';
5
5
  export default class CustomPropertiesList extends BaseCommand {
6
6
  static description = "Список дополнительных полей";
7
7
  static examples = [
8
- "Получить кастомные поля профиля:\n $ pachca custom-properties list"
8
+ "Получить кастомные поля профиля — Получи список дополнительных полей для сотрудников:\n $ pachca custom-properties list"
9
9
  ];
10
10
  static hiddenAliases = ["common:custom-properties"];
11
11
  static scope = "custom_properties:read";
@@ -57,7 +57,7 @@ export default class CustomPropertiesList extends BaseCommand {
57
57
  'entity_type': flags['entity-type'],
58
58
  },
59
59
  });
60
- const responseBody = data;
60
+ const responseBody = (data ?? {});
61
61
  const result = responseBody.data ?? responseBody;
62
62
  this.output(result);
63
63
  }
@@ -84,17 +84,6 @@ export default class FilesDirectUrl extends BaseCommand {
84
84
  let formData;
85
85
  if (flags.file) {
86
86
  formData = new FormData();
87
- if (flags.file === '-') {
88
- const chunks = [];
89
- for await (const chunk of process.stdin)
90
- chunks.push(chunk);
91
- const blob = new Blob([Buffer.concat(chunks)]);
92
- formData.append('file', blob, 'stdin');
93
- }
94
- else {
95
- const blob = new Blob([fs.readFileSync(flags.file)]);
96
- formData.append('file', blob, path.basename(flags.file));
97
- }
98
87
  if (flags['content-disposition'])
99
88
  formData.append('Content-Disposition', String(flags['content-disposition']));
100
89
  if (flags['acl'])
@@ -111,6 +100,17 @@ export default class FilesDirectUrl extends BaseCommand {
111
100
  formData.append('x-amz-signature', String(flags['x-amz-signature']));
112
101
  if (flags['key'])
113
102
  formData.append('key', String(flags['key']));
103
+ if (flags.file === '-') {
104
+ const chunks = [];
105
+ for await (const chunk of process.stdin)
106
+ chunks.push(chunk);
107
+ const blob = new Blob([Buffer.concat(chunks)]);
108
+ formData.append('file', blob, 'stdin');
109
+ }
110
+ else {
111
+ const blob = new Blob([fs.readFileSync(flags.file)]);
112
+ formData.append('file', blob, path.basename(flags.file));
113
+ }
114
114
  }
115
115
  const { data } = await this.apiRequest({
116
116
  method: 'POST',
@@ -118,7 +118,7 @@ export default class FilesDirectUrl extends BaseCommand {
118
118
  formData,
119
119
  noAuth: true,
120
120
  });
121
- const responseBody = data;
121
+ const responseBody = (data ?? {});
122
122
  const result = responseBody.data ?? responseBody;
123
123
  this.output(result);
124
124
  }
@@ -2,7 +2,7 @@ import { BaseCommand } from '../../base-command.js';
2
2
  export default class FilesUploads extends BaseCommand {
3
3
  static description = "Получение подписи, ключа и других параметров";
4
4
  static examples = [
5
- "Изменить вложения сообщения:\n $ pachca files uploads"
5
+ "Изменить вложения сообщения — Если нужно добавить новый файл — загрузи его:\n $ pachca files uploads"
6
6
  ];
7
7
  static hiddenAliases = ["common:uploads"];
8
8
  static scope = "uploads:write";
@@ -20,7 +20,7 @@ export default class FilesUploads extends BaseCommand {
20
20
  method: 'POST',
21
21
  path: '/uploads',
22
22
  });
23
- const responseBody = data;
23
+ const responseBody = (data ?? {});
24
24
  const result = responseBody.data ?? responseBody;
25
25
  this.output(result);
26
26
  }
@@ -5,7 +5,7 @@ import * as clack from '@clack/prompts';
5
5
  export default class GroupTagsCreate extends BaseCommand {
6
6
  static description = "Новый тег";
7
7
  static examples = [
8
- "Массовое создание сотрудников с тегами:\n $ pachca group-tags create"
8
+ "Массовое создание сотрудников с тегами — Создай тег (если нужен):\n $ pachca group-tags create"
9
9
  ];
10
10
  static scope = "group_tags:write";
11
11
  static apiMethod = "POST";
@@ -69,7 +69,7 @@ export default class GroupTagsCreate extends BaseCommand {
69
69
  path: '/group_tags',
70
70
  body,
71
71
  });
72
- const responseBody = data;
72
+ const responseBody = (data ?? {});
73
73
  const result = responseBody.data ?? responseBody;
74
74
  this.output(result);
75
75
  }
@@ -23,7 +23,7 @@ export default class GroupTagsGet extends BaseCommand {
23
23
  method: 'GET',
24
24
  path: `/group_tags/${args.id}`,
25
25
  });
26
- const responseBody = data;
26
+ const responseBody = (data ?? {});
27
27
  const result = responseBody.data ?? responseBody;
28
28
  this.output(result);
29
29
  }
@@ -4,7 +4,7 @@ import { BaseCommand } from '../../base-command.js';
4
4
  export default class GroupTagsListUsers extends BaseCommand {
5
5
  static description = "Список сотрудников тега";
6
6
  static examples = [
7
- "Получить всех сотрудников тега/департамента:\n $ pachca group-tags list-users"
7
+ "Получить всех сотрудников тега/департамента — Получи всех участников тега:\n $ pachca group-tags list-users"
8
8
  ];
9
9
  static scope = "group_tags:read";
10
10
  static apiMethod = "GET";
@@ -90,7 +90,7 @@ export default class GroupTagsListUsers extends BaseCommand {
90
90
  cursor: flags.cursor,
91
91
  },
92
92
  });
93
- const responseBody = data;
93
+ const responseBody = (data ?? {});
94
94
  const items = responseBody.data ?? responseBody;
95
95
  this.output(items);
96
96
  }