@pachca/cli 2026.9.1 → 2026.10.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +23 -0
- package/README.md +10 -0
- package/dist/base-command.js +35 -0
- package/dist/commands/auth/login.js +4 -4
- package/dist/commands/bots/create-token.js +84 -0
- package/dist/commands/bots/create.js +32 -5
- package/dist/commands/bots/delete-token.js +46 -0
- package/dist/commands/bots/delete.js +1 -1
- package/dist/commands/bots/get.js +1 -1
- package/dist/commands/bots/list-scopes.js +32 -0
- package/dist/commands/bots/list-tokens.js +94 -0
- package/dist/commands/bots/list.js +1 -1
- package/dist/commands/bots/recreate-token-self.js +1 -1
- package/dist/commands/bots/recreate-token.js +2 -2
- package/dist/commands/bots/reissue-token.js +37 -0
- package/dist/commands/bots/rotate-client-secret.js +30 -0
- package/dist/commands/bots/update-token.js +60 -0
- package/dist/commands/bots/update.js +35 -6
- package/dist/commands/chats/mark-unread.js +44 -0
- package/dist/commands/chats/remove-avatar.js +45 -0
- package/dist/commands/chats/update-avatar.js +54 -0
- package/dist/commands/drafts/create.js +1 -1
- package/dist/commands/drafts/update.js +1 -1
- package/dist/commands/messages/create.js +3 -3
- package/dist/commands/messages/update.js +1 -1
- package/dist/commands/security/list.js +1 -1
- package/dist/commands/tasks/create.js +1 -1
- package/dist/commands/tasks/list.js +1 -1
- package/dist/commands/tasks/update.js +1 -1
- package/dist/commands/users/create.js +1 -1
- package/dist/commands/users/update.js +1 -1
- package/dist/data/alternatives.json +10 -0
- package/dist/data/changelog.json +105 -0
- package/dist/data/endpoints.json +1 -1
- package/dist/data/workflows.json +54 -3
- package/dist/oauth.js +28 -3
- package/oclif.manifest.json +1 -1
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,28 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 2026.10.0 (13 октября 2026)
|
|
4
|
+
|
|
5
|
+
- **Добавлено** (bots list-tokens, bots create-token, bots update-token, bots reissue-token, bots delete-token): Новые команды — токены бота: у бота может быть несколько токенов, у каждого своё имя и права. Значение токена показывается один раз, при выпуске и перевыпуске.
|
|
6
|
+
- **Добавлено** (bots list-scopes): Новая команда — каталог прав, которые можно выдать боту: его токенам и авторизации от имени сотрудника
|
|
7
|
+
- **Добавлено** (bots rotate-client-secret): Новая команда — новый `client_secret` для авторизации от имени сотрудника. Прежний секрет перестаёт работать сразу.
|
|
8
|
+
- **Добавлено** (bots create, bots update): Флаги `--oauth-client` и `--promo` включают авторизацию от имени сотрудника и заполняют страницу бота в витрине, `--kind` выбирает источник входящего вебхука, а `--unfurl-domains` задаёт домены для разворачивания ссылок. У `bots create` появился `--empty` для бота без токена, у `bots update` — `--single-chat`.
|
|
9
|
+
- **Добавлено** (chats mark-unread): Новая команда — отметка чата непрочитанным для владельца токена
|
|
10
|
+
- **Добавлено** (chats update-avatar, chats remove-avatar): Новые команды — аватар беседы или канала: загрузка файла флагом `--file` и удаление. В выводе команд с чатами появилось поле `avatar_url`.
|
|
11
|
+
- **Добавлено** (security list): Флаг `--event-key` принимает значения `bot_token_created`, `bot_token_updated`, `bot_token_reissued`, `bot_token_deleted`, `bot_oauth_client_secret_issued`, `bot_oauth_client_secret_rotated`, `bot_oauth_client_disabled` и `oauth_token_revoked`
|
|
12
|
+
- **Исправлено** (search users, search chats, search messages, users list): В справке описан ответ `504` с кодом `timeout`, если поиск не уложился по времени
|
|
13
|
+
- **Исправлено** (messages create, messages update, drafts create, drafts update): У файлов в справке указан предел голосового сообщения в 30 минут
|
|
14
|
+
- **Добавлено** (auth login): Вход через браузер передаёт серверу имя устройства, по которому вход узнаётся в списке токенов CLI: имя машины, а у профиля не по умолчанию ещё и имя профиля в скобках, например `macbook (ci)`
|
|
15
|
+
- **Изменено**: После ответа `401` CLI подсказывает, что делать дальше: войти заново, если вход через браузер отозван или завершён, или сохранить новый токен. В подсказку подставлен профиль, с которым запускалась команда, например `pachca auth login --profile ci`
|
|
16
|
+
- **Исправлено** (views open): В справке у блоков `radio` и `checkbox` до 20 вариантов
|
|
17
|
+
- **Исправлено** (bots create, bots update): Справка к `--ignore-self-messages` говорит, что настройка не присылает боту и его собственные реакции
|
|
18
|
+
- **Исправлено** (guide): Сценарии разворачивания ссылок и поллинга описывают раздел «Интеграции»: событие ссылок с доменами вместо отдельного типа бота и история событий без Webhook URL флагом `--events-history-enabled`, который попал и в примеры `bots update`
|
|
19
|
+
- **Исправлено** (messages create, messages update, tasks list): Подсказки к `--content` и `--chat-ids` описывают, что меняется для мульти-гостя
|
|
20
|
+
- **Исправлено** (messages create, messages update, drafts create, drafts update, tasks create, tasks update): В справке к `--content` указан предел длины текста: 40 000 байт, а в пространстве с продвинутым шифрованием 27 000
|
|
21
|
+
- **Исправлено** (messages create, threads add, threads create, threads get, threads list): Подсказка к `--parent-message-id` описывает ответ с цитатой в любом чате, в том числе в треде. В описаниях тредов сказано, какой идентификатор куда передавать: `id` — чтобы написать в тред, `chat_id` — чтобы прочитать его сообщения.
|
|
22
|
+
- **Исправлено** (bots list-events): В справке у события отправки формы сказано, что `chat_id` фиксируется при открытии формы, а не при её отправке
|
|
23
|
+
- **Исправлено** (chats create, members add, users update): В справке описаны отказы для бота с настройкой «Ограничить одним чатом» — `single_chat_bot_occupied`, если он уже состоит в другой беседе или канале, и `single_chat_bot_tag_denied` при добавлении в тег. У `users update` сказано, что у бота меняются только имя, ник, теги и `suspended`.
|
|
24
|
+
- **Исправлено** (tasks get): В справке сказано, что напоминание видят все, кому доступен его чат, по тому же правилу, что у фильтра `--chat-ids` в `tasks list`
|
|
25
|
+
|
|
3
26
|
## 2026.9.1 (16 сентября 2026)
|
|
4
27
|
|
|
5
28
|
- **Изменено**: Повтор после `429` ждёт не меньше срока из `Retry-After`: прежний потолок в 60 секунд убран. Пауза дольше минуты не повторяется — команда сразу возвращает ошибку с заголовком, потому что повтор внутри паузы её удваивает
|
package/README.md
CHANGED
|
@@ -86,6 +86,13 @@ pachca guide # список всех сценариев
|
|
|
86
86
|
| `pachca bots update` | Редактирование бота |
|
|
87
87
|
| `pachca bots delete` | Удаление бота |
|
|
88
88
|
| `pachca bots recreate-token` | Ротация токена бота |
|
|
89
|
+
| `pachca bots rotate-client-secret` | Ротация секрета клиента |
|
|
90
|
+
| `pachca bots list-scopes` | Каталог прав бота |
|
|
91
|
+
| `pachca bots list-tokens` | Список токенов бота |
|
|
92
|
+
| `pachca bots create-token` | Новый токен бота |
|
|
93
|
+
| `pachca bots update-token` | Изменение токена бота |
|
|
94
|
+
| `pachca bots delete-token` | Удаление токена бота |
|
|
95
|
+
| `pachca bots reissue-token` | Перевыпуск токена бота |
|
|
89
96
|
| `pachca bots list-company` | Список ботов пространства |
|
|
90
97
|
| `pachca bots list-events` | История событий |
|
|
91
98
|
| `pachca bots remove-event` | Удаление события |
|
|
@@ -101,7 +108,10 @@ pachca guide # список всех сценариев
|
|
|
101
108
|
| `pachca chats get` | Информация о чате |
|
|
102
109
|
| `pachca chats update` | Редактирование чата |
|
|
103
110
|
| `pachca chats archive` | Архивация чата |
|
|
111
|
+
| `pachca chats update-avatar` | Загрузка аватара чата |
|
|
112
|
+
| `pachca chats remove-avatar` | Удаление аватара чата |
|
|
104
113
|
| `pachca chats unarchive` | Разархивация чата |
|
|
114
|
+
| `pachca chats mark-unread` | Отметка чата непрочитанным |
|
|
105
115
|
| `pachca chats list-company` | Список чатов пространства |
|
|
106
116
|
|
|
107
117
|
### members
|
package/dist/base-command.js
CHANGED
|
@@ -272,6 +272,38 @@ export class BaseCommand extends Command {
|
|
|
272
272
|
}
|
|
273
273
|
this.exit(3);
|
|
274
274
|
}
|
|
275
|
+
/**
|
|
276
|
+
* What to do after a 401: the login was revoked, or the saved token no longer works.
|
|
277
|
+
*
|
|
278
|
+
* A browser login is fixed by logging in again, a saved token by saving a new one.
|
|
279
|
+
* The command carries the profile the call ran with, so it can be pasted as is.
|
|
280
|
+
* JSON output gets the bare command, like the other hints there.
|
|
281
|
+
*/
|
|
282
|
+
reloginHint() {
|
|
283
|
+
const format = this.getOutputFormat();
|
|
284
|
+
const human = format !== 'json' && process.stderr.isTTY;
|
|
285
|
+
let resolved;
|
|
286
|
+
try {
|
|
287
|
+
resolved = resolveToken({ token: this.parsedFlags.token, profile: this.parsedFlags.profile });
|
|
288
|
+
}
|
|
289
|
+
catch {
|
|
290
|
+
// no profile resolved — the token came in from a flag or the environment
|
|
291
|
+
}
|
|
292
|
+
if (!resolved?.profile || !resolved.profileName) {
|
|
293
|
+
return human
|
|
294
|
+
? 'Токен из --token или PACHCA_TOKEN недействителен или отозван.'
|
|
295
|
+
: 'token from --token or PACHCA_TOKEN is invalid or revoked';
|
|
296
|
+
}
|
|
297
|
+
const flag = resolved.profileName === 'default' ? '' : ` --profile ${resolved.profileName}`;
|
|
298
|
+
if (getAuthMethod(resolved.profile) === 'oauth') {
|
|
299
|
+
return human
|
|
300
|
+
? `Вход завершён или отозван. Войдите заново: pachca auth login${flag}`
|
|
301
|
+
: `pachca auth login${flag}`;
|
|
302
|
+
}
|
|
303
|
+
return human
|
|
304
|
+
? `Токен недействителен или отозван. Сохраните новый: pachca auth login${flag} --token <токен>`
|
|
305
|
+
: `pachca auth login${flag} --token <token>`;
|
|
306
|
+
}
|
|
275
307
|
/**
|
|
276
308
|
* Handle errors with structured output.
|
|
277
309
|
*/
|
|
@@ -280,6 +312,9 @@ export class BaseCommand extends Command {
|
|
|
280
312
|
this.reportScopeError(err);
|
|
281
313
|
}
|
|
282
314
|
if (err instanceof ApiError) {
|
|
315
|
+
if (err.details.code === 401 && !err.details.hint) {
|
|
316
|
+
err.details.hint = this.reloginHint();
|
|
317
|
+
}
|
|
283
318
|
outputError(err.details, this.getOutputFormat());
|
|
284
319
|
this.exit(getExitCode(err));
|
|
285
320
|
}
|
|
@@ -4,7 +4,7 @@ import { setProfile, setActiveProfile, getActiveProfile, getProfile, getSecretSt
|
|
|
4
4
|
import { isSecretStoreAvailable } from '../../secret-store.js';
|
|
5
5
|
import { ApiError, request } from '../../client.js';
|
|
6
6
|
import { outputError } from '../../output.js';
|
|
7
|
-
import { OAuthError, copyToClipboard, openBrowser, pollForToken, requestDeviceCode, } from '../../oauth.js';
|
|
7
|
+
import { OAuthError, copyToClipboard, deviceName, openBrowser, pollForToken, requestDeviceCode, } from '../../oauth.js';
|
|
8
8
|
export default class AuthLogin extends BaseCommand {
|
|
9
9
|
static description = 'Вход в аккаунт и сохранение профиля';
|
|
10
10
|
static examples = [
|
|
@@ -66,7 +66,7 @@ export default class AuthLogin extends BaseCommand {
|
|
|
66
66
|
}
|
|
67
67
|
const grant = flags.token
|
|
68
68
|
? { access_token: flags.token }
|
|
69
|
-
: await this.deviceLogin(!flags['no-browser'], flags.quiet);
|
|
69
|
+
: await this.deviceLogin(profileName, !flags['no-browser'], flags.quiet);
|
|
70
70
|
const tokenInfo = await this.verifyToken(grant.access_token);
|
|
71
71
|
const identity = await this.fetchIdentity(grant.access_token, tokenInfo.user_id);
|
|
72
72
|
const scopes = grant.scope ? grant.scope.split(' ').filter(Boolean) : tokenInfo.scopes;
|
|
@@ -105,10 +105,10 @@ export default class AuthLogin extends BaseCommand {
|
|
|
105
105
|
* the code baked in is never shown — a link like that works as phishing when it
|
|
106
106
|
* is forwarded to someone else's device.
|
|
107
107
|
*/
|
|
108
|
-
async deviceLogin(useBrowser, quiet) {
|
|
108
|
+
async deviceLogin(profileName, useBrowser, quiet) {
|
|
109
109
|
let grant;
|
|
110
110
|
try {
|
|
111
|
-
grant = await requestDeviceCode();
|
|
111
|
+
grant = await requestDeviceCode(deviceName(profileName));
|
|
112
112
|
}
|
|
113
113
|
catch (error) {
|
|
114
114
|
this.failOAuth(error, 'Не удалось начать вход');
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
// Auto-generated from openapi.yaml — DO NOT EDIT
|
|
2
|
+
import { Args, Flags } from '@oclif/core';
|
|
3
|
+
import { BaseCommand } from '../../base-command.js';
|
|
4
|
+
import * as clack from '@clack/prompts';
|
|
5
|
+
export default class BotsCreateToken extends BaseCommand {
|
|
6
|
+
static description = "Новый токен бота";
|
|
7
|
+
static examples = [
|
|
8
|
+
"Выпустить боту отдельный токен — Выпусти токен с именем и правами из каталога. Без `--scopes` токен выпускается без прав, право не из каталога отклоняется с `400`:\n $ pachca bots create-token"
|
|
9
|
+
];
|
|
10
|
+
static scope = "bots:write";
|
|
11
|
+
static apiMethod = "POST";
|
|
12
|
+
static apiPath = "/bots/{id}/tokens";
|
|
13
|
+
static defaultColumns = ["id", "name", "created_at", "token", "user_id"];
|
|
14
|
+
static requiredFlags = ["name"];
|
|
15
|
+
static args = {
|
|
16
|
+
id: Args.integer({
|
|
17
|
+
description: "Идентификатор бота (pachca bots list)",
|
|
18
|
+
required: true,
|
|
19
|
+
}),
|
|
20
|
+
};
|
|
21
|
+
static flags = {
|
|
22
|
+
...BaseCommand.baseFlags,
|
|
23
|
+
'name': Flags.string({
|
|
24
|
+
description: "Имя токена: по нему токены различают в списке (макс. 255 символов)",
|
|
25
|
+
}),
|
|
26
|
+
'scopes': Flags.string({
|
|
27
|
+
description: "Права токена. Доступны только права, которые можно выдать боту. Если не передать, токен выпускается без прав.",
|
|
28
|
+
}),
|
|
29
|
+
};
|
|
30
|
+
async run() {
|
|
31
|
+
const { args, flags } = await this.parse(BotsCreateToken);
|
|
32
|
+
this.parsedFlags = flags;
|
|
33
|
+
const missingRequired = [
|
|
34
|
+
{ flag: 'name', label: "Имя токена: по нему токены различают в списке", type: 'string' },
|
|
35
|
+
].filter((f) => flags[f.flag] === undefined || flags[f.flag] === null);
|
|
36
|
+
if (missingRequired.length > 0) {
|
|
37
|
+
if (this.isInteractive()) {
|
|
38
|
+
for (const field of missingRequired) {
|
|
39
|
+
const value = await clack.text({ message: field.label, validate: (v) => v.length === 0 ? 'Обязательное поле' : undefined });
|
|
40
|
+
if (clack.isCancel(value)) {
|
|
41
|
+
process.stderr.write('Отменено.\n');
|
|
42
|
+
this.exit(0);
|
|
43
|
+
}
|
|
44
|
+
if (field.type === 'integer') {
|
|
45
|
+
flags[field.flag] = Number.parseInt(value, 10);
|
|
46
|
+
}
|
|
47
|
+
else if (field.type === 'boolean') {
|
|
48
|
+
flags[field.flag] = value === 'true';
|
|
49
|
+
}
|
|
50
|
+
else {
|
|
51
|
+
flags[field.flag] = value;
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
else {
|
|
56
|
+
this.validationError(missingRequired.map((f) => ({ message: `Обязательный флаг --${f.flag} не передан`, flag: f.flag })), { hint: "Обязательные: --name <string>. pachca introspect bots create-token" });
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
const validationErrors = [];
|
|
60
|
+
if (flags['name'] && String(flags['name']).length > 255) {
|
|
61
|
+
validationErrors.push({ message: `--name: максимум 255 символов (передано: ${String(flags['name']).length})`, flag: 'name' });
|
|
62
|
+
}
|
|
63
|
+
if (validationErrors.length > 0) {
|
|
64
|
+
this.validationError(validationErrors);
|
|
65
|
+
}
|
|
66
|
+
const body = {
|
|
67
|
+
name: flags['name'],
|
|
68
|
+
scopes: flags['scopes'] ? this.parseJSON(flags['scopes'], 'scopes') : undefined,
|
|
69
|
+
};
|
|
70
|
+
// Clean undefined fields
|
|
71
|
+
for (const [k, v] of Object.entries(body)) {
|
|
72
|
+
if (v === undefined)
|
|
73
|
+
delete body[k];
|
|
74
|
+
}
|
|
75
|
+
const { data } = await this.apiRequest({
|
|
76
|
+
method: 'POST',
|
|
77
|
+
path: `/bots/${args.id}/tokens`,
|
|
78
|
+
body,
|
|
79
|
+
});
|
|
80
|
+
const responseBody = (data ?? {});
|
|
81
|
+
const result = responseBody.data ?? responseBody;
|
|
82
|
+
this.output(result);
|
|
83
|
+
}
|
|
84
|
+
}
|
|
@@ -11,7 +11,7 @@ export default class BotsCreate extends BaseCommand {
|
|
|
11
11
|
static scope = "bots:write";
|
|
12
12
|
static apiMethod = "POST";
|
|
13
13
|
static apiPath = "/bots";
|
|
14
|
-
static defaultColumns = ["id", "
|
|
14
|
+
static defaultColumns = ["id", "name", "created_at", "nickname", "avatar_url"];
|
|
15
15
|
static requiredFlags = ["name"];
|
|
16
16
|
static args = {};
|
|
17
17
|
static flags = {
|
|
@@ -51,7 +51,7 @@ export default class BotsCreate extends BaseCommand {
|
|
|
51
51
|
allowNo: true,
|
|
52
52
|
}),
|
|
53
53
|
'ignore-self-messages': Flags.boolean({
|
|
54
|
-
description: "
|
|
54
|
+
description: "Не присылать боту события о его собственных сообщениях и реакциях",
|
|
55
55
|
allowNo: true,
|
|
56
56
|
}),
|
|
57
57
|
'events-history-enabled': Flags.boolean({
|
|
@@ -65,9 +65,25 @@ export default class BotsCreate extends BaseCommand {
|
|
|
65
65
|
description: "Роли, которым, помимо создателя, разрешено редактировать настройки бота. Создатель может редактировать всегда. Пустой массив — редактировать может только создатель.",
|
|
66
66
|
}),
|
|
67
67
|
'single-chat': Flags.boolean({
|
|
68
|
-
description: "Ограничивает бота одной беседой или каналом: `true` — бота можно добавить только в один такой чат, `false` — в несколько. Личные чаты и треды в ограничение не входят.
|
|
68
|
+
description: "Ограничивает бота одной беседой или каналом: `true` — бота можно добавить только в один такой чат, `false` — в несколько. Личные чаты и треды в ограничение не входят.",
|
|
69
69
|
allowNo: true,
|
|
70
70
|
}),
|
|
71
|
+
'kind': Flags.string({
|
|
72
|
+
description: "Источник входящего вебхука. Без `empty` по умолчанию `simple`, а у пустого бота входящий вебхук включается только этим полем.",
|
|
73
|
+
}),
|
|
74
|
+
'unfurl-domains': Flags.string({
|
|
75
|
+
description: "Домены, ссылки на которые бот разворачивает, не больше 5. Работают вместе с событием `message_link_shared`.",
|
|
76
|
+
}),
|
|
77
|
+
'empty': Flags.boolean({
|
|
78
|
+
description: "Создать пустого бота: без токена и без входящего вебхука. Без этого поля бот создаётся с входящим вебхуком в своём формате, а его токен приходит в `access_token`.",
|
|
79
|
+
allowNo: true,
|
|
80
|
+
}),
|
|
81
|
+
'oauth-client': Flags.string({
|
|
82
|
+
description: "Включить авторизацию от имени сотрудника сразу при создании",
|
|
83
|
+
}),
|
|
84
|
+
'promo': Flags.string({
|
|
85
|
+
description: "Страница бота в витрине. Задаётся только вместе с `oauth_client`: без авторизации страницы у бота нет.",
|
|
86
|
+
}),
|
|
71
87
|
};
|
|
72
88
|
async run() {
|
|
73
89
|
const { args, flags } = await this.parse(BotsCreate);
|
|
@@ -108,7 +124,8 @@ export default class BotsCreate extends BaseCommand {
|
|
|
108
124
|
if (validationErrors.length > 0) {
|
|
109
125
|
this.validationError(validationErrors);
|
|
110
126
|
}
|
|
111
|
-
const body = {
|
|
127
|
+
const body = {
|
|
128
|
+
webhook: {
|
|
112
129
|
name: flags['name'],
|
|
113
130
|
nickname: flags['nickname'],
|
|
114
131
|
outgoing_url: flags['outgoing-url'],
|
|
@@ -125,13 +142,23 @@ export default class BotsCreate extends BaseCommand {
|
|
|
125
142
|
who_can_add: flags['who-can-add'],
|
|
126
143
|
can_edit: flags['can-edit'] ? this.parseJSON(flags['can-edit'], 'can-edit') : undefined,
|
|
127
144
|
single_chat: flags['single-chat'],
|
|
128
|
-
|
|
145
|
+
kind: flags['kind'],
|
|
146
|
+
unfurl_domains: flags['unfurl-domains'] ? this.parseJSON(flags['unfurl-domains'], 'unfurl-domains') : undefined,
|
|
147
|
+
},
|
|
148
|
+
empty: flags['empty'],
|
|
149
|
+
oauth_client: flags['oauth-client'] ? this.parseJSON(flags['oauth-client'], 'oauth-client') : undefined,
|
|
150
|
+
promo: flags['promo'] ? this.parseJSON(flags['promo'], 'promo') : undefined,
|
|
151
|
+
};
|
|
129
152
|
// Clean undefined fields
|
|
130
153
|
const inner = body['webhook'];
|
|
131
154
|
for (const [k, v] of Object.entries(inner)) {
|
|
132
155
|
if (v === undefined)
|
|
133
156
|
delete inner[k];
|
|
134
157
|
}
|
|
158
|
+
for (const [k, v] of Object.entries(body)) {
|
|
159
|
+
if (k !== 'webhook' && v === undefined)
|
|
160
|
+
delete body[k];
|
|
161
|
+
}
|
|
135
162
|
const { data } = await this.apiRequest({
|
|
136
163
|
method: 'POST',
|
|
137
164
|
path: '/bots',
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
// Auto-generated from openapi.yaml — DO NOT EDIT
|
|
2
|
+
import { Args, Flags } from '@oclif/core';
|
|
3
|
+
import { BaseCommand } from '../../base-command.js';
|
|
4
|
+
import * as clack from '@clack/prompts';
|
|
5
|
+
export default class BotsDeleteToken extends BaseCommand {
|
|
6
|
+
static description = "Удаление токена бота";
|
|
7
|
+
static scope = "bots:write";
|
|
8
|
+
static apiMethod = "DELETE";
|
|
9
|
+
static apiPath = "/bots/{id}/tokens/{token_id}";
|
|
10
|
+
static args = {
|
|
11
|
+
id: Args.integer({
|
|
12
|
+
description: "Идентификатор бота (pachca bots list)",
|
|
13
|
+
required: true,
|
|
14
|
+
}),
|
|
15
|
+
token_id: Args.integer({
|
|
16
|
+
description: "Идентификатор токена",
|
|
17
|
+
required: true,
|
|
18
|
+
}),
|
|
19
|
+
};
|
|
20
|
+
static flags = {
|
|
21
|
+
...BaseCommand.baseFlags,
|
|
22
|
+
force: Flags.boolean({
|
|
23
|
+
description: 'Пропустить подтверждение',
|
|
24
|
+
default: false,
|
|
25
|
+
}),
|
|
26
|
+
};
|
|
27
|
+
async run() {
|
|
28
|
+
const { args, flags } = await this.parse(BotsDeleteToken);
|
|
29
|
+
this.parsedFlags = flags;
|
|
30
|
+
if (!flags.force) {
|
|
31
|
+
if (!this.isInteractive()) {
|
|
32
|
+
this.validationError([{ message: 'Деструктивная операция требует флага --force', flag: 'force' }], { type: 'PACHCA_DESTRUCTIVE_OP_ERROR', hint: "pachca bots delete-token <id> <token_id> --force" });
|
|
33
|
+
}
|
|
34
|
+
const confirm = await clack.confirm({ message: 'Вы уверены?' });
|
|
35
|
+
if (clack.isCancel(confirm) || !confirm) {
|
|
36
|
+
process.stderr.write('Отменено.\n');
|
|
37
|
+
this.exit(0);
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
const { data } = await this.apiRequest({
|
|
41
|
+
method: 'DELETE',
|
|
42
|
+
path: `/bots/${args.id}/tokens/${args.token_id}`,
|
|
43
|
+
});
|
|
44
|
+
this.success('Удалено');
|
|
45
|
+
}
|
|
46
|
+
}
|
|
@@ -5,7 +5,7 @@ import * as clack from '@clack/prompts';
|
|
|
5
5
|
export default class BotsDelete extends BaseCommand {
|
|
6
6
|
static description = "Удаление бота";
|
|
7
7
|
static examples = [
|
|
8
|
-
"Найти и удалить бота — Возьми `id` нужного бота из списка и удали его (скоуп `bots:write`). Доступно администратору,
|
|
8
|
+
"Найти и удалить бота — Возьми `id` нужного бота из списка и удали его (скоуп `bots:write`). Доступно создателю бота и администратору, если бот открыт администраторам, — владельцы чатов удалять бота не могут. Все токены бота инвалидируются сразу, бот исключается из всех чатов, его вебхуки удаляются:\n $ pachca bots delete"
|
|
9
9
|
];
|
|
10
10
|
static scope = "bots:write";
|
|
11
11
|
static apiMethod = "DELETE";
|
|
@@ -6,7 +6,7 @@ export default class BotsGet extends BaseCommand {
|
|
|
6
6
|
static scope = "bots:read";
|
|
7
7
|
static apiMethod = "GET";
|
|
8
8
|
static apiPath = "/bots/{id}";
|
|
9
|
-
static defaultColumns = ["id", "
|
|
9
|
+
static defaultColumns = ["id", "name", "created_at", "nickname", "avatar_url"];
|
|
10
10
|
static args = {
|
|
11
11
|
id: Args.integer({
|
|
12
12
|
description: "Идентификатор бота (pachca bots list)",
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
// Auto-generated from openapi.yaml — DO NOT EDIT
|
|
2
|
+
import { Args } from '@oclif/core';
|
|
3
|
+
import { BaseCommand } from '../../base-command.js';
|
|
4
|
+
export default class BotsListScopes extends BaseCommand {
|
|
5
|
+
static description = "Каталог прав бота";
|
|
6
|
+
static examples = [
|
|
7
|
+
"Выпустить боту отдельный токен — Пользовательским токеном (создатель бота или администратор, если бот открыт администраторам) получи каталог прав, которые можно выдать этому боту:\n $ pachca bots list-scopes"
|
|
8
|
+
];
|
|
9
|
+
static scope = "bots:read";
|
|
10
|
+
static apiMethod = "GET";
|
|
11
|
+
static apiPath = "/bots/{id}/scopes";
|
|
12
|
+
static args = {
|
|
13
|
+
id: Args.integer({
|
|
14
|
+
description: "Идентификатор бота (pachca bots list)",
|
|
15
|
+
required: true,
|
|
16
|
+
}),
|
|
17
|
+
};
|
|
18
|
+
static flags = {
|
|
19
|
+
...BaseCommand.baseFlags,
|
|
20
|
+
};
|
|
21
|
+
async run() {
|
|
22
|
+
const { args, flags } = await this.parse(BotsListScopes);
|
|
23
|
+
this.parsedFlags = flags;
|
|
24
|
+
const { data } = await this.apiRequest({
|
|
25
|
+
method: 'GET',
|
|
26
|
+
path: `/bots/${args.id}/scopes`,
|
|
27
|
+
});
|
|
28
|
+
const responseBody = (data ?? {});
|
|
29
|
+
const result = responseBody.data ?? responseBody;
|
|
30
|
+
this.output(result);
|
|
31
|
+
}
|
|
32
|
+
}
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
// Auto-generated from openapi.yaml — DO NOT EDIT
|
|
2
|
+
import { Args, Flags } from '@oclif/core';
|
|
3
|
+
import { BaseCommand } from '../../base-command.js';
|
|
4
|
+
export default class BotsListTokens extends BaseCommand {
|
|
5
|
+
static description = "Список токенов бота";
|
|
6
|
+
static scope = "bots:read";
|
|
7
|
+
static apiMethod = "GET";
|
|
8
|
+
static apiPath = "/bots/{id}/tokens";
|
|
9
|
+
static defaultColumns = ["id", "name", "created_at", "token", "user_id"];
|
|
10
|
+
static args = {
|
|
11
|
+
id: Args.integer({
|
|
12
|
+
description: "Идентификатор бота (pachca bots list)",
|
|
13
|
+
required: true,
|
|
14
|
+
}),
|
|
15
|
+
};
|
|
16
|
+
static flags = {
|
|
17
|
+
...BaseCommand.baseFlags,
|
|
18
|
+
limit: Flags.integer({
|
|
19
|
+
description: 'Количество результатов на страницу',
|
|
20
|
+
}),
|
|
21
|
+
cursor: Flags.string({
|
|
22
|
+
description: 'Курсор для следующей страницы',
|
|
23
|
+
}),
|
|
24
|
+
all: Flags.boolean({
|
|
25
|
+
description: 'Загрузить все страницы автоматически',
|
|
26
|
+
default: false,
|
|
27
|
+
}),
|
|
28
|
+
};
|
|
29
|
+
async run() {
|
|
30
|
+
const { args, flags } = await this.parse(BotsListTokens);
|
|
31
|
+
this.parsedFlags = flags;
|
|
32
|
+
if (flags.all) {
|
|
33
|
+
// Auto-pagination
|
|
34
|
+
const allData = [];
|
|
35
|
+
let nextCursor = undefined;
|
|
36
|
+
let pages = 0;
|
|
37
|
+
const seenCursors = new Set();
|
|
38
|
+
while (pages < 500) {
|
|
39
|
+
const query = {
|
|
40
|
+
limit: flags.limit,
|
|
41
|
+
cursor: nextCursor,
|
|
42
|
+
};
|
|
43
|
+
const response = await this.apiRequest({ method: 'GET', path: `/bots/${args.id}/tokens`, query });
|
|
44
|
+
const body = response.data;
|
|
45
|
+
const items = body.data;
|
|
46
|
+
if (items)
|
|
47
|
+
allData.push(...items);
|
|
48
|
+
const meta = body.meta;
|
|
49
|
+
const paginate = meta?.paginate;
|
|
50
|
+
nextCursor = paginate?.next_page;
|
|
51
|
+
pages++;
|
|
52
|
+
// Условие конца: списочные методы — has_next === false; методы поиска и /users?query= (без has_next) — пустой data
|
|
53
|
+
const hasNext = paginate?.has_next;
|
|
54
|
+
if (typeof hasNext === 'boolean') {
|
|
55
|
+
if (!hasNext)
|
|
56
|
+
break;
|
|
57
|
+
}
|
|
58
|
+
else if (!items || items.length === 0) {
|
|
59
|
+
break;
|
|
60
|
+
}
|
|
61
|
+
if (process.stderr.isTTY) {
|
|
62
|
+
const total = paginate?.total;
|
|
63
|
+
const progress = total ? `${allData.length} / ${total}` : String(allData.length);
|
|
64
|
+
process.stderr.write(`\r Загружено: ${progress}...`);
|
|
65
|
+
}
|
|
66
|
+
if (!nextCursor)
|
|
67
|
+
break;
|
|
68
|
+
if (seenCursors.has(nextCursor)) {
|
|
69
|
+
process.stderr.write('\n⚠ Обнаружен цикл пагинации, остановка.\n');
|
|
70
|
+
break;
|
|
71
|
+
}
|
|
72
|
+
seenCursors.add(nextCursor);
|
|
73
|
+
}
|
|
74
|
+
if (pages >= 500) {
|
|
75
|
+
process.stderr.write('\n⚠ Достигнут лимит 500 страниц.\n');
|
|
76
|
+
}
|
|
77
|
+
if (process.stderr.isTTY)
|
|
78
|
+
process.stderr.write('\n');
|
|
79
|
+
this.output(allData);
|
|
80
|
+
return;
|
|
81
|
+
}
|
|
82
|
+
const { data } = await this.apiRequest({
|
|
83
|
+
method: 'GET',
|
|
84
|
+
path: `/bots/${args.id}/tokens`,
|
|
85
|
+
query: {
|
|
86
|
+
limit: flags.limit,
|
|
87
|
+
cursor: flags.cursor,
|
|
88
|
+
},
|
|
89
|
+
});
|
|
90
|
+
const responseBody = (data ?? {});
|
|
91
|
+
const items = responseBody.data ?? responseBody;
|
|
92
|
+
this.output(items);
|
|
93
|
+
}
|
|
94
|
+
}
|
|
@@ -9,7 +9,7 @@ export default class BotsList extends BaseCommand {
|
|
|
9
9
|
static scope = "bots:read";
|
|
10
10
|
static apiMethod = "GET";
|
|
11
11
|
static apiPath = "/bots";
|
|
12
|
-
static defaultColumns = ["id", "
|
|
12
|
+
static defaultColumns = ["id", "name", "created_at", "nickname", "avatar_url"];
|
|
13
13
|
static args = {};
|
|
14
14
|
static flags = {
|
|
15
15
|
...BaseCommand.baseFlags,
|
|
@@ -2,7 +2,7 @@ import { BaseCommand } from '../../base-command.js';
|
|
|
2
2
|
export default class BotsRecreateTokenSelf extends BaseCommand {
|
|
3
3
|
static description = "Ротация собственного токена бота";
|
|
4
4
|
static examples = [
|
|
5
|
-
"Ротация токена бота — Или: бот перевыпускает
|
|
5
|
+
"Ротация токена бота — Или: бот перевыпускает свой основной токен сам (скоуп `bot_self:write`). Если запрос сделан основным токеном, он инвалидируется сразу — обязательно сохрани новый `access_token` из ответа, иначе бот потеряет доступ к API:\n $ pachca bots recreate-token-self"
|
|
6
6
|
];
|
|
7
7
|
static scope = "bot_self:write";
|
|
8
8
|
static apiMethod = "POST";
|
|
@@ -4,12 +4,12 @@ import { BaseCommand } from '../../base-command.js';
|
|
|
4
4
|
export default class BotsRecreateToken extends BaseCommand {
|
|
5
5
|
static description = "Ротация токена бота";
|
|
6
6
|
static examples = [
|
|
7
|
-
"Ротация токена бота — Пользовательским токеном (администратор,
|
|
7
|
+
"Ротация токена бота — Пользовательским токеном (создатель бота или администратор, если бот открыт администраторам) — перевыпусти основной токен по `id` бота: тот, что без имени, а если такого нет, самый старый. Прежнее значение инвалидируется сразу:\n $ pachca bots recreate-token"
|
|
8
8
|
];
|
|
9
9
|
static scope = "bots:write";
|
|
10
10
|
static apiMethod = "POST";
|
|
11
11
|
static apiPath = "/bots/{id}/recreate_token";
|
|
12
|
-
static defaultColumns = ["id", "
|
|
12
|
+
static defaultColumns = ["id", "name", "created_at", "nickname", "avatar_url"];
|
|
13
13
|
static args = {
|
|
14
14
|
id: Args.integer({
|
|
15
15
|
description: "Идентификатор бота (pachca bots list)",
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
// Auto-generated from openapi.yaml — DO NOT EDIT
|
|
2
|
+
import { Args } from '@oclif/core';
|
|
3
|
+
import { BaseCommand } from '../../base-command.js';
|
|
4
|
+
export default class BotsReissueToken extends BaseCommand {
|
|
5
|
+
static description = "Перевыпуск токена бота";
|
|
6
|
+
static examples = [
|
|
7
|
+
"Ротация токена бота — Отдельный токен бота перевыпускай по его `id` из `pachca bots list-tokens`: значение меняется, имя и права остаются:\n $ pachca bots reissue-token"
|
|
8
|
+
];
|
|
9
|
+
static scope = "bots:write";
|
|
10
|
+
static apiMethod = "POST";
|
|
11
|
+
static apiPath = "/bots/{id}/tokens/{token_id}/reissue";
|
|
12
|
+
static defaultColumns = ["id", "name", "created_at", "token", "user_id"];
|
|
13
|
+
static args = {
|
|
14
|
+
id: Args.integer({
|
|
15
|
+
description: "Идентификатор бота (pachca bots list)",
|
|
16
|
+
required: true,
|
|
17
|
+
}),
|
|
18
|
+
token_id: Args.integer({
|
|
19
|
+
description: "Идентификатор токена",
|
|
20
|
+
required: true,
|
|
21
|
+
}),
|
|
22
|
+
};
|
|
23
|
+
static flags = {
|
|
24
|
+
...BaseCommand.baseFlags,
|
|
25
|
+
};
|
|
26
|
+
async run() {
|
|
27
|
+
const { args, flags } = await this.parse(BotsReissueToken);
|
|
28
|
+
this.parsedFlags = flags;
|
|
29
|
+
const { data } = await this.apiRequest({
|
|
30
|
+
method: 'POST',
|
|
31
|
+
path: `/bots/${args.id}/tokens/${args.token_id}/reissue`,
|
|
32
|
+
});
|
|
33
|
+
const responseBody = (data ?? {});
|
|
34
|
+
const result = responseBody.data ?? responseBody;
|
|
35
|
+
this.output(result);
|
|
36
|
+
}
|
|
37
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
// Auto-generated from openapi.yaml — DO NOT EDIT
|
|
2
|
+
import { Args } from '@oclif/core';
|
|
3
|
+
import { BaseCommand } from '../../base-command.js';
|
|
4
|
+
export default class BotsRotateClientSecret extends BaseCommand {
|
|
5
|
+
static description = "Ротация секрета клиента";
|
|
6
|
+
static scope = "bots:write";
|
|
7
|
+
static apiMethod = "POST";
|
|
8
|
+
static apiPath = "/bots/{id}/rotate_client_secret";
|
|
9
|
+
static defaultColumns = ["id", "name", "created_at", "nickname", "avatar_url"];
|
|
10
|
+
static args = {
|
|
11
|
+
id: Args.integer({
|
|
12
|
+
description: "Идентификатор бота (pachca bots list)",
|
|
13
|
+
required: true,
|
|
14
|
+
}),
|
|
15
|
+
};
|
|
16
|
+
static flags = {
|
|
17
|
+
...BaseCommand.baseFlags,
|
|
18
|
+
};
|
|
19
|
+
async run() {
|
|
20
|
+
const { args, flags } = await this.parse(BotsRotateClientSecret);
|
|
21
|
+
this.parsedFlags = flags;
|
|
22
|
+
const { data } = await this.apiRequest({
|
|
23
|
+
method: 'POST',
|
|
24
|
+
path: `/bots/${args.id}/rotate_client_secret`,
|
|
25
|
+
});
|
|
26
|
+
const responseBody = (data ?? {});
|
|
27
|
+
const result = responseBody.data ?? responseBody;
|
|
28
|
+
this.output(result);
|
|
29
|
+
}
|
|
30
|
+
}
|