ktalk-cli 1.0.0__tar.gz → 2.1.0__tar.gz
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.
- {ktalk_cli-1.0.0 → ktalk_cli-2.1.0}/PKG-INFO +55 -7
- {ktalk_cli-1.0.0 → ktalk_cli-2.1.0}/README.md +54 -6
- {ktalk_cli-1.0.0 → ktalk_cli-2.1.0}/pyproject.toml +1 -1
- {ktalk_cli-1.0.0 → ktalk_cli-2.1.0}/src/ktalk_cli/cli.py +1 -1
- {ktalk_cli-1.0.0 → ktalk_cli-2.1.0}/src/ktalk_cli/cli_content.py +78 -5
- {ktalk_cli-1.0.0 → ktalk_cli-2.1.0}/src/ktalk_cli/formatters.py +34 -6
- ktalk_cli-2.1.0/src/ktalk_cli/transcript_identity.py +69 -0
- {ktalk_cli-1.0.0 → ktalk_cli-2.1.0}/tests/test_cli_content.py +8 -2
- ktalk_cli-2.1.0/tests/test_fr41_last_synced.py +243 -0
- ktalk_cli-2.1.0/tests/test_issue3_null_id_fallback.py +177 -0
- ktalk_cli-2.1.0/tests/test_nfr17_identity_verification.py +739 -0
- {ktalk_cli-1.0.0 → ktalk_cli-2.1.0}/.gitignore +0 -0
- {ktalk_cli-1.0.0 → ktalk_cli-2.1.0}/LICENSE +0 -0
- {ktalk_cli-1.0.0 → ktalk_cli-2.1.0}/src/ktalk_cli/CLAUDE.md +0 -0
- {ktalk_cli-1.0.0 → ktalk_cli-2.1.0}/src/ktalk_cli/__init__.py +0 -0
- {ktalk_cli-1.0.0 → ktalk_cli-2.1.0}/src/ktalk_cli/auth.py +0 -0
- {ktalk_cli-1.0.0 → ktalk_cli-2.1.0}/src/ktalk_cli/calendar_reader.py +0 -0
- {ktalk_cli-1.0.0 → ktalk_cli-2.1.0}/src/ktalk_cli/cli_contacts.py +0 -0
- {ktalk_cli-1.0.0 → ktalk_cli-2.1.0}/src/ktalk_cli/cli_meeting.py +0 -0
- {ktalk_cli-1.0.0 → ktalk_cli-2.1.0}/src/ktalk_cli/cli_meeting_args.py +0 -0
- {ktalk_cli-1.0.0 → ktalk_cli-2.1.0}/src/ktalk_cli/cli_meeting_confirm.py +0 -0
- {ktalk_cli-1.0.0 → ktalk_cli-2.1.0}/src/ktalk_cli/cli_meetings_read.py +0 -0
- {ktalk_cli-1.0.0 → ktalk_cli-2.1.0}/src/ktalk_cli/cli_sanction.py +0 -0
- {ktalk_cli-1.0.0 → ktalk_cli-2.1.0}/src/ktalk_cli/cli_store.py +0 -0
- {ktalk_cli-1.0.0 → ktalk_cli-2.1.0}/src/ktalk_cli/cli_sync.py +0 -0
- {ktalk_cli-1.0.0 → ktalk_cli-2.1.0}/src/ktalk_cli/cli_token.py +0 -0
- {ktalk_cli-1.0.0 → ktalk_cli-2.1.0}/src/ktalk_cli/client.py +0 -0
- {ktalk_cli-1.0.0 → ktalk_cli-2.1.0}/src/ktalk_cli/config.py +0 -0
- {ktalk_cli-1.0.0 → ktalk_cli-2.1.0}/src/ktalk_cli/confirmation.py +0 -0
- {ktalk_cli-1.0.0 → ktalk_cli-2.1.0}/src/ktalk_cli/contacts.py +0 -0
- {ktalk_cli-1.0.0 → ktalk_cli-2.1.0}/src/ktalk_cli/contour_diagnostics.py +0 -0
- {ktalk_cli-1.0.0 → ktalk_cli-2.1.0}/src/ktalk_cli/download.py +0 -0
- {ktalk_cli-1.0.0 → ktalk_cli-2.1.0}/src/ktalk_cli/endpoints.py +0 -0
- {ktalk_cli-1.0.0 → ktalk_cli-2.1.0}/src/ktalk_cli/enrichment.py +0 -0
- {ktalk_cli-1.0.0 → ktalk_cli-2.1.0}/src/ktalk_cli/host_config.py +0 -0
- {ktalk_cli-1.0.0 → ktalk_cli-2.1.0}/src/ktalk_cli/meeting_body.py +0 -0
- {ktalk_cli-1.0.0 → ktalk_cli-2.1.0}/src/ktalk_cli/meeting_cancel.py +0 -0
- {ktalk_cli-1.0.0 → ktalk_cli-2.1.0}/src/ktalk_cli/meeting_scheduling.py +0 -0
- {ktalk_cli-1.0.0 → ktalk_cli-2.1.0}/src/ktalk_cli/pagination.py +0 -0
- {ktalk_cli-1.0.0 → ktalk_cli-2.1.0}/src/ktalk_cli/reconciliation.py +0 -0
- {ktalk_cli-1.0.0 → ktalk_cli-2.1.0}/src/ktalk_cli/registry.py +0 -0
- {ktalk_cli-1.0.0 → ktalk_cli-2.1.0}/src/ktalk_cli/rooms.py +0 -0
- {ktalk_cli-1.0.0 → ktalk_cli-2.1.0}/src/ktalk_cli/store.py +0 -0
- {ktalk_cli-1.0.0 → ktalk_cli-2.1.0}/src/ktalk_cli/store_migration.py +0 -0
- {ktalk_cli-1.0.0 → ktalk_cli-2.1.0}/src/ktalk_cli/token_file.py +0 -0
- {ktalk_cli-1.0.0 → ktalk_cli-2.1.0}/src/ktalk_cli/write_journal.py +0 -0
- {ktalk_cli-1.0.0 → ktalk_cli-2.1.0}/src/ktalk_cli/write_sanction.py +0 -0
- {ktalk_cli-1.0.0 → ktalk_cli-2.1.0}/tests/conftest.py +0 -0
- {ktalk_cli-1.0.0 → ktalk_cli-2.1.0}/tests/fixtures/access-info-expired.json +0 -0
- {ktalk_cli-1.0.0 → ktalk_cli-2.1.0}/tests/fixtures/access-info-full.json +0 -0
- {ktalk_cli-1.0.0 → ktalk_cli-2.1.0}/tests/fixtures/archive-8col.md +0 -0
- {ktalk_cli-1.0.0 → ktalk_cli-2.1.0}/tests/fixtures/archive-dup-id.md +0 -0
- {ktalk_cli-1.0.0 → ktalk_cli-2.1.0}/tests/fixtures/archive-escaped-pipe-7col.md +0 -0
- {ktalk_cli-1.0.0 → ktalk_cli-2.1.0}/tests/fixtures/archive-page1.json +0 -0
- {ktalk_cli-1.0.0 → ktalk_cli-2.1.0}/tests/fixtures/archive-page2-empty.json +0 -0
- {ktalk_cli-1.0.0 → ktalk_cli-2.1.0}/tests/fixtures/archive-pipe-in-name.md +0 -0
- {ktalk_cli-1.0.0 → ktalk_cli-2.1.0}/tests/fixtures/calendar-item-session.json +0 -0
- {ktalk_cli-1.0.0 → ktalk_cli-2.1.0}/tests/fixtures/chat-messages-session.json +0 -0
- {ktalk_cli-1.0.0 → ktalk_cli-2.1.0}/tests/fixtures/conference-history-session-oversized.json +0 -0
- {ktalk_cli-1.0.0 → ktalk_cli-2.1.0}/tests/fixtures/conference-history-session.json +0 -0
- {ktalk_cli-1.0.0 → ktalk_cli-2.1.0}/tests/fixtures/error-body-problem-json.json +0 -0
- {ktalk_cli-1.0.0 → ktalk_cli-2.1.0}/tests/fixtures/error-body-validation.json +0 -0
- {ktalk_cli-1.0.0 → ktalk_cli-2.1.0}/tests/fixtures/recording-detail-session-full-participants.json +0 -0
- {ktalk_cli-1.0.0 → ktalk_cli-2.1.0}/tests/fixtures/recording-detail-session-oversized-partial.json +0 -0
- {ktalk_cli-1.0.0 → ktalk_cli-2.1.0}/tests/fixtures/recording-detail-with-qualities.json +0 -0
- {ktalk_cli-1.0.0 → ktalk_cli-2.1.0}/tests/fixtures/recording-list-item-session.json +0 -0
- {ktalk_cli-1.0.0 → ktalk_cli-2.1.0}/tests/fixtures/recordings-list-apikey-page1.json +0 -0
- {ktalk_cli-1.0.0 → ktalk_cli-2.1.0}/tests/fixtures/recordings-list-apikey-page2.json +0 -0
- {ktalk_cli-1.0.0 → ktalk_cli-2.1.0}/tests/fixtures/registry-archive-2026-04.md +0 -0
- {ktalk_cli-1.0.0 → ktalk_cli-2.1.0}/tests/fixtures/registry.md +0 -0
- {ktalk_cli-1.0.0 → ktalk_cli-2.1.0}/tests/fixtures/room-detail-session.json +0 -0
- {ktalk_cli-1.0.0 → ktalk_cli-2.1.0}/tests/test_adr022_ktalk_cli_rename.py +0 -0
- {ktalk_cli-1.0.0 → ktalk_cli-2.1.0}/tests/test_archive.py +0 -0
- {ktalk_cli-1.0.0 → ktalk_cli-2.1.0}/tests/test_auth_modes.py +0 -0
- {ktalk_cli-1.0.0 → ktalk_cli-2.1.0}/tests/test_calendar.py +0 -0
- {ktalk_cli-1.0.0 → ktalk_cli-2.1.0}/tests/test_chat.py +0 -0
- {ktalk_cli-1.0.0 → ktalk_cli-2.1.0}/tests/test_cli.py +0 -0
- {ktalk_cli-1.0.0 → ktalk_cli-2.1.0}/tests/test_cli_config_show.py +0 -0
- {ktalk_cli-1.0.0 → ktalk_cli-2.1.0}/tests/test_cli_export_store_isolation.py +0 -0
- {ktalk_cli-1.0.0 → ktalk_cli-2.1.0}/tests/test_cli_host_config_wiring.py +0 -0
- {ktalk_cli-1.0.0 → ktalk_cli-2.1.0}/tests/test_cli_meeting.py +0 -0
- {ktalk_cli-1.0.0 → ktalk_cli-2.1.0}/tests/test_cli_meeting_sanctioned.py +0 -0
- {ktalk_cli-1.0.0 → ktalk_cli-2.1.0}/tests/test_cli_meetings_surface.py +0 -0
- {ktalk_cli-1.0.0 → ktalk_cli-2.1.0}/tests/test_cli_migrate_to_central_store.py +0 -0
- {ktalk_cli-1.0.0 → ktalk_cli-2.1.0}/tests/test_client.py +0 -0
- {ktalk_cli-1.0.0 → ktalk_cli-2.1.0}/tests/test_concurrency.py +0 -0
- {ktalk_cli-1.0.0 → ktalk_cli-2.1.0}/tests/test_config.py +0 -0
- {ktalk_cli-1.0.0 → ktalk_cli-2.1.0}/tests/test_confirmation.py +0 -0
- {ktalk_cli-1.0.0 → ktalk_cli-2.1.0}/tests/test_contour_diagnostics.py +0 -0
- {ktalk_cli-1.0.0 → ktalk_cli-2.1.0}/tests/test_diagnostics.py +0 -0
- {ktalk_cli-1.0.0 → ktalk_cli-2.1.0}/tests/test_download.py +0 -0
- {ktalk_cli-1.0.0 → ktalk_cli-2.1.0}/tests/test_enrichment.py +0 -0
- {ktalk_cli-1.0.0 → ktalk_cli-2.1.0}/tests/test_formatters.py +0 -0
- {ktalk_cli-1.0.0 → ktalk_cli-2.1.0}/tests/test_fr19_auth_status.py +0 -0
- {ktalk_cli-1.0.0 → ktalk_cli-2.1.0}/tests/test_fr21_no_vault_layout.py +0 -0
- {ktalk_cli-1.0.0 → ktalk_cli-2.1.0}/tests/test_fr39_calendar_inclusive_end.py +0 -0
- {ktalk_cli-1.0.0 → ktalk_cli-2.1.0}/tests/test_fr40_timezone_format.py +0 -0
- {ktalk_cli-1.0.0 → ktalk_cli-2.1.0}/tests/test_host_config.py +0 -0
- {ktalk_cli-1.0.0 → ktalk_cli-2.1.0}/tests/test_meeting_body.py +0 -0
- {ktalk_cli-1.0.0 → ktalk_cli-2.1.0}/tests/test_meeting_cancel.py +0 -0
- {ktalk_cli-1.0.0 → ktalk_cli-2.1.0}/tests/test_meeting_scheduling.py +0 -0
- {ktalk_cli-1.0.0 → ktalk_cli-2.1.0}/tests/test_migration.py +0 -0
- {ktalk_cli-1.0.0 → ktalk_cli-2.1.0}/tests/test_pagination.py +0 -0
- {ktalk_cli-1.0.0 → ktalk_cli-2.1.0}/tests/test_reconciliation.py +0 -0
- {ktalk_cli-1.0.0 → ktalk_cli-2.1.0}/tests/test_registry.py +0 -0
- {ktalk_cli-1.0.0 → ktalk_cli-2.1.0}/tests/test_rooms.py +0 -0
- {ktalk_cli-1.0.0 → ktalk_cli-2.1.0}/tests/test_search_contacts.py +0 -0
- {ktalk_cli-1.0.0 → ktalk_cli-2.1.0}/tests/test_secret_masking.py +0 -0
- {ktalk_cli-1.0.0 → ktalk_cli-2.1.0}/tests/test_store.py +0 -0
- {ktalk_cli-1.0.0 → ktalk_cli-2.1.0}/tests/test_store_migration.py +0 -0
- {ktalk_cli-1.0.0 → ktalk_cli-2.1.0}/tests/test_token_file.py +0 -0
- {ktalk_cli-1.0.0 → ktalk_cli-2.1.0}/tests/test_write_journal.py +0 -0
- {ktalk_cli-1.0.0 → ktalk_cli-2.1.0}/tests/test_write_sanction.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: ktalk-cli
|
|
3
|
-
Version: 1.0
|
|
3
|
+
Version: 2.1.0
|
|
4
4
|
Summary: CLI for accessing Kontur Talk (KTalk) recordings, transcripts and summaries
|
|
5
5
|
Project-URL: Homepage, https://github.com/mdemyanov/ktalk-cli
|
|
6
6
|
Project-URL: Repository, https://github.com/mdemyanov/ktalk-cli
|
|
@@ -26,15 +26,18 @@ Description-Content-Type: text/markdown
|
|
|
26
26
|
[](https://pypi.org/project/ktalk-cli/)
|
|
27
27
|
[](https://pypi.org/project/ktalk-cli/)
|
|
28
28
|
|
|
29
|
-
CLI `ktalk` для
|
|
30
|
-
|
|
31
|
-
обработки записей на SQLite.
|
|
29
|
+
CLI `ktalk` — интерфейс командной строки для тех, кто работает с записями видеовстреч
|
|
30
|
+
[Контур.Толк](https://ktalk.ru) (KTalk) программно: читает записи, транскрипты и саммари,
|
|
31
|
+
управляет расписанием, ведёт локальный реестр обработки записей на SQLite. Годится и как
|
|
32
|
+
самостоятельный инструмент, и как предусловие плагина Claude Code `ktalk` — подробнее в
|
|
33
|
+
разделе «Пакет и плагин Claude Code» ниже.
|
|
32
34
|
|
|
33
35
|
> Раньше пакет назывался `ktalk-mcp` и, помимо CLI, поднимал MCP-сервер для
|
|
34
36
|
> Claude Code (инструменты вида `ktalk_list_recordings`). Этот слой снят
|
|
35
37
|
> целиком — MCP в пакете больше нет, единственная точка входа — команда
|
|
36
|
-
> `ktalk`.
|
|
37
|
-
>
|
|
38
|
+
> `ktalk`. Пришли по старой ссылке или ищете `ktalk-mcp` — это тот же проект
|
|
39
|
+
> под новым именем, старый пакет дальше не развивается (о конфликте имени
|
|
40
|
+
> команды при апгрейде — ниже, в «Установке»).
|
|
38
41
|
|
|
39
42
|
Умеет:
|
|
40
43
|
- Список записей конференций и детали одной записи.
|
|
@@ -73,6 +76,18 @@ uv tool uninstall ktalk-mcp
|
|
|
73
76
|
uv tool install ktalk-cli
|
|
74
77
|
```
|
|
75
78
|
|
|
79
|
+
**Проверка версии** — после установки или обновления:
|
|
80
|
+
|
|
81
|
+
```bash
|
|
82
|
+
ktalk --version # печатает, например: ktalk-cli 2.1.0
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
**Обновление** до последней версии — та же команда `install`, только `upgrade`:
|
|
86
|
+
|
|
87
|
+
```bash
|
|
88
|
+
uv tool upgrade ktalk-cli
|
|
89
|
+
```
|
|
90
|
+
|
|
76
91
|
## Авторизация
|
|
77
92
|
|
|
78
93
|
CLI поддерживает два способа авторизации: session token (кука браузера) и
|
|
@@ -222,11 +237,20 @@ ktalk auth-status
|
|
|
222
237
|
Все команды поддерживают `--json` (валидный JSON в stdout; ошибки — в stderr с
|
|
223
238
|
ненулевым кодом возврата).
|
|
224
239
|
|
|
240
|
+
### Коды возврата
|
|
241
|
+
|
|
242
|
+
| Код | Значение |
|
|
243
|
+
|---|---|
|
|
244
|
+
| `0` | Успех. |
|
|
245
|
+
| `1` | Отказ вызова — сеть, сервер, конфигурация. |
|
|
246
|
+
| `2` | Usage error — неверные аргументы CLI (`argparse`). |
|
|
247
|
+
| `3` | Только `ktalk get-transcript`. Данные получены и напечатаны полностью, но независимая сверка идентичности не сошлась (`identity_check.result == "mismatch"`) — состав участников транскрипта разошёлся с составом записи. Это не сбой команды: код 3 отличает «данные есть, но сверка не сошлась» от `0` (сошлось или не проверялось) и от `1`/`2` (данных нет вовсе). Подробности — в самом теле ответа, поле `identity_check` (ADR-024 §Д1). |
|
|
248
|
+
|
|
225
249
|
| Команда | Назначение |
|
|
226
250
|
|---|---|
|
|
227
251
|
| `ktalk list-recordings [--query Q] [--start-from ISO] [--start-to ISO] [--top N] [--order O] [--page-token T]` | Список записей. `--top` 1–1000 (по умолчанию 30); `--order`: `byTimeNewFirst` (умолчание), `byTimeOldFirst`, `byTitle`, `bySizeBigFirst`, `bySizeSmallFirst`. |
|
|
228
252
|
| `ktalk get-recording <recording_key>` | Детали записи — автор, дата, длительность, участники (список ограничен 6, полный состав — `get-participants`). |
|
|
229
|
-
| `ktalk get-transcript <recording_key> [--chunk N] [--chunk-size N]` | Транскрипт по спикерам с таймкодами. Длинный транскрипт режется на чанки по границам реплик: `--chunk 0` (умолчание) — целиком или первый чанк; `--chunk-size` — макс. символов в чанке (умолчание 30000, ~7500 токенов). |
|
|
253
|
+
| `ktalk get-transcript <recording_key> [--chunk N] [--chunk-size N]` | Транскрипт по спикерам с таймкодами. Длинный транскрипт режется на чанки по границам реплик: `--chunk 0` (умолчание) — целиком или первый чанк; `--chunk-size` — макс. символов в чанке (умолчание 30000, ~7500 токенов). Независимая сверка идентичности включена по умолчанию (`--no-verify-identity` отключает); `--chunk` вне диапазона сверку по сети не запускает вовсе, `identity_check.result == "not_checked"`/`reason: "chunk_out_of_range"`. |
|
|
230
254
|
| `ktalk get-summary <recording_key>` | Полное саммари (краткое резюме + протокол). |
|
|
231
255
|
| `ktalk get-summary-type <recording_key> --type shortSummary\|protocol` | Саммари одного типа. |
|
|
232
256
|
| `ktalk get-participants <recording_key>` | Полный состав участников, включая анонимных — обходит лимит в 6, который отдают `get-recording`/`list-recordings`. |
|
|
@@ -386,6 +410,30 @@ uv run ruff check .
|
|
|
386
410
|
KTALK_SESSION_TOKEN=... KTALK_BASE_URL=... uv run ktalk auth-status
|
|
387
411
|
```
|
|
388
412
|
|
|
413
|
+
## Пакет и плагин Claude Code
|
|
414
|
+
|
|
415
|
+
`ktalk-cli` работает и сам по себе, и как предусловие плагина Claude Code `ktalk`. Плагин не
|
|
416
|
+
обращается к KTalk напрямую и не поднимает MCP-сервер — он вызывает эту же команду `ktalk`
|
|
417
|
+
как единственную точку входа в контур.
|
|
418
|
+
|
|
419
|
+
Плагин пинует точную версию пакета (не нижний порог: «ровно эта версия», не «эта или новее») в
|
|
420
|
+
собственном файле совместимости. Если что-то в интеграции с плагином ведёт себя не так, как
|
|
421
|
+
описано в его документации, — первым делом сверьте версию:
|
|
422
|
+
|
|
423
|
+
```bash
|
|
424
|
+
ktalk --version # см. «Проверка версии» в разделе «Установка»
|
|
425
|
+
```
|
|
426
|
+
|
|
427
|
+
Версия не совпадает с той, что требует плагин, — обновите пакет тем же способом, что при
|
|
428
|
+
установке (`uv tool upgrade ktalk-cli`, см. «Установка»); не совпадает в другую сторону
|
|
429
|
+
(пакет новее, чем ожидает плагин) — не откатывайте его самостоятельно, сверьтесь с тем, кто
|
|
430
|
+
настраивал плагин.
|
|
431
|
+
|
|
432
|
+
## Проблемы и вопросы
|
|
433
|
+
|
|
434
|
+
Нашли баг, некорректное поведение или неточность в документации — заведите issue в этом
|
|
435
|
+
репозитории: https://github.com/mdemyanov/ktalk-cli/issues.
|
|
436
|
+
|
|
389
437
|
## Лицензия
|
|
390
438
|
|
|
391
439
|
MIT
|
|
@@ -3,15 +3,18 @@
|
|
|
3
3
|
[](https://pypi.org/project/ktalk-cli/)
|
|
4
4
|
[](https://pypi.org/project/ktalk-cli/)
|
|
5
5
|
|
|
6
|
-
CLI `ktalk` для
|
|
7
|
-
|
|
8
|
-
обработки записей на SQLite.
|
|
6
|
+
CLI `ktalk` — интерфейс командной строки для тех, кто работает с записями видеовстреч
|
|
7
|
+
[Контур.Толк](https://ktalk.ru) (KTalk) программно: читает записи, транскрипты и саммари,
|
|
8
|
+
управляет расписанием, ведёт локальный реестр обработки записей на SQLite. Годится и как
|
|
9
|
+
самостоятельный инструмент, и как предусловие плагина Claude Code `ktalk` — подробнее в
|
|
10
|
+
разделе «Пакет и плагин Claude Code» ниже.
|
|
9
11
|
|
|
10
12
|
> Раньше пакет назывался `ktalk-mcp` и, помимо CLI, поднимал MCP-сервер для
|
|
11
13
|
> Claude Code (инструменты вида `ktalk_list_recordings`). Этот слой снят
|
|
12
14
|
> целиком — MCP в пакете больше нет, единственная точка входа — команда
|
|
13
|
-
> `ktalk`.
|
|
14
|
-
>
|
|
15
|
+
> `ktalk`. Пришли по старой ссылке или ищете `ktalk-mcp` — это тот же проект
|
|
16
|
+
> под новым именем, старый пакет дальше не развивается (о конфликте имени
|
|
17
|
+
> команды при апгрейде — ниже, в «Установке»).
|
|
15
18
|
|
|
16
19
|
Умеет:
|
|
17
20
|
- Список записей конференций и детали одной записи.
|
|
@@ -50,6 +53,18 @@ uv tool uninstall ktalk-mcp
|
|
|
50
53
|
uv tool install ktalk-cli
|
|
51
54
|
```
|
|
52
55
|
|
|
56
|
+
**Проверка версии** — после установки или обновления:
|
|
57
|
+
|
|
58
|
+
```bash
|
|
59
|
+
ktalk --version # печатает, например: ktalk-cli 2.1.0
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
**Обновление** до последней версии — та же команда `install`, только `upgrade`:
|
|
63
|
+
|
|
64
|
+
```bash
|
|
65
|
+
uv tool upgrade ktalk-cli
|
|
66
|
+
```
|
|
67
|
+
|
|
53
68
|
## Авторизация
|
|
54
69
|
|
|
55
70
|
CLI поддерживает два способа авторизации: session token (кука браузера) и
|
|
@@ -199,11 +214,20 @@ ktalk auth-status
|
|
|
199
214
|
Все команды поддерживают `--json` (валидный JSON в stdout; ошибки — в stderr с
|
|
200
215
|
ненулевым кодом возврата).
|
|
201
216
|
|
|
217
|
+
### Коды возврата
|
|
218
|
+
|
|
219
|
+
| Код | Значение |
|
|
220
|
+
|---|---|
|
|
221
|
+
| `0` | Успех. |
|
|
222
|
+
| `1` | Отказ вызова — сеть, сервер, конфигурация. |
|
|
223
|
+
| `2` | Usage error — неверные аргументы CLI (`argparse`). |
|
|
224
|
+
| `3` | Только `ktalk get-transcript`. Данные получены и напечатаны полностью, но независимая сверка идентичности не сошлась (`identity_check.result == "mismatch"`) — состав участников транскрипта разошёлся с составом записи. Это не сбой команды: код 3 отличает «данные есть, но сверка не сошлась» от `0` (сошлось или не проверялось) и от `1`/`2` (данных нет вовсе). Подробности — в самом теле ответа, поле `identity_check` (ADR-024 §Д1). |
|
|
225
|
+
|
|
202
226
|
| Команда | Назначение |
|
|
203
227
|
|---|---|
|
|
204
228
|
| `ktalk list-recordings [--query Q] [--start-from ISO] [--start-to ISO] [--top N] [--order O] [--page-token T]` | Список записей. `--top` 1–1000 (по умолчанию 30); `--order`: `byTimeNewFirst` (умолчание), `byTimeOldFirst`, `byTitle`, `bySizeBigFirst`, `bySizeSmallFirst`. |
|
|
205
229
|
| `ktalk get-recording <recording_key>` | Детали записи — автор, дата, длительность, участники (список ограничен 6, полный состав — `get-participants`). |
|
|
206
|
-
| `ktalk get-transcript <recording_key> [--chunk N] [--chunk-size N]` | Транскрипт по спикерам с таймкодами. Длинный транскрипт режется на чанки по границам реплик: `--chunk 0` (умолчание) — целиком или первый чанк; `--chunk-size` — макс. символов в чанке (умолчание 30000, ~7500 токенов). |
|
|
230
|
+
| `ktalk get-transcript <recording_key> [--chunk N] [--chunk-size N]` | Транскрипт по спикерам с таймкодами. Длинный транскрипт режется на чанки по границам реплик: `--chunk 0` (умолчание) — целиком или первый чанк; `--chunk-size` — макс. символов в чанке (умолчание 30000, ~7500 токенов). Независимая сверка идентичности включена по умолчанию (`--no-verify-identity` отключает); `--chunk` вне диапазона сверку по сети не запускает вовсе, `identity_check.result == "not_checked"`/`reason: "chunk_out_of_range"`. |
|
|
207
231
|
| `ktalk get-summary <recording_key>` | Полное саммари (краткое резюме + протокол). |
|
|
208
232
|
| `ktalk get-summary-type <recording_key> --type shortSummary\|protocol` | Саммари одного типа. |
|
|
209
233
|
| `ktalk get-participants <recording_key>` | Полный состав участников, включая анонимных — обходит лимит в 6, который отдают `get-recording`/`list-recordings`. |
|
|
@@ -363,6 +387,30 @@ uv run ruff check .
|
|
|
363
387
|
KTALK_SESSION_TOKEN=... KTALK_BASE_URL=... uv run ktalk auth-status
|
|
364
388
|
```
|
|
365
389
|
|
|
390
|
+
## Пакет и плагин Claude Code
|
|
391
|
+
|
|
392
|
+
`ktalk-cli` работает и сам по себе, и как предусловие плагина Claude Code `ktalk`. Плагин не
|
|
393
|
+
обращается к KTalk напрямую и не поднимает MCP-сервер — он вызывает эту же команду `ktalk`
|
|
394
|
+
как единственную точку входа в контур.
|
|
395
|
+
|
|
396
|
+
Плагин пинует точную версию пакета (не нижний порог: «ровно эта версия», не «эта или новее») в
|
|
397
|
+
собственном файле совместимости. Если что-то в интеграции с плагином ведёт себя не так, как
|
|
398
|
+
описано в его документации, — первым делом сверьте версию:
|
|
399
|
+
|
|
400
|
+
```bash
|
|
401
|
+
ktalk --version # см. «Проверка версии» в разделе «Установка»
|
|
402
|
+
```
|
|
403
|
+
|
|
404
|
+
Версия не совпадает с той, что требует плагин, — обновите пакет тем же способом, что при
|
|
405
|
+
установке (`uv tool upgrade ktalk-cli`, см. «Установка»); не совпадает в другую сторону
|
|
406
|
+
(пакет новее, чем ожидает плагин) — не откатывайте его самостоятельно, сверьтесь с тем, кто
|
|
407
|
+
настраивал плагин.
|
|
408
|
+
|
|
409
|
+
## Проблемы и вопросы
|
|
410
|
+
|
|
411
|
+
Нашли баг, некорректное поведение или неточность в документации — заведите issue в этом
|
|
412
|
+
репозитории: https://github.com/mdemyanov/ktalk-cli/issues.
|
|
413
|
+
|
|
366
414
|
## Лицензия
|
|
367
415
|
|
|
368
416
|
MIT
|
|
@@ -212,7 +212,7 @@ def _cmd_dashboard(reg: Registry, args) -> int:
|
|
|
212
212
|
new = [r for r in recs if r["status"] == "new"]
|
|
213
213
|
stats = {s: sum(1 for r in recs if r["status"] == s) for s in _STATUSES}
|
|
214
214
|
if args.json:
|
|
215
|
-
_print_json({"new": new, "stats": stats})
|
|
215
|
+
_print_json({"new": new, "stats": stats, "last_synced": reg.get_meta("last_synced")})
|
|
216
216
|
return 0
|
|
217
217
|
print("# Дашборд KTalk\n")
|
|
218
218
|
print("## Новые записи")
|
|
@@ -11,6 +11,7 @@ from __future__ import annotations
|
|
|
11
11
|
|
|
12
12
|
import argparse
|
|
13
13
|
import asyncio
|
|
14
|
+
import json
|
|
14
15
|
import sys
|
|
15
16
|
from collections.abc import Awaitable, Callable
|
|
16
17
|
|
|
@@ -26,7 +27,9 @@ from ktalk_cli.formatters import (
|
|
|
26
27
|
format_summary_by_type,
|
|
27
28
|
render_tool_output,
|
|
28
29
|
render_transcript_output,
|
|
30
|
+
resolve_chunk_range,
|
|
29
31
|
)
|
|
32
|
+
from ktalk_cli.transcript_identity import check_identity
|
|
30
33
|
|
|
31
34
|
|
|
32
35
|
def register_subparsers(sub) -> None:
|
|
@@ -48,6 +51,12 @@ def register_subparsers(sub) -> None:
|
|
|
48
51
|
p_tr.add_argument("--chunk", type=int, default=0)
|
|
49
52
|
p_tr.add_argument("--chunk-size", type=int, default=30000)
|
|
50
53
|
p_tr.add_argument("--json", action="store_true")
|
|
54
|
+
p_tr.add_argument(
|
|
55
|
+
"--no-verify-identity",
|
|
56
|
+
action="store_true",
|
|
57
|
+
help="Отключить сверку идентичности спикеров с участниками записи (NFR-17, "
|
|
58
|
+
"по умолчанию включена)",
|
|
59
|
+
)
|
|
51
60
|
|
|
52
61
|
p_sum = sub.add_parser("get-summary", help="Полное саммари записи (сеть)")
|
|
53
62
|
p_sum.add_argument("recording_key")
|
|
@@ -111,18 +120,82 @@ def cmd_get_recording(_reg, args: argparse.Namespace) -> int:
|
|
|
111
120
|
return _run(_get_recording(args), format_recording, json_flag=args.json)
|
|
112
121
|
|
|
113
122
|
|
|
123
|
+
async def _verify_transcript_identity(client: KTalkClient, recording_key: str, transcript: dict) -> dict:
|
|
124
|
+
"""Оркестрация NFR-17 (ADR-023 §1, ред. 2a2f6e3): независимый вызов
|
|
125
|
+
`get_recording`; отказ вызова -> `not_checked` (маскирование той же функцией,
|
|
126
|
+
что и основная ошибка, NFR-5), не исключение наружу — основной результат
|
|
127
|
+
транскрипта возвращается в любом случае."""
|
|
128
|
+
try:
|
|
129
|
+
recording = await client.get_recording(recording_key)
|
|
130
|
+
except Exception as exc: # noqa: BLE001 - not_checked, не отказ команды
|
|
131
|
+
return {"result": "not_checked", "reason": redact_secrets(str(exc))}
|
|
132
|
+
return check_identity(transcript, recording)
|
|
133
|
+
|
|
134
|
+
|
|
135
|
+
def _render_transcript_with_identity(
|
|
136
|
+
output_text: str, identity_check: dict | None, *, json_flag: bool, in_range: bool = True
|
|
137
|
+
) -> str:
|
|
138
|
+
"""Сборка вывода `get-transcript` (companion-спека «Оркестрация», шаг 5).
|
|
139
|
+
`identity_check is None` -> сверка отключена (`--no-verify-identity`), выводим
|
|
140
|
+
как раньше. `in_range` — валидность `--chunk` уже известна вызывающей стороне
|
|
141
|
+
(`resolve_chunk_range`, ADR-024 §Д3) — на пути вне диапазона JSON-конверт
|
|
142
|
+
собирается явно (`{"error": …}`), без `try/except JSONDecodeError`."""
|
|
143
|
+
if identity_check is None:
|
|
144
|
+
return output_text
|
|
145
|
+
|
|
146
|
+
if not json_flag:
|
|
147
|
+
line = f"[identity-check] {identity_check['result']}"
|
|
148
|
+
if "reason" in identity_check:
|
|
149
|
+
line += f" ({identity_check['reason']})"
|
|
150
|
+
return f"{output_text}\n{line}"
|
|
151
|
+
|
|
152
|
+
if not in_range:
|
|
153
|
+
return json.dumps(
|
|
154
|
+
{"error": output_text, "identity_check": identity_check},
|
|
155
|
+
ensure_ascii=False,
|
|
156
|
+
indent=2,
|
|
157
|
+
)
|
|
158
|
+
|
|
159
|
+
parsed = json.loads(output_text)
|
|
160
|
+
return json.dumps(
|
|
161
|
+
{"transcript": parsed, "identity_check": identity_check}, ensure_ascii=False, indent=2
|
|
162
|
+
)
|
|
163
|
+
|
|
164
|
+
|
|
114
165
|
def cmd_get_transcript(_reg, args: argparse.Namespace) -> int:
|
|
115
|
-
|
|
166
|
+
fmt = "raw" if args.json else "markdown"
|
|
167
|
+
|
|
168
|
+
async def _fetch() -> tuple[dict, dict | None, bool]:
|
|
116
169
|
async with KTalkClient.from_settings(Settings()) as client:
|
|
117
|
-
|
|
170
|
+
data = await client.get_transcript(args.recording_key)
|
|
171
|
+
in_range, _total_chunks = resolve_chunk_range(data, fmt, args.chunk, args.chunk_size)
|
|
172
|
+
identity_check = None
|
|
173
|
+
if not args.no_verify_identity:
|
|
174
|
+
if in_range:
|
|
175
|
+
identity_check = await _verify_transcript_identity(
|
|
176
|
+
client, args.recording_key, data
|
|
177
|
+
)
|
|
178
|
+
else:
|
|
179
|
+
# ADR-024 §Д3: чанк заведомо вне диапазона — сверка не
|
|
180
|
+
# оплачивается сетевым вызовом, `not_checked` формируется
|
|
181
|
+
# локально, переиспользуя словарь исходов ADR-023.
|
|
182
|
+
identity_check = {"result": "not_checked", "reason": "chunk_out_of_range"}
|
|
183
|
+
return data, identity_check, in_range
|
|
118
184
|
|
|
119
185
|
try:
|
|
120
|
-
data = asyncio.run(_fetch())
|
|
186
|
+
data, identity_check, in_range = asyncio.run(_fetch())
|
|
121
187
|
except Exception as exc: # noqa: BLE001 - surface as CLI error, NFR-5 маскирует
|
|
122
188
|
print(f"Ошибка: {redact_secrets(str(exc))}", file=sys.stderr)
|
|
123
189
|
return 1
|
|
124
|
-
|
|
125
|
-
print(
|
|
190
|
+
output_text = render_transcript_output(data, fmt, args.chunk, args.chunk_size)
|
|
191
|
+
print(
|
|
192
|
+
_render_transcript_with_identity(
|
|
193
|
+
output_text, identity_check, json_flag=args.json, in_range=in_range
|
|
194
|
+
)
|
|
195
|
+
)
|
|
196
|
+
if identity_check is not None and identity_check.get("result") == "mismatch":
|
|
197
|
+
# ADR-024 §Д1: отказ становится громким — код 3, отдельный от 0/1/2.
|
|
198
|
+
return 3
|
|
126
199
|
return 0
|
|
127
200
|
|
|
128
201
|
|
|
@@ -123,7 +123,7 @@ def format_recording(data: dict) -> str:
|
|
|
123
123
|
title = data.get("title", "Без названия")
|
|
124
124
|
lines = [f"# {title}", ""]
|
|
125
125
|
|
|
126
|
-
lines.append(f"- **Ключ:** {data.get('id'
|
|
126
|
+
lines.append(f"- **Ключ:** {data.get('id') or data.get('key') or 'N/A'}")
|
|
127
127
|
lines.append(f"- **Дата:** {_format_datetime(data.get('createdDate'))}")
|
|
128
128
|
|
|
129
129
|
created_by = data.get("createdBy")
|
|
@@ -162,7 +162,7 @@ def format_recordings_list(data: dict) -> str:
|
|
|
162
162
|
]
|
|
163
163
|
|
|
164
164
|
for rec in entities:
|
|
165
|
-
rec_id = rec.get("id"
|
|
165
|
+
rec_id = rec.get("id") or rec.get("key") or "N/A"
|
|
166
166
|
title = rec.get("title", "Без названия")
|
|
167
167
|
date = _format_datetime(rec.get("createdDate"))
|
|
168
168
|
created_by = rec.get("createdBy") or {}
|
|
@@ -318,6 +318,37 @@ def chunk_transcript_raw(data: dict, chunk_size: int) -> list[str]:
|
|
|
318
318
|
return chunks
|
|
319
319
|
|
|
320
320
|
|
|
321
|
+
def _chunk_transcript(data: dict, fmt: str, chunk_size: int, full_text: str) -> list[str]:
|
|
322
|
+
"""Общая точка чанкинга для `render_transcript_output`/`resolve_chunk_range` —
|
|
323
|
+
не дублирует ветвление raw/markdown (ADR-024 companion-спека §3)."""
|
|
324
|
+
if fmt == "raw":
|
|
325
|
+
return chunk_transcript_raw(data, chunk_size)
|
|
326
|
+
return chunk_transcript_markdown(full_text, chunk_size)
|
|
327
|
+
|
|
328
|
+
|
|
329
|
+
def resolve_chunk_range(data: dict, fmt: str, chunk: int, chunk_size: int) -> tuple[bool, int]:
|
|
330
|
+
"""Определяет, лежит ли `chunk` в валидном диапазоне для `data`, БЕЗ сети
|
|
331
|
+
(ADR-024 §Д3, issue #9) — переиспользует чанкинг `render_transcript_output`,
|
|
332
|
+
не дублирует его. Отрицательный `chunk` — тоже вне диапазона (не Python-
|
|
333
|
+
отрицательная индексация).
|
|
334
|
+
|
|
335
|
+
Возвращает `(in_range, total_chunks)`.
|
|
336
|
+
"""
|
|
337
|
+
if fmt == "raw":
|
|
338
|
+
full_text = format_raw(data)
|
|
339
|
+
else:
|
|
340
|
+
full_text = format_transcript(data)
|
|
341
|
+
|
|
342
|
+
if chunk == 0 and len(full_text) <= chunk_size:
|
|
343
|
+
return True, 1
|
|
344
|
+
|
|
345
|
+
chunks = _chunk_transcript(data, fmt, chunk_size, full_text)
|
|
346
|
+
total_chunks = len(chunks)
|
|
347
|
+
chunk_index = 0 if chunk == 0 else chunk - 1
|
|
348
|
+
|
|
349
|
+
return 0 <= chunk_index < total_chunks, total_chunks
|
|
350
|
+
|
|
351
|
+
|
|
321
352
|
def render_transcript_output(data: dict, fmt: str, chunk: int, chunk_size: int) -> str:
|
|
322
353
|
"""Общий слой чтения транскрипта с чанкингом — единственная точка правды для
|
|
323
354
|
`ktalk_get_transcript` (MCP) и `ktalk get-transcript` (CLI, DEV-002 волны 3):
|
|
@@ -332,10 +363,7 @@ def render_transcript_output(data: dict, fmt: str, chunk: int, chunk_size: int)
|
|
|
332
363
|
if chunk == 0 and total_characters <= chunk_size:
|
|
333
364
|
return full_text
|
|
334
365
|
|
|
335
|
-
|
|
336
|
-
chunks = chunk_transcript_raw(data, chunk_size)
|
|
337
|
-
else:
|
|
338
|
-
chunks = chunk_transcript_markdown(full_text, chunk_size)
|
|
366
|
+
chunks = _chunk_transcript(data, fmt, chunk_size, full_text)
|
|
339
367
|
|
|
340
368
|
total_chunks = len(chunks)
|
|
341
369
|
chunk_index = 0 if chunk == 0 else chunk - 1
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
"""Сверка идентичности транскрипта (NFR-17, issue #5, ADR-023 §1 + companion-спека
|
|
2
|
+
«Компоненты — NFR-17»): сверяет состав спикеров транскрипта с составом участников
|
|
3
|
+
записи того же `recording_id` через независимый вызов `get_recording` — обнаруживает
|
|
4
|
+
подмену транскрипта под конкуренцией (RES-006), не связанную с валидностью самого
|
|
5
|
+
запроса.
|
|
6
|
+
|
|
7
|
+
Чистые функции без сети и побочных эффектов — сеть (`client.get_recording`) и исход
|
|
8
|
+
`not_checked` (отказ вызова) живут в оркестрации `cmd_get_transcript`
|
|
9
|
+
(`cli_content.py`), не здесь.
|
|
10
|
+
"""
|
|
11
|
+
|
|
12
|
+
from __future__ import annotations
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def _identity_key(user_ref: dict | None) -> str | None:
|
|
16
|
+
"""Идентификатор пользователя: `userInfo.key` или `.login` (именованный),
|
|
17
|
+
`anonymousId` или `anonymousName` (аноним), `None` если ссылка пуста — та же
|
|
18
|
+
приоритетность, что `_format_user_name(with_id=True)` в `formatters.py`, но
|
|
19
|
+
возвращает сырой токен для сравнения, не строку для вывода."""
|
|
20
|
+
if not user_ref:
|
|
21
|
+
return None
|
|
22
|
+
if user_ref.get("isAnonymous"):
|
|
23
|
+
return user_ref.get("anonymousId") or user_ref.get("anonymousName")
|
|
24
|
+
user_info = user_ref.get("userInfo")
|
|
25
|
+
if not user_info:
|
|
26
|
+
return None
|
|
27
|
+
return user_info.get("key") or user_info.get("login")
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def speaker_identities(transcript: dict) -> set[str]:
|
|
31
|
+
"""Состав спикеров транскрипта: обход `tracks[]`, `speaker`, при отсутствии —
|
|
32
|
+
`diarizedSpeaker`; треки без обоих не подмешивают `None` в результат."""
|
|
33
|
+
identities: set[str] = set()
|
|
34
|
+
for track in transcript.get("tracks") or []:
|
|
35
|
+
speaker_ref = track.get("speaker") or track.get("diarizedSpeaker")
|
|
36
|
+
key = _identity_key(speaker_ref)
|
|
37
|
+
if key is not None:
|
|
38
|
+
identities.add(key)
|
|
39
|
+
return identities
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
def participant_identities(recording: dict) -> set[str]:
|
|
43
|
+
"""Состав участников записи: обход `recording["participants"]` (`TalkUserRef[]`)."""
|
|
44
|
+
identities: set[str] = set()
|
|
45
|
+
for participant in recording.get("participants") or []:
|
|
46
|
+
key = _identity_key(participant)
|
|
47
|
+
if key is not None:
|
|
48
|
+
identities.add(key)
|
|
49
|
+
return identities
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
def check_identity(transcript: dict, recording: dict) -> dict:
|
|
53
|
+
"""Четыре исхода (`not_checked` не формируется здесь — исход уровня оркестрации,
|
|
54
|
+
когда сам вызов `get_recording` падает):
|
|
55
|
+
- `inconclusive` — любое множество пусто, сравнивать не с чем;
|
|
56
|
+
- `match` — пересечение непусто;
|
|
57
|
+
- `mismatch` — оба множества непусты и не пересекаются (сигнатура RES-006)."""
|
|
58
|
+
transcript_speakers = speaker_identities(transcript)
|
|
59
|
+
recording_participants = participant_identities(recording)
|
|
60
|
+
|
|
61
|
+
if not transcript_speakers or not recording_participants:
|
|
62
|
+
return {"result": "inconclusive"}
|
|
63
|
+
|
|
64
|
+
result = "match" if transcript_speakers & recording_participants else "mismatch"
|
|
65
|
+
return {
|
|
66
|
+
"result": result,
|
|
67
|
+
"transcript_speakers": sorted(transcript_speakers),
|
|
68
|
+
"recording_participants": sorted(recording_participants),
|
|
69
|
+
}
|
|
@@ -113,11 +113,15 @@ def test_get_recording_error_goes_to_stderr_with_nonzero_exit(httpx_mock: HTTPXM
|
|
|
113
113
|
|
|
114
114
|
@pytest.mark.httpx_mock(assert_all_responses_were_requested=False)
|
|
115
115
|
def test_get_transcript_json_default_returns_raw_json(httpx_mock: HTTPXMock, capsys):
|
|
116
|
+
"""`--no-verify-identity`: предмет теста — форма raw-JSON транскрипта, не NFR-17
|
|
117
|
+
(ADR-023 §1 сделал сверку идентичности умолчанием — без флага здесь потребовался
|
|
118
|
+
бы и мок `get_recording`; отдельное покрытие умолчания-включено —
|
|
119
|
+
`tests/test_nfr17_identity_verification.py`)."""
|
|
116
120
|
from ktalk_cli.cli import main
|
|
117
121
|
|
|
118
122
|
httpx_mock.add_response(json={"status": "done", "tracks": []})
|
|
119
123
|
|
|
120
|
-
rc = main(["get-transcript", "REC-1", "--json"])
|
|
124
|
+
rc = main(["get-transcript", "REC-1", "--json", "--no-verify-identity"])
|
|
121
125
|
|
|
122
126
|
assert rc == 0
|
|
123
127
|
out = json.loads(capsys.readouterr().out)
|
|
@@ -126,11 +130,13 @@ def test_get_transcript_json_default_returns_raw_json(httpx_mock: HTTPXMock, cap
|
|
|
126
130
|
|
|
127
131
|
@pytest.mark.httpx_mock(assert_all_responses_were_requested=False)
|
|
128
132
|
def test_get_transcript_markdown_default(httpx_mock: HTTPXMock, capsys):
|
|
133
|
+
"""`--no-verify-identity` — см. комментарий у `test_get_transcript_json_default_
|
|
134
|
+
returns_raw_json` выше: предмет здесь — markdown-рендер, не NFR-17."""
|
|
129
135
|
from ktalk_cli.cli import main
|
|
130
136
|
|
|
131
137
|
httpx_mock.add_response(json={"status": "done", "tracks": []})
|
|
132
138
|
|
|
133
|
-
rc = main(["get-transcript", "REC-1"])
|
|
139
|
+
rc = main(["get-transcript", "REC-1", "--no-verify-identity"])
|
|
134
140
|
|
|
135
141
|
assert rc == 0
|
|
136
142
|
assert "Транскрипт" in capsys.readouterr().out
|