ktalk-cli 3.0.2__tar.gz → 4.0.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.
Files changed (136) hide show
  1. {ktalk_cli-3.0.2 → ktalk_cli-4.0.0}/.gitignore +2 -1
  2. {ktalk_cli-3.0.2 → ktalk_cli-4.0.0}/PKG-INFO +57 -32
  3. {ktalk_cli-3.0.2 → ktalk_cli-4.0.0}/README.md +56 -31
  4. {ktalk_cli-3.0.2 → ktalk_cli-4.0.0}/pyproject.toml +1 -1
  5. ktalk_cli-4.0.0/src/ktalk_cli/CLAUDE.md +39 -0
  6. {ktalk_cli-3.0.2 → ktalk_cli-4.0.0}/src/ktalk_cli/auth.py +15 -3
  7. {ktalk_cli-3.0.2 → ktalk_cli-4.0.0}/src/ktalk_cli/cli.py +23 -54
  8. ktalk_cli-4.0.0/src/ktalk_cli/cli_config.py +128 -0
  9. {ktalk_cli-3.0.2 → ktalk_cli-4.0.0}/src/ktalk_cli/cli_doctor.py +38 -8
  10. {ktalk_cli-3.0.2 → ktalk_cli-4.0.0}/src/ktalk_cli/cli_meetings_read.py +9 -2
  11. ktalk_cli-4.0.0/src/ktalk_cli/cli_sync.py +233 -0
  12. {ktalk_cli-3.0.2 → ktalk_cli-4.0.0}/src/ktalk_cli/client.py +9 -2
  13. ktalk_cli-4.0.0/src/ktalk_cli/config.py +270 -0
  14. ktalk_cli-4.0.0/src/ktalk_cli/fs_guard.py +58 -0
  15. {ktalk_cli-3.0.2 → ktalk_cli-4.0.0}/src/ktalk_cli/reconciliation.py +22 -12
  16. {ktalk_cli-3.0.2 → ktalk_cli-4.0.0}/src/ktalk_cli/registry.py +1 -19
  17. ktalk_cli-4.0.0/src/ktalk_cli/talk_config.py +123 -0
  18. {ktalk_cli-3.0.2 → ktalk_cli-4.0.0}/src/ktalk_cli/token_file.py +12 -9
  19. ktalk_cli-4.0.0/tests/conftest.py +56 -0
  20. {ktalk_cli-3.0.2 → ktalk_cli-4.0.0}/tests/test_adr022_ktalk_cli_rename.py +13 -1
  21. ktalk_cli-4.0.0/tests/test_calendar_window_iso8601.py +142 -0
  22. {ktalk_cli-3.0.2 → ktalk_cli-4.0.0}/tests/test_cli.py +12 -14
  23. ktalk_cli-4.0.0/tests/test_cli_config_set.py +138 -0
  24. {ktalk_cli-3.0.2 → ktalk_cli-4.0.0}/tests/test_cli_content.py +6 -7
  25. {ktalk_cli-3.0.2 → ktalk_cli-4.0.0}/tests/test_cli_meeting.py +24 -3
  26. {ktalk_cli-3.0.2 → ktalk_cli-4.0.0}/tests/test_cli_meeting_sanctioned.py +24 -3
  27. {ktalk_cli-3.0.2 → ktalk_cli-4.0.0}/tests/test_cli_meetings_surface.py +24 -3
  28. ktalk_cli-4.0.0/tests/test_code_review_dev003.py +295 -0
  29. {ktalk_cli-3.0.2 → ktalk_cli-4.0.0}/tests/test_config.py +51 -20
  30. {ktalk_cli-3.0.2 → ktalk_cli-4.0.0}/tests/test_diagnostics.py +37 -4
  31. {ktalk_cli-3.0.2 → ktalk_cli-4.0.0}/tests/test_fr19_auth_status.py +9 -10
  32. {ktalk_cli-3.0.2 → ktalk_cli-4.0.0}/tests/test_fr39_calendar_inclusive_end.py +24 -3
  33. {ktalk_cli-3.0.2 → ktalk_cli-4.0.0}/tests/test_fr40_timezone_format.py +24 -3
  34. {ktalk_cli-3.0.2 → ktalk_cli-4.0.0}/tests/test_fr41_last_synced.py +24 -5
  35. {ktalk_cli-3.0.2 → ktalk_cli-4.0.0}/tests/test_fr42_single_credential_source.py +32 -27
  36. {ktalk_cli-3.0.2 → ktalk_cli-4.0.0}/tests/test_fr43_legacy_key_removal_warning.py +31 -27
  37. {ktalk_cli-3.0.2 → ktalk_cli-4.0.0}/tests/test_fr44_auth_status_accepted_rejected.py +20 -35
  38. {ktalk_cli-3.0.2 → ktalk_cli-4.0.0}/tests/test_fr48_doctor_summary_diagnostics.py +44 -93
  39. ktalk_cli-4.0.0/tests/test_legacy_config_detection.py +425 -0
  40. {ktalk_cli-3.0.2 → ktalk_cli-4.0.0}/tests/test_nfr17_identity_verification.py +4 -4
  41. {ktalk_cli-3.0.2 → ktalk_cli-4.0.0}/tests/test_pagination.py +6 -20
  42. {ktalk_cli-3.0.2 → ktalk_cli-4.0.0}/tests/test_reconciliation.py +11 -20
  43. {ktalk_cli-3.0.2 → ktalk_cli-4.0.0}/tests/test_registry.py +4 -18
  44. {ktalk_cli-3.0.2 → ktalk_cli-4.0.0}/tests/test_search_contacts.py +1 -4
  45. ktalk_cli-4.0.0/tests/test_sec001_control_char.py +36 -0
  46. ktalk_cli-4.0.0/tests/test_sec001_legacy_var_401.py +44 -0
  47. ktalk_cli-4.0.0/tests/test_sec001_symlink_guard.py +67 -0
  48. {ktalk_cli-3.0.2 → ktalk_cli-4.0.0}/tests/test_secret_masking.py +45 -32
  49. ktalk_cli-4.0.0/tests/test_settings_config_sources.py +237 -0
  50. {ktalk_cli-3.0.2 → ktalk_cli-4.0.0}/tests/test_store.py +24 -11
  51. ktalk_cli-4.0.0/tests/test_sync_honest_window.py +315 -0
  52. ktalk_cli-4.0.0/tests/test_talk_config.py +132 -0
  53. {ktalk_cli-3.0.2 → ktalk_cli-4.0.0}/tests/test_token_file.py +12 -4
  54. {ktalk_cli-3.0.2 → ktalk_cli-4.0.0}/tests/test_write_journal.py +20 -3
  55. ktalk_cli-3.0.2/src/ktalk_cli/CLAUDE.md +0 -30
  56. ktalk_cli-3.0.2/src/ktalk_cli/cli_sync.py +0 -154
  57. ktalk_cli-3.0.2/src/ktalk_cli/config.py +0 -187
  58. ktalk_cli-3.0.2/tests/conftest.py +0 -16
  59. {ktalk_cli-3.0.2 → ktalk_cli-4.0.0}/LICENSE +0 -0
  60. {ktalk_cli-3.0.2 → ktalk_cli-4.0.0}/src/ktalk_cli/__init__.py +0 -0
  61. {ktalk_cli-3.0.2 → ktalk_cli-4.0.0}/src/ktalk_cli/calendar_reader.py +0 -0
  62. {ktalk_cli-3.0.2 → ktalk_cli-4.0.0}/src/ktalk_cli/cli_contacts.py +0 -0
  63. {ktalk_cli-3.0.2 → ktalk_cli-4.0.0}/src/ktalk_cli/cli_content.py +0 -0
  64. {ktalk_cli-3.0.2 → ktalk_cli-4.0.0}/src/ktalk_cli/cli_meeting.py +0 -0
  65. {ktalk_cli-3.0.2 → ktalk_cli-4.0.0}/src/ktalk_cli/cli_meeting_args.py +0 -0
  66. {ktalk_cli-3.0.2 → ktalk_cli-4.0.0}/src/ktalk_cli/cli_meeting_confirm.py +0 -0
  67. {ktalk_cli-3.0.2 → ktalk_cli-4.0.0}/src/ktalk_cli/cli_sanction.py +0 -0
  68. {ktalk_cli-3.0.2 → ktalk_cli-4.0.0}/src/ktalk_cli/cli_store.py +0 -0
  69. {ktalk_cli-3.0.2 → ktalk_cli-4.0.0}/src/ktalk_cli/cli_token.py +0 -0
  70. {ktalk_cli-3.0.2 → ktalk_cli-4.0.0}/src/ktalk_cli/confirmation.py +0 -0
  71. {ktalk_cli-3.0.2 → ktalk_cli-4.0.0}/src/ktalk_cli/contacts.py +0 -0
  72. {ktalk_cli-3.0.2 → ktalk_cli-4.0.0}/src/ktalk_cli/contour_diagnostics.py +0 -0
  73. {ktalk_cli-3.0.2 → ktalk_cli-4.0.0}/src/ktalk_cli/download.py +0 -0
  74. {ktalk_cli-3.0.2 → ktalk_cli-4.0.0}/src/ktalk_cli/endpoints.py +0 -0
  75. {ktalk_cli-3.0.2 → ktalk_cli-4.0.0}/src/ktalk_cli/enrichment.py +0 -0
  76. {ktalk_cli-3.0.2 → ktalk_cli-4.0.0}/src/ktalk_cli/formatters.py +0 -0
  77. {ktalk_cli-3.0.2 → ktalk_cli-4.0.0}/src/ktalk_cli/host_config.py +0 -0
  78. {ktalk_cli-3.0.2 → ktalk_cli-4.0.0}/src/ktalk_cli/meeting_body.py +0 -0
  79. {ktalk_cli-3.0.2 → ktalk_cli-4.0.0}/src/ktalk_cli/meeting_cancel.py +0 -0
  80. {ktalk_cli-3.0.2 → ktalk_cli-4.0.0}/src/ktalk_cli/meeting_scheduling.py +0 -0
  81. {ktalk_cli-3.0.2 → ktalk_cli-4.0.0}/src/ktalk_cli/pagination.py +0 -0
  82. {ktalk_cli-3.0.2 → ktalk_cli-4.0.0}/src/ktalk_cli/rooms.py +0 -0
  83. {ktalk_cli-3.0.2 → ktalk_cli-4.0.0}/src/ktalk_cli/store.py +0 -0
  84. {ktalk_cli-3.0.2 → ktalk_cli-4.0.0}/src/ktalk_cli/store_migration.py +0 -0
  85. {ktalk_cli-3.0.2 → ktalk_cli-4.0.0}/src/ktalk_cli/transcript_identity.py +0 -0
  86. {ktalk_cli-3.0.2 → ktalk_cli-4.0.0}/src/ktalk_cli/write_journal.py +0 -0
  87. {ktalk_cli-3.0.2 → ktalk_cli-4.0.0}/src/ktalk_cli/write_sanction.py +0 -0
  88. {ktalk_cli-3.0.2 → ktalk_cli-4.0.0}/tests/fixtures/access-info-expired.json +0 -0
  89. {ktalk_cli-3.0.2 → ktalk_cli-4.0.0}/tests/fixtures/access-info-full.json +0 -0
  90. {ktalk_cli-3.0.2 → ktalk_cli-4.0.0}/tests/fixtures/archive-8col.md +0 -0
  91. {ktalk_cli-3.0.2 → ktalk_cli-4.0.0}/tests/fixtures/archive-dup-id.md +0 -0
  92. {ktalk_cli-3.0.2 → ktalk_cli-4.0.0}/tests/fixtures/archive-escaped-pipe-7col.md +0 -0
  93. {ktalk_cli-3.0.2 → ktalk_cli-4.0.0}/tests/fixtures/archive-page1.json +0 -0
  94. {ktalk_cli-3.0.2 → ktalk_cli-4.0.0}/tests/fixtures/archive-page2-empty.json +0 -0
  95. {ktalk_cli-3.0.2 → ktalk_cli-4.0.0}/tests/fixtures/archive-pipe-in-name.md +0 -0
  96. {ktalk_cli-3.0.2 → ktalk_cli-4.0.0}/tests/fixtures/calendar-item-session.json +0 -0
  97. {ktalk_cli-3.0.2 → ktalk_cli-4.0.0}/tests/fixtures/chat-messages-session.json +0 -0
  98. {ktalk_cli-3.0.2 → ktalk_cli-4.0.0}/tests/fixtures/conference-history-session-oversized.json +0 -0
  99. {ktalk_cli-3.0.2 → ktalk_cli-4.0.0}/tests/fixtures/conference-history-session.json +0 -0
  100. {ktalk_cli-3.0.2 → ktalk_cli-4.0.0}/tests/fixtures/error-body-problem-json.json +0 -0
  101. {ktalk_cli-3.0.2 → ktalk_cli-4.0.0}/tests/fixtures/error-body-validation.json +0 -0
  102. {ktalk_cli-3.0.2 → ktalk_cli-4.0.0}/tests/fixtures/recording-detail-session-full-participants.json +0 -0
  103. {ktalk_cli-3.0.2 → ktalk_cli-4.0.0}/tests/fixtures/recording-detail-session-oversized-partial.json +0 -0
  104. {ktalk_cli-3.0.2 → ktalk_cli-4.0.0}/tests/fixtures/recording-detail-with-qualities.json +0 -0
  105. {ktalk_cli-3.0.2 → ktalk_cli-4.0.0}/tests/fixtures/recording-list-item-session.json +0 -0
  106. {ktalk_cli-3.0.2 → ktalk_cli-4.0.0}/tests/fixtures/recordings-list-apikey-page1.json +0 -0
  107. {ktalk_cli-3.0.2 → ktalk_cli-4.0.0}/tests/fixtures/recordings-list-apikey-page2.json +0 -0
  108. {ktalk_cli-3.0.2 → ktalk_cli-4.0.0}/tests/fixtures/registry-archive-2026-04.md +0 -0
  109. {ktalk_cli-3.0.2 → ktalk_cli-4.0.0}/tests/fixtures/registry.md +0 -0
  110. {ktalk_cli-3.0.2 → ktalk_cli-4.0.0}/tests/fixtures/room-detail-session.json +0 -0
  111. {ktalk_cli-3.0.2 → ktalk_cli-4.0.0}/tests/test_archive.py +0 -0
  112. {ktalk_cli-3.0.2 → ktalk_cli-4.0.0}/tests/test_auth_modes.py +0 -0
  113. {ktalk_cli-3.0.2 → ktalk_cli-4.0.0}/tests/test_calendar.py +0 -0
  114. {ktalk_cli-3.0.2 → ktalk_cli-4.0.0}/tests/test_chat.py +0 -0
  115. {ktalk_cli-3.0.2 → ktalk_cli-4.0.0}/tests/test_cli_config_show.py +0 -0
  116. {ktalk_cli-3.0.2 → ktalk_cli-4.0.0}/tests/test_cli_export_store_isolation.py +0 -0
  117. {ktalk_cli-3.0.2 → ktalk_cli-4.0.0}/tests/test_cli_host_config_wiring.py +0 -0
  118. {ktalk_cli-3.0.2 → ktalk_cli-4.0.0}/tests/test_cli_migrate_to_central_store.py +0 -0
  119. {ktalk_cli-3.0.2 → ktalk_cli-4.0.0}/tests/test_client.py +0 -0
  120. {ktalk_cli-3.0.2 → ktalk_cli-4.0.0}/tests/test_concurrency.py +0 -0
  121. {ktalk_cli-3.0.2 → ktalk_cli-4.0.0}/tests/test_confirmation.py +0 -0
  122. {ktalk_cli-3.0.2 → ktalk_cli-4.0.0}/tests/test_contour_diagnostics.py +0 -0
  123. {ktalk_cli-3.0.2 → ktalk_cli-4.0.0}/tests/test_download.py +0 -0
  124. {ktalk_cli-3.0.2 → ktalk_cli-4.0.0}/tests/test_enrichment.py +0 -0
  125. {ktalk_cli-3.0.2 → ktalk_cli-4.0.0}/tests/test_formatters.py +0 -0
  126. {ktalk_cli-3.0.2 → ktalk_cli-4.0.0}/tests/test_fr21_no_vault_layout.py +0 -0
  127. {ktalk_cli-3.0.2 → ktalk_cli-4.0.0}/tests/test_fr47_missing_fields_completeness.py +0 -0
  128. {ktalk_cli-3.0.2 → ktalk_cli-4.0.0}/tests/test_host_config.py +0 -0
  129. {ktalk_cli-3.0.2 → ktalk_cli-4.0.0}/tests/test_issue3_null_id_fallback.py +0 -0
  130. {ktalk_cli-3.0.2 → ktalk_cli-4.0.0}/tests/test_meeting_body.py +0 -0
  131. {ktalk_cli-3.0.2 → ktalk_cli-4.0.0}/tests/test_meeting_cancel.py +0 -0
  132. {ktalk_cli-3.0.2 → ktalk_cli-4.0.0}/tests/test_meeting_scheduling.py +0 -0
  133. {ktalk_cli-3.0.2 → ktalk_cli-4.0.0}/tests/test_migration.py +0 -0
  134. {ktalk_cli-3.0.2 → ktalk_cli-4.0.0}/tests/test_rooms.py +0 -0
  135. {ktalk_cli-3.0.2 → ktalk_cli-4.0.0}/tests/test_store_migration.py +0 -0
  136. {ktalk_cli-3.0.2 → ktalk_cli-4.0.0}/tests/test_write_sanction.py +0 -0
@@ -12,7 +12,8 @@ venv/
12
12
  .pytest_cache/
13
13
  .ruff_cache/
14
14
  .mypy_cache/
15
- .claude/settings.local.json
15
+ .claude/settings.local.json*
16
+ .claude/settings.json.bak*
16
17
  .worktrees/
17
18
 
18
19
  # Снимки DevTools: содержат живые токены, куки и перс. данные (NFR-5/SEC-001)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: ktalk-cli
3
- Version: 3.0.2
3
+ Version: 4.0.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
@@ -90,6 +90,14 @@ uv tool upgrade ktalk-cli
90
90
 
91
91
  ## Авторизация
92
92
 
93
+ **С версии 4.0.0 конфигурация каждого значения имеет ровно один источник**
94
+ (ADR-027): session token — только файл, записанный `ktalk token set`; адрес
95
+ стенда — только `config.toml`, записанный `ktalk config set base-url`. Ни одна
96
+ переменная `KTALK_*` и ни один `.env` в рабочем каталоге больше не читаются как
97
+ источник значения — их присутствие не проходит молча: `ktalk auth-status`,
98
+ `ktalk doctor` и тексты отказов называют обнаруженную снятую переменную по
99
+ имени (значение не печатается никогда) и советуют её убрать.
100
+
93
101
  **С версии 3.0.0 CLI работает только через session token** (кука браузера) — режим
94
102
  персонального API-ключа (`KTALK_PERSONAL_API_KEY`) снят целиком (ADR-025): он
95
103
  конкурировал с сессией молча (при обеих заданных переменных побеждал ключ без
@@ -101,9 +109,11 @@ uv tool upgrade ktalk-cli
101
109
  ручного обновления токена по мере его протухания (`ktalk token set -`, см. ниже) —
102
110
  это не восстанавливается автоматически снятием ключа.
103
111
 
104
- Переменная `KTALK_PERSONAL_API_KEY`, если она всё ещё задана в окружении, не
105
- читается как credential ни на одном шаге — CLI печатает об этом одно предупреждение
106
- на stderr при каждом вызове и продолжает работу на сессионном токене.
112
+ Переменные `KTALK_PERSONAL_API_KEY`, `KTALK_SESSION_TOKEN` и остальные
113
+ `KTALK_*`, если они всё ещё заданы в окружении (частая причина — блок `env` в
114
+ `~/.claude/settings.json`), не читаются ни на одном шаге — CLI печатает об этом
115
+ одно предупреждение на stderr при каждом вызове и продолжает работу на файле
116
+ токена/`config.toml`.
107
117
 
108
118
  ### Session token
109
119
 
@@ -134,14 +144,20 @@ ktalk token status # есть ли файл, права, маска знач
134
144
  ktalk auth-status # жива ли авторизация — реальный запрос, не имитация
135
145
  ```
136
146
 
137
- Путь переопределяется переменной `KTALK_TOKEN_FILE`; каталог уважает `XDG_CONFIG_HOME`.
147
+ Путь файла — `${XDG_CONFIG_HOME:-~/.config}/ktalk-mcp/token`; переменной-
148
+ переопределения нет (снятая `KTALK_TOKEN_FILE` не читается, ADR-027).
138
149
 
139
- **Порядок источников** — первый непустой выигрывает:
150
+ **Единственный источник токена** — этот файл (ADR-027). Переменная
151
+ `KTALK_SESSION_TOKEN` и `.env` в рабочем каталоге не читаются вовсе: заданная
152
+ переменная больше не перекрывает файл ни молча, ни с предупреждением — она
153
+ просто не участвует в разрешении credential, а CLI называет её в выводе
154
+ `auth-status`/`doctor` как снятую.
140
155
 
141
- | # | Источник | Комментарий |
142
- |---|---|---|
143
- | 1 | `KTALK_SESSION_TOKEN` (окружение или `.env` в рабочей директории) | заданное явно сильнее лежащего на диске |
144
- | 2 | `~/.config/ktalk-mcp/token` | дефолтный путь для повседневной работы |
156
+ > Ротация больше не может «застрять» на невидимой переменной: `ktalk token set -`
157
+ > перезаписывает файл, и следующий же вызов читает новое значение. Разводка
158
+ > источников из старых установок (переменная против файла, рунбук
159
+ > [OPS-003](content/70-operations/OPS-003-env-var-shadows-token-file.md)) снята
160
+ > самим устройством 4.0.0 — переменную достаточно убрать из окружения.
145
161
 
146
162
  Ни один запрос не несёт заголовок `X-Auth-Token` — единственный транспорт credential
147
163
  теперь query-параметр `sessionToken`.
@@ -165,22 +181,25 @@ ktalk auth-status # жива ли авторизация — реальны
165
181
  > ошибку авторизации, повторите те же два шага — `ktalk token set -` перезаписывает
166
182
  > файл, права переставлять не нужно.
167
183
 
168
- ### Переменные окружения
184
+ ### Конфигурация: адрес стенда (`config.toml`)
185
+
186
+ Адрес контура задаётся один раз, машинной командой:
169
187
 
170
188
  ```bash
171
- export KTALK_SESSION_TOKEN="ваш_session_token"
172
- export KTALK_BASE_URL="https://your-domain.ktalk.ru"
189
+ ktalk config set base-url https://your-domain.ktalk.ru
173
190
  ```
174
191
 
175
- Переменная не обязательна: без неё читается файл `~/.config/ktalk-mcp/token`
176
- (см. «Session token»).
192
+ Команда пишет `${XDG_CONFIG_HOME:-~/.config}/ktalk-mcp/config.toml` (рядом с
193
+ файлом токена, права `0644` — адрес не секрет) и отвергает значение без схемы
194
+ http(s) или без хоста до записи. Проверка — `ktalk config show`: секция
195
+ `## Толк` называет адрес, путь `config.toml` и статус файла токена.
177
196
 
178
- Также поддерживается файл `.env` в рабочей директории:
179
-
180
- ```env
181
- KTALK_SESSION_TOKEN=ваш_session_token
182
- KTALK_BASE_URL=https://your-domain.ktalk.ru
183
- ```
197
+ **Переменные окружения и `.env` больше не читаются вовсе (4.0.0, ADR-027).**
198
+ `KTALK_BASE_URL`, `KTALK_SESSION_TOKEN`, `KTALK_PERSONAL_API_KEY`,
199
+ `KTALK_REGISTRY_DB`, `KTALK_TOKEN_FILE` и `.env` в рабочем каталоге не
200
+ участвуют в разрешении ни одного значения. Если какая-то из них всё ещё задана,
201
+ `ktalk auth-status` и `ktalk doctor` называют её по имени и советуют убрать —
202
+ значение при этом не печатается никогда.
184
203
 
185
204
  ## Диагностика авторизации
186
205
 
@@ -193,10 +212,12 @@ ktalk auth-status
193
212
  Диагностика различает два случая, которые снаружи выглядят одинаково — просто ошибка, —
194
213
  но чинятся по-разному:
195
214
 
196
- - **401** — токен невалиден либо истёк. Обновите его: `ktalk token set -` (или
197
- переменную `KTALK_SESSION_TOKEN`, если она задана).
198
- - **403** — токен рабочий, но у текущей сессии нет прав на эту операцию.
199
- Перевыпускать токен не нужно.
215
+ - **401** — токен невалиден либо истёк. Вердикт `alive: false`, код возврата `1`.
216
+ Обновите токен: `ktalk token set -` — файл перезаписывается, права переставлять
217
+ не нужно.
218
+ - **403** — токен рабочий, но у текущей сессии нет прав на эту операцию. Вердикт
219
+ `alive: true`, код возврата `0`: нехватка прав не является отказом токена, и
220
+ перевыпускать его не нужно.
200
221
 
201
222
  У session token понятия scope и срока действия нет — диагностика выполняет реальный
202
223
  пробный запрос (список записей), а не имитацию без сети.
@@ -320,15 +341,15 @@ CLI работает с KTalk Web API через единственный (sessi
320
341
 
321
342
  Та же команда `ktalk` ведёт операционный реестр обработки записей на SQLite.
322
343
  Вся детерминированная механика (синхронизация списка записей, дедуп,
323
- экспирация, смена статусов, рендер дашборда и markdown-зеркала, разовая
344
+ смена статусов, рендер дашборда и markdown-зеркала, разовая
324
345
  миграция) живёт в коде, а не в рассуждениях модели.
325
346
 
326
347
  **SQLite — операционный source of truth.** Markdown-файл `registry.md` —
327
348
  генерируемое read-only зеркало для git (`ktalk export`), руками не редактируется.
328
349
 
329
- Путь к базе: флаг `--db PATH` > переменная `KTALK_REGISTRY_DB` > дефолт
330
- `95_TRANSCRIPTS/.registry.db` (относительно текущего каталога). Бинарную БД
331
- нужно добавить в `.gitignore` (`.registry.db`, `.registry.db-wal`, `.registry.db-shm`).
350
+ Путь к базе: флаг `--db PATH` > `registry.db_path` из `.ktalk.toml`
351
+ проекта-хозяина > машинный дефолт централизованного хранилища (ADR-013;
352
+ переменная `KTALK_REGISTRY_DB` снята в 4.0.0 и не читается, ADR-027).
332
353
 
333
354
  `ktalk auth-status`, `ktalk create-meeting-preview` и `ktalk create-meeting-confirm`
334
355
  реестр не открывают вовсе — им он не нужен. В частности, `auth-status` работает
@@ -337,10 +358,12 @@ CLI работает с KTalk Web API через единственный (sessi
337
358
 
338
359
  | Команда | Назначение |
339
360
  |---|---|
340
- | `ktalk sync [--days 7] [--json] [--dry-run]` | Загрузить записи из KTalk, upsert новых (`new`), экспирировать `new` старше N дней → `skipped`, показать дашборд. Идемпотентно. `--dry-run` — сверить id с реестром без записи, ничего не пишет. |
361
+ | `ktalk sync [--days N] [--json] [--dry-run]` | Загрузить записи из KTalk и upsert'нуть их в реестр (новые — `new`, существующие — с обновлёнными не-статусными полями; статус ни одной записи не меняется). Без `--days` окно — от момента последней синхронизации минус 1 день запаса (первый запуск — 90 дней); `--days N` — явное переопределение нижней границы. Идемпотентно. `--dry-run` — сверить id с реестром без записи, ничего не пишет. В `--json`-ответе ключа `expired` больше нет (4.0.0): смены статуса как побочного эффекта чтения не происходит. |
341
362
  | `ktalk token set <значение\|->` | Записать session-токен в `~/.config/ktalk-mcp/token` (`0600`). `-` — прочитать из stdin: `pbpaste \| ktalk token set -`. Значение не печатается. |
342
363
  | `ktalk token status [--json]` | Есть ли файл токена, его права и маска значения. |
343
364
  | `ktalk auth-status [--json]` | Диагностика активной авторизации — жив ли токен. См. «Диагностика авторизации». |
365
+ | `ktalk config set base-url <url>` | Записать адрес стенда в `~/.config/ktalk-mcp/config.toml` (`0644`). См. «Конфигурация: адрес стенда». |
366
+ | `ktalk config show [--json]` | Машинная конфигурация оператора (адрес, путь `config.toml`, статус файла токена) и резолвленный `.ktalk.toml` проекта-хозяина. |
344
367
  | `ktalk dashboard [--json]` | Дашборд: новые записи, статистика по статусам. |
345
368
  | `ktalk list [--status S] [--json]` | Список записей с фильтром по статусу. |
346
369
  | `ktalk show <id> [--json]` | Детали записи: участники, статус, пути, длительность. |
@@ -368,8 +391,10 @@ uv run pytest -v
368
391
  # Линтинг
369
392
  uv run ruff check .
370
393
 
371
- # Локальный запуск CLI (session token — см. «Авторизация»)
372
- KTALK_SESSION_TOKEN=... KTALK_BASE_URL=... uv run ktalk auth-status
394
+ # Локальный запуск CLI (токен и адрес — файлами, см. «Авторизация»)
395
+ pbpaste | uv run ktalk token set -
396
+ uv run ktalk config set base-url https://your-domain.ktalk.ru
397
+ uv run ktalk auth-status
373
398
  ```
374
399
 
375
400
  ## Пакет и плагин Claude Code
@@ -67,6 +67,14 @@ uv tool upgrade ktalk-cli
67
67
 
68
68
  ## Авторизация
69
69
 
70
+ **С версии 4.0.0 конфигурация каждого значения имеет ровно один источник**
71
+ (ADR-027): session token — только файл, записанный `ktalk token set`; адрес
72
+ стенда — только `config.toml`, записанный `ktalk config set base-url`. Ни одна
73
+ переменная `KTALK_*` и ни один `.env` в рабочем каталоге больше не читаются как
74
+ источник значения — их присутствие не проходит молча: `ktalk auth-status`,
75
+ `ktalk doctor` и тексты отказов называют обнаруженную снятую переменную по
76
+ имени (значение не печатается никогда) и советуют её убрать.
77
+
70
78
  **С версии 3.0.0 CLI работает только через session token** (кука браузера) — режим
71
79
  персонального API-ключа (`KTALK_PERSONAL_API_KEY`) снят целиком (ADR-025): он
72
80
  конкурировал с сессией молча (при обеих заданных переменных побеждал ключ без
@@ -78,9 +86,11 @@ uv tool upgrade ktalk-cli
78
86
  ручного обновления токена по мере его протухания (`ktalk token set -`, см. ниже) —
79
87
  это не восстанавливается автоматически снятием ключа.
80
88
 
81
- Переменная `KTALK_PERSONAL_API_KEY`, если она всё ещё задана в окружении, не
82
- читается как credential ни на одном шаге — CLI печатает об этом одно предупреждение
83
- на stderr при каждом вызове и продолжает работу на сессионном токене.
89
+ Переменные `KTALK_PERSONAL_API_KEY`, `KTALK_SESSION_TOKEN` и остальные
90
+ `KTALK_*`, если они всё ещё заданы в окружении (частая причина — блок `env` в
91
+ `~/.claude/settings.json`), не читаются ни на одном шаге — CLI печатает об этом
92
+ одно предупреждение на stderr при каждом вызове и продолжает работу на файле
93
+ токена/`config.toml`.
84
94
 
85
95
  ### Session token
86
96
 
@@ -111,14 +121,20 @@ ktalk token status # есть ли файл, права, маска знач
111
121
  ktalk auth-status # жива ли авторизация — реальный запрос, не имитация
112
122
  ```
113
123
 
114
- Путь переопределяется переменной `KTALK_TOKEN_FILE`; каталог уважает `XDG_CONFIG_HOME`.
124
+ Путь файла — `${XDG_CONFIG_HOME:-~/.config}/ktalk-mcp/token`; переменной-
125
+ переопределения нет (снятая `KTALK_TOKEN_FILE` не читается, ADR-027).
115
126
 
116
- **Порядок источников** — первый непустой выигрывает:
127
+ **Единственный источник токена** — этот файл (ADR-027). Переменная
128
+ `KTALK_SESSION_TOKEN` и `.env` в рабочем каталоге не читаются вовсе: заданная
129
+ переменная больше не перекрывает файл ни молча, ни с предупреждением — она
130
+ просто не участвует в разрешении credential, а CLI называет её в выводе
131
+ `auth-status`/`doctor` как снятую.
117
132
 
118
- | # | Источник | Комментарий |
119
- |---|---|---|
120
- | 1 | `KTALK_SESSION_TOKEN` (окружение или `.env` в рабочей директории) | заданное явно сильнее лежащего на диске |
121
- | 2 | `~/.config/ktalk-mcp/token` | дефолтный путь для повседневной работы |
133
+ > Ротация больше не может «застрять» на невидимой переменной: `ktalk token set -`
134
+ > перезаписывает файл, и следующий же вызов читает новое значение. Разводка
135
+ > источников из старых установок (переменная против файла, рунбук
136
+ > [OPS-003](content/70-operations/OPS-003-env-var-shadows-token-file.md)) снята
137
+ > самим устройством 4.0.0 — переменную достаточно убрать из окружения.
122
138
 
123
139
  Ни один запрос не несёт заголовок `X-Auth-Token` — единственный транспорт credential
124
140
  теперь query-параметр `sessionToken`.
@@ -142,22 +158,25 @@ ktalk auth-status # жива ли авторизация — реальны
142
158
  > ошибку авторизации, повторите те же два шага — `ktalk token set -` перезаписывает
143
159
  > файл, права переставлять не нужно.
144
160
 
145
- ### Переменные окружения
161
+ ### Конфигурация: адрес стенда (`config.toml`)
162
+
163
+ Адрес контура задаётся один раз, машинной командой:
146
164
 
147
165
  ```bash
148
- export KTALK_SESSION_TOKEN="ваш_session_token"
149
- export KTALK_BASE_URL="https://your-domain.ktalk.ru"
166
+ ktalk config set base-url https://your-domain.ktalk.ru
150
167
  ```
151
168
 
152
- Переменная не обязательна: без неё читается файл `~/.config/ktalk-mcp/token`
153
- (см. «Session token»).
169
+ Команда пишет `${XDG_CONFIG_HOME:-~/.config}/ktalk-mcp/config.toml` (рядом с
170
+ файлом токена, права `0644` — адрес не секрет) и отвергает значение без схемы
171
+ http(s) или без хоста до записи. Проверка — `ktalk config show`: секция
172
+ `## Толк` называет адрес, путь `config.toml` и статус файла токена.
154
173
 
155
- Также поддерживается файл `.env` в рабочей директории:
156
-
157
- ```env
158
- KTALK_SESSION_TOKEN=ваш_session_token
159
- KTALK_BASE_URL=https://your-domain.ktalk.ru
160
- ```
174
+ **Переменные окружения и `.env` больше не читаются вовсе (4.0.0, ADR-027).**
175
+ `KTALK_BASE_URL`, `KTALK_SESSION_TOKEN`, `KTALK_PERSONAL_API_KEY`,
176
+ `KTALK_REGISTRY_DB`, `KTALK_TOKEN_FILE` и `.env` в рабочем каталоге не
177
+ участвуют в разрешении ни одного значения. Если какая-то из них всё ещё задана,
178
+ `ktalk auth-status` и `ktalk doctor` называют её по имени и советуют убрать —
179
+ значение при этом не печатается никогда.
161
180
 
162
181
  ## Диагностика авторизации
163
182
 
@@ -170,10 +189,12 @@ ktalk auth-status
170
189
  Диагностика различает два случая, которые снаружи выглядят одинаково — просто ошибка, —
171
190
  но чинятся по-разному:
172
191
 
173
- - **401** — токен невалиден либо истёк. Обновите его: `ktalk token set -` (или
174
- переменную `KTALK_SESSION_TOKEN`, если она задана).
175
- - **403** — токен рабочий, но у текущей сессии нет прав на эту операцию.
176
- Перевыпускать токен не нужно.
192
+ - **401** — токен невалиден либо истёк. Вердикт `alive: false`, код возврата `1`.
193
+ Обновите токен: `ktalk token set -` — файл перезаписывается, права переставлять
194
+ не нужно.
195
+ - **403** — токен рабочий, но у текущей сессии нет прав на эту операцию. Вердикт
196
+ `alive: true`, код возврата `0`: нехватка прав не является отказом токена, и
197
+ перевыпускать его не нужно.
177
198
 
178
199
  У session token понятия scope и срока действия нет — диагностика выполняет реальный
179
200
  пробный запрос (список записей), а не имитацию без сети.
@@ -297,15 +318,15 @@ CLI работает с KTalk Web API через единственный (sessi
297
318
 
298
319
  Та же команда `ktalk` ведёт операционный реестр обработки записей на SQLite.
299
320
  Вся детерминированная механика (синхронизация списка записей, дедуп,
300
- экспирация, смена статусов, рендер дашборда и markdown-зеркала, разовая
321
+ смена статусов, рендер дашборда и markdown-зеркала, разовая
301
322
  миграция) живёт в коде, а не в рассуждениях модели.
302
323
 
303
324
  **SQLite — операционный source of truth.** Markdown-файл `registry.md` —
304
325
  генерируемое read-only зеркало для git (`ktalk export`), руками не редактируется.
305
326
 
306
- Путь к базе: флаг `--db PATH` > переменная `KTALK_REGISTRY_DB` > дефолт
307
- `95_TRANSCRIPTS/.registry.db` (относительно текущего каталога). Бинарную БД
308
- нужно добавить в `.gitignore` (`.registry.db`, `.registry.db-wal`, `.registry.db-shm`).
327
+ Путь к базе: флаг `--db PATH` > `registry.db_path` из `.ktalk.toml`
328
+ проекта-хозяина > машинный дефолт централизованного хранилища (ADR-013;
329
+ переменная `KTALK_REGISTRY_DB` снята в 4.0.0 и не читается, ADR-027).
309
330
 
310
331
  `ktalk auth-status`, `ktalk create-meeting-preview` и `ktalk create-meeting-confirm`
311
332
  реестр не открывают вовсе — им он не нужен. В частности, `auth-status` работает
@@ -314,10 +335,12 @@ CLI работает с KTalk Web API через единственный (sessi
314
335
 
315
336
  | Команда | Назначение |
316
337
  |---|---|
317
- | `ktalk sync [--days 7] [--json] [--dry-run]` | Загрузить записи из KTalk, upsert новых (`new`), экспирировать `new` старше N дней → `skipped`, показать дашборд. Идемпотентно. `--dry-run` — сверить id с реестром без записи, ничего не пишет. |
338
+ | `ktalk sync [--days N] [--json] [--dry-run]` | Загрузить записи из KTalk и upsert'нуть их в реестр (новые — `new`, существующие — с обновлёнными не-статусными полями; статус ни одной записи не меняется). Без `--days` окно — от момента последней синхронизации минус 1 день запаса (первый запуск — 90 дней); `--days N` — явное переопределение нижней границы. Идемпотентно. `--dry-run` — сверить id с реестром без записи, ничего не пишет. В `--json`-ответе ключа `expired` больше нет (4.0.0): смены статуса как побочного эффекта чтения не происходит. |
318
339
  | `ktalk token set <значение\|->` | Записать session-токен в `~/.config/ktalk-mcp/token` (`0600`). `-` — прочитать из stdin: `pbpaste \| ktalk token set -`. Значение не печатается. |
319
340
  | `ktalk token status [--json]` | Есть ли файл токена, его права и маска значения. |
320
341
  | `ktalk auth-status [--json]` | Диагностика активной авторизации — жив ли токен. См. «Диагностика авторизации». |
342
+ | `ktalk config set base-url <url>` | Записать адрес стенда в `~/.config/ktalk-mcp/config.toml` (`0644`). См. «Конфигурация: адрес стенда». |
343
+ | `ktalk config show [--json]` | Машинная конфигурация оператора (адрес, путь `config.toml`, статус файла токена) и резолвленный `.ktalk.toml` проекта-хозяина. |
321
344
  | `ktalk dashboard [--json]` | Дашборд: новые записи, статистика по статусам. |
322
345
  | `ktalk list [--status S] [--json]` | Список записей с фильтром по статусу. |
323
346
  | `ktalk show <id> [--json]` | Детали записи: участники, статус, пути, длительность. |
@@ -345,8 +368,10 @@ uv run pytest -v
345
368
  # Линтинг
346
369
  uv run ruff check .
347
370
 
348
- # Локальный запуск CLI (session token — см. «Авторизация»)
349
- KTALK_SESSION_TOKEN=... KTALK_BASE_URL=... uv run ktalk auth-status
371
+ # Локальный запуск CLI (токен и адрес — файлами, см. «Авторизация»)
372
+ pbpaste | uv run ktalk token set -
373
+ uv run ktalk config set base-url https://your-domain.ktalk.ru
374
+ uv run ktalk auth-status
350
375
  ```
351
376
 
352
377
  ## Пакет и плагин Claude Code
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "ktalk-cli"
3
- version = "3.0.2"
3
+ version = "4.0.0"
4
4
  description = "CLI for accessing Kontur Talk (KTalk) recordings, transcripts and summaries"
5
5
  readme = "README.md"
6
6
  license = "MIT"
@@ -0,0 +1,39 @@
1
+ # ktalk-cli — API Контур.Толк
2
+
3
+ Справочник вынесен из корневого `CLAUDE.md`: грузится только при работе под `src/`.
4
+
5
+ ## Контракт API
6
+ - OpenAPI спецификация (справочник, **есть расхождения с реальностью**): `talk.public.api-api-2.json`
7
+ - Base URL: https://your-domain.ktalk.ru
8
+ - **Режим авторизации один — сессионный токен** ([ADR-025](content/00-project/adr/ADR-025-single-auth-mode.md)
9
+ снял двухрежимность [ADR-003](content/00-project/adr/ADR-003-auth-modes.md) в 3.0.0):
10
+ `sessionToken=` в query при чтении, заголовок `Authorization: Session <token>` при записи
11
+ (`meeting_scheduling.py`). `X-Auth-Token` не шлётся ни одним запросом. `KTALK_PERSONAL_API_KEY`
12
+ не читается ни на одном шаге: заданная, она даёт однократное предупреждение в stderr
13
+ (`config.py:181-187`) и больше ничего.
14
+ - **Порядок источников токена, он же ловушка эксплуатации** — первый непустой выигрывает:
15
+ `KTALK_SESSION_TOKEN` (окружение или `.env` рабочего каталога) > `~/.config/ktalk-mcp/token`
16
+ (`token_file.py`, DEV-018). Файл читается валидатором `Settings` **только когда переменная
17
+ пуста** (`config.py:111-130`) — поэтому переменная переживает `ktalk token set` и делает
18
+ ротацию токена невидимой: `auth-status` печатает `alive: false` на свежезаписанный рабочий
19
+ файл. Разведение — `env -u KTALK_SESSION_TOKEN ktalk auth-status` против обычного вызова;
20
+ процедура — [OPS-003](content/70-operations/OPS-003-env-var-shadows-token-file.md). Права
21
+ шире `0600` — файла как будто нет. Запись — `ktalk token set -`, формат проверяется до записи.
22
+ - **Путь есть не у каждой операции** — интеграторский контур (`/api/Domain/*`, `/api/Recordings/*`,
23
+ `/api/ConferenceReports/*`) отдаёт 401/403 по сессии, поэтому пути живут в таблице
24
+ `OPERATION_PROFILES` (`endpoints.py`, реэкспорт через `auth.py`), а не хардкодом в методах.
25
+ Профиль `None` (`list_archive`) — отказ до сети, код возврата 1.
26
+ - Пути под сессией: `GET /api/recordings`, `/api/recordings/{id}`, `/api/conferencesHistory/{key}`,
27
+ `/api/recordings/{key}/transcript`, `/api/recordings/v2/{key}/summary`,
28
+ `/api/recordings/{key}/summary/{type}`
29
+
30
+ ### Поведение API, проверенное эмпирически (спеке здесь верить нельзя)
31
+ - `top` максимум **100**, не 1000: `400 «The field Top must be between 1 and 100»`.
32
+ - `nextPageToken` во внутреннем контуре **не существует** — пагинация только через `skip`.
33
+ - `startFrom`/`startTo` **игнорируются**: окно дат обеспечивает клиент (`clip_to_window`),
34
+ обход прекращается на первой странице за порогом. Выдача отсортирована от новых к старым.
35
+ - `maxParticipantCount` в списке имеет максимум 10 и дефолт 6 — полный состав участников
36
+ берётся дообогащением по каждой записи, а не из списка.
37
+ - Чат требует необъявленный в спеке параметр `channel` (рабочее значение `general`).
38
+ - **401 ≠ 403**: 401 — ключ/токен невалиден, 403 — валиден, но не хватает scope. Тело 403
39
+ обычно пустое, диагностика строится на коде ответа и требуемом scope операции.
@@ -69,8 +69,14 @@ def classify_response(response: object) -> None:
69
69
  `contour_diagnostics._status_hint`."""
70
70
  status = response.status_code # type: ignore[attr-defined]
71
71
  if status == 401:
72
+ from ktalk_cli.config import _with_legacy_notice
73
+
72
74
  raise _auth_error(
73
- "Токен сессии истёк или невалиден. Обновите его: `ktalk token set -` (или переменную KTALK_SESSION_TOKEN, если она задана) — см. README.", 401
75
+ _with_legacy_notice(
76
+ "Токен сессии истёк или невалиден. Обновите его: "
77
+ "`ktalk token set -` (см. README)."
78
+ ),
79
+ 401,
74
80
  )
75
81
  if status == 403:
76
82
  # У сессионного токена нет понятия scope, но 403 — это не 401. ADR-003
@@ -101,9 +107,15 @@ class AuthContext:
101
107
  def resolve(*, session_token: str | None) -> AuthContext:
102
108
  if session_token:
103
109
  return AuthContext(session_token)
110
+ # ADR-027: тот же текст отказа, что `Settings.auth_credential` (FR-E —
111
+ # только инструкция, переменная окружения больше не предлагается).
112
+ from ktalk_cli.config import _with_legacy_notice
113
+
104
114
  raise KTalkConfigError(
105
- "Не задана KTALK_SESSION_TOKEN, и файла токена нет. Задайте "
106
- "переменную или выполните `ktalk token set -` (см. README)."
115
+ _with_legacy_notice(
116
+ "Сессионный токен не задан: файла токена нет. Выполните "
117
+ "`ktalk token set -` (см. README)."
118
+ )
107
119
  )
108
120
 
109
121
 
@@ -10,6 +10,8 @@ from pathlib import Path
10
10
 
11
11
  from ktalk_cli.cli_contacts import cmd_search_contacts
12
12
  from ktalk_cli.cli_contacts import register_subparsers as register_contacts_subparsers
13
+ from ktalk_cli.cli_config import cmd_config
14
+ from ktalk_cli.cli_config import register_subparsers as register_config_subparsers
13
15
  from ktalk_cli.cli_content import (
14
16
  cmd_download_recording,
15
17
  cmd_get_participants,
@@ -45,8 +47,8 @@ from ktalk_cli.cli_token import register_subparsers as register_token_subparsers
45
47
  from ktalk_cli.cli_store import cmd_migrate_to_central_store
46
48
  from ktalk_cli.cli_store import register_subparsers as register_store_subparsers
47
49
  from ktalk_cli.cli_sync import cmd_auth_status, cmd_sync
48
- from ktalk_cli.config import redact_secrets, resolve_db_path, warn_if_legacy_key_present
49
- from ktalk_cli.host_config import HostConfig, discover_host_config
50
+ from ktalk_cli.config import legacy_config_notice, redact_secrets, resolve_db_path
51
+ from ktalk_cli.host_config import discover_host_config
50
52
  from ktalk_cli.registry import Registry, migrate_from_vault, render_markdown_mirror
51
53
 
52
54
  _STATUSES = ("new", "processing", "done", "skipped", "partial")
@@ -108,18 +110,20 @@ def build_parser() -> argparse.ArgumentParser:
108
110
  p_mig.add_argument("--json", action="store_true")
109
111
 
110
112
  p_sync = sub.add_parser("sync", help="Синхронизация с KTalk")
111
- p_sync.add_argument("--days", type=int, default=7)
113
+ p_sync.add_argument(
114
+ "--days",
115
+ type=int,
116
+ default=None,
117
+ help="Явное переопределение нижней границы окна (N дней назад от сегодня); "
118
+ "без флага — от момента последней синхронизации (ADR-027)",
119
+ )
112
120
  p_sync.add_argument("--json", action="store_true")
113
121
  p_sync.add_argument("--dry-run", action="store_true", help="Сверка id без записи (FR-15)")
114
122
 
115
123
  p_auth = sub.add_parser("auth-status", help="Диагностика авторизации (FR-11)")
116
124
  p_auth.add_argument("--json", action="store_true")
117
125
 
118
- p_config = sub.add_parser("config", help="Конфигурация проекта-хозяина (.ktalk.toml)")
119
- config_sub = p_config.add_subparsers(dest="config_command")
120
- p_config_show = config_sub.add_parser("show", help="Показать резолвленный .ktalk.toml")
121
- p_config_show.add_argument("--json", action="store_true")
122
-
126
+ register_config_subparsers(sub)
123
127
  register_meeting_subparsers(sub)
124
128
  register_meeting_confirm_subparsers(sub)
125
129
  register_sanction_subparsers(sub)
@@ -272,45 +276,6 @@ def _cmd_migrate(reg: Registry, args) -> int:
272
276
  return 0
273
277
 
274
278
 
275
- def _host_config_to_dict(host_config: HostConfig | None) -> dict:
276
- if host_config is None:
277
- return {"registry": {}, "directories": {}, "routing": {}, "integrations": {}}
278
- return {
279
- "registry": host_config.registry,
280
- "directories": host_config.directories,
281
- "routing": host_config.routing,
282
- "integrations": host_config.integrations,
283
- # FR-48: `doctor` и `config show` называют один и тот же путь одним
284
- # значением (ADR-026 spec §2) — ключ появляется только когда конфиг
285
- # найден, отсутствие файла не путь, а нормальная ветка.
286
- "path": str(host_config.path),
287
- }
288
-
289
-
290
- def _cmd_config(reg: Registry | None, args) -> int:
291
- if getattr(args, "config_command", None) != "show":
292
- print("Неизвестная подкоманда config (доступно: show)", file=sys.stderr)
293
- return 2
294
- host_config = discover_host_config()
295
- data = _host_config_to_dict(host_config)
296
- if args.json:
297
- _print_json(data)
298
- return 0
299
- print("# Конфигурация проекта-хозяина (.ktalk.toml)\n")
300
- if host_config is None:
301
- print("(конфиг не найден — машинные дефолты)")
302
- return 0
303
- for section_name in ("registry", "directories", "routing", "integrations"):
304
- section = data[section_name]
305
- print(f"## {section_name}")
306
- if not section:
307
- print("(не объявлено)")
308
- else:
309
- for key, value in section.items():
310
- print(f"- {key} = {value}")
311
- return 0
312
-
313
-
314
279
  _REGISTRY_FREE_COMMANDS = {
315
280
  "auth-status",
316
281
  "config",
@@ -361,7 +326,7 @@ _HANDLERS = {
361
326
  "migrate": _cmd_migrate,
362
327
  "sync": cmd_sync,
363
328
  "auth-status": cmd_auth_status,
364
- "config": _cmd_config,
329
+ "config": cmd_config,
365
330
  "create-meeting-preview": cmd_create_meeting_preview,
366
331
  "create-meeting-confirm": cmd_create_meeting_confirm,
367
332
  "cancel-meeting-preview": cmd_cancel_meeting_preview,
@@ -386,9 +351,13 @@ _HANDLERS = {
386
351
 
387
352
 
388
353
  def main(argv: list[str] | None = None) -> int:
389
- # FR-43: обнаружение снятой KTALK_PERSONAL_API_KEY не проходит молча — ровно
390
- # одна строка на процесс, до диспетчеризации любой команды.
391
- warn_if_legacy_key_present()
354
+ # FR-D (ADR-027 п.3, обобщение FR-43 с одной переменной на пять): обнаружение
355
+ # снятых переменных конфигурации не проходит молча — ровно одна строка на
356
+ # процесс, до диспетчеризации любой команды, через барьер маскирования;
357
+ # уведомление информационно, команду не блокирует.
358
+ notice = legacy_config_notice()
359
+ if notice is not None:
360
+ print(redact_secrets(notice), file=sys.stderr)
392
361
  parser = build_parser()
393
362
  args = parser.parse_args(argv)
394
363
  if getattr(args, "version", False):
@@ -431,15 +400,15 @@ def main(argv: list[str] | None = None) -> int:
431
400
  # дефолту, пока сам реестр читался из `.ktalk.toml`.
432
401
  args.db = str(db_path)
433
402
  # Code review (epic-capability-pairing, Р5): признак «путь резолвился к
434
- # машинному дефолту централизованного хранилища», а не к --db/env/
403
+ # машинному дефолту централизованного хранилища», а не к --db/
435
404
  # конфигу хозяина — считается ДО перезаписи `args.db` выше, той же
436
- # логикой приоритета, что `resolve_db_path` (ADR-013 §3), без повторного
405
+ # логикой приоритета, что `resolve_db_path` (ADR-013 §3 в редакции
406
+ # ADR-027: среднего члена KTALK_REGISTRY_DB больше нет), без повторного
437
407
  # вызова `resolve_store_root()` (не создавать хранилище стороны ради
438
408
  # одной проверки). Использует только `_cmd_export`, чтобы не положить
439
409
  # зеркало проекта-хозяина внутрь хранилища (NFR-16 AC).
440
410
  args.db_from_machine_default = not (
441
411
  raw_cli_db
442
- or os.environ.get("KTALK_REGISTRY_DB")
443
412
  or (host_config is not None and host_config.registry.get("db_path"))
444
413
  )
445
414
  reg = Registry(db_path)