@pachca/cli 2026.10.3 → 2026.11.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +35 -0
- package/README.md +5 -5
- package/README.template.md +5 -5
- package/dist/base-command.js +68 -36
- package/dist/client.js +2 -1
- package/dist/commands/api.js +3 -3
- package/dist/commands/auth/login.js +12 -9
- package/dist/commands/bots/create.js +13 -7
- package/dist/commands/bots/list-company.js +4 -2
- package/dist/commands/bots/list-events.js +4 -2
- package/dist/commands/bots/list-tokens.js +4 -2
- package/dist/commands/bots/list.js +5 -3
- package/dist/commands/bots/update-webhook.js +8 -1
- package/dist/commands/bots/update.js +14 -7
- package/dist/commands/chats/list-company.js +4 -2
- package/dist/commands/chats/list.js +4 -2
- package/dist/commands/chats/update.js +1 -1
- package/dist/commands/doctor.js +60 -35
- package/dist/commands/drafts/create.js +6 -3
- package/dist/commands/drafts/list.js +4 -2
- package/dist/commands/drafts/update.js +7 -4
- package/dist/commands/group-tags/list-users.js +4 -2
- package/dist/commands/group-tags/list.js +4 -2
- package/dist/commands/important-messages/list.js +4 -2
- package/dist/commands/members/add.js +1 -1
- package/dist/commands/members/list.js +4 -2
- package/dist/commands/messages/create.js +7 -5
- package/dist/commands/messages/list.js +4 -2
- package/dist/commands/messages/update.js +16 -4
- package/dist/commands/profile/update-status.js +5 -4
- package/dist/commands/reactions/add.js +9 -2
- package/dist/commands/reactions/list.js +4 -2
- package/dist/commands/reactions/remove.js +1 -28
- package/dist/commands/read-member/list-readers.js +4 -2
- package/dist/commands/search/list-chats.js +5 -3
- package/dist/commands/search/list-messages.js +5 -3
- package/dist/commands/search/list-users.js +5 -3
- package/dist/commands/security/list.js +5 -3
- package/dist/commands/tasks/create.js +3 -29
- package/dist/commands/tasks/list.js +4 -2
- package/dist/commands/tasks/update.js +4 -2
- package/dist/commands/threads/list.js +4 -2
- package/dist/commands/upload.js +27 -8
- package/dist/commands/users/create.js +5 -2
- package/dist/commands/users/list.js +5 -3
- package/dist/commands/users/update-status.js +5 -4
- package/dist/commands/users/update.js +8 -5
- package/dist/commands/views/open.js +1 -0
- package/dist/data/changelog.json +150 -0
- package/dist/data/endpoints.json +1 -1
- package/dist/hooks/init.js +8 -4
- package/dist/lib/flags.js +25 -0
- package/dist/oauth.js +2 -1
- package/dist/profiles.js +22 -4
- package/oclif.manifest.json +1 -1
- package/package.json +2 -2
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,40 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 2026.11.0 (4 ноября 2026)
|
|
4
|
+
|
|
5
|
+
- **Изменено** (tasks create): Работает без `--kind` и не спрашивает тип в терминале: по умолчанию создаётся простое напоминание
|
|
6
|
+
- **Изменено** (reactions remove): Снимает реакцию по `--name` без `--code`
|
|
7
|
+
- **Добавлено** (profile update-status, users update-status): `--expires-at null` убирает срок статуса, а `--away-message null` убирает текст «Нет на месте»
|
|
8
|
+
- **Добавлено** (bots create, bots update, bots update-webhook, messages update, reactions add, users create, users update): Длина значений проверяется до запроса: `--outgoing-url`, `--challenge-key`, `--display-avatar-url`, `--display-name` и `--title` до 255 символов, а `--code` у `reactions add` до 16
|
|
9
|
+
- **Добавлено**: В выводе команд с сообщениями и черновиками есть поле `voice_content.transcript_status`: статус расшифровки голосового сообщения
|
|
10
|
+
- **Изменено** (api): Справка `--docs` и схема `--spec` показывают необязательными поля деталей журнала аудита, которых нет в записи без значения, а список скоупов без `files:write`, `webhooks:read` и `webhooks:write`
|
|
11
|
+
- **Изменено**: Справка команд следует уточнённым описаниям API: ник сотрудника и бота, пустой бот, события, команды и `--ignore-self-messages` бота, поиск сотрудников и ботов, длина поискового запроса, подпись сообщения бота, название чата, упоминания в черновике, текст «Нет на месте», смена почты, приостановка владельца, `--actor-id` у `pachca security list` и `--done-at` у `pachca tasks update`
|
|
12
|
+
|
|
13
|
+
## 2026.10.4 (31 октября 2026)
|
|
14
|
+
|
|
15
|
+
- **Исправлено** (api): Справка `--docs` и схема `--spec` показывают `dlp_rule_id` в деталях события `dlp_violation_detected` строкой, как его отдаёт API
|
|
16
|
+
- **Добавлено** (upload): Флаг `--name` задаёт имя файла, которым заканчивается `key`: так данные из stdin получают расширение и уходят в сообщение под этим именем
|
|
17
|
+
- **Исправлено** (upload): Файл, которого нет на диске, команда отклоняет до запроса подписи, в том числе с `--dry-run`
|
|
18
|
+
- **Изменено** (doctor): Проверяет тот же токен, что и остальные команды, в том же порядке: `--token`, `--profile`, `PACHCA_TOKEN`, `PACHCA_PROFILE`, активный профиль, поэтому в CI с одной `PACHCA_TOKEN` команда проходит
|
|
19
|
+
- **Изменено** (doctor): При ошибке хотя бы одной проверки команда завершается с кодом `1` в любом формате вывода, в том числе с `-o json`
|
|
20
|
+
- **Изменено** (doctor): Без файла конфига команда показывает путь, по которому его ищет
|
|
21
|
+
- **Исправлено**: `--dry-run` показывает запрос без входа и без `PACHCA_TOKEN`, а токен в заголовке `Authorization` заменён маской
|
|
22
|
+
- **Исправлено** (files direct-url): С `--dry-run` запрос показан без заголовка `Authorization`, как он и уходит в хранилище
|
|
23
|
+
- **Изменено** (bots update, drafts update): Слово `null` во флаге поля, которое принимает `null`, уходит в API как JSON null: `--kind null` удаляет входящий вебхук бота, а `--parent-message-id null` снимает ответ с черновика
|
|
24
|
+
- **Исправлено** (auth login): Вход через браузер с `--quiet` печатает адрес и код подтверждения, а флаг убирает только строку ожидания и отчёт о входе
|
|
25
|
+
- **Изменено** (auth login): Пример в `--help` и подсказка при `--token -` без потока ввода передают токен из буфера обмена: `pbpaste | pachca auth login --token -`
|
|
26
|
+
- **Исправлено** (messages create, messages update, drafts create, drafts update, tasks create, tasks update): Текст из потока ввода попадает в запрос, только когда поток не пуст, а с пустым потоком, как у скрипта или агента, команда отправляет поля из флагов
|
|
27
|
+
- **Изменено**: Отказ `403` с `role_forbidden` в терминале объясняется словами: право у токена есть, но действие не разрешает роль, поэтому новый вход или токен не помогут. Вывод с `-o json` и в пайпе прежний.
|
|
28
|
+
- **Изменено**: Объяснение отказа `insufficient_scope` в терминале зависит от источника токена: с `--token` или `PACHCA_TOKEN` команда предлагает `pachca oauth token-info -o json`, а если право есть в сохранённом списке профиля, советует обновить его через `pachca auth status --remote`
|
|
29
|
+
- **Изменено**: Когда вход через браузер завершён или отозван, сообщение и подсказка `pachca auth login` называют профиль, с которым шла команда
|
|
30
|
+
- **Исправлено** (api): Эквивалентная команда в справке `--describe` повторяет пример запроса из спецификации и работает при копировании
|
|
31
|
+
- **Исправлено** (api): Пример `pachca api POST /messages` в `--help` передаёт `message[entity_id]` в кавычках и работает при копировании, а описание команды и ошибка `--describe` для неизвестного пути говорят «метод»
|
|
32
|
+
- **Изменено** (views open): Выводит ответ API `{ "success": true }`
|
|
33
|
+
- **Изменено**: Справка команд следует обновлённым описаниям API: удаление входящего вебхука, правило совпадения команд бота, правило упоминаний в тексте сообщения, `duration_ms` и `file_type` у файлов, срок ссылки на поток событий, напоминания вместо задач, поле `scopes` у `bot_token_updated` и другие поля деталей журнала аудита, значения настройки бота у `restricted_bot_tag_denied` в подсказке `--list-tags`, добавление участников в тред, создание треда, `--skip-invite-mentions`, правка без уведомления и список прочитавших
|
|
34
|
+
- **Исправлено** (api): Справка `--docs` и схема `--spec` описывают `scopes` в деталях бота журнала аудита так, как его писал API до 7 октября 2026 года: список прав у `bot_token_created` и объект с `previous` и `new` у `bot_token_updated`
|
|
35
|
+
- **Изменено** (search): С `--all` команды поиска останавливаются по `has_next` и показывают в терминале, сколько результатов загружено из общего числа найденных
|
|
36
|
+
- **Изменено** (api): Справка `--docs` и схема `--spec` методов поиска показывают поле `has_next` в `meta.paginate`
|
|
37
|
+
|
|
3
38
|
## 2026.10.3 (28 октября 2026)
|
|
4
39
|
|
|
5
40
|
- **Изменено** (guide): Команда убрана вместе со сценариями: как решать задачи в Пачке, агенту рассказывают скиллы (https://dev.pachca.com/guides/mcp/plugin), а флаги каждой команды показывает `pachca <команда> --help`
|
package/README.md
CHANGED
|
@@ -286,11 +286,11 @@ pachca auth switch bot-notify
|
|
|
286
286
|
# Загрузить файл (получает подпись и загружает автоматически)
|
|
287
287
|
pachca upload photo.jpg
|
|
288
288
|
|
|
289
|
-
# Из stdin
|
|
290
|
-
cat data.csv | pachca upload -
|
|
289
|
+
# Из stdin: имя с расширением задаёт --name
|
|
290
|
+
cat data.csv | pachca upload - --name data.csv
|
|
291
291
|
```
|
|
292
292
|
|
|
293
|
-
Команда `upload` автоматически получает подпись (`POST /uploads`) и загружает файл на S3. Возвращает `key` — используйте его в `--files` при создании сообщений.
|
|
293
|
+
Команда `upload` автоматически получает подпись (`POST /uploads`) и загружает файл на S3. Возвращает `key` — используйте его в `--files` при создании сообщений. Расширение `name` в файле сообщения должно совпадать с расширением имени в `key`.
|
|
294
294
|
|
|
295
295
|
## Пагинация
|
|
296
296
|
|
|
@@ -340,7 +340,7 @@ pachca users list --columns id,email,role
|
|
|
340
340
|
pachca users list --columns email --no-header
|
|
341
341
|
|
|
342
342
|
# Пайп — цвет отключается автоматически
|
|
343
|
-
pachca users list -o json | jq '.[].
|
|
343
|
+
pachca users list -o json | jq -r '.[] | "\(.first_name) \(.last_name)"'
|
|
344
344
|
|
|
345
345
|
# Текст из файла или pipe
|
|
346
346
|
pachca messages create --entity-id 123 < message.txt
|
|
@@ -373,7 +373,7 @@ Escape hatch для нестандартных запросов без сген
|
|
|
373
373
|
|
|
374
374
|
```
|
|
375
375
|
pachca api GET /messages --query chat_id=123
|
|
376
|
-
pachca api POST /messages -F message[
|
|
376
|
+
pachca api POST /messages -F 'message[entity_id]=12345' -f 'message[content]=Привет'
|
|
377
377
|
pachca api POST /messages --input payload.json
|
|
378
378
|
pachca api GET /profile -o yaml --dry-run
|
|
379
379
|
```
|
package/README.template.md
CHANGED
|
@@ -68,11 +68,11 @@ pachca auth switch bot-notify
|
|
|
68
68
|
# Загрузить файл (получает подпись и загружает автоматически)
|
|
69
69
|
pachca upload photo.jpg
|
|
70
70
|
|
|
71
|
-
# Из stdin
|
|
72
|
-
cat data.csv | pachca upload -
|
|
71
|
+
# Из stdin: имя с расширением задаёт --name
|
|
72
|
+
cat data.csv | pachca upload - --name data.csv
|
|
73
73
|
```
|
|
74
74
|
|
|
75
|
-
Команда `upload` автоматически получает подпись (`POST /uploads`) и загружает файл на S3. Возвращает `key` — используйте его в `--files` при создании сообщений.
|
|
75
|
+
Команда `upload` автоматически получает подпись (`POST /uploads`) и загружает файл на S3. Возвращает `key` — используйте его в `--files` при создании сообщений. Расширение `name` в файле сообщения должно совпадать с расширением имени в `key`.
|
|
76
76
|
|
|
77
77
|
## Пагинация
|
|
78
78
|
|
|
@@ -122,7 +122,7 @@ pachca users list --columns id,email,role
|
|
|
122
122
|
pachca users list --columns email --no-header
|
|
123
123
|
|
|
124
124
|
# Пайп — цвет отключается автоматически
|
|
125
|
-
pachca users list -o json | jq '.[].
|
|
125
|
+
pachca users list -o json | jq -r '.[] | "\(.first_name) \(.last_name)"'
|
|
126
126
|
|
|
127
127
|
# Текст из файла или pipe
|
|
128
128
|
pachca messages create --entity-id 123 < message.txt
|
|
@@ -155,7 +155,7 @@ Escape hatch для нестандартных запросов без сген
|
|
|
155
155
|
|
|
156
156
|
```
|
|
157
157
|
pachca api GET /messages --query chat_id=123
|
|
158
|
-
pachca api POST /messages -F message[
|
|
158
|
+
pachca api POST /messages -F 'message[entity_id]=12345' -f 'message[content]=Привет'
|
|
159
159
|
pachca api POST /messages --input payload.json
|
|
160
160
|
pachca api GET /profile -o yaml --dry-run
|
|
161
161
|
```
|
package/dist/base-command.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { Command, Flags } from '@oclif/core';
|
|
2
|
-
import { resolveToken, TokenNotFoundError, ProfileNotFoundError, getAuthMethod, getDefaults, } from './profiles.js';
|
|
2
|
+
import { resolveToken, TokenNotFoundError, ProfileNotFoundError, getAuthMethod, getDefaults, profileFlag, } from './profiles.js';
|
|
3
3
|
import { ApiError, getExitCode, formatDryRun, request } from './client.js';
|
|
4
4
|
import { outputData, outputError, outputSuccess } from './output.js';
|
|
5
5
|
import { defaultOutputFormat, isInteractive } from './utils.js';
|
|
@@ -181,8 +181,10 @@ export class BaseCommand extends Command {
|
|
|
181
181
|
* Make an authenticated API request.
|
|
182
182
|
*/
|
|
183
183
|
async apiRequest(opts) {
|
|
184
|
-
const { token } = this.resolveAuth();
|
|
185
184
|
const clientFlags = this.getClientFlags();
|
|
185
|
+
// --dry-run sends nothing, so it needs no token: the preview masks the header
|
|
186
|
+
// either way. Resolving first made it fail with «Token not found» before login.
|
|
187
|
+
const token = clientFlags['dry-run'] ? '' : this.resolveAuth().token;
|
|
186
188
|
const fullOpts = { ...opts, token };
|
|
187
189
|
if (clientFlags['dry-run']) {
|
|
188
190
|
const dryRunOutput = formatDryRun(fullOpts, this.getOutputFormat() === 'json');
|
|
@@ -235,9 +237,11 @@ export class BaseCommand extends Command {
|
|
|
235
237
|
* Explain a scope refusal instead of printing a bare 403.
|
|
236
238
|
*
|
|
237
239
|
* An agent cannot walk through a consent screen — the most useful thing it can
|
|
238
|
-
* do is hand control back to a human with the reason spelled out.
|
|
239
|
-
*
|
|
240
|
-
*
|
|
240
|
+
* do is hand control back to a human with the reason spelled out.
|
|
241
|
+
*
|
|
242
|
+
* `insufficient_scope` always means the token itself lacks the scope. A role
|
|
243
|
+
* that does not allow the action comes back as `role_forbidden` instead, see
|
|
244
|
+
* reportRoleRefusal. The fix differs by how the profile was authorised.
|
|
241
245
|
*/
|
|
242
246
|
reportScopeError(err) {
|
|
243
247
|
const format = this.getOutputFormat();
|
|
@@ -246,44 +250,69 @@ export class BaseCommand extends Command {
|
|
|
246
250
|
outputError(err.details, format);
|
|
247
251
|
this.exit(3);
|
|
248
252
|
}
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
253
|
+
const resolved = this.tryResolveProfile();
|
|
254
|
+
const profile = resolved?.profile;
|
|
255
|
+
process.stderr.write(`✗ Не хватает права${scope ? ` ${scope}` : ''}.\n`);
|
|
256
|
+
if (!profile) {
|
|
257
|
+
process.stderr.write(` У токена ${scope ? 'этого' : 'нужного'} права нет.\n`);
|
|
258
|
+
process.stderr.write(` Проверить права токена: pachca oauth token-info -o json\n`);
|
|
259
|
+
this.exit(3);
|
|
255
260
|
}
|
|
256
|
-
|
|
257
|
-
|
|
261
|
+
// The scope list in the profile is a copy taken at login. When it still names
|
|
262
|
+
// the scope the server just refused, the copy is stale: the token's scopes were
|
|
263
|
+
// changed after login. Naming the scope needs the server's message to carry it.
|
|
264
|
+
const flag = profileFlag(resolved?.profileName);
|
|
265
|
+
const listedLocally = !!scope && !!profile.scopes?.includes(scope);
|
|
266
|
+
if (listedLocally) {
|
|
267
|
+
process.stderr.write(` В сохранённом списке право есть, но у токена его уже нет: список устарел.\n`);
|
|
268
|
+
process.stderr.write(` Обновить список: pachca auth status --remote${flag}\n`);
|
|
258
269
|
}
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
270
|
+
else {
|
|
271
|
+
process.stderr.write(` У токена ${scope ? 'этого' : 'нужного'} права нет.\n`);
|
|
272
|
+
}
|
|
273
|
+
if (getAuthMethod(profile) === 'oauth') {
|
|
274
|
+
process.stderr.write(` При входе через браузер права выдаются по роли на момент подтверждения.\n`);
|
|
275
|
+
process.stderr.write(` Если роль с тех пор расширили, войдите заново: pachca auth login${flag}\n`);
|
|
276
|
+
}
|
|
277
|
+
else {
|
|
278
|
+
process.stderr.write(` Добавьте право токену в Пачке или выпустите новый токен с ним.\n`);
|
|
279
|
+
}
|
|
280
|
+
this.exit(3);
|
|
281
|
+
}
|
|
282
|
+
/**
|
|
283
|
+
* Explain a role refusal: the token carries the scope, but the role of the
|
|
284
|
+
* account behind it does not allow the action. No new login or token changes
|
|
285
|
+
* that, so say who can. The API names no scope here; a generated command
|
|
286
|
+
* knows the one its method requires.
|
|
287
|
+
*/
|
|
288
|
+
reportRoleRefusal(err) {
|
|
289
|
+
const format = this.getOutputFormat();
|
|
290
|
+
if (format === 'json' || !process.stderr.isTTY) {
|
|
291
|
+
outputError(err.details, format);
|
|
292
|
+
this.exit(3);
|
|
272
293
|
}
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
294
|
+
const profile = this.tryResolveProfile()?.profile;
|
|
295
|
+
const scope = this.constructor.scope;
|
|
296
|
+
const right = scope ? `Право ${scope}` : 'Право';
|
|
297
|
+
if (profile?.type === 'bot') {
|
|
298
|
+
process.stderr.write(`✗ ${right} есть у токена, но его не даёт роль бота.\n`);
|
|
299
|
+
process.stderr.write(` Это действие выполняют персональным токеном сотрудника с подходящей ролью.\n`);
|
|
278
300
|
}
|
|
279
301
|
else {
|
|
280
|
-
process.stderr.write(
|
|
281
|
-
process.stderr.write(
|
|
282
|
-
? ` Проверить права токена: pachca auth status\n`
|
|
283
|
-
: ` Проверить права токена: pachca api GET /oauth/token/info\n`);
|
|
302
|
+
process.stderr.write(`✗ ${right} есть у токена, но его не даёт роль.\n`);
|
|
303
|
+
process.stderr.write(` Новый вход или новый токен не помогут: роль меняет администратор пространства.\n`);
|
|
284
304
|
}
|
|
285
305
|
this.exit(3);
|
|
286
306
|
}
|
|
307
|
+
/** The profile the call ran with, or undefined when the token came from --token or PACHCA_TOKEN. */
|
|
308
|
+
tryResolveProfile() {
|
|
309
|
+
try {
|
|
310
|
+
return resolveToken({ token: this.parsedFlags.token, profile: this.parsedFlags.profile });
|
|
311
|
+
}
|
|
312
|
+
catch {
|
|
313
|
+
return undefined;
|
|
314
|
+
}
|
|
315
|
+
}
|
|
287
316
|
/**
|
|
288
317
|
* What to do after a 401: the login was revoked, or the saved token no longer works.
|
|
289
318
|
*
|
|
@@ -306,7 +335,7 @@ export class BaseCommand extends Command {
|
|
|
306
335
|
? 'Токен из --token или PACHCA_TOKEN недействителен или отозван.'
|
|
307
336
|
: 'token from --token or PACHCA_TOKEN is invalid or revoked';
|
|
308
337
|
}
|
|
309
|
-
const flag = resolved.profileName
|
|
338
|
+
const flag = profileFlag(resolved.profileName);
|
|
310
339
|
if (getAuthMethod(resolved.profile) === 'oauth') {
|
|
311
340
|
return human
|
|
312
341
|
? `Вход завершён или отозван. Войдите заново: pachca auth login${flag}`
|
|
@@ -323,6 +352,9 @@ export class BaseCommand extends Command {
|
|
|
323
352
|
if (err instanceof ApiError && err.details.type === 'PACHCA_SCOPE_ERROR') {
|
|
324
353
|
this.reportScopeError(err);
|
|
325
354
|
}
|
|
355
|
+
if (err instanceof ApiError && err.details.code === 403 && err.details.message === 'role_forbidden') {
|
|
356
|
+
this.reportRoleRefusal(err);
|
|
357
|
+
}
|
|
326
358
|
if (err instanceof ApiError) {
|
|
327
359
|
if (err.details.code === 401 && !err.details.hint) {
|
|
328
360
|
err.details.hint = this.reloginHint();
|
package/dist/client.js
CHANGED
|
@@ -57,7 +57,8 @@ function maskToken(token) {
|
|
|
57
57
|
export function formatDryRun(opts, isJson) {
|
|
58
58
|
const url = buildUrl(opts.path, opts.query);
|
|
59
59
|
const headers = {
|
|
60
|
-
|
|
60
|
+
// The real request carries the token; the preview never shows it, and works without one.
|
|
61
|
+
...(opts.noAuth ? {} : { Authorization: `Bearer ${maskToken(opts.token)}` }),
|
|
61
62
|
...(opts.body ? { 'Content-Type': 'application/json' } : {}),
|
|
62
63
|
...opts.headers,
|
|
63
64
|
};
|
package/dist/commands/api.js
CHANGED
|
@@ -4,10 +4,10 @@ import { BaseCommand } from '../base-command.js';
|
|
|
4
4
|
import { outputError } from '../output.js';
|
|
5
5
|
import { buildListRows, detectIntrospectMode, positionals, findEndpoint, renderIntrospect, } from '../api-introspect.js';
|
|
6
6
|
export default class Api extends BaseCommand {
|
|
7
|
-
static description = 'Произвольный запрос к API. Список и справка: `api ls` (список
|
|
7
|
+
static description = 'Произвольный запрос к API. Список и справка: `api ls` (список методов), `api <МЕТОД> <путь> --describe` / `--spec` / `--docs`';
|
|
8
8
|
static examples = [
|
|
9
9
|
'<%= config.bin %> api GET /messages --query chat_id=123',
|
|
10
|
-
|
|
10
|
+
"<%= config.bin %> api POST /messages -F 'message[entity_id]=12345' -f 'message[content]=Привет'",
|
|
11
11
|
'<%= config.bin %> api POST /messages --input payload.json',
|
|
12
12
|
`<%= config.bin %> api POST /messages --data '{"message":{"entity_id":123,"content":"Привет"}}'`,
|
|
13
13
|
'<%= config.bin %> api GET /profile -o yaml',
|
|
@@ -195,7 +195,7 @@ export default class Api extends BaseCommand {
|
|
|
195
195
|
const entry = findEndpoint(entries, method, path);
|
|
196
196
|
if (!entry) {
|
|
197
197
|
outputError({
|
|
198
|
-
error:
|
|
198
|
+
error: `Метод не найден: ${method.toUpperCase()} ${path}. Список: pachca api ls`,
|
|
199
199
|
type: 'PACHCA_NOT_FOUND_ERROR',
|
|
200
200
|
code: null,
|
|
201
201
|
}, errFormat);
|
|
@@ -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
|
|
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:
|
|
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
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
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 {
|
|
@@ -16,19 +16,19 @@ export default class BotsCreate extends BaseCommand {
|
|
|
16
16
|
description: "Имя бота (макс. 255 символов)",
|
|
17
17
|
}),
|
|
18
18
|
'nickname': Flags.string({
|
|
19
|
-
description: "Никнейм
|
|
19
|
+
description: "Никнейм бота: латинские буквы, цифры и `_`, в конце `_bot`, свободный в пространстве. Ник не по формату отвечает `400` на поле `bot.nickname` с кодом `invalid`, а занятый ник — с кодом `taken`. Без поля ник собирается из имени в латинской транслитерации с `_bot` в конце, а если такой ник занят, с `_2_bot`, `_3_bot` и далее. (макс. 255 символов)",
|
|
20
20
|
}),
|
|
21
21
|
'outgoing-url': Flags.string({
|
|
22
|
-
description: "URL исходящего вебхука",
|
|
22
|
+
description: "URL исходящего вебхука (макс. 255 символов)",
|
|
23
23
|
}),
|
|
24
24
|
'events': Flags.string({
|
|
25
|
-
description: "События, на которые подписан бот",
|
|
25
|
+
description: "События, на которые подписан бот. Без поля бот подписан на `message_new`.",
|
|
26
26
|
}),
|
|
27
27
|
'trigger-on': Flags.string({
|
|
28
28
|
description: "Условие срабатывания исходящего вебхука",
|
|
29
29
|
}),
|
|
30
30
|
'commands': Flags.string({
|
|
31
|
-
description: "Команды бота (триггер-слова), на которые он реагирует при trigger_on = commands. Суммарная длина
|
|
31
|
+
description: "Команды бота (триггер-слова), на которые он реагирует при trigger_on = commands. Событие о сообщении приходит боту, когда первое слово текста совпадает с одной из команд целиком, с учётом регистра. Команду можно задать и без слэша в начале. Суммарная длина команд вместе с разделителями `, ` (запятая и пробел) не должна превышать 255 символов.",
|
|
32
32
|
}),
|
|
33
33
|
'scopes': Flags.string({
|
|
34
34
|
description: "Скоупы (права доступа) токена бота. Если не указано, бот получает набор по умолчанию. Боту доступны не все скоупы: часть из них разрешена только пользовательским ролям, и попытка назначить такой скоуп возвращает `400`. Служебные значения `bot` и `all` назначать нельзя.",
|
|
@@ -40,14 +40,14 @@ export default class BotsCreate extends BaseCommand {
|
|
|
40
40
|
description: "Шаблонизатор для обработки шаблона входящего вебхука. Без этого поля бот получает `liquid`, а пустой бот (`empty: true`) — `mustache`.",
|
|
41
41
|
}),
|
|
42
42
|
'challenge-key': Flags.string({
|
|
43
|
-
description: "Название поля проверки для верификации входящего вебхука",
|
|
43
|
+
description: "Название поля проверки для верификации входящего вебхука (макс. 255 символов)",
|
|
44
44
|
}),
|
|
45
45
|
'link-preview-enabled': Flags.boolean({
|
|
46
46
|
description: "Показывать превью ссылок в сообщениях входящего вебхука",
|
|
47
47
|
allowNo: true,
|
|
48
48
|
}),
|
|
49
49
|
'ignore-self-messages': Flags.boolean({
|
|
50
|
-
description: "Не присылать боту события о
|
|
50
|
+
description: "Не присылать боту события о сообщениях, которые он отправил сам, и о реакциях на эти сообщения, кто бы их ни поставил или снял. Нажатия кнопок приходят всегда.",
|
|
51
51
|
allowNo: true,
|
|
52
52
|
}),
|
|
53
53
|
'events-history-enabled': Flags.boolean({
|
|
@@ -71,7 +71,7 @@ export default class BotsCreate extends BaseCommand {
|
|
|
71
71
|
description: "Домены, ссылки на которые бот разворачивает, не больше 5. Работают вместе с событием `message_link_shared`.",
|
|
72
72
|
}),
|
|
73
73
|
'empty': Flags.boolean({
|
|
74
|
-
description: "Создать пустого бота: без
|
|
74
|
+
description: "Создать пустого бота: без входящего вебхука и, если не переданы скоупы, без токена. Без этого поля бот создаётся с входящим вебхуком в своём формате, а его токен приходит в `access_token`.",
|
|
75
75
|
allowNo: true,
|
|
76
76
|
}),
|
|
77
77
|
'oauth-client': Flags.string({
|
|
@@ -117,6 +117,12 @@ export default class BotsCreate extends BaseCommand {
|
|
|
117
117
|
if (flags['nickname'] && String(flags['nickname']).length > 255) {
|
|
118
118
|
validationErrors.push({ message: `--nickname: максимум 255 символов (передано: ${String(flags['nickname']).length})`, flag: 'nickname' });
|
|
119
119
|
}
|
|
120
|
+
if (flags['outgoing-url'] && String(flags['outgoing-url']).length > 255) {
|
|
121
|
+
validationErrors.push({ message: `--outgoing-url: максимум 255 символов (передано: ${String(flags['outgoing-url']).length})`, flag: 'outgoing-url' });
|
|
122
|
+
}
|
|
123
|
+
if (flags['challenge-key'] && String(flags['challenge-key']).length > 255) {
|
|
124
|
+
validationErrors.push({ message: `--challenge-key: максимум 255 символов (передано: ${String(flags['challenge-key']).length})`, flag: 'challenge-key' });
|
|
125
|
+
}
|
|
120
126
|
if (validationErrors.length > 0) {
|
|
121
127
|
this.validationError(validationErrors);
|
|
122
128
|
}
|
|
@@ -34,6 +34,7 @@ export default class BotsListCompany extends BaseCommand {
|
|
|
34
34
|
let nextCursor = undefined;
|
|
35
35
|
let pages = 0;
|
|
36
36
|
const seenCursors = new Set();
|
|
37
|
+
let total;
|
|
37
38
|
while (pages < 500) {
|
|
38
39
|
const query = {
|
|
39
40
|
query: flags['query'],
|
|
@@ -49,7 +50,7 @@ export default class BotsListCompany extends BaseCommand {
|
|
|
49
50
|
const paginate = meta?.paginate;
|
|
50
51
|
nextCursor = paginate?.next_page;
|
|
51
52
|
pages++;
|
|
52
|
-
// Условие конца:
|
|
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 BotsListCompany extends BaseCommand {
|
|
|
59
60
|
break;
|
|
60
61
|
}
|
|
61
62
|
if (process.stderr.isTTY) {
|
|
62
|
-
|
|
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
|
}
|
|
@@ -30,6 +30,7 @@ export default class BotsListEvents extends BaseCommand {
|
|
|
30
30
|
let nextCursor = undefined;
|
|
31
31
|
let pages = 0;
|
|
32
32
|
const seenCursors = new Set();
|
|
33
|
+
let total;
|
|
33
34
|
while (pages < 500) {
|
|
34
35
|
const query = {
|
|
35
36
|
limit: flags.limit,
|
|
@@ -44,7 +45,7 @@ export default class BotsListEvents extends BaseCommand {
|
|
|
44
45
|
const paginate = meta?.paginate;
|
|
45
46
|
nextCursor = paginate?.next_page;
|
|
46
47
|
pages++;
|
|
47
|
-
// Условие конца:
|
|
48
|
+
// Условие конца: has_next === false, а в ответе без has_next — пустой data
|
|
48
49
|
const hasNext = paginate?.has_next;
|
|
49
50
|
if (typeof hasNext === 'boolean') {
|
|
50
51
|
if (!hasNext)
|
|
@@ -54,7 +55,8 @@ export default class BotsListEvents extends BaseCommand {
|
|
|
54
55
|
break;
|
|
55
56
|
}
|
|
56
57
|
if (process.stderr.isTTY) {
|
|
57
|
-
|
|
58
|
+
// total лежит в meta у методов поиска. Берём его с первой страницы: при поиске сообщений по дате следующие страницы считают только остаток
|
|
59
|
+
total ??= meta?.total;
|
|
58
60
|
const progress = total ? `${allData.length} / ${total}` : String(allData.length);
|
|
59
61
|
process.stderr.write(`\r Загружено: ${progress}...`);
|
|
60
62
|
}
|
|
@@ -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
|
-
// Условие конца:
|
|
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
|
-
|
|
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
|
}
|
|
@@ -11,7 +11,7 @@ export default class BotsList extends BaseCommand {
|
|
|
11
11
|
static flags = {
|
|
12
12
|
...BaseCommand.baseFlags,
|
|
13
13
|
'query': Flags.string({
|
|
14
|
-
description: "Поисковая фраза для фильтрации ботов по имени",
|
|
14
|
+
description: "Поисковая фраза для фильтрации ботов по имени или нику",
|
|
15
15
|
}),
|
|
16
16
|
limit: Flags.integer({
|
|
17
17
|
description: 'Количество результатов на страницу',
|
|
@@ -33,6 +33,7 @@ export default class BotsList extends BaseCommand {
|
|
|
33
33
|
let nextCursor = undefined;
|
|
34
34
|
let pages = 0;
|
|
35
35
|
const seenCursors = new Set();
|
|
36
|
+
let total;
|
|
36
37
|
while (pages < 500) {
|
|
37
38
|
const query = {
|
|
38
39
|
query: flags['query'],
|
|
@@ -48,7 +49,7 @@ export default class BotsList extends BaseCommand {
|
|
|
48
49
|
const paginate = meta?.paginate;
|
|
49
50
|
nextCursor = paginate?.next_page;
|
|
50
51
|
pages++;
|
|
51
|
-
// Условие конца:
|
|
52
|
+
// Условие конца: has_next === false, а в ответе без has_next — пустой data
|
|
52
53
|
const hasNext = paginate?.has_next;
|
|
53
54
|
if (typeof hasNext === 'boolean') {
|
|
54
55
|
if (!hasNext)
|
|
@@ -58,7 +59,8 @@ export default class BotsList extends BaseCommand {
|
|
|
58
59
|
break;
|
|
59
60
|
}
|
|
60
61
|
if (process.stderr.isTTY) {
|
|
61
|
-
|
|
62
|
+
// total лежит в meta у методов поиска. Берём его с первой страницы: при поиске сообщений по дате следующие страницы считают только остаток
|
|
63
|
+
total ??= meta?.total;
|
|
62
64
|
const progress = total ? `${allData.length} / ${total}` : String(allData.length);
|
|
63
65
|
process.stderr.write(`\r Загружено: ${progress}...`);
|
|
64
66
|
}
|
|
@@ -13,7 +13,7 @@ export default class BotsUpdateWebhook extends BaseCommand {
|
|
|
13
13
|
static flags = {
|
|
14
14
|
...BaseCommand.baseFlags,
|
|
15
15
|
'outgoing-url': Flags.string({
|
|
16
|
-
description: "URL исходящего вебхука. Пустая строка отключает вебхук.",
|
|
16
|
+
description: "URL исходящего вебхука. Пустая строка отключает вебхук. (макс. 255 символов)",
|
|
17
17
|
}),
|
|
18
18
|
};
|
|
19
19
|
async run() {
|
|
@@ -45,6 +45,13 @@ export default class BotsUpdateWebhook extends BaseCommand {
|
|
|
45
45
|
this.validationError(missingRequired.map((f) => ({ message: `Обязательный флаг --${f.flag} не передан`, flag: f.flag })), { hint: "Обязательные: --outgoing-url <string>. pachca introspect bots update-webhook" });
|
|
46
46
|
}
|
|
47
47
|
}
|
|
48
|
+
const validationErrors = [];
|
|
49
|
+
if (flags['outgoing-url'] && String(flags['outgoing-url']).length > 255) {
|
|
50
|
+
validationErrors.push({ message: `--outgoing-url: максимум 255 символов (передано: ${String(flags['outgoing-url']).length})`, flag: 'outgoing-url' });
|
|
51
|
+
}
|
|
52
|
+
if (validationErrors.length > 0) {
|
|
53
|
+
this.validationError(validationErrors);
|
|
54
|
+
}
|
|
48
55
|
const body = { webhook: {
|
|
49
56
|
outgoing_url: flags['outgoing-url'],
|
|
50
57
|
} };
|