direct-cli 0.5.2__tar.gz → 0.5.3__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 (434) hide show
  1. {direct_cli-0.5.2 → direct_cli-0.5.3}/.github/workflows/quality.yml +3 -0
  2. {direct_cli-0.5.2 → direct_cli-0.5.3}/CHANGELOG.md +574 -3
  3. {direct_cli-0.5.2 → direct_cli-0.5.3}/PKG-INFO +98 -2
  4. {direct_cli-0.5.2 → direct_cli-0.5.3}/README.md +96 -1
  5. {direct_cli-0.5.2 → direct_cli-0.5.3}/direct_cli/auth.py +3 -10
  6. {direct_cli-0.5.2 → direct_cli-0.5.3}/direct_cli/browser/__init__.py +4 -2
  7. {direct_cli-0.5.2 → direct_cli-0.5.3}/direct_cli/browser/_chrome_crypto.py +35 -0
  8. direct_cli-0.5.3/direct_cli/browser/change_history.py +360 -0
  9. {direct_cli-0.5.2 → direct_cli-0.5.3}/direct_cli/browser/masters.py +2254 -314
  10. direct_cli-0.5.3/direct_cli/browser/session.py +1224 -0
  11. {direct_cli-0.5.2 → direct_cli-0.5.3}/direct_cli/cli.py +32 -15
  12. {direct_cli-0.5.2 → direct_cli-0.5.3}/direct_cli/commands/_lifecycle.py +1 -1
  13. {direct_cli-0.5.2 → direct_cli-0.5.3}/direct_cli/commands/auth.py +3 -6
  14. {direct_cli-0.5.2 → direct_cli-0.5.3}/direct_cli/commands/balance.py +3 -1
  15. {direct_cli-0.5.2 → direct_cli-0.5.3}/direct_cli/commands/bidmodifiers.py +35 -4
  16. {direct_cli-0.5.2 → direct_cli-0.5.3}/direct_cli/commands/browser_session.py +6 -1
  17. {direct_cli-0.5.2 → direct_cli-0.5.3}/direct_cli/commands/clients.py +20 -1
  18. {direct_cli-0.5.2 → direct_cli-0.5.3}/direct_cli/commands/dictionaries.py +57 -3
  19. direct_cli-0.5.3/direct_cli/commands/history.py +154 -0
  20. {direct_cli-0.5.2 → direct_cli-0.5.3}/direct_cli/commands/masters.py +1410 -53
  21. {direct_cli-0.5.2 → direct_cli-0.5.3}/direct_cli/commands/sitelinks.py +1 -0
  22. {direct_cli-0.5.2 → direct_cli-0.5.3}/direct_cli/commands/strategies.py +3 -1
  23. {direct_cli-0.5.2 → direct_cli-0.5.3}/direct_cli/commands/v4events.py +2 -2
  24. {direct_cli-0.5.2 → direct_cli-0.5.3}/direct_cli/commands/v4finance.py +5 -2
  25. {direct_cli-0.5.2 → direct_cli-0.5.3}/direct_cli/commands/v4wordstat.py +2 -2
  26. {direct_cli-0.5.2 → direct_cli-0.5.3}/direct_cli/i18n.py +28 -2
  27. {direct_cli-0.5.2 → direct_cli-0.5.3}/direct_cli/output.py +74 -1
  28. {direct_cli-0.5.2 → direct_cli-0.5.3}/direct_cli/smoke_matrix.py +15 -0
  29. {direct_cli-0.5.2 → direct_cli-0.5.3}/direct_cli/translations/bidmodifiers.json +3 -1
  30. {direct_cli-0.5.2 → direct_cli-0.5.3}/direct_cli/translations/clients.json +2 -1
  31. direct_cli-0.5.3/direct_cli/translations/dictionaries.json +14 -0
  32. {direct_cli-0.5.2 → direct_cli-0.5.3}/direct_cli/v4/parse.py +5 -4
  33. {direct_cli-0.5.2 → direct_cli-0.5.3}/direct_cli/wsdl_coverage.py +13 -1
  34. {direct_cli-0.5.2 → direct_cli-0.5.3}/direct_cli.egg-info/PKG-INFO +98 -2
  35. {direct_cli-0.5.2 → direct_cli-0.5.3}/direct_cli.egg-info/SOURCES.txt +7 -0
  36. {direct_cli-0.5.2 → direct_cli-0.5.3}/direct_cli.egg-info/requires.txt +1 -0
  37. {direct_cli-0.5.2 → direct_cli-0.5.3}/pyproject.toml +23 -1
  38. {direct_cli-0.5.2 → direct_cli-0.5.3}/scripts/audit_wire_shape.py +1 -1
  39. {direct_cli-0.5.2 → direct_cli-0.5.3}/scripts/build_api_coverage_checklist.py +3 -1
  40. {direct_cli-0.5.2 → direct_cli-0.5.3}/scripts/build_api_coverage_report.py +7 -12
  41. direct_cli-0.5.3/scripts/recon_812_video_upload.py +382 -0
  42. direct_cli-0.5.3/scripts/recon_871_bidmodifiers_enum.py +118 -0
  43. {direct_cli-0.5.2 → direct_cli-0.5.3}/scripts/sandbox_write_audit.py +2 -3
  44. {direct_cli-0.5.2 → direct_cli-0.5.3}/setup.cfg +1 -0
  45. {direct_cli-0.5.2 → direct_cli-0.5.3}/tests/WSDL_OPTIONAL_FIELD_AUDIT.md +2 -1
  46. direct_cli-0.5.3/tests/_cli_tree.py +19 -0
  47. {direct_cli-0.5.2 → direct_cli-0.5.3}/tests/api_coverage_payloads.py +6 -0
  48. {direct_cli-0.5.2 → direct_cli-0.5.3}/tests/conftest.py +7 -2
  49. direct_cli-0.5.3/tests/fixtures/user_action_log.json +85 -0
  50. {direct_cli-0.5.2 → direct_cli-0.5.3}/tests/test_adgroups_build_adgroup_object.py +54 -54
  51. {direct_cli-0.5.2 → direct_cli-0.5.3}/tests/test_adgroups_build_adgroup_update_object.py +14 -14
  52. {direct_cli-0.5.2 → direct_cli-0.5.3}/tests/test_ads_build_ad_object.py +46 -42
  53. {direct_cli-0.5.2 → direct_cli-0.5.3}/tests/test_ads_build_ad_update_object.py +24 -20
  54. {direct_cli-0.5.2 → direct_cli-0.5.3}/tests/test_api_coverage.py +10 -5
  55. {direct_cli-0.5.2 → direct_cli-0.5.3}/tests/test_audit_wire_shape.py +1 -1
  56. {direct_cli-0.5.2 → direct_cli-0.5.3}/tests/test_auth_oauth.py +2 -1
  57. {direct_cli-0.5.2 → direct_cli-0.5.3}/tests/test_balance.py +2 -1
  58. direct_cli-0.5.3/tests/test_change_history.py +653 -0
  59. {direct_cli-0.5.2 → direct_cli-0.5.3}/tests/test_chrome_crypto.py +41 -0
  60. {direct_cli-0.5.2 → direct_cli-0.5.3}/tests/test_cli.py +75 -0
  61. {direct_cli-0.5.2 → direct_cli-0.5.3}/tests/test_comprehensive.py +1 -0
  62. {direct_cli-0.5.2 → direct_cli-0.5.3}/tests/test_dry_run_bids.py +64 -0
  63. {direct_cli-0.5.2 → direct_cli-0.5.3}/tests/test_dry_run_clients.py +14 -0
  64. {direct_cli-0.5.2 → direct_cli-0.5.3}/tests/test_i18n.py +18 -2
  65. {direct_cli-0.5.2 → direct_cli-0.5.3}/tests/test_integration_write.py +2 -2
  66. {direct_cli-0.5.2 → direct_cli-0.5.3}/tests/test_low_coverage_payloads.py +57 -0
  67. {direct_cli-0.5.2 → direct_cli-0.5.3}/tests/test_masters.py +20764 -15541
  68. direct_cli-0.5.3/tests/test_playwright_session.py +706 -0
  69. {direct_cli-0.5.2 → direct_cli-0.5.3}/tests/test_reports_parsing.py +3 -1
  70. {direct_cli-0.5.2 → direct_cli-0.5.3}/tests/test_smoke_matrix.py +3 -2
  71. {direct_cli-0.5.2 → direct_cli-0.5.3}/tests/test_transport_contract.py +6 -2
  72. {direct_cli-0.5.2 → direct_cli-0.5.3}/tests/test_v4_contracts.py +6 -2
  73. {direct_cli-0.5.2 → direct_cli-0.5.3}/tests/test_v4_output_options.py +1 -0
  74. {direct_cli-0.5.2 → direct_cli-0.5.3}/tests/test_v4account.py +3 -2
  75. {direct_cli-0.5.2 → direct_cli-0.5.3}/tests/test_v4adimage.py +2 -1
  76. {direct_cli-0.5.2 → direct_cli-0.5.3}/tests/test_v4events.py +2 -1
  77. {direct_cli-0.5.2 → direct_cli-0.5.3}/tests/test_v4finance_money.py +2 -1
  78. {direct_cli-0.5.2 → direct_cli-0.5.3}/tests/test_v4finance_read.py +2 -1
  79. {direct_cli-0.5.2 → direct_cli-0.5.3}/tests/test_v4forecast.py +2 -1
  80. {direct_cli-0.5.2 → direct_cli-0.5.3}/tests/test_v4goals.py +2 -1
  81. {direct_cli-0.5.2 → direct_cli-0.5.3}/tests/test_v4keywords.py +2 -1
  82. {direct_cli-0.5.2 → direct_cli-0.5.3}/tests/test_v4tags.py +2 -1
  83. {direct_cli-0.5.2 → direct_cli-0.5.3}/tests/test_v4wordstat.py +2 -1
  84. {direct_cli-0.5.2 → direct_cli-0.5.3}/tests/test_v5_live_write.py +7 -10
  85. {direct_cli-0.5.2 → direct_cli-0.5.3}/tests/test_vendor_imports.py +2 -2
  86. {direct_cli-0.5.2 → direct_cli-0.5.3}/tests/test_wsdl_parity_gate.py +14 -2
  87. {direct_cli-0.5.2 → direct_cli-0.5.3}/tests/wsdl_cache/adimages.xml +4 -0
  88. {direct_cli-0.5.2 → direct_cli-0.5.3}/tests/wsdl_cache/campaigns.xml +6 -0
  89. {direct_cli-0.5.2 → direct_cli-0.5.3}/tests/wsdl_cache/imports/generalclients.xsd +2 -0
  90. {direct_cli-0.5.2 → direct_cli-0.5.3}/tests/wsdl_cache/strategies.xml +14 -0
  91. direct_cli-0.5.2/direct_cli/browser/session.py +0 -709
  92. direct_cli-0.5.2/direct_cli/translations/dictionaries.json +0 -10
  93. direct_cli-0.5.2/tests/test_playwright_session.py +0 -306
  94. {direct_cli-0.5.2 → direct_cli-0.5.3}/.env.example +0 -0
  95. {direct_cli-0.5.2 → direct_cli-0.5.3}/.github/copilot-instructions.md +0 -0
  96. {direct_cli-0.5.2 → direct_cli-0.5.3}/.github/workflows/api-coverage.yml +0 -0
  97. {direct_cli-0.5.2 → direct_cli-0.5.3}/.github/workflows/claude.yml +0 -0
  98. {direct_cli-0.5.2 → direct_cli-0.5.3}/.gitignore +0 -0
  99. {direct_cli-0.5.2 → direct_cli-0.5.3}/.mcp.json +0 -0
  100. {direct_cli-0.5.2 → direct_cli-0.5.3}/AGENTS.md +0 -0
  101. {direct_cli-0.5.2 → direct_cli-0.5.3}/CLAUDE.md +0 -0
  102. {direct_cli-0.5.2 → direct_cli-0.5.3}/MANIFEST.in +0 -0
  103. {direct_cli-0.5.2 → direct_cli-0.5.3}/README_ru.md +0 -0
  104. {direct_cli-0.5.2 → direct_cli-0.5.3}/direct_cli/__init__.py +0 -0
  105. {direct_cli-0.5.2 → direct_cli-0.5.3}/direct_cli/_autotargeting.py +0 -0
  106. {direct_cli-0.5.2 → direct_cli-0.5.3}/direct_cli/_bidding_strategy.py +0 -0
  107. {direct_cli-0.5.2 → direct_cli-0.5.3}/direct_cli/_captcha.py +0 -0
  108. {direct_cli-0.5.2 → direct_cli-0.5.3}/direct_cli/_deprecated.py +0 -0
  109. {direct_cli-0.5.2 → direct_cli-0.5.3}/direct_cli/_flag_validation.py +0 -0
  110. {direct_cli-0.5.2 → direct_cli-0.5.3}/direct_cli/_smoke_probes.py +0 -0
  111. {direct_cli-0.5.2 → direct_cli-0.5.3}/direct_cli/_vendor/__init__.py +0 -0
  112. {direct_cli-0.5.2 → direct_cli-0.5.3}/direct_cli/_vendor/tapi_yandex_direct/__init__.py +0 -0
  113. {direct_cli-0.5.2 → direct_cli-0.5.3}/direct_cli/_vendor/tapi_yandex_direct/endpoints.py +0 -0
  114. {direct_cli-0.5.2 → direct_cli-0.5.3}/direct_cli/_vendor/tapi_yandex_direct/exceptions.py +0 -0
  115. {direct_cli-0.5.2 → direct_cli-0.5.3}/direct_cli/_vendor/tapi_yandex_direct/resource_mapping.py +0 -0
  116. {direct_cli-0.5.2 → direct_cli-0.5.3}/direct_cli/_vendor/tapi_yandex_direct/tapi_yandex_direct.py +0 -0
  117. {direct_cli-0.5.2 → direct_cli-0.5.3}/direct_cli/_vendor/tapi_yandex_direct/tapi_yandex_direct.pyi +0 -0
  118. {direct_cli-0.5.2 → direct_cli-0.5.3}/direct_cli/_vendor/tapi_yandex_direct/v4/__init__.py +0 -0
  119. {direct_cli-0.5.2 → direct_cli-0.5.3}/direct_cli/_vendor/tapi_yandex_direct/v4/adapter.py +0 -0
  120. {direct_cli-0.5.2 → direct_cli-0.5.3}/direct_cli/_vendor/tapi_yandex_direct/v4/adapter.pyi +0 -0
  121. {direct_cli-0.5.2 → direct_cli-0.5.3}/direct_cli/_vendor/tapi_yandex_direct/v4/resource_mapping.py +0 -0
  122. {direct_cli-0.5.2 → direct_cli-0.5.3}/direct_cli/api.py +0 -0
  123. {direct_cli-0.5.2 → direct_cli-0.5.3}/direct_cli/browser/_clock.py +0 -0
  124. {direct_cli-0.5.2 → direct_cli-0.5.3}/direct_cli/browser/diagnostics.py +0 -0
  125. {direct_cli-0.5.2 → direct_cli-0.5.3}/direct_cli/browser/store.py +0 -0
  126. {direct_cli-0.5.2 → direct_cli-0.5.3}/direct_cli/commands/__init__.py +0 -0
  127. {direct_cli-0.5.2 → direct_cli-0.5.3}/direct_cli/commands/_batch.py +0 -0
  128. {direct_cli-0.5.2 → direct_cli-0.5.3}/direct_cli/commands/_campaigns_base.py +0 -0
  129. {direct_cli-0.5.2 → direct_cli-0.5.3}/direct_cli/commands/_campaigns_cpm_banner.py +0 -0
  130. {direct_cli-0.5.2 → direct_cli-0.5.3}/direct_cli/commands/_campaigns_dynamic.py +0 -0
  131. {direct_cli-0.5.2 → direct_cli-0.5.3}/direct_cli/commands/_campaigns_mobile_app.py +0 -0
  132. {direct_cli-0.5.2 → direct_cli-0.5.3}/direct_cli/commands/_campaigns_smart.py +0 -0
  133. {direct_cli-0.5.2 → direct_cli-0.5.3}/direct_cli/commands/_campaigns_text.py +0 -0
  134. {direct_cli-0.5.2 → direct_cli-0.5.3}/direct_cli/commands/_campaigns_unified.py +0 -0
  135. {direct_cli-0.5.2 → direct_cli-0.5.3}/direct_cli/commands/_execute.py +0 -0
  136. {direct_cli-0.5.2 → direct_cli-0.5.3}/direct_cli/commands/_get.py +0 -0
  137. {direct_cli-0.5.2 → direct_cli-0.5.3}/direct_cli/commands/_set_bids.py +0 -0
  138. {direct_cli-0.5.2 → direct_cli-0.5.3}/direct_cli/commands/adextensions.py +0 -0
  139. {direct_cli-0.5.2 → direct_cli-0.5.3}/direct_cli/commands/adgroups.py +0 -0
  140. {direct_cli-0.5.2 → direct_cli-0.5.3}/direct_cli/commands/adimages.py +0 -0
  141. {direct_cli-0.5.2 → direct_cli-0.5.3}/direct_cli/commands/ads/__init__.py +0 -0
  142. {direct_cli-0.5.2 → direct_cli-0.5.3}/direct_cli/commands/ads/_cli.py +0 -0
  143. {direct_cli-0.5.2 → direct_cli-0.5.3}/direct_cli/commands/ads/base.py +0 -0
  144. {direct_cli-0.5.2 → direct_cli-0.5.3}/direct_cli/commands/ads/batch.py +0 -0
  145. {direct_cli-0.5.2 → direct_cli-0.5.3}/direct_cli/commands/ads/builder.py +0 -0
  146. {direct_cli-0.5.2 → direct_cli-0.5.3}/direct_cli/commands/ads/mobile_app.py +0 -0
  147. {direct_cli-0.5.2 → direct_cli-0.5.3}/direct_cli/commands/ads/objects.py +0 -0
  148. {direct_cli-0.5.2 → direct_cli-0.5.3}/direct_cli/commands/ads/responsive.py +0 -0
  149. {direct_cli-0.5.2 → direct_cli-0.5.3}/direct_cli/commands/ads/shopping.py +0 -0
  150. {direct_cli-0.5.2 → direct_cli-0.5.3}/direct_cli/commands/ads/text.py +0 -0
  151. {direct_cli-0.5.2 → direct_cli-0.5.3}/direct_cli/commands/advideos.py +0 -0
  152. {direct_cli-0.5.2 → direct_cli-0.5.3}/direct_cli/commands/agencyclients.py +0 -0
  153. {direct_cli-0.5.2 → direct_cli-0.5.3}/direct_cli/commands/audiencetargets.py +0 -0
  154. {direct_cli-0.5.2 → direct_cli-0.5.3}/direct_cli/commands/bids.py +0 -0
  155. {direct_cli-0.5.2 → direct_cli-0.5.3}/direct_cli/commands/businesses.py +0 -0
  156. {direct_cli-0.5.2 → direct_cli-0.5.3}/direct_cli/commands/campaigns.py +0 -0
  157. {direct_cli-0.5.2 → direct_cli-0.5.3}/direct_cli/commands/changes.py +0 -0
  158. {direct_cli-0.5.2 → direct_cli-0.5.3}/direct_cli/commands/creatives.py +0 -0
  159. {direct_cli-0.5.2 → direct_cli-0.5.3}/direct_cli/commands/dynamicads.py +0 -0
  160. {direct_cli-0.5.2 → direct_cli-0.5.3}/direct_cli/commands/dynamicfeedadtargets.py +0 -0
  161. {direct_cli-0.5.2 → direct_cli-0.5.3}/direct_cli/commands/feeds.py +0 -0
  162. {direct_cli-0.5.2 → direct_cli-0.5.3}/direct_cli/commands/keywordbids.py +0 -0
  163. {direct_cli-0.5.2 → direct_cli-0.5.3}/direct_cli/commands/keywords.py +0 -0
  164. {direct_cli-0.5.2 → direct_cli-0.5.3}/direct_cli/commands/keywordsresearch.py +0 -0
  165. {direct_cli-0.5.2 → direct_cli-0.5.3}/direct_cli/commands/leads.py +0 -0
  166. {direct_cli-0.5.2 → direct_cli-0.5.3}/direct_cli/commands/negativekeywordsharedsets.py +0 -0
  167. {direct_cli-0.5.2 → direct_cli-0.5.3}/direct_cli/commands/reports.py +0 -0
  168. {direct_cli-0.5.2 → direct_cli-0.5.3}/direct_cli/commands/retargeting.py +0 -0
  169. {direct_cli-0.5.2 → direct_cli-0.5.3}/direct_cli/commands/smartadtargets.py +0 -0
  170. {direct_cli-0.5.2 → direct_cli-0.5.3}/direct_cli/commands/trackingparams.py +0 -0
  171. {direct_cli-0.5.2 → direct_cli-0.5.3}/direct_cli/commands/turbopages.py +0 -0
  172. {direct_cli-0.5.2 → direct_cli-0.5.3}/direct_cli/commands/v4account.py +0 -0
  173. {direct_cli-0.5.2 → direct_cli-0.5.3}/direct_cli/commands/v4adimage.py +0 -0
  174. {direct_cli-0.5.2 → direct_cli-0.5.3}/direct_cli/commands/v4forecast.py +0 -0
  175. {direct_cli-0.5.2 → direct_cli-0.5.3}/direct_cli/commands/v4goals.py +0 -0
  176. {direct_cli-0.5.2 → direct_cli-0.5.3}/direct_cli/commands/v4keywords.py +0 -0
  177. {direct_cli-0.5.2 → direct_cli-0.5.3}/direct_cli/commands/v4shells.py +0 -0
  178. {direct_cli-0.5.2 → direct_cli-0.5.3}/direct_cli/commands/v4tags.py +0 -0
  179. {direct_cli-0.5.2 → direct_cli-0.5.3}/direct_cli/commands/vcards.py +0 -0
  180. {direct_cli-0.5.2 → direct_cli-0.5.3}/direct_cli/reports_coverage.py +0 -0
  181. {direct_cli-0.5.2 → direct_cli-0.5.3}/direct_cli/tracking_params.py +0 -0
  182. {direct_cli-0.5.2 → direct_cli-0.5.3}/direct_cli/translations/adextensions.json +0 -0
  183. {direct_cli-0.5.2 → direct_cli-0.5.3}/direct_cli/translations/adgroups.json +0 -0
  184. {direct_cli-0.5.2 → direct_cli-0.5.3}/direct_cli/translations/adimages.json +0 -0
  185. {direct_cli-0.5.2 → direct_cli-0.5.3}/direct_cli/translations/ads.json +0 -0
  186. {direct_cli-0.5.2 → direct_cli-0.5.3}/direct_cli/translations/advideos.json +0 -0
  187. {direct_cli-0.5.2 → direct_cli-0.5.3}/direct_cli/translations/agencyclients.json +0 -0
  188. {direct_cli-0.5.2 → direct_cli-0.5.3}/direct_cli/translations/audiencetargets.json +0 -0
  189. {direct_cli-0.5.2 → direct_cli-0.5.3}/direct_cli/translations/auth.json +0 -0
  190. {direct_cli-0.5.2 → direct_cli-0.5.3}/direct_cli/translations/balance.json +0 -0
  191. {direct_cli-0.5.2 → direct_cli-0.5.3}/direct_cli/translations/bids.json +0 -0
  192. {direct_cli-0.5.2 → direct_cli-0.5.3}/direct_cli/translations/businesses.json +0 -0
  193. {direct_cli-0.5.2 → direct_cli-0.5.3}/direct_cli/translations/campaigns.json +0 -0
  194. {direct_cli-0.5.2 → direct_cli-0.5.3}/direct_cli/translations/changes.json +0 -0
  195. {direct_cli-0.5.2 → direct_cli-0.5.3}/direct_cli/translations/common.json +0 -0
  196. {direct_cli-0.5.2 → direct_cli-0.5.3}/direct_cli/translations/creatives.json +0 -0
  197. {direct_cli-0.5.2 → direct_cli-0.5.3}/direct_cli/translations/dynamicads.json +0 -0
  198. {direct_cli-0.5.2 → direct_cli-0.5.3}/direct_cli/translations/dynamicfeedadtargets.json +0 -0
  199. {direct_cli-0.5.2 → direct_cli-0.5.3}/direct_cli/translations/feeds.json +0 -0
  200. {direct_cli-0.5.2 → direct_cli-0.5.3}/direct_cli/translations/keywordbids.json +0 -0
  201. {direct_cli-0.5.2 → direct_cli-0.5.3}/direct_cli/translations/keywords.json +0 -0
  202. {direct_cli-0.5.2 → direct_cli-0.5.3}/direct_cli/translations/keywordsresearch.json +0 -0
  203. {direct_cli-0.5.2 → direct_cli-0.5.3}/direct_cli/translations/leads.json +0 -0
  204. {direct_cli-0.5.2 → direct_cli-0.5.3}/direct_cli/translations/negativekeywordsharedsets.json +0 -0
  205. {direct_cli-0.5.2 → direct_cli-0.5.3}/direct_cli/translations/reports.json +0 -0
  206. {direct_cli-0.5.2 → direct_cli-0.5.3}/direct_cli/translations/retargeting.json +0 -0
  207. {direct_cli-0.5.2 → direct_cli-0.5.3}/direct_cli/translations/sitelinks.json +0 -0
  208. {direct_cli-0.5.2 → direct_cli-0.5.3}/direct_cli/translations/smartadtargets.json +0 -0
  209. {direct_cli-0.5.2 → direct_cli-0.5.3}/direct_cli/translations/strategies.json +0 -0
  210. {direct_cli-0.5.2 → direct_cli-0.5.3}/direct_cli/translations/turbopages.json +0 -0
  211. {direct_cli-0.5.2 → direct_cli-0.5.3}/direct_cli/translations/v4account.json +0 -0
  212. {direct_cli-0.5.2 → direct_cli-0.5.3}/direct_cli/translations/v4adimage.json +0 -0
  213. {direct_cli-0.5.2 → direct_cli-0.5.3}/direct_cli/translations/v4events.json +0 -0
  214. {direct_cli-0.5.2 → direct_cli-0.5.3}/direct_cli/translations/v4finance.json +0 -0
  215. {direct_cli-0.5.2 → direct_cli-0.5.3}/direct_cli/translations/v4forecast.json +0 -0
  216. {direct_cli-0.5.2 → direct_cli-0.5.3}/direct_cli/translations/v4goals.json +0 -0
  217. {direct_cli-0.5.2 → direct_cli-0.5.3}/direct_cli/translations/v4keywords.json +0 -0
  218. {direct_cli-0.5.2 → direct_cli-0.5.3}/direct_cli/translations/v4shells.json +0 -0
  219. {direct_cli-0.5.2 → direct_cli-0.5.3}/direct_cli/translations/v4tags.json +0 -0
  220. {direct_cli-0.5.2 → direct_cli-0.5.3}/direct_cli/translations/v4wordstat.json +0 -0
  221. {direct_cli-0.5.2 → direct_cli-0.5.3}/direct_cli/translations/vcards.json +0 -0
  222. {direct_cli-0.5.2 → direct_cli-0.5.3}/direct_cli/utils.py +0 -0
  223. {direct_cli-0.5.2 → direct_cli-0.5.3}/direct_cli/v4/__init__.py +0 -0
  224. {direct_cli-0.5.2 → direct_cli-0.5.3}/direct_cli/v4/emit.py +0 -0
  225. {direct_cli-0.5.2 → direct_cli-0.5.3}/direct_cli/v4/money.py +0 -0
  226. {direct_cli-0.5.2 → direct_cli-0.5.3}/direct_cli/v4_contracts.py +0 -0
  227. {direct_cli-0.5.2 → direct_cli-0.5.3}/direct_cli.egg-info/dependency_links.txt +0 -0
  228. {direct_cli-0.5.2 → direct_cli-0.5.3}/direct_cli.egg-info/entry_points.txt +0 -0
  229. {direct_cli-0.5.2 → direct_cli-0.5.3}/direct_cli.egg-info/top_level.txt +0 -0
  230. {direct_cli-0.5.2 → direct_cli-0.5.3}/docs/audits/API_COVERAGE.md +0 -0
  231. {direct_cli-0.5.2 → direct_cli-0.5.3}/docs/audits/PROJECT_WIRE_SHAPE_AUDIT_2026-05-30.md +0 -0
  232. {direct_cli-0.5.2 → direct_cli-0.5.3}/docs/audits/WIRE_SHAPE_TRIAGE_2026-05-30.md +0 -0
  233. {direct_cli-0.5.2 → direct_cli-0.5.3}/docs/audits/issue-198-mutating-wsdl-audit.md +0 -0
  234. {direct_cli-0.5.2 → direct_cli-0.5.3}/docs/audits/wire_shape.json +0 -0
  235. {direct_cli-0.5.2 → direct_cli-0.5.3}/docs/superpowers/plans/2026-04-12-issue-32-completion.md +0 -0
  236. {direct_cli-0.5.2 → direct_cli-0.5.3}/docs/superpowers/specs/2026-04-23-vendor-tapi-yandex-direct-design.md +0 -0
  237. {direct_cli-0.5.2 → direct_cli-0.5.3}/scripts/anonymize_cassettes.py +0 -0
  238. {direct_cli-0.5.2 → direct_cli-0.5.3}/scripts/build_wsdl_optional_field_audit.py +0 -0
  239. {direct_cli-0.5.2 → direct_cli-0.5.3}/scripts/check_all_docs_urls.py +0 -0
  240. {direct_cli-0.5.2 → direct_cli-0.5.3}/scripts/check_reports_drift.py +0 -0
  241. {direct_cli-0.5.2 → direct_cli-0.5.3}/scripts/check_wsdl_drift.py +0 -0
  242. {direct_cli-0.5.2 → direct_cli-0.5.3}/scripts/measure_criteria_limits.py +0 -0
  243. {direct_cli-0.5.2 → direct_cli-0.5.3}/scripts/patch_vendor_imports.py +0 -0
  244. {direct_cli-0.5.2 → direct_cli-0.5.3}/scripts/preflight_check.sh +0 -0
  245. {direct_cli-0.5.2 → direct_cli-0.5.3}/scripts/probe_drift_urls.sh +0 -0
  246. {direct_cli-0.5.2 → direct_cli-0.5.3}/scripts/recon_761_utm_split.py +0 -0
  247. {direct_cli-0.5.2 → direct_cli-0.5.3}/scripts/recon_774_audit_saved_utm.py +0 -0
  248. {direct_cli-0.5.2 → direct_cli-0.5.3}/scripts/recon_774_utm_reorder.py +0 -0
  249. {direct_cli-0.5.2 → direct_cli-0.5.3}/scripts/refresh_reports_cache.py +0 -0
  250. {direct_cli-0.5.2 → direct_cli-0.5.3}/scripts/refresh_wsdl_cache.py +0 -0
  251. {direct_cli-0.5.2 → direct_cli-0.5.3}/scripts/release_pypi.sh +0 -0
  252. {direct_cli-0.5.2 → direct_cli-0.5.3}/scripts/sandbox_write_live.py +0 -0
  253. {direct_cli-0.5.2 → direct_cli-0.5.3}/scripts/test_dangerous_commands.sh +0 -0
  254. {direct_cli-0.5.2 → direct_cli-0.5.3}/scripts/test_safe_commands.sh +0 -0
  255. {direct_cli-0.5.2 → direct_cli-0.5.3}/scripts/test_sandbox_write.sh +0 -0
  256. {direct_cli-0.5.2 → direct_cli-0.5.3}/scripts/update_vendor.sh +0 -0
  257. {direct_cli-0.5.2 → direct_cli-0.5.3}/setup.py +0 -0
  258. {direct_cli-0.5.2 → direct_cli-0.5.3}/tests/API_COVERAGE.md +0 -0
  259. {direct_cli-0.5.2 → direct_cli-0.5.3}/tests/API_ISSUE_AUDIT.md +0 -0
  260. {direct_cli-0.5.2 → direct_cli-0.5.3}/tests/MANUAL_COVERAGE.md +0 -0
  261. {direct_cli-0.5.2 → direct_cli-0.5.3}/tests/__init__.py +0 -0
  262. {direct_cli-0.5.2 → direct_cli-0.5.3}/tests/_orphan_store.py +0 -0
  263. {direct_cli-0.5.2 → direct_cli-0.5.3}/tests/cassettes/test_integration_write/TestWriteAdExtensions.test_add_delete.yaml +0 -0
  264. {direct_cli-0.5.2 → direct_cli-0.5.3}/tests/cassettes/test_integration_write/TestWriteAdGroups.test_add_update_delete.yaml +0 -0
  265. {direct_cli-0.5.2 → direct_cli-0.5.3}/tests/cassettes/test_integration_write/TestWriteAdImages.test_add_delete.yaml +0 -0
  266. {direct_cli-0.5.2 → direct_cli-0.5.3}/tests/cassettes/test_integration_write/TestWriteAds.test_add_text_ad_update_delete.yaml +0 -0
  267. {direct_cli-0.5.2 → direct_cli-0.5.3}/tests/cassettes/test_integration_write/TestWriteAudienceTargets.test_add_delete.yaml +0 -0
  268. {direct_cli-0.5.2 → direct_cli-0.5.3}/tests/cassettes/test_integration_write/TestWriteBidModifiersAdd.test_add_delete_mobile.yaml +0 -0
  269. {direct_cli-0.5.2 → direct_cli-0.5.3}/tests/cassettes/test_integration_write/TestWriteBidModifiersSet.test_set_without_id_is_rejected.yaml +0 -0
  270. {direct_cli-0.5.2 → direct_cli-0.5.3}/tests/cassettes/test_integration_write/TestWriteBids.test_set_bid.yaml +0 -0
  271. {direct_cli-0.5.2 → direct_cli-0.5.3}/tests/cassettes/test_integration_write/TestWriteBidsRead.test_bids_get.yaml +0 -0
  272. {direct_cli-0.5.2 → direct_cli-0.5.3}/tests/cassettes/test_integration_write/TestWriteBidsRead.test_bids_set_auto.yaml +0 -0
  273. {direct_cli-0.5.2 → direct_cli-0.5.3}/tests/cassettes/test_integration_write/TestWriteCampaignDraftLifecycle.test_draft_create_get_delete.yaml +0 -0
  274. {direct_cli-0.5.2 → direct_cli-0.5.3}/tests/cassettes/test_integration_write/TestWriteCampaigns.test_campaign_lifecycle.yaml +0 -0
  275. {direct_cli-0.5.2 → direct_cli-0.5.3}/tests/cassettes/test_integration_write/TestWriteDynamicAds.test_add_update_delete.yaml +0 -0
  276. {direct_cli-0.5.2 → direct_cli-0.5.3}/tests/cassettes/test_integration_write/TestWriteFeeds.test_add_update_delete.yaml +0 -0
  277. {direct_cli-0.5.2 → direct_cli-0.5.3}/tests/cassettes/test_integration_write/TestWriteKeywordBids.test_set_keyword_bid.yaml +0 -0
  278. {direct_cli-0.5.2 → direct_cli-0.5.3}/tests/cassettes/test_integration_write/TestWriteKeywords.test_add_update_delete.yaml +0 -0
  279. {direct_cli-0.5.2 → direct_cli-0.5.3}/tests/cassettes/test_integration_write/TestWriteNegativeKeywordSharedSets.test_add_update_delete.yaml +0 -0
  280. {direct_cli-0.5.2 → direct_cli-0.5.3}/tests/cassettes/test_integration_write/TestWriteRetargeting.test_add_delete.yaml +0 -0
  281. {direct_cli-0.5.2 → direct_cli-0.5.3}/tests/cassettes/test_integration_write/TestWriteRetargetingUpdate.test_retargeting_update.yaml +0 -0
  282. {direct_cli-0.5.2 → direct_cli-0.5.3}/tests/cassettes/test_integration_write/TestWriteSitelinks.test_add_delete.yaml +0 -0
  283. {direct_cli-0.5.2 → direct_cli-0.5.3}/tests/cassettes/test_integration_write/TestWriteSmartAdTargets.test_add_update_delete.yaml +0 -0
  284. {direct_cli-0.5.2 → direct_cli-0.5.3}/tests/cassettes/test_integration_write/TestWriteStrategies.test_strategies_lifecycle.yaml +0 -0
  285. {direct_cli-0.5.2 → direct_cli-0.5.3}/tests/cassettes/test_integration_write/TestWriteVCards.test_add_delete.yaml +0 -0
  286. {direct_cli-0.5.2 → direct_cli-0.5.3}/tests/cassettes/test_read_cassettes/test_balance_get_no_shared_account.yaml +0 -0
  287. {direct_cli-0.5.2 → direct_cli-0.5.3}/tests/cassettes/test_read_cassettes/test_read_command[adextensions_get].yaml +0 -0
  288. {direct_cli-0.5.2 → direct_cli-0.5.3}/tests/cassettes/test_read_cassettes/test_read_command[adgroups_get].yaml +0 -0
  289. {direct_cli-0.5.2 → direct_cli-0.5.3}/tests/cassettes/test_read_cassettes/test_read_command[adimages_get].yaml +0 -0
  290. {direct_cli-0.5.2 → direct_cli-0.5.3}/tests/cassettes/test_read_cassettes/test_read_command[ads_get].yaml +0 -0
  291. {direct_cli-0.5.2 → direct_cli-0.5.3}/tests/cassettes/test_read_cassettes/test_read_command[advideos_get].yaml +0 -0
  292. {direct_cli-0.5.2 → direct_cli-0.5.3}/tests/cassettes/test_read_cassettes/test_read_command[audiencetargets_get].yaml +0 -0
  293. {direct_cli-0.5.2 → direct_cli-0.5.3}/tests/cassettes/test_read_cassettes/test_read_command[bidmodifiers_get].yaml +0 -0
  294. {direct_cli-0.5.2 → direct_cli-0.5.3}/tests/cassettes/test_read_cassettes/test_read_command[bids_get].yaml +0 -0
  295. {direct_cli-0.5.2 → direct_cli-0.5.3}/tests/cassettes/test_read_cassettes/test_read_command[businesses_get].yaml +0 -0
  296. {direct_cli-0.5.2 → direct_cli-0.5.3}/tests/cassettes/test_read_cassettes/test_read_command[campaigns_get].yaml +0 -0
  297. {direct_cli-0.5.2 → direct_cli-0.5.3}/tests/cassettes/test_read_cassettes/test_read_command[changes_check].yaml +0 -0
  298. {direct_cli-0.5.2 → direct_cli-0.5.3}/tests/cassettes/test_read_cassettes/test_read_command[changes_check_campaigns].yaml +0 -0
  299. {direct_cli-0.5.2 → direct_cli-0.5.3}/tests/cassettes/test_read_cassettes/test_read_command[changes_check_dictionaries].yaml +0 -0
  300. {direct_cli-0.5.2 → direct_cli-0.5.3}/tests/cassettes/test_read_cassettes/test_read_command[clients_get].yaml +0 -0
  301. {direct_cli-0.5.2 → direct_cli-0.5.3}/tests/cassettes/test_read_cassettes/test_read_command[creatives_get].yaml +0 -0
  302. {direct_cli-0.5.2 → direct_cli-0.5.3}/tests/cassettes/test_read_cassettes/test_read_command[dictionaries_get].yaml +0 -0
  303. {direct_cli-0.5.2 → direct_cli-0.5.3}/tests/cassettes/test_read_cassettes/test_read_command[dynamicads_get].yaml +0 -0
  304. {direct_cli-0.5.2 → direct_cli-0.5.3}/tests/cassettes/test_read_cassettes/test_read_command[dynamicfeedadtargets_get].yaml +0 -0
  305. {direct_cli-0.5.2 → direct_cli-0.5.3}/tests/cassettes/test_read_cassettes/test_read_command[feeds_get].yaml +0 -0
  306. {direct_cli-0.5.2 → direct_cli-0.5.3}/tests/cassettes/test_read_cassettes/test_read_command[keywordbids_get].yaml +0 -0
  307. {direct_cli-0.5.2 → direct_cli-0.5.3}/tests/cassettes/test_read_cassettes/test_read_command[keywords_get].yaml +0 -0
  308. {direct_cli-0.5.2 → direct_cli-0.5.3}/tests/cassettes/test_read_cassettes/test_read_command[keywordsresearch_deduplicate].yaml +0 -0
  309. {direct_cli-0.5.2 → direct_cli-0.5.3}/tests/cassettes/test_read_cassettes/test_read_command[keywordsresearch_has_search_volume].yaml +0 -0
  310. {direct_cli-0.5.2 → direct_cli-0.5.3}/tests/cassettes/test_read_cassettes/test_read_command[leads_get].yaml +0 -0
  311. {direct_cli-0.5.2 → direct_cli-0.5.3}/tests/cassettes/test_read_cassettes/test_read_command[negativekeywordsharedsets_get].yaml +0 -0
  312. {direct_cli-0.5.2 → direct_cli-0.5.3}/tests/cassettes/test_read_cassettes/test_read_command[reports_get].yaml +0 -0
  313. {direct_cli-0.5.2 → direct_cli-0.5.3}/tests/cassettes/test_read_cassettes/test_read_command[retargeting_get].yaml +0 -0
  314. {direct_cli-0.5.2 → direct_cli-0.5.3}/tests/cassettes/test_read_cassettes/test_read_command[sitelinks_get].yaml +0 -0
  315. {direct_cli-0.5.2 → direct_cli-0.5.3}/tests/cassettes/test_read_cassettes/test_read_command[smartadtargets_get].yaml +0 -0
  316. {direct_cli-0.5.2 → direct_cli-0.5.3}/tests/cassettes/test_read_cassettes/test_read_command[strategies_get].yaml +0 -0
  317. {direct_cli-0.5.2 → direct_cli-0.5.3}/tests/cassettes/test_read_cassettes/test_read_command[turbopages_get].yaml +0 -0
  318. {direct_cli-0.5.2 → direct_cli-0.5.3}/tests/cassettes/test_read_cassettes/test_read_command[v4adimage_get].yaml +0 -0
  319. {direct_cli-0.5.2 → direct_cli-0.5.3}/tests/cassettes/test_read_cassettes/test_read_command[v4events_get_events_log].yaml +0 -0
  320. {direct_cli-0.5.2 → direct_cli-0.5.3}/tests/cassettes/test_read_cassettes/test_read_command[v4finance_get_clients_units].yaml +0 -0
  321. {direct_cli-0.5.2 → direct_cli-0.5.3}/tests/cassettes/test_read_cassettes/test_read_command[v4forecast_list].yaml +0 -0
  322. {direct_cli-0.5.2 → direct_cli-0.5.3}/tests/cassettes/test_read_cassettes/test_read_command[v4goals_get_stat_goals].yaml +0 -0
  323. {direct_cli-0.5.2 → direct_cli-0.5.3}/tests/cassettes/test_read_cassettes/test_read_command[v4tags_get_campaigns].yaml +0 -0
  324. {direct_cli-0.5.2 → direct_cli-0.5.3}/tests/cassettes/test_read_cassettes/test_read_command[v4wordstat_list_reports].yaml +0 -0
  325. {direct_cli-0.5.2 → direct_cli-0.5.3}/tests/cassettes/test_read_cassettes/test_read_command[vcards_get].yaml +0 -0
  326. {direct_cli-0.5.2 → direct_cli-0.5.3}/tests/cassettes/test_read_cassettes/test_v4account_get_accounts_no_shared_account.yaml +0 -0
  327. {direct_cli-0.5.2 → direct_cli-0.5.3}/tests/cassettes/test_read_cassettes/test_v4finance_check_payment_unknown_transaction.yaml +0 -0
  328. {direct_cli-0.5.2 → direct_cli-0.5.3}/tests/cassettes/test_read_cassettes/test_v4forecast_get_unknown_forecast.yaml +0 -0
  329. {direct_cli-0.5.2 → direct_cli-0.5.3}/tests/cassettes/test_read_cassettes/test_v4goals_get_retargeting_goals_server_error.yaml +0 -0
  330. {direct_cli-0.5.2 → direct_cli-0.5.3}/tests/cassettes/test_read_cassettes/test_v4tags_get_banners_server_error.yaml +0 -0
  331. {direct_cli-0.5.2 → direct_cli-0.5.3}/tests/cassettes/test_read_cassettes/test_v4wordstat_get_report_unknown_report.yaml +0 -0
  332. {direct_cli-0.5.2 → direct_cli-0.5.3}/tests/cassettes/test_v5_live_write/test_v5_live_draft_adgroups_add_update_delete.yaml +0 -0
  333. {direct_cli-0.5.2 → direct_cli-0.5.3}/tests/cassettes/test_v5_live_write/test_v5_live_draft_adimages_add_get_delete.yaml +0 -0
  334. {direct_cli-0.5.2 → direct_cli-0.5.3}/tests/cassettes/test_v5_live_write/test_v5_live_draft_ads_add_update_delete.yaml +0 -0
  335. {direct_cli-0.5.2 → direct_cli-0.5.3}/tests/cassettes/test_v5_live_write/test_v5_live_draft_ads_suspend_resume_archive_unarchive.yaml +0 -0
  336. {direct_cli-0.5.2 → direct_cli-0.5.3}/tests/cassettes/test_v5_live_write/test_v5_live_draft_advideos_add_get.yaml +0 -0
  337. {direct_cli-0.5.2 → direct_cli-0.5.3}/tests/cassettes/test_v5_live_write/test_v5_live_draft_audiencetargets_add_delete.yaml +0 -0
  338. {direct_cli-0.5.2 → direct_cli-0.5.3}/tests/cassettes/test_v5_live_write/test_v5_live_draft_audiencetargets_suspend_resume.yaml +0 -0
  339. {direct_cli-0.5.2 → direct_cli-0.5.3}/tests/cassettes/test_v5_live_write/test_v5_live_draft_bids_set.yaml +0 -0
  340. {direct_cli-0.5.2 → direct_cli-0.5.3}/tests/cassettes/test_v5_live_write/test_v5_live_draft_campaign_create_get_delete.yaml +0 -0
  341. {direct_cli-0.5.2 → direct_cli-0.5.3}/tests/cassettes/test_v5_live_write/test_v5_live_draft_creatives_chain_advideo_to_creative.yaml +0 -0
  342. {direct_cli-0.5.2 → direct_cli-0.5.3}/tests/cassettes/test_v5_live_write/test_v5_live_draft_dynamicads_add_delete.yaml +0 -0
  343. {direct_cli-0.5.2 → direct_cli-0.5.3}/tests/cassettes/test_v5_live_write/test_v5_live_draft_dynamicads_suspend_resume.yaml +0 -0
  344. {direct_cli-0.5.2 → direct_cli-0.5.3}/tests/cassettes/test_v5_live_write/test_v5_live_draft_feeds_add_update_delete.yaml +0 -0
  345. {direct_cli-0.5.2 → direct_cli-0.5.3}/tests/cassettes/test_v5_live_write/test_v5_live_draft_keywordbids_set.yaml +0 -0
  346. {direct_cli-0.5.2 → direct_cli-0.5.3}/tests/cassettes/test_v5_live_write/test_v5_live_draft_keywords_add_update_delete.yaml +0 -0
  347. {direct_cli-0.5.2 → direct_cli-0.5.3}/tests/cassettes/test_v5_live_write/test_v5_live_draft_keywords_suspend_resume.yaml +0 -0
  348. {direct_cli-0.5.2 → direct_cli-0.5.3}/tests/cassettes/test_v5_live_write/test_v5_live_draft_retargeting_add_update_delete.yaml +0 -0
  349. {direct_cli-0.5.2 → direct_cli-0.5.3}/tests/cassettes/test_v5_live_write/test_v5_live_draft_sitelinks_add_get_delete.yaml +0 -0
  350. {direct_cli-0.5.2 → direct_cli-0.5.3}/tests/cassettes/test_v5_live_write/test_v5_live_draft_smartadtargets_add_update_delete.yaml +0 -0
  351. {direct_cli-0.5.2 → direct_cli-0.5.3}/tests/cassettes/test_v5_live_write/test_v5_live_draft_smartadtargets_suspend_resume.yaml +0 -0
  352. {direct_cli-0.5.2 → direct_cli-0.5.3}/tests/cassettes/test_v5_live_write/test_v5_live_draft_strategies_add_update_archive_unarchive.yaml +0 -0
  353. {direct_cli-0.5.2 → direct_cli-0.5.3}/tests/fixtures/masters_grid_campaigns.json +0 -0
  354. {direct_cli-0.5.2 → direct_cli-0.5.3}/tests/fixtures/masters_wizard_create.html +0 -0
  355. {direct_cli-0.5.2 → direct_cli-0.5.3}/tests/fixtures/masters_wizard_draft_overview.html +0 -0
  356. {direct_cli-0.5.2 → direct_cli-0.5.3}/tests/fixtures/masters_wizard_edit_moderation.html +0 -0
  357. {direct_cli-0.5.2 → direct_cli-0.5.3}/tests/fixtures/masters_wizard_edit_name.html +0 -0
  358. {direct_cli-0.5.2 → direct_cli-0.5.3}/tests/fixtures/masters_wizard_edit_stage_a.html +0 -0
  359. {direct_cli-0.5.2 → direct_cli-0.5.3}/tests/fixtures/masters_wizard_edit_stage_b.html +0 -0
  360. {direct_cli-0.5.2 → direct_cli-0.5.3}/tests/fixtures/masters_wizard_edit_stage_d.html +0 -0
  361. {direct_cli-0.5.2 → direct_cli-0.5.3}/tests/fixtures/masters_wizard_overview.html +0 -0
  362. {direct_cli-0.5.2 → direct_cli-0.5.3}/tests/fixtures/test-video.mp4 +0 -0
  363. {direct_cli-0.5.2 → direct_cli-0.5.3}/tests/reports_cache/raw/fields-list.html +0 -0
  364. {direct_cli-0.5.2 → direct_cli-0.5.3}/tests/reports_cache/raw/headers.html +0 -0
  365. {direct_cli-0.5.2 → direct_cli-0.5.3}/tests/reports_cache/raw/period.html +0 -0
  366. {direct_cli-0.5.2 → direct_cli-0.5.3}/tests/reports_cache/raw/spec.html +0 -0
  367. {direct_cli-0.5.2 → direct_cli-0.5.3}/tests/reports_cache/raw/type.html +0 -0
  368. {direct_cli-0.5.2 → direct_cli-0.5.3}/tests/reports_cache/spec.json +0 -0
  369. {direct_cli-0.5.2 → direct_cli-0.5.3}/tests/test_auth_bw.py +0 -0
  370. {direct_cli-0.5.2 → direct_cli-0.5.3}/tests/test_auth_op.py +0 -0
  371. {direct_cli-0.5.2 → direct_cli-0.5.3}/tests/test_auth_write_json.py +0 -0
  372. {direct_cli-0.5.2 → direct_cli-0.5.3}/tests/test_autotargeting.py +0 -0
  373. {direct_cli-0.5.2 → direct_cli-0.5.3}/tests/test_bidding_strategy_constants.py +0 -0
  374. {direct_cli-0.5.2 → direct_cli-0.5.3}/tests/test_cassette_integrity.py +0 -0
  375. {direct_cli-0.5.2 → direct_cli-0.5.3}/tests/test_changes.py +0 -0
  376. {direct_cli-0.5.2 → direct_cli-0.5.3}/tests/test_check_all_docs_urls.py +0 -0
  377. {direct_cli-0.5.2 → direct_cli-0.5.3}/tests/test_cli_contract.py +0 -0
  378. {direct_cli-0.5.2 → direct_cli-0.5.3}/tests/test_dry_run.py +0 -0
  379. {direct_cli-0.5.2 → direct_cli-0.5.3}/tests/test_dry_run_adgroups.py +0 -0
  380. {direct_cli-0.5.2 → direct_cli-0.5.3}/tests/test_dry_run_ads.py +0 -0
  381. {direct_cli-0.5.2 → direct_cli-0.5.3}/tests/test_dry_run_campaigns.py +0 -0
  382. {direct_cli-0.5.2 → direct_cli-0.5.3}/tests/test_dry_run_common.py +0 -0
  383. {direct_cli-0.5.2 → direct_cli-0.5.3}/tests/test_dry_run_extensions.py +0 -0
  384. {direct_cli-0.5.2 → direct_cli-0.5.3}/tests/test_dry_run_keywords.py +0 -0
  385. {direct_cli-0.5.2 → direct_cli-0.5.3}/tests/test_dry_run_shared.py +0 -0
  386. {direct_cli-0.5.2 → direct_cli-0.5.3}/tests/test_dry_run_strategies.py +0 -0
  387. {direct_cli-0.5.2 → direct_cli-0.5.3}/tests/test_dry_run_strategy_smart.py +0 -0
  388. {direct_cli-0.5.2 → direct_cli-0.5.3}/tests/test_dry_run_strategy_text.py +0 -0
  389. {direct_cli-0.5.2 → direct_cli-0.5.3}/tests/test_dry_run_strategy_unified.py +0 -0
  390. {direct_cli-0.5.2 → direct_cli-0.5.3}/tests/test_dry_run_targets.py +0 -0
  391. {direct_cli-0.5.2 → direct_cli-0.5.3}/tests/test_env_loading.py +0 -0
  392. {direct_cli-0.5.2 → direct_cli-0.5.3}/tests/test_field_names_option.py +0 -0
  393. {direct_cli-0.5.2 → direct_cli-0.5.3}/tests/test_flag_validation.py +0 -0
  394. {direct_cli-0.5.2 → direct_cli-0.5.3}/tests/test_handle_api_errors.py +0 -0
  395. {direct_cli-0.5.2 → direct_cli-0.5.3}/tests/test_integration.py +0 -0
  396. {direct_cli-0.5.2 → direct_cli-0.5.3}/tests/test_read_cassettes.py +0 -0
  397. {direct_cli-0.5.2 → direct_cli-0.5.3}/tests/test_reference_output.py +0 -0
  398. {direct_cli-0.5.2 → direct_cli-0.5.3}/tests/test_reports_drift.py +0 -0
  399. {direct_cli-0.5.2 → direct_cli-0.5.3}/tests/test_sandbox_write_audit.py +0 -0
  400. {direct_cli-0.5.2 → direct_cli-0.5.3}/tests/test_unknown_option_hints.py +0 -0
  401. {direct_cli-0.5.2 → direct_cli-0.5.3}/tests/test_v4_exit_codes.py +0 -0
  402. {direct_cli-0.5.2 → direct_cli-0.5.3}/tests/test_v4_foundation.py +0 -0
  403. {direct_cli-0.5.2 → direct_cli-0.5.3}/tests/test_v4_live_contracts.py +0 -0
  404. {direct_cli-0.5.2 → direct_cli-0.5.3}/tests/test_v4_runtime_shape.py +0 -0
  405. {direct_cli-0.5.2 → direct_cli-0.5.3}/tests/test_v4_safety.py +0 -0
  406. {direct_cli-0.5.2 → direct_cli-0.5.3}/tests/test_v4meta.py +0 -0
  407. {direct_cli-0.5.2 → direct_cli-0.5.3}/tests/wsdl_cache/adextensions.xml +0 -0
  408. {direct_cli-0.5.2 → direct_cli-0.5.3}/tests/wsdl_cache/adgroups.xml +0 -0
  409. {direct_cli-0.5.2 → direct_cli-0.5.3}/tests/wsdl_cache/ads.xml +0 -0
  410. {direct_cli-0.5.2 → direct_cli-0.5.3}/tests/wsdl_cache/advideos.xml +0 -0
  411. {direct_cli-0.5.2 → direct_cli-0.5.3}/tests/wsdl_cache/agencyclients.xml +0 -0
  412. {direct_cli-0.5.2 → direct_cli-0.5.3}/tests/wsdl_cache/audiencetargets.xml +0 -0
  413. {direct_cli-0.5.2 → direct_cli-0.5.3}/tests/wsdl_cache/bidmodifiers.xml +0 -0
  414. {direct_cli-0.5.2 → direct_cli-0.5.3}/tests/wsdl_cache/bids.xml +0 -0
  415. {direct_cli-0.5.2 → direct_cli-0.5.3}/tests/wsdl_cache/businesses.xml +0 -0
  416. {direct_cli-0.5.2 → direct_cli-0.5.3}/tests/wsdl_cache/changes.xml +0 -0
  417. {direct_cli-0.5.2 → direct_cli-0.5.3}/tests/wsdl_cache/clients.xml +0 -0
  418. {direct_cli-0.5.2 → direct_cli-0.5.3}/tests/wsdl_cache/creatives.xml +0 -0
  419. {direct_cli-0.5.2 → direct_cli-0.5.3}/tests/wsdl_cache/dictionaries.xml +0 -0
  420. {direct_cli-0.5.2 → direct_cli-0.5.3}/tests/wsdl_cache/dynamicfeedadtargets.xml +0 -0
  421. {direct_cli-0.5.2 → direct_cli-0.5.3}/tests/wsdl_cache/dynamictextadtargets.xml +0 -0
  422. {direct_cli-0.5.2 → direct_cli-0.5.3}/tests/wsdl_cache/feeds.xml +0 -0
  423. {direct_cli-0.5.2 → direct_cli-0.5.3}/tests/wsdl_cache/imports/adextensiontypes.xsd +0 -0
  424. {direct_cli-0.5.2 → direct_cli-0.5.3}/tests/wsdl_cache/imports/general.xsd +0 -0
  425. {direct_cli-0.5.2 → direct_cli-0.5.3}/tests/wsdl_cache/keywordbids.xml +0 -0
  426. {direct_cli-0.5.2 → direct_cli-0.5.3}/tests/wsdl_cache/keywords.xml +0 -0
  427. {direct_cli-0.5.2 → direct_cli-0.5.3}/tests/wsdl_cache/keywordsresearch.xml +0 -0
  428. {direct_cli-0.5.2 → direct_cli-0.5.3}/tests/wsdl_cache/leads.xml +0 -0
  429. {direct_cli-0.5.2 → direct_cli-0.5.3}/tests/wsdl_cache/negativekeywordsharedsets.xml +0 -0
  430. {direct_cli-0.5.2 → direct_cli-0.5.3}/tests/wsdl_cache/retargetinglists.xml +0 -0
  431. {direct_cli-0.5.2 → direct_cli-0.5.3}/tests/wsdl_cache/sitelinks.xml +0 -0
  432. {direct_cli-0.5.2 → direct_cli-0.5.3}/tests/wsdl_cache/smartadtargets.xml +0 -0
  433. {direct_cli-0.5.2 → direct_cli-0.5.3}/tests/wsdl_cache/turbopages.xml +0 -0
  434. {direct_cli-0.5.2 → direct_cli-0.5.3}/tests/wsdl_cache/vcards.xml +0 -0
@@ -38,6 +38,9 @@ jobs:
38
38
  - name: Type check
39
39
  run: mypy .
40
40
 
41
+ - name: Pyright
42
+ run: pyright
43
+
41
44
  - name: Run offline tests
42
45
  # Offline default comes from pyproject addopts (skips the live-network
43
46
  # markers). integration_write stays in — it replays VCR cassettes offline.
@@ -1,9 +1,279 @@
1
1
  # Changelog
2
2
 
3
- ## Unreleased
3
+ ## 0.5.3 — 2026-10-07
4
+
5
+ ### Fixed
6
+
7
+ **`masters update` — the whole-form save can no longer silently wipe the
8
+ campaign's target actions or Metrika counters, whichever field the call
9
+ mutates (#872).**
10
+
11
+ The edit page has exactly one save button and the save submits the WHOLE
12
+ form, so every update — not just a URL/UTM one — resubmits the "Целевые
13
+ действия" table and the counters section. The #836 protection (settled
14
+ pre-save baselines + pre-click guard + post-save verification) was gated
15
+ on `landing_url`/`tracking_params` being present, so a
16
+ `masters update --weekly-budget` run saved a mid-hydration, emptied
17
+ target-action table as a success: 4 live Мастер кампаний lost their
18
+ goals and stopped spending (0₽/day with a healthy weekly budget). The
19
+ protection now keys on the save itself: every mutating `update_master`
20
+ call reads the same baselines, refuses to click Save when a protected
21
+ section no longer matches its baseline ("Refusing to save"), and
22
+ verifies the untouched goal set after the reload — an unchanged-table
23
+ call with an unreadable baseline on a rendered section aborts instead of
24
+ saving blind. A max-clicks campaign (no target-actions section rendered)
25
+ and add/remove-target-action calls keep their existing semantics. On
26
+ success the result row now carries `TargetActions` — the verified goal
27
+ set that survived the save — so a `--from-file` batch shows the
28
+ protected goals per campaign instead of an implicit absence of failure.
29
+ Cost: an unchanged-table update spends a few extra seconds stabilizing
30
+ the section reads.
31
+
32
+ The protection also checks each goal's **price (CPA)**, not just which
33
+ goals are present (#876). The save resubmits every goal's bid, so a price
34
+ that shifts or resets while the goal stays would let the strategy keep
35
+ spending at a different bid with no visible symptom. Both the pre-click
36
+ guard and the post-save check now compare the full `{GoalId: Price}` map
37
+ (the baseline with any `--target-action-price` changes applied), and
38
+ `TargetActions` in the result row is `[{"GoalId": …, "Price": …}]`.
39
+
40
+ **`direct playwright login` — honest `verified` reporting (#870).**
41
+
42
+ `verified` in the command's output now reflects what actually happened:
43
+ a render timeout during verification saves the session with
44
+ `verified: false` and exit code 0 (previously the whole command failed;
45
+ the #692 invariant — never CLAIM a verified session without a rendered
46
+ page — is now carried by the explicit `false`, which JSON consumers
47
+ should check). A live check that lands on Yandex's login page judges the
48
+ auth cookies' (`Session_id`/`sessionid2`) `expires_utc` before claiming
49
+ they expired — long-lived tracking cookies no longer mask a dead session
50
+ — and a headless run gets one headful retry for the bot challenge. A
51
+ stale Chromium ProcessSingleton lock (a previous CLI browser process
52
+ that died with its `hostname-PID` symlink still on disk) is cleared and
53
+ the launch retried once instead of failing with a raw
54
+ `SingletonLock: File exists` traceback; `masters login` gained
55
+ `--allow-no-tty` for scripted runs whose window is visible anyway.
4
56
 
5
57
  ### Added
6
58
 
59
+ **`bidmodifiers` — live-only type `RETARGETING_SEARCH_ADJUSTMENT` (#871).**
60
+
61
+ Adds the search-retargeting bid modifier type the live API accepts but the
62
+ published WSDL and docs do not declare (confirmed by direct probes,
63
+ 2026-10-07): the add payload nests under
64
+ `RetargetingSearchAdjustments` (a list, like `RetargetingAdjustments`),
65
+ requires `--retargeting-condition-id` together with `--value`, and `get`
66
+ selects it via `--types` alone — the type has no per-type
67
+ `RetargetingSearchAdjustmentFieldNames` parameter (the live API rejects it).
68
+
69
+ **Known limitation (#890):** Yandex currently rejects creating this
70
+ modifier server-side (5005 on every documented retargeting-list class,
71
+ while older types create fine on the same campaign) — the type appears to
72
+ be a half-enabled pilot. The CLI mirrors the confirmed signature so
73
+ `bidmodifiers add --type RETARGETING_SEARCH_ADJUSTMENT` works the moment
74
+ the API enables acceptance; `--dry-run` shows the exact payload.
75
+
76
+ **`masters list` — strategy and weekly budget per campaign.**
77
+
78
+ Every GridCampaigns row already carries the grid's «Бюджет и стратегия»
79
+ column (live capture 2026-10-03), so `masters list` now surfaces it with no
80
+ extra page request: `Strategy` (the grid's raw enum — `OPTIMIZE_CLICKS` /
81
+ `OPTIMIZE_CONVERSIONS` — passed through verbatim rather than remapped),
82
+ `WeeklyBudget` (`strategy.budget.sum` when its `period` is `WEEK`), and the
83
+ strategy scalars `AvgCpa` / `GoalId` / `PayForConversion` / `IsAutoBudget`,
84
+ plus `EndDate` alongside the existing `StartDate`. Rows without a strategy
85
+ object (e.g. drafts) report all of these as `null`.
86
+
87
+ **`masters status` — inspect the active browser session with no network call (#862).**
88
+
89
+ Reports which of `_open_session`'s tiers `masters` commands would currently
90
+ use — the CLI's own persistent profile from `masters login` (tier 1.5), a
91
+ saved `direct playwright login` session (tier 2), or neither (tier 3,
92
+ fresh Chrome-cookie decrypt on every call) — plus the persistent profile's
93
+ path/usability and the saved session's existence/expiry (reusing
94
+ `direct_cli.browser.store.session_status`, the same read `direct playwright
95
+ doctor` already does). Never launches a browser or touches Yandex; unlike
96
+ every other `masters` subcommand, `status` is pure on-disk inspection.
97
+ Defaults to `text` output like other local reference/diagnostic commands
98
+ (`direct playwright doctor`, `trackingparams`). An explicit `--profile-dir`
99
+ on the `status` call itself reports tier 1 ("explicit --profile-dir"),
100
+ matching `_open_session`'s real precedence: a real `masters` read command
101
+ given the same flag always decrypts fresh from that profile, bypassing
102
+ tier 1.5/2 entirely — `status` models that override instead of reporting a
103
+ tier the real command would not actually use (cycle-review of #864).
104
+
105
+ **`masters update --add-video-url` — select an already-uploaded video (#812).**
106
+
107
+ Adds a video to a campaign's "Варианты видео" section by selecting one the
108
+ account has already uploaded to some other campaign, instead of uploading a
109
+ new local file. Yandex keeps a single account-wide video library shared
110
+ across all campaigns, exposed on the video manager modal's "Ваши кампании"
111
+ tab.
112
+
113
+ Live-verified 2026-08-26 (campaign 713234191, throwaway, read-only up to
114
+ Cancel — no Save was clicked): selecting a card there
115
+ (`VideoSuggestionsEditor.CreativeSourcePanel.USER` →
116
+ `SuggestedCreativesContainer.SuggestedCreative.Select.<url>`) immediately
117
+ moves it into the modal's `SelectedCreativesGrid` — no async upload-processing
118
+ wait needed, since the video already exists server-side. The 2-video cap is
119
+ enforced client-side: selecting a 3rd disables `VideoSuggestionsEditor.Save`
120
+ and changes its label to "Выбрано больше 2 видео", the same Save button the
121
+ upload flow uses. Mutually exclusive with `--add-video` (and `AddVideoUrl`
122
+ with `AddVideo` in `--from-file`/`--masters-json` batch rows).
123
+
124
+ `--add-video`'s own file-upload sequence remains **NOT LIVE-VERIFIED**
125
+ (issue #812's original ask) — only the surrounding modal's DOM was confirmed
126
+ live in #811; a real `set_input_files` upload was never attempted on this
127
+ account.
128
+
129
+ Requesting a URL already present in the campaign's video set is a safe
130
+ no-op — the library tab's Select control is a TOGGLE, not an idempotent
131
+ "ensure selected" button, so a blind click on an already-selected card
132
+ would have deselected it and Save would have persisted that as a removal.
133
+ `--add-video-url` now checks the current set first and returns without
134
+ opening the modal when the video is already there, and correctly reports
135
+ no change was made (no `AddedVideoUrl` in the JSON output, no false "did
136
+ not save as requested" from the post-save verification pass).
137
+
138
+ **`direct dictionaries get-retargeting-goals` — domain/app → `ExternalId` (#852).**
139
+
140
+ Direct Commander can resolve a site address or app name into the
141
+ `ExternalId` used by `RetargetingList(Type=AUDIENCE) -> Rules[].Arguments[].ExternalId`,
142
+ but `direct-cli` had no equivalent. It calls `getRetargetingGoals`, an
143
+ **undocumented/internal v5 method** not declared in the `dictionaries` WSDL
144
+ (confirmed live: it is absent from the cached WSDL, no `X-Direct-Commander`
145
+ header is required or wanted, and Yandex gives no stability guarantees for
146
+ it). `--name` and `--ids` are mutually exclusive and one is required:
147
+
148
+ ```
149
+ direct dictionaries get-retargeting-goals --name exist.ru
150
+ direct dictionaries get-retargeting-goals --ids 19000000660,19000001592
151
+ ```
152
+
153
+ A `--name` search can return more than one goal `Type` for similar names
154
+ (e.g. `HOST` and `APPLICATION`) — pick the entry with the `Type` your use
155
+ case needs. Unblocks `axisrow/yandex-direct-mcp-plugin#168`.
156
+
157
+ **`direct history get` — read «История изменений» (#837).**
158
+
159
+ The API has no per-field change journal at all: `changes.checkCampaigns`
160
+ reports only `ChangesIn: SELF|CHILDREN|STAT`, `changes.check` returns bare
161
+ lists of changed IDs, and Live v4's `GetEventsLog` logs system events
162
+ (`BannerModerated`, `MoneyOut`, …). None of them answer "who changed this
163
+ field, when, and from what to what" — the question that arises after a bulk
164
+ edit silently drops a strategy goal or a Metrika counter.
165
+
166
+ That history exists only in the web interface, so this is a new browser-backed
167
+ group alongside `masters`, sharing its session stack (`--headful`/
168
+ `--profile-dir`/`--chrome-profile`, no `--login`):
169
+
170
+ ```
171
+ direct history get --campaign-ids 77593206 --categories CAMPAIGN_STRATEGY
172
+ direct history get --date-from 2026-07-01 --date-to 2026-08-13 --limit 50
173
+ ```
174
+
175
+ A separate group rather than a `masters` subcommand because the section is
176
+ account-wide: its records cover ordinary ТГО/ЕПК campaigns, ad groups and ads
177
+ as much as Мастера, and `--campaign-ids` is only a filter over that.
178
+
179
+ Like `masters list` (#639), it does not scrape the DOM — it replays the
180
+ section's own data call, `POST /web-api/user-action-log/api`. Live recon
181
+ established two things that differ from the campaigns grid: `variables.login`
182
+ is required (dropping it returns HTTP 200 with `errors: ["Нет прав"]`, not an
183
+ auth failure), while the URL-level `ulogin` parameter is *not* needed — the
184
+ opposite of `GridCampaigns`, where one's own login as `ulogin` causes HTTP
185
+ 401. Filters are applied server-side; results paginate automatically.
186
+
187
+ Each record carries `Datetime`, `Login`, `ChangeSource`, `Category`,
188
+ `EventType` and `CampaignId`/`CampaignName`, with the event's own fields under
189
+ `Event`, passed through as-is because it is a union with a different shape per
190
+ `EventType`. A dropped strategy goal reads directly off
191
+ `Event.oldStrategy.goalId` → `Event.newStrategy.goalId`.
192
+
193
+ Pagination de-duplicates on `gtid`: the cursor is a timestamp with one-second
194
+ granularity, so consecutive pages overlap on records sharing a second. Live
195
+ measurement over 2026-07-01..08-13 returned 6877 rows for 3257 distinct
196
+ records — an inflation invisible in a two-page check, since the first overlap
197
+ landed on page 3.
198
+
199
+ **`masters update --from-file` / `--masters-json` — update many campaigns in
200
+ one browser session (#834).**
201
+
202
+ `masters update` took exactly one campaign id, so editing 10–20 Мастеров
203
+ кампаний meant 10–20 separate CLI processes, each launching its own Chromium
204
+ and re-entering Yandex. A live run of 17 campaigns (#829) showed that it is
205
+ the repeated re-entry — not the editing — that fails.
206
+
207
+ Batch mode follows the existing API-command convention (`keywords add`,
208
+ #562): a JSONL plan via `--from-file`, or the same array inline via
209
+ `--masters-json`, mutually exclusive with each other and with a positional
210
+ `CAMPAIGN_ID`, and supported only with `--format json`.
211
+
212
+ ```
213
+ direct masters update --from-file plan.jsonl --moderation-statuses
214
+ ```
215
+
216
+ ```jsonl
217
+ {"CampaignId": 713234064, "Headlines": {"1": "Новый заголовок"}}
218
+ {"CampaignId": 713231614, "LandingUrl": "https://lp.example.ru/x"}
219
+ ```
220
+
221
+ Each row is one campaign: PascalCase keys that mirror this command's own
222
+ flags 1:1, with slot numbers and positions **1-based exactly as in
223
+ `--headline "2=..."`**. Rows are fully typed-checked before the first save
224
+ (the same bounds, enum values and duplicate rules Click enforces for the
225
+ flags), so a typo in the last row cannot leave earlier campaigns already
226
+ mutated. An unknown key is an error naming the row and the allowed set,
227
+ never a silent skip.
228
+
229
+ Execution notes:
230
+
231
+ - the whole plan runs over **one** browser session, paced between campaigns
232
+ (`--pacing-ms`, default 1000);
233
+ - a campaign that fails is reported with an `Error` and does not stop the
234
+ rest; the command exits non-zero afterwards (#816/#831 semantics);
235
+ - if the session goes stale mid-plan it is re-opened once and the run
236
+ resumes from the first campaign that was never saved — **an
237
+ already-saved campaign is never saved twice**;
238
+ - `--moderation-statuses` reads each campaign's current statuses after its
239
+ save. Moderation usually has not run yet at that point, so this is a
240
+ snapshot, not a final verdict;
241
+ - `--dry-run` validates the plan (including that local image/video files
242
+ exist) and prints what would be applied without opening a browser. It now
243
+ works for a single `CAMPAIGN_ID` too, printing the same report shape.
244
+
245
+ **`masters get --tracking-params` — read a campaign's UTM string (#824).**
246
+
247
+ `masters get` never returned `TrackingParams`, either set or empty: the
248
+ overview page it reads has no such field at all — "UTM-метки и параметры
249
+ URL" only exists on the edit page, behind the "Дополнительные параметры"
250
+ spoiler. This left `masters update --tracking-params`'s own result as the
251
+ only evidence a save had taken effect, with no independent way to audit a
252
+ batch of campaigns afterwards.
253
+
254
+ A flag on the existing `get`, not a new subcommand — same shape as
255
+ `--moderation-statuses` (#814): one more edit-page navigation, merged into
256
+ the same result rather than paying for it on every `get`.
257
+
258
+ ```
259
+ direct masters get 74736436 --tracking-params
260
+ ```
261
+
262
+ adds `TrackingParams` to the result:
263
+
264
+ - a UTM string is set → the string itself;
265
+ - the field is mounted and genuinely empty → `""`;
266
+ - the section could not be confirmed readable within the wait budget →
267
+ `TrackingParams` is omitted, rather than reporting a misleading `""`
268
+ (mirrors how the DRAFT overview path already omits an unreadable
269
+ `LandingUrl`).
270
+
271
+ Reuses `_read_tracking_params`/`_wait_for_utm_section`, the same reader
272
+ `masters update`'s own verify path already relies on (#769/#774), so `get`
273
+ and `update --tracking-params` agree on what "empty" vs "unreadable" means
274
+ for this field. Without the flag, `get`'s output and page loads are
275
+ unchanged.
276
+
7
277
  **`masters get --moderation-statuses` — detect per-element moderation
8
278
  rejections in Мастер кампаний (#814).**
9
279
 
@@ -107,10 +377,277 @@ All real-page interactions in this recon pass were abandoned via the
107
377
  modal's Cancel button (never Save) — the campaign's saved image set is
108
378
  confirmed unchanged before and after.
109
379
 
380
+ **`masters counters get` — read a Мастер's linked Metrika counters (#842).**
381
+
382
+ Sibling of `masters targetactions get`/`masters audience get`, and the missing
383
+ half of the target-action story: a goal can only be added from a *linked*
384
+ counter's goals, so "`--add-target-action` can't find the goal" and "no counter
385
+ is linked at all" used to look identical from the CLI.
386
+
387
+ ```
388
+ direct masters counters get 713234204
389
+ ```
390
+
391
+ Each entry carries the raw tag `Text` plus the `CounterId`/`Domain` parsed from
392
+ it. `SectionPresent` disambiguates the empty case: `false` means the "Счетчики
393
+ Яндекс Метрики" block is not on the page at all (see #843 below), `true` with
394
+ `Count: 0` is the genuinely counter-less campaign.
395
+
396
+ Two read hazards found live and guarded against, both of which make a
397
+ single-sample read confidently wrong rather than merely early:
398
+
399
+ * The block is briefly present in the initial render and is **unmounted** once
400
+ the form settles on a `max-clicks` campaign (measured on 74736436: `block=1`
401
+ at t+0, `block=0` from t+3s) — the mirror image of this module's usual
402
+ hydration races, where absence is the transient state.
403
+ * A linked tag's own text hydrates in two stages — `"72112213"` at t+0, then
404
+ `"gc.ksamata.ru • 72112213\n30 целей"` from t+4s. The early value has no
405
+ separator, so it parses to `CounterId: null` even though the id is right
406
+ there.
407
+
408
+ Both are handled by polling for a *stable* reading, mirroring
409
+ `_wait_for_target_actions_settled`.
410
+
110
411
  ### Fixed
111
412
 
112
- **`masters` — `_metrika_counter_identity` now normalizes surrounding
113
- whitespace around a non-empty counter id (#809).**
413
+ **`masters login` — no more visible blinking probe tab next to the login
414
+ form (#858).**
415
+
416
+ `login_persistent_session` (`direct_cli/browser/session.py`) detected a
417
+ completed manual login by keeping a second, visible tab open in the same
418
+ browser window and re-navigating it to the campaigns grid once a second
419
+ (`probe.goto(GRID_URL)` + HTML marker scans in a loop) until the session
420
+ appeared or the timeout hit. Next to the form the user was typing into, that
421
+ tab blinked/reloaded every second with no explanation — the motivation (never
422
+ navigate the page the human is typing into, so a half-filled form or a pending
423
+ 2FA prompt is never wiped) was right, but the mechanism was both noisy UX and
424
+ a heavyweight way to ask "is there a session cookie yet".
425
+
426
+ The wait now polls the shared cookie jar instead: `context.cookies()` is read
427
+ every second for Yandex's SSO session cookies (`Session_id`/`sessionid2` —
428
+ the same jar `_chrome_crypto` copies out of Chrome to authenticate a fresh
429
+ context, so their presence *is* the Direct session). No navigation and no
430
+ second tab happen while the user is still typing. Only when a session-cookie
431
+ signature appears (or changes after a rejected attempt — a stale
432
+ `Session_id` left in a reused profile does not re-trigger the probe every
433
+ second) is a short-lived probe tab opened for a single verification
434
+ navigation, running the same fail-closed checks as before (`commit`
435
+ navigation per #686, marker poll per #692, captcha and login-page content
436
+ scans) on the full marker budget rather than a 1s slice, then closed. The
437
+ page the human is typing into is never navigated, exactly as before.
438
+
439
+ Verification outcomes are now a tri-state: authenticated (login recorded,
440
+ command exits), login-page-served (conclusive "jar does not add up to a valid
441
+ session" — wait for different cookies), or inconclusive (unrendered page,
442
+ retried next tick). The one-shot verification's navigation reuses #865's
443
+ `_goto_with_network_retry` unchanged: a transient network-layer abort
444
+ (issue #857's `net::ERR_ABORTED` on a flaky VPN) is retried in place, and a
445
+ connection that stays dead fails as `BrowserNetworkError` — never mistaken
446
+ for an auth problem. A browser/context that dies mid-wait (crash, or the
447
+ user closing the window to abort the login) fails fast with a clean "run
448
+ `direct masters login` again" error instead of a raw PlaywrightError
449
+ traceback — every call such a death can break is CDP-only, so retrying it
450
+ until the timeout could never succeed (review of #858). The session-cookie
451
+ signature also rejects look-alike domains (`notyandex.ru`) that a bare
452
+ `endswith("yandex.ru")` would have accepted.
453
+
454
+ **`masters` status parsing — read the header status element, not the whole
455
+ page body (#848).**
456
+
457
+ `masters archive`/`suspend` refused to act on campaign 107705868 with
458
+ `Could not determine current status … (unrecognised status text) — refusing to
459
+ click blind`, while the same error's own diagnostic reported the page's
460
+ `CampaignHeader.ActionButton.stop` ("Остановить кампанию") as present and
461
+ enabled — i.e. the campaign was plainly ACTIVE. The refusal itself is correct
462
+ behaviour (never click blind), but it left the campaign unreachable by every
463
+ lifecycle command.
464
+
465
+ `_read_status_text` matched four fixed Russian phrases as substrings of
466
+ `inner_text("body")` — the entire page. Live recon 2026-08-14 established that
467
+ `CampaignHeader.Status` carries the status verbatim in *every* state, not just
468
+ DRAFT as this module's older comments assumed: confirmed on real campaigns as
469
+ "Кампания активна" (713234142), "Кампания остановлена" (107705868) and
470
+ "Кампания в\xa0архиве" (100571135). The parser now reads that element first and
471
+ falls back to body text only when it is absent or unrecognised.
472
+
473
+ Matching the header node also removes the substring-over-everything failure
474
+ mode: the dashboard renders free-form moderation prose that mentions the
475
+ campaign (confirmed live on 713234142: "…Кампания запущена из\xa0прошедших
476
+ модерацию элементов объявления"), so which status won depended on which
477
+ unrelated banners happened to be rendered. Marker comparison is additionally
478
+ normalised over case, U+00A0 and collapsed whitespace, so a marker can no
479
+ longer silently never match over an invisible character — a pitfall that had
480
+ already cost a full debugging pass twice (#704, #730).
481
+
482
+ Unrecognised text still returns `None`, so the refuse-to-click-blind guard is
483
+ unchanged.
484
+
485
+ **`masters update --add-metrika-counter` — read the suggestion popup at all,
486
+ and accept a bare counter id (#846).**
487
+
488
+ The counter autocomplete could never match anything, for any input. The option
489
+ search was scoped to `[data-testid="MetrikaCountersTagGroup.editor.listBox"]`,
490
+ but no element carries that testid: live recon (campaign 74099856) found the
491
+ suggestions are portalled into a `dc-Popup` under `<body>` — outside the editor
492
+ subtree entirely — and the `…editor.listBox` string appears only as the *prefix*
493
+ of each option's own testid, `…editor.listBox.{counter id}`. The locator
494
+ resolved to zero elements, `get_by_role("option")` under it always counted 0,
495
+ and every add timed out.
496
+
497
+ The timeout was reported as "No suggestion exactly matching {text} appeared",
498
+ which blamed the caller's text for a selector bug — the operator in #846 spent
499
+ three attempts (bare id, domain, full `label • domain • id` line) trying to
500
+ respell a value that was never being compared against anything.
501
+
502
+ Options are now located directly by that testid prefix, and because the testid
503
+ carries the counter id, `--add-metrika-counter` accepts the **bare numeric id**:
504
+
505
+ ```
506
+ direct masters update <id> --promotion-goal max-conversions \
507
+ --add-metrika-counter 72112213
508
+ ```
509
+
510
+ The id is matched against the option's own id, never as a substring of the
511
+ display text, so a short id cannot silently select a counter whose label or
512
+ domain merely contains those digits. The full suggestion line still works. This
513
+ closes the round trip with `masters counters get`, whose `CounterId` can now be
514
+ passed straight back in — its tag text (`gc.ksamata.ru • 72112213`) has a
515
+ different shape from the suggestion (`Ксамата Директ • gc.ksamata.ru •
516
+ 72112213`) and never could be.
517
+
518
+ Both failure messages now diagnose rather than accuse: a non-match lists what
519
+ Yandex actually offered, and an empty list points at account access to the
520
+ counter, since the search runs server-side on partial input (typing `7211`
521
+ surfaces the full suggestion).
522
+
523
+ Live-verified end to end on campaign 74099856: the counter links, its tag
524
+ renders, and the "Счетчик отсутствует" warning under «Целевые действия» clears.
525
+ That recon was deliberately not saved.
526
+
527
+ **`masters counters get` — stop reporting an unstable `SectionPresent` (#846).**
528
+
529
+ The same campaign could report `SectionPresent: true` in one run and `false` in
530
+ the next with no update in between. `_metrika_counters_section_present` already
531
+ polled for a *stable* answer rather than trusting one sample — the block is
532
+ briefly rendered at t+0 on a max-clicks campaign and unmounted once the form
533
+ settles (~t+3000ms live) — but its stability window was calibrated shorter than
534
+ the transient it guards against.
535
+
536
+ At 500ms × 3 ticks the loop settles after 3 samples spanning 2 gaps, ~1.0s: it
537
+ certified "stably present" a full 2 seconds *before* the unmount. Whether a run
538
+ saw `true` or `false` came down to how the poll ticks happened to line up
539
+ against the flip.
540
+
541
+ The window is now 500ms × 8 (~3.5s of agreement) against its own 8s budget,
542
+ rather than borrowing the autocomplete's 5s. The budget must exceed *flip +
543
+ window*, not just the window, because the run of agreement only starts after
544
+ the flip — at 5s the max-clicks case never settled at all and reached the right
545
+ answer only through the "report the last read" fallback, which is not a
546
+ property worth depending on. Both outcomes now settle deliberately:
547
+ max-conversions at ~3.5s, max-clicks at ~6.5s.
548
+
549
+ The presence locator is also re-resolved per sample instead of once before the
550
+ loop. Playwright locators are lazy and re-query on each `count()`, so this was
551
+ not itself the live bug, but a loop whose entire purpose is watching the DOM
552
+ change should not depend on that implicitly.
553
+
554
+ Regression tests model the live timeline against the fake clock and fail on the
555
+ old constants.
556
+
557
+ **`masters update` — apply Metrika counters BEFORE target actions (#844).**
558
+
559
+ `update_master` linked counters ~200 lines *after* it added target actions, so
560
+ `--add-metrika-counter` together with `--add-target-action` in one call was
561
+ impossible by construction: the "Добавить" popup only lists goals belonging to
562
+ an already-linked counter, so the goal was looked up against the counter set as
563
+ it stood before the counter was attached, and the call failed with "Could not
564
+ find goal N in the 'Добавить' target-action popup".
565
+
566
+ This is an ordering bug, not a hydration race — waiting longer at the old
567
+ position never helped, because the counter simply was not linked yet.
568
+ Live-confirmed on campaign 74736436: the same call that used to fail now links
569
+ the counter and adds the goal, and the restore path
570
+
571
+ ```
572
+ direct masters update <id> --promotion-goal max-conversions \
573
+ --add-metrika-counter "…" --add-target-action "<goal_id>=150"
574
+ ```
575
+
576
+ completes end to end — which matters because none of its three parts can be
577
+ applied on their own (`max-conversions` will not save without a goal, and a goal
578
+ cannot be added without a counter).
579
+
580
+ **`masters update --add-metrika-counter` — name the promotion-goal
581
+ precondition instead of blaming markup drift (#843).**
582
+
583
+ The command failed with "Could not click the 'Счетчики Яндекс Метрики' launcher
584
+ button — Yandex may have changed the page's markup", sending the operator
585
+ hunting for a drifted testid that had not drifted. Live recon confirmed the
586
+ selector is correct and the section simply does not exist yet: on campaign
587
+ 74736436 (goal `max-clicks`) both the launcher and the whole
588
+ `MetrikaCountersBlock` settle to absent, and both mount once the goal is
589
+ switched to `max-conversions`. The error now says so and names the fix — pass
590
+ `--promotion-goal max-conversions` in the same call, since it is applied before
591
+ counters.
592
+
593
+ **`masters update --promotion-goal` — retry a swallowed dropdown click, and
594
+ report Yandex's own rejection text (#840).**
595
+
596
+ `--promotion-goal max-conversions` failed with "Could not find the 'Максимум
597
+ целевых действий' option in the 'Цель продвижения' dropdown — Yandex may have
598
+ changed the page's markup" on campaigns whose markup had not changed at all.
599
+ Live recon (campaign 74736436) confirmed the heading, the trigger XPath and
600
+ the `CampaignTargetSelect.TargetSelect.ListBox.INVOLVED_CONVERSION` testid all
601
+ still match; instrumenting a failing run found the real cause — the same
602
+ menu-trigger hydration race as #723/#725. The trigger already matched
603
+ (`count=1`) while un-hydrated (`aria-expanded=None`, empty `inner_text()`), so
604
+ Playwright's actionability check passed, the click landed on a node with no
605
+ React handler attached, and nothing opened. Because the option rows exist in
606
+ the DOM only while the dropdown is open, the follow-up `option.click()` then
607
+ misreported a hydration race as a markup change. `_set_promotion_goal` now
608
+ opens the dropdown through `_click_and_wait_for_popup` (retrying the open),
609
+ like the other menu/modal triggers in this module.
610
+
611
+ Saves refused by Yandex's client-side validation are also no longer reported
612
+ with a generic "Yandex may have rejected the value". `update_master` now reads
613
+ the inline error text before `_verify_saved`'s reload discards it and quotes it
614
+ back — closing the follow-up the module docstring's "Save verification"
615
+ paragraph had left open. On a campaign with no Metrika goals, switching to
616
+ `max-conversions` is refused outright (the save request is never sent), and the
617
+ error now names why: "Добавьте хотя бы одну цель для сайта, чтобы создать и
618
+ запустить кампанию." Note that the "Счетчики Яндекс Метрики" section itself
619
+ only renders once the promotion goal is `max-conversions`, so a campaign that
620
+ lost both needs the counter, the goal and the promotion goal set in one call.
621
+
622
+ **`masters launch`/`masters update --launch` — diagnose an empty "Целевые
623
+ действия" table instead of a generic redirect timeout (#823).**
624
+
625
+ Yandex silently refuses to save/launch a DRAFT campaign with no Яндекс
626
+ Метрика conversion goal ("Добавьте хотя бы одну цель для сайта, чтобы
627
+ создать и запустить кампанию") — same failure mode `create_master` already
628
+ diagnoses (#777), but the edit page's launch/save-as-draft click
629
+ (`_click_draft_terminal_button`, shared by `launch_master` and
630
+ `update_master --launch`) previously only reported the generic "Yandex did
631
+ not redirect away from the edit page" message on timeout, with no clue as
632
+ to why. Neither terminal button ever gains a disabled/`aria-disabled`
633
+ marker in this state, so a caller had to rediscover the root cause by hand
634
+ each time (live-reported in #823: a cloned Мастер кампаний whose landing
635
+ URL was then changed via `masters update --landing-url` lost its inherited
636
+ goals, and `masters launch` failed identically on three consecutive
637
+ attempts, including `--headful`).
638
+
639
+ On timeout, `_click_draft_terminal_button` now re-reads the "Целевые
640
+ действия" table (reusing `_wait_for_target_actions_settled`/
641
+ `_read_target_actions_or_none`, the same reader `create_master`'s pre-click
642
+ gate uses — confirmed to be the identical widget on both pages) and raises
643
+ a specific error naming the empty goals table when that's the cause,
644
+ falling back to the original generic message otherwise. This diagnosis
645
+ itself polls for up to another ~25s after the original redirect deadline
646
+ already elapsed, so a merely-slow (not stuck) Yandex redirect can land
647
+ *during* the diagnosis; `page.url` is re-checked immediately before each
648
+ `raise` so a redirect landing mid-diagnosis is treated as success rather
649
+ than reported as a false failure on an already-successful, no-rollback
650
+ launch/save (cycle-review PR #826, Codex).
114
651
 
115
652
  The suggestion-text format (`"{label} • {domain/path} • {numeric counter
116
653
  id}"`) and the read-back tag-display format (`"{domain} • {numeric
@@ -331,6 +868,40 @@ Code review on #782/PR #784 found two edge cases in `delete_master`
331
868
 
332
869
  ### BREAKING CHANGES
333
870
 
871
+ **`masters` batch commands stream JSONL instead of a pretty JSON array (#866).**
872
+
873
+ `masters get|suspend|resume|launch|archive` with several campaign IDs used to
874
+ buffer every per-ID outcome in memory and print a single pretty JSON array
875
+ after the whole loop. A process killed mid-batch (a wrapper's subprocess
876
+ timeout — the Yandex Direct MCP plugin runs these with a 180 s default — or
877
+ OOM/crash) took every already-applied mutation's report with it: a caller of
878
+ irreversible `launch`/`archive` had no way to learn which campaigns had
879
+ already changed, and a blind retry re-applied live mutations. Transport-level
880
+ sibling of #766/#816, which fixed the in-process cases.
881
+
882
+ - With `--format json` (the default), each ID's outcome — the resulting row,
883
+ or its `{"CampaignId": ..., "Error": ...}` entry — is printed the moment it
884
+ is known, as one compact JSON object per line (JSONL), with an explicit
885
+ `flush()` after every record. stdout in a non-tty subprocess is
886
+ block-buffered, so without the per-record flush nothing would survive a
887
+ kill; on POSIX `subprocess.run` attaches the flushed partial stdout to
888
+ `TimeoutExpired`, so wrappers (and the plugin's
889
+ `CliTimeoutError.partial_stdout`) can now reconcile a partial batch without
890
+ any plugin-side change.
891
+ - **Migration:** parse stdout line-by-line instead of as one JSON document
892
+ (`for line in stdout.splitlines(): json.loads(line)`). Single-ID output
893
+ remains a single JSON object (one line, still `json.loads`-able whole); a
894
+ zero-ID batch prints `[]`. An empty batch keeps `json.loads`-compatibility
895
+ for that case only.
896
+ - `_with_session`'s stale-session self-heal re-runs the whole operation, so
897
+ IDs completed before the auth failure appear once per real attempt — every
898
+ line is a genuine outcome, and the trailing lines are the final attempt's.
899
+ - `--output` gets the same per-record durability: the file is opened once and
900
+ each record is written+flushed into it as its ID completes (previously a
901
+ single pretty array written at the end).
902
+ - Non-JSON formats (`text`/`table`/`csv`/`tsv`) are unchanged — consolidated
903
+ end-of-run output; they are human-readable reports, not machine streams.
904
+
334
905
  **`masters add` now requires `--add-target-action` (#777).**
335
906
 
336
907
  `masters add` previously could not create a campaign at all. Yandex's create