@pachca/cli 2026.10.3 → 2026.10.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +25 -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 +1 -1
- 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 +4 -2
- package/dist/commands/bots/update.js +4 -3
- package/dist/commands/chats/list-company.js +4 -2
- package/dist/commands/chats/list.js +4 -2
- package/dist/commands/doctor.js +60 -35
- package/dist/commands/drafts/create.js +5 -2
- package/dist/commands/drafts/list.js +4 -2
- package/dist/commands/drafts/update.js +6 -3
- 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 +5 -3
- package/dist/commands/messages/list.js +4 -2
- package/dist/commands/messages/update.js +4 -2
- package/dist/commands/reactions/list.js +4 -2
- package/dist/commands/read-member/list-readers.js +4 -2
- package/dist/commands/search/list-chats.js +4 -2
- package/dist/commands/search/list-messages.js +4 -2
- package/dist/commands/search/list-users.js +4 -2
- package/dist/commands/security/list.js +4 -2
- package/dist/commands/tasks/create.js +3 -1
- package/dist/commands/tasks/list.js +4 -2
- package/dist/commands/tasks/update.js +3 -1
- package/dist/commands/threads/list.js +4 -2
- package/dist/commands/upload.js +27 -8
- package/dist/commands/users/list.js +4 -2
- package/dist/commands/users/update.js +1 -1
- package/dist/commands/views/open.js +1 -0
- package/dist/data/changelog.json +111 -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,30 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 2026.10.4 (31 октября 2026)
|
|
4
|
+
|
|
5
|
+
- **Исправлено** (api): Справка `--docs` и схема `--spec` показывают `dlp_rule_id` в деталях события `dlp_violation_detected` строкой, как его отдаёт API
|
|
6
|
+
- **Добавлено** (upload): Флаг `--name` задаёт имя файла, которым заканчивается `key`: так данные из stdin получают расширение и уходят в сообщение под этим именем
|
|
7
|
+
- **Исправлено** (upload): Файл, которого нет на диске, команда отклоняет до запроса подписи, в том числе с `--dry-run`
|
|
8
|
+
- **Изменено** (doctor): Проверяет тот же токен, что и остальные команды, в том же порядке: `--token`, `--profile`, `PACHCA_TOKEN`, `PACHCA_PROFILE`, активный профиль, поэтому в CI с одной `PACHCA_TOKEN` команда проходит
|
|
9
|
+
- **Изменено** (doctor): При ошибке хотя бы одной проверки команда завершается с кодом `1` в любом формате вывода, в том числе с `-o json`
|
|
10
|
+
- **Изменено** (doctor): Без файла конфига команда показывает путь, по которому его ищет
|
|
11
|
+
- **Исправлено**: `--dry-run` показывает запрос без входа и без `PACHCA_TOKEN`, а токен в заголовке `Authorization` заменён маской
|
|
12
|
+
- **Исправлено** (files direct-url): С `--dry-run` запрос показан без заголовка `Authorization`, как он и уходит в хранилище
|
|
13
|
+
- **Изменено** (bots update, drafts update): Слово `null` во флаге поля, которое принимает `null`, уходит в API как JSON null: `--kind null` удаляет входящий вебхук бота, а `--parent-message-id null` снимает ответ с черновика
|
|
14
|
+
- **Исправлено** (auth login): Вход через браузер с `--quiet` печатает адрес и код подтверждения, а флаг убирает только строку ожидания и отчёт о входе
|
|
15
|
+
- **Изменено** (auth login): Пример в `--help` и подсказка при `--token -` без потока ввода передают токен из буфера обмена: `pbpaste | pachca auth login --token -`
|
|
16
|
+
- **Исправлено** (messages create, messages update, drafts create, drafts update, tasks create, tasks update): Текст из потока ввода попадает в запрос, только когда поток не пуст, а с пустым потоком, как у скрипта или агента, команда отправляет поля из флагов
|
|
17
|
+
- **Изменено**: Отказ `403` с `role_forbidden` в терминале объясняется словами: право у токена есть, но действие не разрешает роль, поэтому новый вход или токен не помогут. Вывод с `-o json` и в пайпе прежний.
|
|
18
|
+
- **Изменено**: Объяснение отказа `insufficient_scope` в терминале зависит от источника токена: с `--token` или `PACHCA_TOKEN` команда предлагает `pachca oauth token-info -o json`, а если право есть в сохранённом списке профиля, советует обновить его через `pachca auth status --remote`
|
|
19
|
+
- **Изменено**: Когда вход через браузер завершён или отозван, сообщение и подсказка `pachca auth login` называют профиль, с которым шла команда
|
|
20
|
+
- **Исправлено** (api): Эквивалентная команда в справке `--describe` повторяет пример запроса из спецификации и работает при копировании
|
|
21
|
+
- **Исправлено** (api): Пример `pachca api POST /messages` в `--help` передаёт `message[entity_id]` в кавычках и работает при копировании, а описание команды и ошибка `--describe` для неизвестного пути говорят «метод»
|
|
22
|
+
- **Изменено** (views open): Выводит ответ API `{ "success": true }`
|
|
23
|
+
- **Изменено**: Справка команд следует обновлённым описаниям API: удаление входящего вебхука, правило совпадения команд бота, правило упоминаний в тексте сообщения, `duration_ms` и `file_type` у файлов, срок ссылки на поток событий, напоминания вместо задач, поле `scopes` у `bot_token_updated` и другие поля деталей журнала аудита, значения настройки бота у `restricted_bot_tag_denied` в подсказке `--list-tags`, добавление участников в тред, создание треда, `--skip-invite-mentions`, правка без уведомления и список прочитавших
|
|
24
|
+
- **Исправлено** (api): Справка `--docs` и схема `--spec` описывают `scopes` в деталях бота журнала аудита так, как его писал API до 7 октября 2026 года: список прав у `bot_token_created` и объект с `previous` и `new` у `bot_token_updated`
|
|
25
|
+
- **Изменено** (search): С `--all` команды поиска останавливаются по `has_next` и показывают в терминале, сколько результатов загружено из общего числа найденных
|
|
26
|
+
- **Изменено** (api): Справка `--docs` и схема `--spec` методов поиска показывают поле `has_next` в `meta.paginate`
|
|
27
|
+
|
|
3
28
|
## 2026.10.3 (28 октября 2026)
|
|
4
29
|
|
|
5
30
|
- **Изменено** (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 {
|
|
@@ -28,7 +28,7 @@ export default class BotsCreate extends BaseCommand {
|
|
|
28
28
|
description: "Условие срабатывания исходящего вебхука",
|
|
29
29
|
}),
|
|
30
30
|
'commands': Flags.string({
|
|
31
|
-
description: "Команды бота (триггер-слова), на которые он реагирует при trigger_on = commands. Суммарная длина команд, объединённых через запятую, не должна превышать 255 символов.",
|
|
31
|
+
description: "Команды бота (триггер-слова), на которые он реагирует при trigger_on = commands. Событие о сообщении приходит боту, когда первое слово текста совпадает с одной из команд целиком, с учётом регистра. Команду можно задать и без слэша в начале. Суммарная длина команд, объединённых через запятую, не должна превышать 255 символов.",
|
|
32
32
|
}),
|
|
33
33
|
'scopes': Flags.string({
|
|
34
34
|
description: "Скоупы (права доступа) токена бота. Если не указано, бот получает набор по умолчанию. Боту доступны не все скоупы: часть из них разрешена только пользовательским ролям, и попытка назначить такой скоуп возвращает `400`. Служебные значения `bot` и `all` назначать нельзя.",
|
|
@@ -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
|
}
|
|
@@ -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
|
}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
// Auto-generated from openapi.yaml — DO NOT EDIT
|
|
2
2
|
import { Args, Flags } from '@oclif/core';
|
|
3
3
|
import { BaseCommand } from '../../base-command.js';
|
|
4
|
+
import { nullableString } from '../../lib/flags.js';
|
|
4
5
|
export default class BotsUpdate extends BaseCommand {
|
|
5
6
|
static description = "Редактирование бота";
|
|
6
7
|
static scope = "bots:write";
|
|
@@ -31,7 +32,7 @@ export default class BotsUpdate extends BaseCommand {
|
|
|
31
32
|
description: "Условие срабатывания исходящего вебхука",
|
|
32
33
|
}),
|
|
33
34
|
'commands': Flags.string({
|
|
34
|
-
description: "Команды бота (триггер-слова), на которые он реагирует при trigger_on = commands. Суммарная длина команд, объединённых через запятую, не должна превышать 255 символов.",
|
|
35
|
+
description: "Команды бота (триггер-слова), на которые он реагирует при trigger_on = commands. Событие о сообщении приходит боту, когда первое слово текста совпадает с одной из команд целиком, с учётом регистра. Команду можно задать и без слэша в начале. Суммарная длина команд, объединённых через запятую, не должна превышать 255 символов.",
|
|
35
36
|
}),
|
|
36
37
|
'scopes': Flags.string({
|
|
37
38
|
description: "Скоупы (права доступа) токена бота. Если не указано, бот получает набор по умолчанию. Боту доступны не все скоупы: часть из них разрешена только пользовательским ролям, и попытка назначить такой скоуп возвращает `400`. Служебные значения `bot` и `all` назначать нельзя.",
|
|
@@ -67,8 +68,8 @@ export default class BotsUpdate extends BaseCommand {
|
|
|
67
68
|
description: "Ограничивает бота одной беседой или каналом: `true` — бота можно добавить только в один такой чат, `false` — в несколько. Личные чаты и треды в ограничение не входят.",
|
|
68
69
|
allowNo: true,
|
|
69
70
|
}),
|
|
70
|
-
'kind':
|
|
71
|
-
description: "Источник входящего вебхука. Если у бота входящего вебхука не было, он включается.",
|
|
71
|
+
'kind': nullableString({
|
|
72
|
+
description: "Источник входящего вебхука. Если у бота входящего вебхука не было, он включается. `null` удаляет входящий вебхук: адрес перестаёт работать, шаблон и настройки входящего вебхука сбрасываются, а при следующем включении бот получает новый адрес. Чтобы передать null, укажите `--kind null`.",
|
|
72
73
|
}),
|
|
73
74
|
'unfurl-domains': Flags.string({
|
|
74
75
|
description: "Домены, ссылки на которые бот разворачивает, не больше 5. Работают вместе с событием `message_link_shared`. Список заменяется целиком.",
|
|
@@ -35,6 +35,7 @@ export default class ChatsListCompany 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
|
activity: flags['activity'],
|
|
@@ -50,7 +51,7 @@ export default class ChatsListCompany extends BaseCommand {
|
|
|
50
51
|
const paginate = meta?.paginate;
|
|
51
52
|
nextCursor = paginate?.next_page;
|
|
52
53
|
pages++;
|
|
53
|
-
// Условие конца:
|
|
54
|
+
// Условие конца: has_next === false, а в ответе без has_next — пустой data
|
|
54
55
|
const hasNext = paginate?.has_next;
|
|
55
56
|
if (typeof hasNext === 'boolean') {
|
|
56
57
|
if (!hasNext)
|
|
@@ -60,7 +61,8 @@ export default class ChatsListCompany extends BaseCommand {
|
|
|
60
61
|
break;
|
|
61
62
|
}
|
|
62
63
|
if (process.stderr.isTTY) {
|
|
63
|
-
|
|
64
|
+
// total лежит в meta у методов поиска. Берём его с первой страницы: при поиске сообщений по дате следующие страницы считают только остаток
|
|
65
|
+
total ??= meta?.total;
|
|
64
66
|
const progress = total ? `${allData.length} / ${total}` : String(allData.length);
|
|
65
67
|
process.stderr.write(`\r Загружено: ${progress}...`);
|
|
66
68
|
}
|
|
@@ -60,6 +60,7 @@ export default class ChatsList extends BaseCommand {
|
|
|
60
60
|
let nextCursor = undefined;
|
|
61
61
|
let pages = 0;
|
|
62
62
|
const seenCursors = new Set();
|
|
63
|
+
let total;
|
|
63
64
|
while (pages < 500) {
|
|
64
65
|
const query = {
|
|
65
66
|
sort: flags['sort'],
|
|
@@ -81,7 +82,7 @@ export default class ChatsList extends BaseCommand {
|
|
|
81
82
|
const paginate = meta?.paginate;
|
|
82
83
|
nextCursor = paginate?.next_page;
|
|
83
84
|
pages++;
|
|
84
|
-
// Условие конца:
|
|
85
|
+
// Условие конца: has_next === false, а в ответе без has_next — пустой data
|
|
85
86
|
const hasNext = paginate?.has_next;
|
|
86
87
|
if (typeof hasNext === 'boolean') {
|
|
87
88
|
if (!hasNext)
|
|
@@ -91,7 +92,8 @@ export default class ChatsList extends BaseCommand {
|
|
|
91
92
|
break;
|
|
92
93
|
}
|
|
93
94
|
if (process.stderr.isTTY) {
|
|
94
|
-
|
|
95
|
+
// total лежит в meta у методов поиска. Берём его с первой страницы: при поиске сообщений по дате следующие страницы считают только остаток
|
|
96
|
+
total ??= meta?.total;
|
|
95
97
|
const progress = total ? `${allData.length} / ${total}` : String(allData.length);
|
|
96
98
|
process.stderr.write(`\r Загружено: ${progress}...`);
|
|
97
99
|
}
|