direct-cli 0.4.3__tar.gz → 0.5.1__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 (412) hide show
  1. {direct_cli-0.4.3 → direct_cli-0.5.1}/.github/workflows/quality.yml +3 -1
  2. {direct_cli-0.4.3 → direct_cli-0.5.1}/.gitignore +3 -0
  3. {direct_cli-0.4.3 → direct_cli-0.5.1}/CHANGELOG.md +461 -0
  4. {direct_cli-0.4.3 → direct_cli-0.5.1}/CLAUDE.md +16 -3
  5. {direct_cli-0.4.3/direct_cli.egg-info → direct_cli-0.5.1}/PKG-INFO +225 -4
  6. direct_cli-0.4.3/PKG-INFO → direct_cli-0.5.1/README.md +218 -44
  7. {direct_cli-0.4.3 → direct_cli-0.5.1}/direct_cli/_bidding_strategy.py +62 -235
  8. direct_cli-0.5.1/direct_cli/_captcha.py +43 -0
  9. {direct_cli-0.4.3 → direct_cli-0.5.1}/direct_cli/api.py +20 -0
  10. direct_cli-0.5.1/direct_cli/browser/__init__.py +24 -0
  11. direct_cli-0.5.1/direct_cli/browser/_chrome_crypto.py +458 -0
  12. direct_cli-0.5.1/direct_cli/browser/diagnostics.py +287 -0
  13. direct_cli-0.5.1/direct_cli/browser/masters.py +1497 -0
  14. direct_cli-0.5.1/direct_cli/browser/session.py +546 -0
  15. direct_cli-0.5.1/direct_cli/browser/store.py +214 -0
  16. {direct_cli-0.4.3 → direct_cli-0.5.1}/direct_cli/cli.py +19 -2
  17. direct_cli-0.5.1/direct_cli/commands/_campaigns_base.py +798 -0
  18. direct_cli-0.5.1/direct_cli/commands/_campaigns_cpm_banner.py +127 -0
  19. direct_cli-0.5.1/direct_cli/commands/_campaigns_dynamic.py +290 -0
  20. direct_cli-0.5.1/direct_cli/commands/_campaigns_mobile_app.py +198 -0
  21. direct_cli-0.5.1/direct_cli/commands/_campaigns_smart.py +429 -0
  22. direct_cli-0.5.1/direct_cli/commands/_campaigns_text.py +276 -0
  23. direct_cli-0.5.1/direct_cli/commands/_campaigns_unified.py +270 -0
  24. direct_cli-0.5.1/direct_cli/commands/_execute.py +30 -0
  25. direct_cli-0.5.1/direct_cli/commands/_get.py +259 -0
  26. {direct_cli-0.4.3 → direct_cli-0.5.1}/direct_cli/commands/_lifecycle.py +15 -0
  27. direct_cli-0.5.1/direct_cli/commands/adextensions.py +81 -0
  28. {direct_cli-0.4.3 → direct_cli-0.5.1}/direct_cli/commands/adgroups.py +122 -182
  29. {direct_cli-0.4.3 → direct_cli-0.5.1}/direct_cli/commands/adimages.py +23 -57
  30. direct_cli-0.5.1/direct_cli/commands/ads/__init__.py +22 -0
  31. direct_cli-0.5.1/direct_cli/commands/ads/_cli.py +1178 -0
  32. direct_cli-0.5.1/direct_cli/commands/ads/base.py +236 -0
  33. direct_cli-0.5.1/direct_cli/commands/ads/batch.py +467 -0
  34. direct_cli-0.5.1/direct_cli/commands/ads/builder.py +202 -0
  35. direct_cli-0.5.1/direct_cli/commands/ads/mobile_app.py +88 -0
  36. direct_cli-0.5.1/direct_cli/commands/ads/objects.py +823 -0
  37. direct_cli-0.5.1/direct_cli/commands/ads/responsive.py +96 -0
  38. direct_cli-0.5.1/direct_cli/commands/ads/shopping.py +142 -0
  39. direct_cli-0.5.1/direct_cli/commands/ads/text.py +153 -0
  40. direct_cli-0.5.1/direct_cli/commands/advideos.py +61 -0
  41. {direct_cli-0.4.3 → direct_cli-0.5.1}/direct_cli/commands/agencyclients.py +82 -146
  42. {direct_cli-0.4.3 → direct_cli-0.5.1}/direct_cli/commands/audiencetargets.py +71 -92
  43. {direct_cli-0.4.3 → direct_cli-0.5.1}/direct_cli/commands/bidmodifiers.py +141 -221
  44. {direct_cli-0.4.3 → direct_cli-0.5.1}/direct_cli/commands/bids.py +45 -74
  45. direct_cli-0.5.1/direct_cli/commands/browser_session.py +175 -0
  46. direct_cli-0.5.1/direct_cli/commands/businesses.py +22 -0
  47. {direct_cli-0.4.3 → direct_cli-0.5.1}/direct_cli/commands/campaigns.py +203 -1884
  48. {direct_cli-0.4.3 → direct_cli-0.5.1}/direct_cli/commands/clients.py +61 -120
  49. direct_cli-0.5.1/direct_cli/commands/creatives.py +88 -0
  50. {direct_cli-0.4.3 → direct_cli-0.5.1}/direct_cli/commands/dictionaries.py +4 -3
  51. {direct_cli-0.4.3 → direct_cli-0.5.1}/direct_cli/commands/dynamicads.py +33 -96
  52. {direct_cli-0.4.3 → direct_cli-0.5.1}/direct_cli/commands/dynamicfeedadtargets.py +41 -93
  53. {direct_cli-0.4.3 → direct_cli-0.5.1}/direct_cli/commands/feeds.py +31 -105
  54. {direct_cli-0.4.3 → direct_cli-0.5.1}/direct_cli/commands/keywordbids.py +3 -14
  55. {direct_cli-0.4.3 → direct_cli-0.5.1}/direct_cli/commands/keywords.py +76 -134
  56. direct_cli-0.5.1/direct_cli/commands/masters.py +794 -0
  57. {direct_cli-0.4.3 → direct_cli-0.5.1}/direct_cli/commands/negativekeywordsharedsets.py +14 -62
  58. {direct_cli-0.4.3 → direct_cli-0.5.1}/direct_cli/commands/retargeting.py +18 -61
  59. {direct_cli-0.4.3 → direct_cli-0.5.1}/direct_cli/commands/sitelinks.py +23 -78
  60. {direct_cli-0.4.3 → direct_cli-0.5.1}/direct_cli/commands/smartadtargets.py +34 -103
  61. {direct_cli-0.4.3 → direct_cli-0.5.1}/direct_cli/commands/strategies.py +126 -256
  62. direct_cli-0.5.1/direct_cli/commands/trackingparams.py +24 -0
  63. direct_cli-0.5.1/direct_cli/commands/turbopages.py +44 -0
  64. {direct_cli-0.4.3 → direct_cli-0.5.1}/direct_cli/commands/v4account.py +19 -46
  65. {direct_cli-0.4.3 → direct_cli-0.5.1}/direct_cli/commands/v4adimage.py +17 -27
  66. {direct_cli-0.4.3 → direct_cli-0.5.1}/direct_cli/commands/v4finance.py +20 -55
  67. {direct_cli-0.4.3 → direct_cli-0.5.1}/direct_cli/commands/v4goals.py +7 -9
  68. {direct_cli-0.4.3 → direct_cli-0.5.1}/direct_cli/commands/v4tags.py +7 -25
  69. {direct_cli-0.4.3 → direct_cli-0.5.1}/direct_cli/commands/vcards.py +12 -52
  70. {direct_cli-0.4.3 → direct_cli-0.5.1}/direct_cli/output.py +66 -26
  71. {direct_cli-0.4.3 → direct_cli-0.5.1}/direct_cli/reports_coverage.py +8 -9
  72. {direct_cli-0.4.3 → direct_cli-0.5.1}/direct_cli/smoke_matrix.py +43 -1
  73. direct_cli-0.5.1/direct_cli/tracking_params.py +123 -0
  74. {direct_cli-0.4.3 → direct_cli-0.5.1}/direct_cli/translations/ads.json +1 -1
  75. {direct_cli-0.4.3 → direct_cli-0.5.1}/direct_cli/translations/common.json +0 -1
  76. {direct_cli-0.4.3 → direct_cli-0.5.1}/direct_cli/translations/v4finance.json +0 -1
  77. {direct_cli-0.4.3 → direct_cli-0.5.1}/direct_cli/utils.py +70 -7
  78. direct_cli-0.5.1/direct_cli/v4/parse.py +135 -0
  79. {direct_cli-0.4.3 → direct_cli-0.5.1}/direct_cli/wsdl_coverage.py +19 -9
  80. direct_cli-0.4.3/README.md → direct_cli-0.5.1/direct_cli.egg-info/PKG-INFO +265 -1
  81. {direct_cli-0.4.3 → direct_cli-0.5.1}/direct_cli.egg-info/SOURCES.txt +54 -1
  82. {direct_cli-0.4.3 → direct_cli-0.5.1}/direct_cli.egg-info/requires.txt +7 -2
  83. {direct_cli-0.4.3 → direct_cli-0.5.1}/pyproject.toml +22 -3
  84. {direct_cli-0.4.3 → direct_cli-0.5.1}/scripts/build_api_coverage_report.py +20 -6
  85. {direct_cli-0.4.3 → direct_cli-0.5.1}/scripts/check_all_docs_urls.py +2 -0
  86. direct_cli-0.5.1/scripts/measure_criteria_limits.py +213 -0
  87. {direct_cli-0.4.3 → direct_cli-0.5.1}/scripts/test_dangerous_commands.sh +19 -0
  88. {direct_cli-0.4.3 → direct_cli-0.5.1}/tests/conftest.py +42 -0
  89. direct_cli-0.5.1/tests/fixtures/masters_grid_campaigns.json +72 -0
  90. direct_cli-0.5.1/tests/fixtures/masters_wizard_create.html +279 -0
  91. direct_cli-0.5.1/tests/fixtures/masters_wizard_edit_stage_a.html +137 -0
  92. direct_cli-0.5.1/tests/fixtures/masters_wizard_overview.html +118 -0
  93. {direct_cli-0.4.3 → direct_cli-0.5.1}/tests/test_api_coverage.py +112 -31
  94. {direct_cli-0.4.3 → direct_cli-0.5.1}/tests/test_audit_wire_shape.py +6 -13
  95. {direct_cli-0.4.3 → direct_cli-0.5.1}/tests/test_cassette_integrity.py +3 -9
  96. direct_cli-0.5.1/tests/test_chrome_crypto.py +452 -0
  97. {direct_cli-0.4.3 → direct_cli-0.5.1}/tests/test_cli.py +1 -1
  98. {direct_cli-0.4.3 → direct_cli-0.5.1}/tests/test_cli_contract.py +8 -0
  99. {direct_cli-0.4.3 → direct_cli-0.5.1}/tests/test_comprehensive.py +3 -0
  100. direct_cli-0.5.1/tests/test_dry_run.py +132 -0
  101. direct_cli-0.5.1/tests/test_dry_run_adgroups.py +2070 -0
  102. direct_cli-0.5.1/tests/test_dry_run_ads.py +3968 -0
  103. direct_cli-0.5.1/tests/test_dry_run_bids.py +892 -0
  104. direct_cli-0.5.1/tests/test_dry_run_campaigns.py +3657 -0
  105. direct_cli-0.5.1/tests/test_dry_run_clients.py +691 -0
  106. direct_cli-0.5.1/tests/test_dry_run_common.py +768 -0
  107. direct_cli-0.5.1/tests/test_dry_run_extensions.py +940 -0
  108. direct_cli-0.5.1/tests/test_dry_run_keywords.py +970 -0
  109. direct_cli-0.5.1/tests/test_dry_run_shared.py +66 -0
  110. direct_cli-0.5.1/tests/test_dry_run_strategies.py +608 -0
  111. direct_cli-0.5.1/tests/test_dry_run_strategy_smart.py +1949 -0
  112. direct_cli-0.5.1/tests/test_dry_run_strategy_text.py +5149 -0
  113. direct_cli-0.5.1/tests/test_dry_run_strategy_unified.py +2359 -0
  114. direct_cli-0.5.1/tests/test_dry_run_targets.py +612 -0
  115. {direct_cli-0.4.3 → direct_cli-0.5.1}/tests/test_integration.py +69 -0
  116. {direct_cli-0.4.3 → direct_cli-0.5.1}/tests/test_integration_write.py +6 -6
  117. direct_cli-0.5.1/tests/test_masters.py +3426 -0
  118. direct_cli-0.5.1/tests/test_playwright_session.py +306 -0
  119. direct_cli-0.5.1/tests/test_reference_output.py +104 -0
  120. {direct_cli-0.4.3 → direct_cli-0.5.1}/tests/test_reports_parsing.py +0 -1
  121. {direct_cli-0.4.3 → direct_cli-0.5.1}/tests/test_smoke_matrix.py +1 -1
  122. {direct_cli-0.4.3 → direct_cli-0.5.1}/tests/test_transport_contract.py +3 -3
  123. {direct_cli-0.4.3 → direct_cli-0.5.1}/tests/test_v4_live_contracts.py +6 -6
  124. {direct_cli-0.4.3 → direct_cli-0.5.1}/tests/test_v5_live_write.py +12 -12
  125. direct_cli-0.4.3/direct_cli/commands/adextensions.py +0 -124
  126. direct_cli-0.4.3/direct_cli/commands/ads.py +0 -3133
  127. direct_cli-0.4.3/direct_cli/commands/advideos.py +0 -96
  128. direct_cli-0.4.3/direct_cli/commands/businesses.py +0 -57
  129. direct_cli-0.4.3/direct_cli/commands/creatives.py +0 -150
  130. direct_cli-0.4.3/direct_cli/commands/turbopages.py +0 -67
  131. direct_cli-0.4.3/tests/test_dry_run.py +0 -24849
  132. {direct_cli-0.4.3 → direct_cli-0.5.1}/.env.example +0 -0
  133. {direct_cli-0.4.3 → direct_cli-0.5.1}/.github/copilot-instructions.md +0 -0
  134. {direct_cli-0.4.3 → direct_cli-0.5.1}/.github/workflows/api-coverage.yml +0 -0
  135. {direct_cli-0.4.3 → direct_cli-0.5.1}/.github/workflows/claude.yml +0 -0
  136. {direct_cli-0.4.3 → direct_cli-0.5.1}/AGENTS.md +0 -0
  137. {direct_cli-0.4.3 → direct_cli-0.5.1}/MANIFEST.in +0 -0
  138. {direct_cli-0.4.3 → direct_cli-0.5.1}/direct_cli/__init__.py +0 -0
  139. {direct_cli-0.4.3 → direct_cli-0.5.1}/direct_cli/_autotargeting.py +0 -0
  140. {direct_cli-0.4.3 → direct_cli-0.5.1}/direct_cli/_deprecated.py +0 -0
  141. {direct_cli-0.4.3 → direct_cli-0.5.1}/direct_cli/_flag_validation.py +0 -0
  142. {direct_cli-0.4.3 → direct_cli-0.5.1}/direct_cli/_smoke_probes.py +0 -0
  143. {direct_cli-0.4.3 → direct_cli-0.5.1}/direct_cli/_vendor/__init__.py +0 -0
  144. {direct_cli-0.4.3 → direct_cli-0.5.1}/direct_cli/_vendor/tapi_yandex_direct/__init__.py +0 -0
  145. {direct_cli-0.4.3 → direct_cli-0.5.1}/direct_cli/_vendor/tapi_yandex_direct/endpoints.py +0 -0
  146. {direct_cli-0.4.3 → direct_cli-0.5.1}/direct_cli/_vendor/tapi_yandex_direct/exceptions.py +0 -0
  147. {direct_cli-0.4.3 → direct_cli-0.5.1}/direct_cli/_vendor/tapi_yandex_direct/resource_mapping.py +0 -0
  148. {direct_cli-0.4.3 → direct_cli-0.5.1}/direct_cli/_vendor/tapi_yandex_direct/tapi_yandex_direct.py +0 -0
  149. {direct_cli-0.4.3 → direct_cli-0.5.1}/direct_cli/_vendor/tapi_yandex_direct/tapi_yandex_direct.pyi +0 -0
  150. {direct_cli-0.4.3 → direct_cli-0.5.1}/direct_cli/_vendor/tapi_yandex_direct/v4/__init__.py +0 -0
  151. {direct_cli-0.4.3 → direct_cli-0.5.1}/direct_cli/_vendor/tapi_yandex_direct/v4/adapter.py +0 -0
  152. {direct_cli-0.4.3 → direct_cli-0.5.1}/direct_cli/_vendor/tapi_yandex_direct/v4/adapter.pyi +0 -0
  153. {direct_cli-0.4.3 → direct_cli-0.5.1}/direct_cli/_vendor/tapi_yandex_direct/v4/resource_mapping.py +0 -0
  154. {direct_cli-0.4.3 → direct_cli-0.5.1}/direct_cli/auth.py +0 -0
  155. {direct_cli-0.4.3 → direct_cli-0.5.1}/direct_cli/commands/__init__.py +0 -0
  156. {direct_cli-0.4.3 → direct_cli-0.5.1}/direct_cli/commands/_batch.py +0 -0
  157. {direct_cli-0.4.3 → direct_cli-0.5.1}/direct_cli/commands/auth.py +0 -0
  158. {direct_cli-0.4.3 → direct_cli-0.5.1}/direct_cli/commands/balance.py +0 -0
  159. {direct_cli-0.4.3 → direct_cli-0.5.1}/direct_cli/commands/changes.py +0 -0
  160. {direct_cli-0.4.3 → direct_cli-0.5.1}/direct_cli/commands/keywordsresearch.py +0 -0
  161. {direct_cli-0.4.3 → direct_cli-0.5.1}/direct_cli/commands/leads.py +0 -0
  162. {direct_cli-0.4.3 → direct_cli-0.5.1}/direct_cli/commands/reports.py +0 -0
  163. {direct_cli-0.4.3 → direct_cli-0.5.1}/direct_cli/commands/v4events.py +0 -0
  164. {direct_cli-0.4.3 → direct_cli-0.5.1}/direct_cli/commands/v4forecast.py +0 -0
  165. {direct_cli-0.4.3 → direct_cli-0.5.1}/direct_cli/commands/v4keywords.py +0 -0
  166. {direct_cli-0.4.3 → direct_cli-0.5.1}/direct_cli/commands/v4shells.py +0 -0
  167. {direct_cli-0.4.3 → direct_cli-0.5.1}/direct_cli/commands/v4wordstat.py +0 -0
  168. {direct_cli-0.4.3 → direct_cli-0.5.1}/direct_cli/i18n.py +0 -0
  169. {direct_cli-0.4.3 → direct_cli-0.5.1}/direct_cli/translations/adextensions.json +0 -0
  170. {direct_cli-0.4.3 → direct_cli-0.5.1}/direct_cli/translations/adgroups.json +0 -0
  171. {direct_cli-0.4.3 → direct_cli-0.5.1}/direct_cli/translations/adimages.json +0 -0
  172. {direct_cli-0.4.3 → direct_cli-0.5.1}/direct_cli/translations/advideos.json +0 -0
  173. {direct_cli-0.4.3 → direct_cli-0.5.1}/direct_cli/translations/agencyclients.json +0 -0
  174. {direct_cli-0.4.3 → direct_cli-0.5.1}/direct_cli/translations/audiencetargets.json +0 -0
  175. {direct_cli-0.4.3 → direct_cli-0.5.1}/direct_cli/translations/auth.json +0 -0
  176. {direct_cli-0.4.3 → direct_cli-0.5.1}/direct_cli/translations/balance.json +0 -0
  177. {direct_cli-0.4.3 → direct_cli-0.5.1}/direct_cli/translations/bidmodifiers.json +0 -0
  178. {direct_cli-0.4.3 → direct_cli-0.5.1}/direct_cli/translations/bids.json +0 -0
  179. {direct_cli-0.4.3 → direct_cli-0.5.1}/direct_cli/translations/businesses.json +0 -0
  180. {direct_cli-0.4.3 → direct_cli-0.5.1}/direct_cli/translations/campaigns.json +0 -0
  181. {direct_cli-0.4.3 → direct_cli-0.5.1}/direct_cli/translations/changes.json +0 -0
  182. {direct_cli-0.4.3 → direct_cli-0.5.1}/direct_cli/translations/clients.json +0 -0
  183. {direct_cli-0.4.3 → direct_cli-0.5.1}/direct_cli/translations/creatives.json +0 -0
  184. {direct_cli-0.4.3 → direct_cli-0.5.1}/direct_cli/translations/dictionaries.json +0 -0
  185. {direct_cli-0.4.3 → direct_cli-0.5.1}/direct_cli/translations/dynamicads.json +0 -0
  186. {direct_cli-0.4.3 → direct_cli-0.5.1}/direct_cli/translations/dynamicfeedadtargets.json +0 -0
  187. {direct_cli-0.4.3 → direct_cli-0.5.1}/direct_cli/translations/feeds.json +0 -0
  188. {direct_cli-0.4.3 → direct_cli-0.5.1}/direct_cli/translations/keywordbids.json +0 -0
  189. {direct_cli-0.4.3 → direct_cli-0.5.1}/direct_cli/translations/keywords.json +0 -0
  190. {direct_cli-0.4.3 → direct_cli-0.5.1}/direct_cli/translations/keywordsresearch.json +0 -0
  191. {direct_cli-0.4.3 → direct_cli-0.5.1}/direct_cli/translations/leads.json +0 -0
  192. {direct_cli-0.4.3 → direct_cli-0.5.1}/direct_cli/translations/negativekeywordsharedsets.json +0 -0
  193. {direct_cli-0.4.3 → direct_cli-0.5.1}/direct_cli/translations/reports.json +0 -0
  194. {direct_cli-0.4.3 → direct_cli-0.5.1}/direct_cli/translations/retargeting.json +0 -0
  195. {direct_cli-0.4.3 → direct_cli-0.5.1}/direct_cli/translations/sitelinks.json +0 -0
  196. {direct_cli-0.4.3 → direct_cli-0.5.1}/direct_cli/translations/smartadtargets.json +0 -0
  197. {direct_cli-0.4.3 → direct_cli-0.5.1}/direct_cli/translations/strategies.json +0 -0
  198. {direct_cli-0.4.3 → direct_cli-0.5.1}/direct_cli/translations/turbopages.json +0 -0
  199. {direct_cli-0.4.3 → direct_cli-0.5.1}/direct_cli/translations/v4account.json +0 -0
  200. {direct_cli-0.4.3 → direct_cli-0.5.1}/direct_cli/translations/v4adimage.json +0 -0
  201. {direct_cli-0.4.3 → direct_cli-0.5.1}/direct_cli/translations/v4events.json +0 -0
  202. {direct_cli-0.4.3 → direct_cli-0.5.1}/direct_cli/translations/v4forecast.json +0 -0
  203. {direct_cli-0.4.3 → direct_cli-0.5.1}/direct_cli/translations/v4goals.json +0 -0
  204. {direct_cli-0.4.3 → direct_cli-0.5.1}/direct_cli/translations/v4keywords.json +0 -0
  205. {direct_cli-0.4.3 → direct_cli-0.5.1}/direct_cli/translations/v4shells.json +0 -0
  206. {direct_cli-0.4.3 → direct_cli-0.5.1}/direct_cli/translations/v4tags.json +0 -0
  207. {direct_cli-0.4.3 → direct_cli-0.5.1}/direct_cli/translations/v4wordstat.json +0 -0
  208. {direct_cli-0.4.3 → direct_cli-0.5.1}/direct_cli/translations/vcards.json +0 -0
  209. {direct_cli-0.4.3 → direct_cli-0.5.1}/direct_cli/v4/__init__.py +0 -0
  210. {direct_cli-0.4.3 → direct_cli-0.5.1}/direct_cli/v4/emit.py +0 -0
  211. {direct_cli-0.4.3 → direct_cli-0.5.1}/direct_cli/v4/money.py +0 -0
  212. {direct_cli-0.4.3 → direct_cli-0.5.1}/direct_cli/v4_contracts.py +0 -0
  213. {direct_cli-0.4.3 → direct_cli-0.5.1}/direct_cli.egg-info/dependency_links.txt +0 -0
  214. {direct_cli-0.4.3 → direct_cli-0.5.1}/direct_cli.egg-info/entry_points.txt +0 -0
  215. {direct_cli-0.4.3 → direct_cli-0.5.1}/direct_cli.egg-info/top_level.txt +0 -0
  216. {direct_cli-0.4.3 → direct_cli-0.5.1}/docs/audits/API_COVERAGE.md +0 -0
  217. {direct_cli-0.4.3 → direct_cli-0.5.1}/docs/audits/PROJECT_WIRE_SHAPE_AUDIT_2026-05-30.md +0 -0
  218. {direct_cli-0.4.3 → direct_cli-0.5.1}/docs/audits/WIRE_SHAPE_TRIAGE_2026-05-30.md +0 -0
  219. {direct_cli-0.4.3 → direct_cli-0.5.1}/docs/audits/issue-198-mutating-wsdl-audit.md +0 -0
  220. {direct_cli-0.4.3 → direct_cli-0.5.1}/docs/audits/wire_shape.json +0 -0
  221. {direct_cli-0.4.3 → direct_cli-0.5.1}/docs/superpowers/plans/2026-04-12-issue-32-completion.md +0 -0
  222. {direct_cli-0.4.3 → direct_cli-0.5.1}/docs/superpowers/specs/2026-04-23-vendor-tapi-yandex-direct-design.md +0 -0
  223. {direct_cli-0.4.3 → direct_cli-0.5.1}/scripts/anonymize_cassettes.py +0 -0
  224. {direct_cli-0.4.3 → direct_cli-0.5.1}/scripts/audit_wire_shape.py +0 -0
  225. {direct_cli-0.4.3 → direct_cli-0.5.1}/scripts/build_api_coverage_checklist.py +0 -0
  226. {direct_cli-0.4.3 → direct_cli-0.5.1}/scripts/build_wsdl_optional_field_audit.py +0 -0
  227. {direct_cli-0.4.3 → direct_cli-0.5.1}/scripts/check_reports_drift.py +0 -0
  228. {direct_cli-0.4.3 → direct_cli-0.5.1}/scripts/check_wsdl_drift.py +0 -0
  229. {direct_cli-0.4.3 → direct_cli-0.5.1}/scripts/patch_vendor_imports.py +0 -0
  230. {direct_cli-0.4.3 → direct_cli-0.5.1}/scripts/preflight_check.sh +0 -0
  231. {direct_cli-0.4.3 → direct_cli-0.5.1}/scripts/probe_drift_urls.sh +0 -0
  232. {direct_cli-0.4.3 → direct_cli-0.5.1}/scripts/refresh_reports_cache.py +0 -0
  233. {direct_cli-0.4.3 → direct_cli-0.5.1}/scripts/refresh_wsdl_cache.py +0 -0
  234. {direct_cli-0.4.3 → direct_cli-0.5.1}/scripts/release_pypi.sh +0 -0
  235. {direct_cli-0.4.3 → direct_cli-0.5.1}/scripts/sandbox_write_audit.py +0 -0
  236. {direct_cli-0.4.3 → direct_cli-0.5.1}/scripts/sandbox_write_live.py +0 -0
  237. {direct_cli-0.4.3 → direct_cli-0.5.1}/scripts/test_safe_commands.sh +0 -0
  238. {direct_cli-0.4.3 → direct_cli-0.5.1}/scripts/test_sandbox_write.sh +0 -0
  239. {direct_cli-0.4.3 → direct_cli-0.5.1}/scripts/update_vendor.sh +0 -0
  240. {direct_cli-0.4.3 → direct_cli-0.5.1}/setup.cfg +0 -0
  241. {direct_cli-0.4.3 → direct_cli-0.5.1}/setup.py +0 -0
  242. {direct_cli-0.4.3 → direct_cli-0.5.1}/tests/API_COVERAGE.md +0 -0
  243. {direct_cli-0.4.3 → direct_cli-0.5.1}/tests/API_ISSUE_AUDIT.md +0 -0
  244. {direct_cli-0.4.3 → direct_cli-0.5.1}/tests/MANUAL_COVERAGE.md +0 -0
  245. {direct_cli-0.4.3 → direct_cli-0.5.1}/tests/WSDL_OPTIONAL_FIELD_AUDIT.md +0 -0
  246. {direct_cli-0.4.3 → direct_cli-0.5.1}/tests/__init__.py +0 -0
  247. {direct_cli-0.4.3 → direct_cli-0.5.1}/tests/_orphan_store.py +0 -0
  248. {direct_cli-0.4.3 → direct_cli-0.5.1}/tests/api_coverage_payloads.py +0 -0
  249. {direct_cli-0.4.3 → direct_cli-0.5.1}/tests/cassettes/test_integration_write/TestWriteAdExtensions.test_add_delete.yaml +0 -0
  250. {direct_cli-0.4.3 → direct_cli-0.5.1}/tests/cassettes/test_integration_write/TestWriteAdGroups.test_add_update_delete.yaml +0 -0
  251. {direct_cli-0.4.3 → direct_cli-0.5.1}/tests/cassettes/test_integration_write/TestWriteAdImages.test_add_delete.yaml +0 -0
  252. {direct_cli-0.4.3 → direct_cli-0.5.1}/tests/cassettes/test_integration_write/TestWriteAds.test_add_text_ad_update_delete.yaml +0 -0
  253. {direct_cli-0.4.3 → direct_cli-0.5.1}/tests/cassettes/test_integration_write/TestWriteAudienceTargets.test_add_delete.yaml +0 -0
  254. {direct_cli-0.4.3 → direct_cli-0.5.1}/tests/cassettes/test_integration_write/TestWriteBidModifiersAdd.test_add_delete_mobile.yaml +0 -0
  255. {direct_cli-0.4.3 → direct_cli-0.5.1}/tests/cassettes/test_integration_write/TestWriteBidModifiersSet.test_set_without_id_is_rejected.yaml +0 -0
  256. {direct_cli-0.4.3 → direct_cli-0.5.1}/tests/cassettes/test_integration_write/TestWriteBids.test_set_bid.yaml +0 -0
  257. {direct_cli-0.4.3 → direct_cli-0.5.1}/tests/cassettes/test_integration_write/TestWriteBidsRead.test_bids_get.yaml +0 -0
  258. {direct_cli-0.4.3 → direct_cli-0.5.1}/tests/cassettes/test_integration_write/TestWriteBidsRead.test_bids_set_auto.yaml +0 -0
  259. {direct_cli-0.4.3 → direct_cli-0.5.1}/tests/cassettes/test_integration_write/TestWriteCampaignDraftLifecycle.test_draft_create_get_delete.yaml +0 -0
  260. {direct_cli-0.4.3 → direct_cli-0.5.1}/tests/cassettes/test_integration_write/TestWriteCampaigns.test_campaign_lifecycle.yaml +0 -0
  261. {direct_cli-0.4.3 → direct_cli-0.5.1}/tests/cassettes/test_integration_write/TestWriteDynamicAds.test_add_update_delete.yaml +0 -0
  262. {direct_cli-0.4.3 → direct_cli-0.5.1}/tests/cassettes/test_integration_write/TestWriteFeeds.test_add_update_delete.yaml +0 -0
  263. {direct_cli-0.4.3 → direct_cli-0.5.1}/tests/cassettes/test_integration_write/TestWriteKeywordBids.test_set_keyword_bid.yaml +0 -0
  264. {direct_cli-0.4.3 → direct_cli-0.5.1}/tests/cassettes/test_integration_write/TestWriteKeywords.test_add_update_delete.yaml +0 -0
  265. {direct_cli-0.4.3 → direct_cli-0.5.1}/tests/cassettes/test_integration_write/TestWriteNegativeKeywordSharedSets.test_add_update_delete.yaml +0 -0
  266. {direct_cli-0.4.3 → direct_cli-0.5.1}/tests/cassettes/test_integration_write/TestWriteRetargeting.test_add_delete.yaml +0 -0
  267. {direct_cli-0.4.3 → direct_cli-0.5.1}/tests/cassettes/test_integration_write/TestWriteRetargetingUpdate.test_retargeting_update.yaml +0 -0
  268. {direct_cli-0.4.3 → direct_cli-0.5.1}/tests/cassettes/test_integration_write/TestWriteSitelinks.test_add_delete.yaml +0 -0
  269. {direct_cli-0.4.3 → direct_cli-0.5.1}/tests/cassettes/test_integration_write/TestWriteSmartAdTargets.test_add_update_delete.yaml +0 -0
  270. {direct_cli-0.4.3 → direct_cli-0.5.1}/tests/cassettes/test_integration_write/TestWriteStrategies.test_strategies_lifecycle.yaml +0 -0
  271. {direct_cli-0.4.3 → direct_cli-0.5.1}/tests/cassettes/test_integration_write/TestWriteVCards.test_add_delete.yaml +0 -0
  272. {direct_cli-0.4.3 → direct_cli-0.5.1}/tests/cassettes/test_read_cassettes/test_read_command[adextensions_get].yaml +0 -0
  273. {direct_cli-0.4.3 → direct_cli-0.5.1}/tests/cassettes/test_read_cassettes/test_read_command[adgroups_get].yaml +0 -0
  274. {direct_cli-0.4.3 → direct_cli-0.5.1}/tests/cassettes/test_read_cassettes/test_read_command[adimages_get].yaml +0 -0
  275. {direct_cli-0.4.3 → direct_cli-0.5.1}/tests/cassettes/test_read_cassettes/test_read_command[ads_get].yaml +0 -0
  276. {direct_cli-0.4.3 → direct_cli-0.5.1}/tests/cassettes/test_read_cassettes/test_read_command[advideos_get].yaml +0 -0
  277. {direct_cli-0.4.3 → direct_cli-0.5.1}/tests/cassettes/test_read_cassettes/test_read_command[audiencetargets_get].yaml +0 -0
  278. {direct_cli-0.4.3 → direct_cli-0.5.1}/tests/cassettes/test_read_cassettes/test_read_command[bidmodifiers_get].yaml +0 -0
  279. {direct_cli-0.4.3 → direct_cli-0.5.1}/tests/cassettes/test_read_cassettes/test_read_command[bids_get].yaml +0 -0
  280. {direct_cli-0.4.3 → direct_cli-0.5.1}/tests/cassettes/test_read_cassettes/test_read_command[businesses_get].yaml +0 -0
  281. {direct_cli-0.4.3 → direct_cli-0.5.1}/tests/cassettes/test_read_cassettes/test_read_command[campaigns_get].yaml +0 -0
  282. {direct_cli-0.4.3 → direct_cli-0.5.1}/tests/cassettes/test_read_cassettes/test_read_command[changes_check].yaml +0 -0
  283. {direct_cli-0.4.3 → direct_cli-0.5.1}/tests/cassettes/test_read_cassettes/test_read_command[changes_check_campaigns].yaml +0 -0
  284. {direct_cli-0.4.3 → direct_cli-0.5.1}/tests/cassettes/test_read_cassettes/test_read_command[changes_check_dictionaries].yaml +0 -0
  285. {direct_cli-0.4.3 → direct_cli-0.5.1}/tests/cassettes/test_read_cassettes/test_read_command[clients_get].yaml +0 -0
  286. {direct_cli-0.4.3 → direct_cli-0.5.1}/tests/cassettes/test_read_cassettes/test_read_command[creatives_get].yaml +0 -0
  287. {direct_cli-0.4.3 → direct_cli-0.5.1}/tests/cassettes/test_read_cassettes/test_read_command[dictionaries_get].yaml +0 -0
  288. {direct_cli-0.4.3 → direct_cli-0.5.1}/tests/cassettes/test_read_cassettes/test_read_command[dynamicads_get].yaml +0 -0
  289. {direct_cli-0.4.3 → direct_cli-0.5.1}/tests/cassettes/test_read_cassettes/test_read_command[dynamicfeedadtargets_get].yaml +0 -0
  290. {direct_cli-0.4.3 → direct_cli-0.5.1}/tests/cassettes/test_read_cassettes/test_read_command[feeds_get].yaml +0 -0
  291. {direct_cli-0.4.3 → direct_cli-0.5.1}/tests/cassettes/test_read_cassettes/test_read_command[keywordbids_get].yaml +0 -0
  292. {direct_cli-0.4.3 → direct_cli-0.5.1}/tests/cassettes/test_read_cassettes/test_read_command[keywords_get].yaml +0 -0
  293. {direct_cli-0.4.3 → direct_cli-0.5.1}/tests/cassettes/test_read_cassettes/test_read_command[keywordsresearch_deduplicate].yaml +0 -0
  294. {direct_cli-0.4.3 → direct_cli-0.5.1}/tests/cassettes/test_read_cassettes/test_read_command[keywordsresearch_has_search_volume].yaml +0 -0
  295. {direct_cli-0.4.3 → direct_cli-0.5.1}/tests/cassettes/test_read_cassettes/test_read_command[leads_get].yaml +0 -0
  296. {direct_cli-0.4.3 → direct_cli-0.5.1}/tests/cassettes/test_read_cassettes/test_read_command[negativekeywordsharedsets_get].yaml +0 -0
  297. {direct_cli-0.4.3 → direct_cli-0.5.1}/tests/cassettes/test_read_cassettes/test_read_command[reports_get].yaml +0 -0
  298. {direct_cli-0.4.3 → direct_cli-0.5.1}/tests/cassettes/test_read_cassettes/test_read_command[retargeting_get].yaml +0 -0
  299. {direct_cli-0.4.3 → direct_cli-0.5.1}/tests/cassettes/test_read_cassettes/test_read_command[sitelinks_get].yaml +0 -0
  300. {direct_cli-0.4.3 → direct_cli-0.5.1}/tests/cassettes/test_read_cassettes/test_read_command[smartadtargets_get].yaml +0 -0
  301. {direct_cli-0.4.3 → direct_cli-0.5.1}/tests/cassettes/test_read_cassettes/test_read_command[strategies_get].yaml +0 -0
  302. {direct_cli-0.4.3 → direct_cli-0.5.1}/tests/cassettes/test_read_cassettes/test_read_command[turbopages_get].yaml +0 -0
  303. {direct_cli-0.4.3 → direct_cli-0.5.1}/tests/cassettes/test_read_cassettes/test_read_command[v4events_get_events_log].yaml +0 -0
  304. {direct_cli-0.4.3 → direct_cli-0.5.1}/tests/cassettes/test_read_cassettes/test_read_command[v4finance_get_clients_units].yaml +0 -0
  305. {direct_cli-0.4.3 → direct_cli-0.5.1}/tests/cassettes/test_read_cassettes/test_read_command[v4forecast_list].yaml +0 -0
  306. {direct_cli-0.4.3 → direct_cli-0.5.1}/tests/cassettes/test_read_cassettes/test_read_command[v4goals_get_stat_goals].yaml +0 -0
  307. {direct_cli-0.4.3 → direct_cli-0.5.1}/tests/cassettes/test_read_cassettes/test_read_command[v4tags_get_campaigns].yaml +0 -0
  308. {direct_cli-0.4.3 → direct_cli-0.5.1}/tests/cassettes/test_read_cassettes/test_read_command[v4wordstat_list_reports].yaml +0 -0
  309. {direct_cli-0.4.3 → direct_cli-0.5.1}/tests/cassettes/test_read_cassettes/test_read_command[vcards_get].yaml +0 -0
  310. {direct_cli-0.4.3 → direct_cli-0.5.1}/tests/cassettes/test_read_cassettes/test_v4finance_check_payment_unknown_transaction.yaml +0 -0
  311. {direct_cli-0.4.3 → direct_cli-0.5.1}/tests/cassettes/test_v5_live_write/test_v5_live_draft_adgroups_add_update_delete.yaml +0 -0
  312. {direct_cli-0.4.3 → direct_cli-0.5.1}/tests/cassettes/test_v5_live_write/test_v5_live_draft_adimages_add_get_delete.yaml +0 -0
  313. {direct_cli-0.4.3 → direct_cli-0.5.1}/tests/cassettes/test_v5_live_write/test_v5_live_draft_ads_add_update_delete.yaml +0 -0
  314. {direct_cli-0.4.3 → direct_cli-0.5.1}/tests/cassettes/test_v5_live_write/test_v5_live_draft_ads_suspend_resume_archive_unarchive.yaml +0 -0
  315. {direct_cli-0.4.3 → direct_cli-0.5.1}/tests/cassettes/test_v5_live_write/test_v5_live_draft_advideos_add_get.yaml +0 -0
  316. {direct_cli-0.4.3 → direct_cli-0.5.1}/tests/cassettes/test_v5_live_write/test_v5_live_draft_audiencetargets_add_delete.yaml +0 -0
  317. {direct_cli-0.4.3 → direct_cli-0.5.1}/tests/cassettes/test_v5_live_write/test_v5_live_draft_audiencetargets_suspend_resume.yaml +0 -0
  318. {direct_cli-0.4.3 → direct_cli-0.5.1}/tests/cassettes/test_v5_live_write/test_v5_live_draft_bids_set.yaml +0 -0
  319. {direct_cli-0.4.3 → direct_cli-0.5.1}/tests/cassettes/test_v5_live_write/test_v5_live_draft_campaign_create_get_delete.yaml +0 -0
  320. {direct_cli-0.4.3 → direct_cli-0.5.1}/tests/cassettes/test_v5_live_write/test_v5_live_draft_creatives_chain_advideo_to_creative.yaml +0 -0
  321. {direct_cli-0.4.3 → direct_cli-0.5.1}/tests/cassettes/test_v5_live_write/test_v5_live_draft_dynamicads_add_delete.yaml +0 -0
  322. {direct_cli-0.4.3 → direct_cli-0.5.1}/tests/cassettes/test_v5_live_write/test_v5_live_draft_dynamicads_suspend_resume.yaml +0 -0
  323. {direct_cli-0.4.3 → direct_cli-0.5.1}/tests/cassettes/test_v5_live_write/test_v5_live_draft_feeds_add_update_delete.yaml +0 -0
  324. {direct_cli-0.4.3 → direct_cli-0.5.1}/tests/cassettes/test_v5_live_write/test_v5_live_draft_keywordbids_set.yaml +0 -0
  325. {direct_cli-0.4.3 → direct_cli-0.5.1}/tests/cassettes/test_v5_live_write/test_v5_live_draft_keywords_add_update_delete.yaml +0 -0
  326. {direct_cli-0.4.3 → direct_cli-0.5.1}/tests/cassettes/test_v5_live_write/test_v5_live_draft_keywords_suspend_resume.yaml +0 -0
  327. {direct_cli-0.4.3 → direct_cli-0.5.1}/tests/cassettes/test_v5_live_write/test_v5_live_draft_retargeting_add_update_delete.yaml +0 -0
  328. {direct_cli-0.4.3 → direct_cli-0.5.1}/tests/cassettes/test_v5_live_write/test_v5_live_draft_sitelinks_add_get_delete.yaml +0 -0
  329. {direct_cli-0.4.3 → direct_cli-0.5.1}/tests/cassettes/test_v5_live_write/test_v5_live_draft_smartadtargets_add_update_delete.yaml +0 -0
  330. {direct_cli-0.4.3 → direct_cli-0.5.1}/tests/cassettes/test_v5_live_write/test_v5_live_draft_smartadtargets_suspend_resume.yaml +0 -0
  331. {direct_cli-0.4.3 → direct_cli-0.5.1}/tests/cassettes/test_v5_live_write/test_v5_live_draft_strategies_add_update_archive_unarchive.yaml +0 -0
  332. {direct_cli-0.4.3 → direct_cli-0.5.1}/tests/fixtures/test-video.mp4 +0 -0
  333. {direct_cli-0.4.3 → direct_cli-0.5.1}/tests/reports_cache/raw/fields-list.html +0 -0
  334. {direct_cli-0.4.3 → direct_cli-0.5.1}/tests/reports_cache/raw/headers.html +0 -0
  335. {direct_cli-0.4.3 → direct_cli-0.5.1}/tests/reports_cache/raw/period.html +0 -0
  336. {direct_cli-0.4.3 → direct_cli-0.5.1}/tests/reports_cache/raw/spec.html +0 -0
  337. {direct_cli-0.4.3 → direct_cli-0.5.1}/tests/reports_cache/raw/type.html +0 -0
  338. {direct_cli-0.4.3 → direct_cli-0.5.1}/tests/reports_cache/spec.json +0 -0
  339. {direct_cli-0.4.3 → direct_cli-0.5.1}/tests/test_adgroups_build_adgroup_object.py +0 -0
  340. {direct_cli-0.4.3 → direct_cli-0.5.1}/tests/test_adgroups_build_adgroup_update_object.py +0 -0
  341. {direct_cli-0.4.3 → direct_cli-0.5.1}/tests/test_ads_build_ad_object.py +0 -0
  342. {direct_cli-0.4.3 → direct_cli-0.5.1}/tests/test_ads_build_ad_update_object.py +0 -0
  343. {direct_cli-0.4.3 → direct_cli-0.5.1}/tests/test_auth_bw.py +0 -0
  344. {direct_cli-0.4.3 → direct_cli-0.5.1}/tests/test_auth_oauth.py +0 -0
  345. {direct_cli-0.4.3 → direct_cli-0.5.1}/tests/test_auth_op.py +0 -0
  346. {direct_cli-0.4.3 → direct_cli-0.5.1}/tests/test_auth_write_json.py +0 -0
  347. {direct_cli-0.4.3 → direct_cli-0.5.1}/tests/test_autotargeting.py +0 -0
  348. {direct_cli-0.4.3 → direct_cli-0.5.1}/tests/test_balance.py +0 -0
  349. {direct_cli-0.4.3 → direct_cli-0.5.1}/tests/test_bidding_strategy_constants.py +0 -0
  350. {direct_cli-0.4.3 → direct_cli-0.5.1}/tests/test_changes.py +0 -0
  351. {direct_cli-0.4.3 → direct_cli-0.5.1}/tests/test_check_all_docs_urls.py +0 -0
  352. {direct_cli-0.4.3 → direct_cli-0.5.1}/tests/test_env_loading.py +0 -0
  353. {direct_cli-0.4.3 → direct_cli-0.5.1}/tests/test_field_names_option.py +0 -0
  354. {direct_cli-0.4.3 → direct_cli-0.5.1}/tests/test_flag_validation.py +0 -0
  355. {direct_cli-0.4.3 → direct_cli-0.5.1}/tests/test_handle_api_errors.py +0 -0
  356. {direct_cli-0.4.3 → direct_cli-0.5.1}/tests/test_i18n.py +0 -0
  357. {direct_cli-0.4.3 → direct_cli-0.5.1}/tests/test_low_coverage_payloads.py +0 -0
  358. {direct_cli-0.4.3 → direct_cli-0.5.1}/tests/test_read_cassettes.py +0 -0
  359. {direct_cli-0.4.3 → direct_cli-0.5.1}/tests/test_reports_drift.py +0 -0
  360. {direct_cli-0.4.3 → direct_cli-0.5.1}/tests/test_sandbox_write_audit.py +0 -0
  361. {direct_cli-0.4.3 → direct_cli-0.5.1}/tests/test_unknown_option_hints.py +0 -0
  362. {direct_cli-0.4.3 → direct_cli-0.5.1}/tests/test_v4_contracts.py +0 -0
  363. {direct_cli-0.4.3 → direct_cli-0.5.1}/tests/test_v4_exit_codes.py +0 -0
  364. {direct_cli-0.4.3 → direct_cli-0.5.1}/tests/test_v4_foundation.py +0 -0
  365. {direct_cli-0.4.3 → direct_cli-0.5.1}/tests/test_v4_output_options.py +0 -0
  366. {direct_cli-0.4.3 → direct_cli-0.5.1}/tests/test_v4_runtime_shape.py +0 -0
  367. {direct_cli-0.4.3 → direct_cli-0.5.1}/tests/test_v4_safety.py +0 -0
  368. {direct_cli-0.4.3 → direct_cli-0.5.1}/tests/test_v4account.py +0 -0
  369. {direct_cli-0.4.3 → direct_cli-0.5.1}/tests/test_v4adimage.py +0 -0
  370. {direct_cli-0.4.3 → direct_cli-0.5.1}/tests/test_v4events.py +0 -0
  371. {direct_cli-0.4.3 → direct_cli-0.5.1}/tests/test_v4finance_money.py +0 -0
  372. {direct_cli-0.4.3 → direct_cli-0.5.1}/tests/test_v4finance_read.py +0 -0
  373. {direct_cli-0.4.3 → direct_cli-0.5.1}/tests/test_v4forecast.py +0 -0
  374. {direct_cli-0.4.3 → direct_cli-0.5.1}/tests/test_v4goals.py +0 -0
  375. {direct_cli-0.4.3 → direct_cli-0.5.1}/tests/test_v4keywords.py +0 -0
  376. {direct_cli-0.4.3 → direct_cli-0.5.1}/tests/test_v4meta.py +0 -0
  377. {direct_cli-0.4.3 → direct_cli-0.5.1}/tests/test_v4tags.py +0 -0
  378. {direct_cli-0.4.3 → direct_cli-0.5.1}/tests/test_v4wordstat.py +0 -0
  379. {direct_cli-0.4.3 → direct_cli-0.5.1}/tests/test_vendor_imports.py +0 -0
  380. {direct_cli-0.4.3 → direct_cli-0.5.1}/tests/test_wsdl_parity_gate.py +0 -0
  381. {direct_cli-0.4.3 → direct_cli-0.5.1}/tests/wsdl_cache/adextensions.xml +0 -0
  382. {direct_cli-0.4.3 → direct_cli-0.5.1}/tests/wsdl_cache/adgroups.xml +0 -0
  383. {direct_cli-0.4.3 → direct_cli-0.5.1}/tests/wsdl_cache/adimages.xml +0 -0
  384. {direct_cli-0.4.3 → direct_cli-0.5.1}/tests/wsdl_cache/ads.xml +0 -0
  385. {direct_cli-0.4.3 → direct_cli-0.5.1}/tests/wsdl_cache/advideos.xml +0 -0
  386. {direct_cli-0.4.3 → direct_cli-0.5.1}/tests/wsdl_cache/agencyclients.xml +0 -0
  387. {direct_cli-0.4.3 → direct_cli-0.5.1}/tests/wsdl_cache/audiencetargets.xml +0 -0
  388. {direct_cli-0.4.3 → direct_cli-0.5.1}/tests/wsdl_cache/bidmodifiers.xml +0 -0
  389. {direct_cli-0.4.3 → direct_cli-0.5.1}/tests/wsdl_cache/bids.xml +0 -0
  390. {direct_cli-0.4.3 → direct_cli-0.5.1}/tests/wsdl_cache/businesses.xml +0 -0
  391. {direct_cli-0.4.3 → direct_cli-0.5.1}/tests/wsdl_cache/campaigns.xml +0 -0
  392. {direct_cli-0.4.3 → direct_cli-0.5.1}/tests/wsdl_cache/changes.xml +0 -0
  393. {direct_cli-0.4.3 → direct_cli-0.5.1}/tests/wsdl_cache/clients.xml +0 -0
  394. {direct_cli-0.4.3 → direct_cli-0.5.1}/tests/wsdl_cache/creatives.xml +0 -0
  395. {direct_cli-0.4.3 → direct_cli-0.5.1}/tests/wsdl_cache/dictionaries.xml +0 -0
  396. {direct_cli-0.4.3 → direct_cli-0.5.1}/tests/wsdl_cache/dynamicfeedadtargets.xml +0 -0
  397. {direct_cli-0.4.3 → direct_cli-0.5.1}/tests/wsdl_cache/dynamictextadtargets.xml +0 -0
  398. {direct_cli-0.4.3 → direct_cli-0.5.1}/tests/wsdl_cache/feeds.xml +0 -0
  399. {direct_cli-0.4.3 → direct_cli-0.5.1}/tests/wsdl_cache/imports/adextensiontypes.xsd +0 -0
  400. {direct_cli-0.4.3 → direct_cli-0.5.1}/tests/wsdl_cache/imports/general.xsd +0 -0
  401. {direct_cli-0.4.3 → direct_cli-0.5.1}/tests/wsdl_cache/imports/generalclients.xsd +0 -0
  402. {direct_cli-0.4.3 → direct_cli-0.5.1}/tests/wsdl_cache/keywordbids.xml +0 -0
  403. {direct_cli-0.4.3 → direct_cli-0.5.1}/tests/wsdl_cache/keywords.xml +0 -0
  404. {direct_cli-0.4.3 → direct_cli-0.5.1}/tests/wsdl_cache/keywordsresearch.xml +0 -0
  405. {direct_cli-0.4.3 → direct_cli-0.5.1}/tests/wsdl_cache/leads.xml +0 -0
  406. {direct_cli-0.4.3 → direct_cli-0.5.1}/tests/wsdl_cache/negativekeywordsharedsets.xml +0 -0
  407. {direct_cli-0.4.3 → direct_cli-0.5.1}/tests/wsdl_cache/retargetinglists.xml +0 -0
  408. {direct_cli-0.4.3 → direct_cli-0.5.1}/tests/wsdl_cache/sitelinks.xml +0 -0
  409. {direct_cli-0.4.3 → direct_cli-0.5.1}/tests/wsdl_cache/smartadtargets.xml +0 -0
  410. {direct_cli-0.4.3 → direct_cli-0.5.1}/tests/wsdl_cache/strategies.xml +0 -0
  411. {direct_cli-0.4.3 → direct_cli-0.5.1}/tests/wsdl_cache/turbopages.xml +0 -0
  412. {direct_cli-0.4.3 → direct_cli-0.5.1}/tests/wsdl_cache/vcards.xml +0 -0
@@ -30,4 +30,6 @@ jobs:
30
30
  run: mypy .
31
31
 
32
32
  - name: Run offline tests
33
- run: pytest -m "not integration"
33
+ # Offline default comes from pyproject addopts (skips the live-network
34
+ # markers). integration_write stays in — it replays VCR cassettes offline.
35
+ run: pytest
@@ -56,3 +56,6 @@ Thumbs.db
56
56
  *.log
57
57
  *.bak
58
58
  *.orig
59
+
60
+ # Local measurement artifacts (regenerated via scripts/measure_criteria_limits.py)
61
+ scripts/_criteria_limits_results.json
@@ -1,7 +1,468 @@
1
1
  # Changelog
2
2
 
3
+ ## Unreleased
4
+
5
+ **Added — `direct masters archive` (#633):**
6
+
7
+ - New command that archives a Мастер кампаний. Live recon confirmed Yandex's
8
+ UI has **no separate "delete" action** for it — neither the campaigns
9
+ grid's row menu nor the overview page's own menu has a "Удалить" item,
10
+ only "Архивировать" (issue #633 comment documents the DOM structure
11
+ found live). This is the closest thing to delete Мастер кампаний has.
12
+ - Clicks the overview page's "⋮" menu then "Архивировать" — both selected
13
+ via stable, confirmed-live `data-testid` attributes
14
+ (`CampaignHeader.MenuTrigger`/`CampaignHeader.Menu.archive`), unlike
15
+ `suspend`/`resume`'s best-effort text-matched candidate buttons.
16
+ - Verifies success by re-reading the campaigns grid (`fetch_masters_list`)
17
+ and confirming the status actually became `ARCHIVED`, rather than
18
+ trusting the click alone. Idempotent: an already-archived campaign is a
19
+ no-op warning, not an error.
20
+ - **Irreversible from this CLI** — there is no `masters unarchive`.
21
+ Classified `DANGEROUS` in `direct_cli/smoke_matrix.py` (same as
22
+ `suspend`/`resume`): Мастер кампаний has no API and thus no `--sandbox`
23
+ isolation, so any exercise of this command hits a real account.
24
+
25
+ **Added — `direct masters update` (#631, Этап A):**
26
+
27
+ - New command that edits a single Мастер кампаний's settings page
28
+ (`/wizard/campaigns/{id}/edit/`). This first stage covers exactly three
29
+ simple scalar fields: `--weekly-budget` (integer, Недельный бюджет),
30
+ `--promotion-goal` (`max-conversions`/`max-clicks`, Цель продвижения), and
31
+ `--directs-helps`/`--no-directs-helps` (Директ помогает — auto-apply
32
+ Yandex recommendations). At least one flag is required.
33
+ - Live investigation (see `tests/fixtures/masters_wizard_edit_stage_a.html`)
34
+ found the edit page is a single form with one "Сохранить кампанию" button
35
+ — there is no per-section independent save. `update_master` therefore
36
+ submits the whole form on every call; fields not passed as flags are left
37
+ untouched (their current on-page value is preserved simply by never
38
+ filling/toggling their input).
39
+ - New `direct_cli/browser/masters.py` functions: `update_master` plus
40
+ private `_set_weekly_budget`/`_set_directs_helps`/`_set_promotion_goal`
41
+ setters. Each field setter follows the module's existing "click, then
42
+ verify the change actually took effect" convention (`_suspend_or_resume`)
43
+ rather than trusting a click alone. `update_master` itself also verifies:
44
+ after clicking save it re-navigates to the edit page and re-reads every
45
+ requested field (`_verify_saved`), raising `BrowserSessionError` on any
46
+ mismatch instead of reporting a false success — so a click that doesn't
47
+ actually persist (rejected validation, session/redirect failure) is a
48
+ hard error, not a silent success.
49
+ - `_click_save` and `_set_promotion_goal`'s option click use
50
+ `get_by_role(..., exact=True)`, not a substring `get_by_text` match — an
51
+ exact accessible-name match, scoped to the actual button/option role,
52
+ avoids clicking an ancestor container whose text merely contains the
53
+ target label.
54
+ - Later Этап (B/C/D) fields — headline/text variant lists, sitelinks,
55
+ audience, Metrika counters/goals, budget adaptation, images/video — are
56
+ tracked separately in issue #631 and not implemented here.
57
+ - No `--sandbox` equivalent exists for this browser-driven mutation (same
58
+ rationale as `masters suspend`/`resume`, #630) — classified `DANGEROUS` in
59
+ `direct_cli/smoke_matrix.py` and documented as manual-only in
60
+ `scripts/test_dangerous_commands.sh`.
61
+
62
+ **Added — `direct masters add` (#632):**
63
+
64
+ - New command that creates a brand-new "Конверсии и трафик" Мастер кампаний
65
+ by driving the same multi-field create wizard a human uses in the browser
66
+ (`https://direct.yandex.ru/wizard/campaigns/new/`) — Мастер кампаний has no
67
+ API surface at all, same as `list`/`get`/`suspend`/`resume`.
68
+ - Live recon (issue #632 step 0, see
69
+ `tests/fixtures/masters_wizard_create.html`) found the create flow is NOT
70
+ the multi-page "Далее" wizard the issue originally assumed: it is one
71
+ micro-step (a landing-page URL field with client-side format validation)
72
+ followed by a single long form, terminating in two buttons — "Запустить
73
+ кампанию" (launch) and "Сохранить как черновик" (save as draft) — instead
74
+ of `masters update`'s one "Сохранить кампанию".
75
+ - `--headline`/`--text`/`--region` are required (repeat the flag for
76
+ multiple values) even though Yandex's own wizard can auto-generate
77
+ headlines/texts by scanning the landing page — `add` refuses to silently
78
+ publish AI-written ad copy the caller never reviewed, given there is no
79
+ sandbox or rollback for Мастер кампаний mutations.
80
+ - `--weekly-budget` is optional. `--draft`/`--launch` (default `--launch`)
81
+ selects which terminal button is clicked.
82
+ - **NOT idempotent**: running this twice with the same arguments creates a
83
+ *second* campaign, not an update to the first — documented prominently in
84
+ the command's own `--help` and in the README, per the issue's explicit
85
+ "Риски" requirement.
86
+ - Classified `DANGEROUS` in `direct_cli/smoke_matrix.py` and documented as
87
+ manual-only (verify with `--draft` first) in
88
+ `scripts/test_dangerous_commands.sh` — no `--sandbox` equivalent exists for
89
+ Мастер кампаний.
90
+ - After clicking the terminal button, re-reads the headline/text/budget
91
+ fields to confirm the form actually reflects what was requested
92
+ (`_verify_created`) rather than trusting the click alone — ported from
93
+ `masters update`'s `_verify_saved` pattern (issue #631 review finding).
94
+ Dropdown/option clicks (`_fill_landing_url`'s "Далее", `_set_region`'s
95
+ suggestion, `_click_terminal_button`'s launch/draft button) use
96
+ `get_by_role(..., exact=True)`, not a substring `get_by_text` match — same
97
+ fix applied to `update_master`'s `_click_save`/`_set_promotion_goal`, to
98
+ avoid clicking an ancestor container whose text merely contains the
99
+ target label.
100
+ - Not end-to-end live-verified: step 0 recon was deliberately read-only (no
101
+ campaign was created or saved during recon) — see the browser module's
102
+ docstring for what remains to be confirmed against a real account before
103
+ relying on this in production. In particular, `_verify_created` cannot
104
+ re-navigate and reload the way `_verify_saved` does (the post-click
105
+ destination URL is unconfirmed), so it only re-reads the current page
106
+ immediately after the click — a strictly weaker check pending a live
107
+ pass.
108
+
109
+ **Added — `direct masters login` (#635):**
110
+
111
+ - New interactive command that opens a visible browser window on a
112
+ persistent Chromium profile owned by the CLI
113
+ (`~/.direct-cli/chrome-profile/` by default, overridable with
114
+ `--profile-dir`). Log in by hand once via Yandex Passport; the command
115
+ polls until the session is confirmed authenticated (or times out after
116
+ `--timeout` seconds, default 300) and exits.
117
+ - Independent of `direct playwright login`/the macOS Keychain entirely —
118
+ this never touches the user's real Chrome profile, so it works
119
+ identically on macOS/Linux/Windows at the cost of a one-time manual login
120
+ instead of a transparent cookie copy.
121
+ - `direct masters` (`list`/`get`/`suspend`/`resume`) prefers this persistent
122
+ profile automatically whenever it exists, ahead of the saved
123
+ `playwright login` session — see the new tier 1.5 in
124
+ `direct_cli/commands/masters.py`'s session-resolution docstring.
125
+ - Interactive by design (blocks waiting for a human); classified
126
+ `DANGEROUS` in `direct_cli/smoke_matrix.py` and documented as
127
+ manual-only in `scripts/test_dangerous_commands.sh` — it cannot run in
128
+ any automated smoke tier.
129
+ - The profile directory is chmod'd `0700` (it holds a live Yandex session in
130
+ plaintext-readable cookies).
131
+ - New `direct masters logout` deletes the profile — the only way to revoke
132
+ the on-disk session short of a manual `rm -rf`. A no-op with a warning
133
+ (not an error) if no profile exists. Also classified `DANGEROUS`
134
+ (local credential-store mutation, same category as `auth login`/`auth
135
+ use`).
136
+ - `masters login` refuses to run without a terminal. It waits for a human,
137
+ so in CI or a script it now fails immediately with a clear message instead
138
+ of blocking for the full `--timeout` on a browser window nobody can see.
139
+ - `masters login` polls for completion on a separate page instead of the
140
+ one the user is typing into. Previously the loop navigated the visible
141
+ Passport tab to the grid once a second, wiping a half-filled login form
142
+ or a pending 2FA prompt out from under the user.
143
+ - `masters login` refuses a `--profile-dir` that already exists without the
144
+ CLI's own marker. Previously the marker was planted into whatever directory
145
+ was named — so `masters login --profile-dir ~` marked the home directory as
146
+ CLI-owned, and `masters logout` on the same path then accepted that marker
147
+ as authorization to `shutil.rmtree` it. A failed login armed it just the
148
+ same, since the marker was written before Chromium even launched.
149
+ - `masters login --profile-dir X` is now honoured by the read commands. The
150
+ chosen directory is recorded, and `list`/`get`/`suspend`/`resume` resolve
151
+ the profile through that record. Previously tier 1.5 looked only at the
152
+ default location and passed no directory through, so a custom login path
153
+ reported "Login confirmed" and was then silently ignored by every read —
154
+ falling back to the Keychain the flag exists to avoid. `masters logout`
155
+ clears the record along with the profile.
156
+ - The recorded profile path is stored and read as absolute. A relative value
157
+ would have been re-resolved against whatever directory a later command ran
158
+ from, so `masters logout` acted on a different profile depending on where
159
+ the user stood.
160
+ - `masters` commands route through the persistent profile only when it holds
161
+ an actual browser session, not merely when the directory exists. An aborted
162
+ `masters login` leaves an empty profile behind; treating that as usable cost
163
+ a wasted browser launch on every later command and bypassed the user's
164
+ working saved session.
165
+ - `masters logout` refuses to delete anything `masters login` did not
166
+ create. Every profile now carries a `.direct-cli-profile` marker file,
167
+ and `logout` rejects a target that lacks it, is a symlink, or is not a
168
+ directory. Without this a mistyped or shell-expanded `--profile-dir`
169
+ (`.`, `~`) went straight into `shutil.rmtree` and recursively deleted an
170
+ arbitrary tree.
171
+
172
+ **Added — `direct masters suspend` / `direct masters resume` (#630):**
173
+
174
+ - First mutating `masters` commands — stop/resume a Мастер кампаний by
175
+ clicking its overview page's action button, then re-reading the status to
176
+ confirm the change actually took effect (the click alone is never treated
177
+ as success). Both are idempotent: a campaign already in the target status
178
+ is a no-op with a warning, not an error. Neither has a `--sandbox`
179
+ equivalent — Мастер кампаний has no API surface at all, so there is no
180
+ isolated test copy; every mutation hits the real account (classified
181
+ `DANGEROUS` in `direct_cli/smoke_matrix.py`, manual-only per
182
+ `scripts/test_dangerous_commands.sh`).
183
+ - **Not live-verified:** the resume button's text ("Возобновить кампанию")
184
+ is confirmed against a live account; the suspend/stop button's exact text
185
+ is not — `direct_cli/browser/masters.py` tries a short list of plausible
186
+ Russian labels and raises a clear error (suggesting `--headful`) if none
187
+ match, rather than clicking the wrong element.
188
+
189
+ **BREAKING CHANGES — `direct masters` no longer accepts `--login` (#639):**
190
+
191
+ - `direct masters list`/`get` dropped the `--login` option. Passing your own
192
+ login there built a `?ulogin=<login>` URL, but `ulogin` is Yandex's
193
+ *managed-client* (agency) parameter — passing your own login as the managed
194
+ client produced "Доступ ограничен" and HTTP 401 on the grid's data calls,
195
+ which is exactly why `masters list` found nothing. `direct masters` only
196
+ ever reads the logged-in browser session's own account now; there is no
197
+ agency/managed-client support and no replacement flag.
198
+
199
+ **Fixed — `direct masters list` found no Мастера кампаний at all (#639):**
200
+
201
+ - Three independent bugs, found via live diagnosis: (1) the `--login`/
202
+ `ulogin` issue above; (2) the DOM selector
203
+ (`a[href*='/wizard/campaigns/']`) never matched anything, because the
204
+ campaigns grid is a virtualized SPA that renders zero such anchors; (3) the
205
+ `status-filter` URL query parameter was silently ignored by the grid.
206
+ `list` now replays the grid's own JSON data call
207
+ (`POST /web-api/grid/api?operationName=GridCampaigns`) instead of scraping
208
+ the DOM, paginates past its 200-row page limit, and filters status on the
209
+ client side using `status.primaryStatus`.
210
+
211
+ **Added — `direct masters list --status`:**
212
+
213
+ - New `--status` option (`not-archived` default, plus `active`, `stopped`,
214
+ `archived`, `all`) lets you see only archived Мастера кампаний, or every
215
+ status at once — previously `list` had no status filtering at all.
216
+
217
+ **New — `direct playwright login` / `direct playwright doctor`:**
218
+
219
+ - `direct masters` previously re-decrypted Chrome's Yandex cookies via the
220
+ macOS Keychain on every single call. `direct playwright login` decrypts
221
+ once and persists the result as a Playwright `storage_state` session
222
+ (`~/.direct-cli/playwright/session.json`, `0600`) — `direct masters`
223
+ automatically prefers this saved session over a fresh Keychain round-trip,
224
+ falling back transparently when no session is saved, `--profile-dir`/
225
+ `--chrome-profile` was passed explicitly, or the saved session has expired.
226
+ Purely additive: `direct masters` continues to work with zero setup.
227
+ - `direct playwright doctor` reports the health of the whole pipeline
228
+ (playwright/cryptography installed, chromium downloaded, Chrome profile
229
+ found, Keychain key derivable, cookies decryptable, saved session
230
+ present/fresh) without ever logging in, launching a browser, or writing to
231
+ disk.
232
+ - Neither command requires a Yandex Direct API token/login.
233
+
234
+ **Fixed — `direct masters` didn't work on macOS at all (#634):**
235
+
236
+ - `direct masters list`/`get` previously copied `Cookies` + `Local State`
237
+ into a throwaway Chrome profile, assuming Chromium would decrypt the
238
+ cookies itself. On macOS this silently failed: the cookie AES key lives
239
+ only in the login Keychain (`Chrome Safe Storage`); `Local State`'s
240
+ `os_crypt.encrypted_key` is populated only on Windows. Every cookie was
241
+ discarded, producing an unauthenticated session that landed on Yandex's
242
+ login page and then timed out (`networkidle` never settles there, since
243
+ the login page holds long-poll connections).
244
+ - `direct masters` now reads the Keychain password itself, derives Chrome's
245
+ AES-128-CBC key, decrypts only the Yandex Direct cookies it needs, and
246
+ injects them into a fresh bundled-Chromium context via `add_cookies()` —
247
+ no more temp-profile copying, and `channel="chrome"` (which required a
248
+ real Google Chrome install) is no longer needed.
249
+ - Login-page and expired-session failures are now reported explicitly
250
+ (`BrowserAuthError`) instead of surfacing as an opaque 30s timeout.
251
+ - New optional dependency in the `browser` extra: `cryptography>=41`.
252
+ - Linux is supported (Chrome's basic/no-keyring password store only);
253
+ Windows is not yet.
254
+
255
+ **New — `direct masters` (#628):**
256
+
257
+ - Added `direct masters list` / `direct masters get <ids>` — read-only access
258
+ to Мастер кампаний (Campaign Wizard), which has **no Yandex Direct API
259
+ surface at all** (do not confuse with `UNIFIED_CAMPAIGN`, an unrelated v5
260
+ API campaign type already supported by `campaigns add/get --type
261
+ unified_campaign`). These commands drive a real Chrome session via
262
+ Playwright, decrypting and reusing the user's own Chrome cookies for
263
+ `yandex.ru` — no separate login flow (see the Fixed entry above for the
264
+ macOS Keychain decryption path added in #634). Requires the optional
265
+ `browser` extra: `pip install "direct-cli[browser]" && playwright install
266
+ chromium`. `direct masters` degrades gracefully (per-section warnings, not
267
+ a hard failure) when Yandex's page markup changes, since this data has no
268
+ API contract to rely on. See `direct_cli/browser/` for the scraping layer.
269
+
270
+ **Internal — campaigns.py split, step 1 — CPM_BANNER (#613, part of #602):**
271
+
272
+ - Extracted the `CPM_BANNER_CAMPAIGN` `add`/`update` subtype-block composition
273
+ (the former inline `elif campaign_type_norm == "CPM_BANNER_CAMPAIGN":`
274
+ branches) into a new sibling module
275
+ `direct_cli/commands/_campaigns_cpm_banner.py` (`build_add_block` /
276
+ `build_update_block`). Both commands now snapshot their CLI parameters once
277
+ (`p = dict(locals())`) and delegate. The CLI surface, every flag and every
278
+ `--dry-run` payload is byte-for-byte identical (28 `test_campaigns_*cpm*`
279
+ fixtures green).
280
+
281
+ **Internal — campaigns.py split, step 3 (#615):**
282
+
283
+ - Extracted the `SMART_CAMPAIGN` add/update subtype-block composition
284
+ (≈285 lines) into a new sibling module
285
+ `direct_cli/commands/_campaigns_smart.py`
286
+ (`build_add_block` / `build_update_block`). The `add` and `update`
287
+ commands now snapshot their CLI parameters once via `p = dict(locals())`
288
+ and delegate; the SmartCampaign builder pulls only the smart-relevant
289
+ flags from `p`. CLI surface, every flag and every `--dry-run` payload is
290
+ byte-for-byte identical (16 `test_campaigns_*smart*` fixtures green,
291
+ offline tier 2521 passed, WSDL parity + API coverage green).
292
+
293
+ **Internal — campaigns.py split, step 1 (#602):**
294
+
295
+ - Extracted all shared constants (`CAMPAIGNS_GET_CRITERIA_LIMITS`, `YES_NO`,
296
+ `ATTRIBUTION_MODELS`, …), typed-flag validators (`_validate_max_length`,
297
+ `_validate_sms_time`), payload builders (`_build_notification`,
298
+ `_build_time_targeting`, `_build_relevant_keywords`,
299
+ `_build_dynamic_placement_types`, `_build_frequency_cap`,
300
+ `_build_package_bidding_strategy`, `_build_smart_package_bidding_strategy`,
301
+ `_priority_goals_update_items`, `_route_cpa_flag`) and the reusable
302
+ TextCampaign strategy `click.option` groups (`_TEXT_*_STRATEGY_OPTIONS` and
303
+ their `*_UPDATE` variants) into a new sibling module
304
+ `direct_cli/commands/_campaigns_base.py`. `campaigns.py` re-imports every
305
+ name, so the CLI surface, every flag and every `--dry-run` payload is
306
+ byte-for-byte identical. First step of an incremental decomposition; the
307
+ per-campaign-type logic (`text`, `unified`, `dynamic`, `smart`,
308
+ `mobile_app`, `cpm_banner`) follows in subsequent PRs.
309
+
310
+ **Internal — `make_get_command` covers the complex Group-4 `get`s (#588):**
311
+
312
+ - `adgroups get`, `keywords get`, `creatives get`, `strategies get` and
313
+ `audiencetargets get` now register through the shared `make_get_command`
314
+ factory (net −345 lines). They reuse the existing `extra_options` /
315
+ `criteria_builder` / `criteria_limits` / `require_criteria_message` /
316
+ `nested_field_options` hooks — the `--status`/`--statuses` mutual-exclusion
317
+ guard (adgroups/keywords) lives in the per-module `criteria_builder`, not a
318
+ new factory flag. adgroups (8) and strategies (16) exercise the factory's
319
+ largest nested-`*FieldNames` sets to date.
320
+ - Factory ordering fix: nested `*FieldNames` are now parsed (and their
321
+ provided-but-empty-CSV `UsageError` raised) *before* both the criteria-limit
322
+ enforcement and the empty-criteria `require_criteria_message` guard, matching
323
+ the order every hand-rolled command used. So a `--<nested> ""` combined with
324
+ either no filter at all or an over-limit array reports the nested error, not
325
+ the require/limit one (pinned by a new `test_dry_run` regression test). The
326
+ parsed dict is still merged after the common params, so payload key order is
327
+ unchanged, and this also restores the pre-factory nested-before-limits order
328
+ for the already-migrated `bidmodifiers get`. No module lacking both a nested
329
+ option and a `criteria_limits`/`require_criteria_message` is affected.
330
+ - No CLI surface change: `--help` (option order, all nested-field options, the
331
+ `--is-archived` choice/default), `--dry-run` payloads, and the error-precedence
332
+ edge cases (empty-nested vs. no-filter and vs. over-limit) are byte-identical,
333
+ verified against 33 pre-migration baselines captured via CliRunner from the
334
+ pre-migration tree. As with every prior `make_get_command` migration, the
335
+ factory resolves the API client only on the live path, so these five `get`s
336
+ now honor `--dry-run` as a token-free test seam (no behavior change vs. the
337
+ other factory-backed commands).
338
+ - Two Group-4 `get`s stay hand-rolled as documented carve-outs (see `_get.py`):
339
+ `ads get` (its `TextAdFieldNames` is always emitted with a per-field default,
340
+ like the carved-out `keywordbids`) and `campaigns get` (its `--fields` rejects
341
+ an explicitly empty CSV instead of falling back to the default `FieldNames`).
342
+
343
+ **Internal — `make_get_command` covers the criteria-limit `get`s `bids` / `bidmodifiers` (completes #587):**
344
+
345
+ - `bids get` (criteria-limit + require-at-least-one-filter, no `--ids`) and
346
+ `bidmodifiers get` (criteria-limit + 13 optional nested `*FieldNames`
347
+ projections + a defaulted `--levels` criteria) now register through the
348
+ shared `make_get_command` factory, reusing its existing `criteria_limits`,
349
+ `require_criteria_message` and `nested_field_options` hooks (no new factory
350
+ parameters). Net −230 lines.
351
+ - No CLI surface change: `--help` (option order, the 13 nested-field options,
352
+ the `--levels` choice/default), `--dry-run` payloads (the criteria-limit
353
+ enforcement, the `bids get` require-filter `UsageError`, the always-present
354
+ `Levels` criteria and nested-field params) are byte-identical (verified
355
+ against pre-migration baselines).
356
+ - This closes the migratable surface of #587. The three remaining `get`s stay
357
+ hand-rolled as documented carve-outs, each for a structural reason the shared
358
+ factory deliberately does not encode: `leads get` (`--datetime-from` /
359
+ `--datetime-to` sit between `--limit` and `--fetch-all` — a bespoke option
360
+ order), `keywordbids get` (its nested `SearchFieldNames` / `NetworkFieldNames`
361
+ carry their own per-field defaults and are always emitted, unlike the factory's
362
+ provided-only nested projections), and `reports get` (a custom non-RPC TSV
363
+ stream, not a JSON-RPC `get`).
364
+
365
+ **Internal — `make_get_command` covers the no-`--ids` / wire-quirk `get`s (part of #587):**
366
+
367
+ - `agencyclients get` (no `--ids`; filters by `--logins` / `--archived`) and
368
+ `adextensions get` now register through the shared `make_get_command` factory.
369
+ The factory gained two more optional parameters: `include_ids` (set `False`
370
+ for resources with no id filter — the command supplies a custom
371
+ `criteria_builder` from its own options) and `adextensions_wire_layout` (a
372
+ single-command flag that reproduces adextensions' exact recorded wire layout:
373
+ always emit `SelectionCriteria` even when empty, and order nested
374
+ `*FieldNames` before `Page`). The empty `SelectionCriteria` is a recorded API
375
+ contract — the `adextensions_get` read cassette replays it and the API accepts
376
+ it, unlike adgroups/ads/keywords.
377
+ - No CLI surface change: `--help`, `--dry-run` payloads (incl. the empty-criteria
378
+ and nested-before-`Page` cases) and the read cassettes are byte-identical
379
+ (verified against pre-migration baselines). Remaining carve-out: `leads get`,
380
+ whose `--datetime-from` / `--datetime-to` options sit between `--limit` and
381
+ `--fetch-all` — a bespoke option order that doesn't fit the shared
382
+ `get_options` stack, so it stays hand-rolled.
383
+
384
+ **Internal — `make_get_command` covers the nested-`*FieldNames` `get`s (part of #587):**
385
+
386
+ - `adimages get`, `retargeting get`, `sitelinks get`, `feeds get` and
387
+ `clients get` now register through the shared `make_get_command` factory
388
+ (net −175 lines). The factory gained three optional, generic parameters:
389
+ `nested_field_options` (a tuple of `(flag, WSDL key, help)` for nested
390
+ `*FieldNames` projections, parsed via the existing `parse_nested_field_names`
391
+ and rendered between `--fields` and `--dry-run`), `ids_criteria_key` (the
392
+ SelectionCriteria key for `--ids`; `clients` uses `ClientIds`), and
393
+ `fields_help` (a resource-specific `--fields` wording, e.g. sitelinks'
394
+ "Comma-separated SitelinksSet FieldNames"). The shared `get_options` decorator
395
+ (`utils.py`) gained matching `nested_options` / `fields_help` hooks, both
396
+ defaulting to current behavior so its other callers are unchanged.
397
+ - No CLI surface change: `--help` (option order, the nested-field option
398
+ position, the custom `--fields` help), `--dry-run` payloads (including the
399
+ `ClientIds` key and nested-field params), and the provided-but-empty
400
+ `*FieldNames` `UsageError` are byte-identical (verified against
401
+ pre-migration baselines). Deferred to a follow-up: `adextensions` (emits an
402
+ empty `SelectionCriteria` + Page-before-nested key order), and `leads` /
403
+ `agencyclients` (no `--ids`; bespoke required criteria).
404
+
405
+ **Internal — `make_get_command` covers the criteria-limit ad-target `get`s (part of #587):**
406
+
407
+ - `dynamicads get`, `smartadtargets get` and `dynamicfeedadtargets get` — three
408
+ byte-for-byte identical command bodies — now register through the shared
409
+ `make_get_command` factory (`direct_cli/commands/_get.py`) instead of hand-rolled
410
+ copies (net −131 lines). The factory gained two optional, generic parameters:
411
+ `criteria_limits` (forwarded to `enforce_criteria_array_limits`, command name
412
+ derived as `"<group> get"`) and `require_criteria_message` (the "provide at
413
+ least one filter" guard), plus a shared `ids_adgroup_campaign_states_criteria`
414
+ builder for the `Ids`/`AdGroupIds`/`CampaignIds`/`States` selection shape.
415
+ - No CLI surface change: `--help`, `--dry-run` payloads, the over-limit and
416
+ empty-criteria errors, and module patchability are byte-identical (verified
417
+ against pre-migration baselines). Group 2 (nested `*FieldNames`) and the
418
+ no-`--ids` commands (`bids`, `keywordbids`, `bidmodifiers`) follow in a
419
+ separate PR.
420
+
421
+ **Features — human-readable `text` output for reference commands (#578):**
422
+
423
+ - Local reference commands now default to a human-readable `text` format instead
424
+ of raw JSON: `trackingparams` (dynamic tracking-parameter reference) and
425
+ `dictionaries list-names` (previously hardcoded JSON with no `--format`). Both
426
+ expose `--format {text,json,table,csv,tsv}` / `--output` via the new shared
427
+ `reference_output_options` decorator (sibling of `v4_output_options`, default
428
+ `text`). API-data commands keep their `json` default.
429
+ - Global `output.py` fix: `--format table/csv/tsv` now render a list of scalar
430
+ values (e.g. dictionary names) as a single `Value` column instead of a Python
431
+ `repr` (`['Currencies', …]`) / empty output. Affects any caller that passes a
432
+ scalar list, not just the reference commands.
433
+
434
+ **Features — preflight `SelectionCriteria` array caps on remaining read-get commands (#571):**
435
+
436
+ - `ads`, `adgroups`, `bids`, `bidmodifiers`, `campaigns`, `keywords`,
437
+ `dynamicfeedadtargets`, `audiencetargets` `get` now reject over-long
438
+ `SelectionCriteria` arrays before the request and surface the array name and
439
+ ceiling instead of the opaque API `error_code=4001`. Extends the
440
+ `enforce_criteria_array_limits` discipline added in #555 to the rest of the
441
+ read-get surface. Per-filter caps were measured live against the Yandex Direct
442
+ sandbox on 2026-06-17 (see `scripts/measure_criteria_limits.py` and the
443
+ `*_GET_CRITERIA_LIMITS` constant in each command module for the exact
444
+ transcript).
445
+ - Closes a downstream consistency gap: the MCP plugin
446
+ (`axisrow/yandex-direct-mcp-plugin#201`) can now drop its own batch-size
447
+ guard and forward the CLI error.
448
+
3
449
  ## 0.4.3
4
450
 
451
+ **Fixes — known limitation: clearing a carousel AdImageHash (#574):**
452
+
453
+ - `ads update --clear-image-hash` builds a valid scalar `AdImageHash: null`
454
+ (visible in `--dry-run`), but the live v5 API can reject it with `Error 5005`
455
+ (`adImageHash=<[<null>]>`). Root cause: the ad carries a **server-side
456
+ carousel image structure** that the documented `Ads.update` API does not
457
+ expose — `TextAd` has only a single `AdImageHash` (no `Carousel` field exists
458
+ anywhere in the v5 WSDL). Such a carousel image cannot be cleared through the
459
+ API; it can only be removed in the Direct web interface. The CLI now surfaces
460
+ a readable hint on `5005`/`AdImageHash` (explaining the carousel cause and the
461
+ workaround) instead of the opaque error. The request payload is unchanged —
462
+ the scalar null the CLI sends is correct; the rejection is the server's.
463
+ Workaround: replace the image with a single `--image-hash <other>`, which the
464
+ API accepts even on an active ad.
465
+
5
466
  **Features — batch `ads add` via `--from-file` / `--ads-json` (#562, #558 follow-up):**
6
467
 
7
468
  - `ads add` now accepts a batch of flag-form ad rows from a JSONL file
@@ -10,7 +10,8 @@ CLI for the Yandex Direct API, built with Python and Click. Installed via pip, p
10
10
 
11
11
  ```bash
12
12
  pip install -e ".[dev]" # Install in dev mode
13
- pytest # Unit tests (no token needed)
13
+ pytest # Offline tests, parallel via xdist (no token needed)
14
+ pytest -n0 # Same offline tests, sequential (for pdb / -s / debugging)
14
15
  pytest -m integration -v # Integration tests (needs .env with token)
15
16
  pytest tests/test_cli.py::TestCLI::test_cli_help # Single test
16
17
  pytest -k "campaigns" # Pattern match
@@ -22,6 +23,8 @@ flake8 direct_cli tests # Lint
22
23
 
23
24
  Click group-of-groups. Each Yandex Direct API resource = one file in `direct_cli/commands/` with a Click group and subcommands (`get`, `add`, `update`, `delete`, `suspend`, `resume`, etc.). All groups registered in `direct_cli/cli.py` via `cli.add_command()`.
24
25
 
26
+ **Campaigns split (issue #602):** `campaigns.py` is being decomposed incrementally. Step 1 moved all shared constants, validators, payload builders and the reusable TextCampaign strategy option groups into a sibling `direct_cli/commands/_campaigns_base.py`; `campaigns.py` re-imports every name, so the CLI surface is unchanged. Subsequent steps will move per-campaign-type logic (`text`, `unified`, `dynamic`, `smart`, `mobile_app`, `cpm_banner`) into the same package — never edit shared helpers in `campaigns.py`, edit `_campaigns_base.py`.
27
+
25
28
  **Request flow:** `cli.py` → `auth.py` (resolves token/login) → `api.py` (`create_client`) → `tapi_yandex_direct.YandexDirect` → Yandex API → `output.py` (format/print).
26
29
 
27
30
  **Credentials priority (CLI):** explicit CLI flags (`--token`, `--login`, `--profile`) > base env/current working directory `.env` (`YANDEX_DIRECT_TOKEN`, `YANDEX_DIRECT_LOGIN`) > active profile from `direct auth login` / `direct auth use` > 1Password/Bitwarden refs. Explicit `--profile` is isolated and does not fall back to base `.env` login. See `direct_cli/auth.py` (`get_credentials`) and README table for the full chain.
@@ -30,7 +33,9 @@ Click group-of-groups. Each Yandex Direct API resource = one file in `direct_cli
30
33
 
31
34
  **Shared utilities** (`utils.py`): `parse_ids`, `parse_json`, `build_selection_criteria`, `build_common_params`, `get_default_fields`, `COMMON_FIELDS` dict. All command modules import from here — don't duplicate.
32
35
 
33
- **Output** (`output.py`): `format_output()` supports json (default), table, csv, tsv. Colored helpers: `print_success`, `print_error`, `print_warning`, `print_info`.
36
+ **Output** (`output.py`): `format_output()` supports json (default), table, csv, tsv, and `text` (human-readable plain blocks via `format_text`). Colored helpers: `print_success`, `print_error`, `print_warning`, `print_info`.
37
+
38
+ **Reference commands** (local value lists / templates, e.g. `trackingparams`, `dictionaries list-names`) default to the human-readable `text` format and expose `--format {text,json,table,csv,tsv}` / `--output` via the shared `reference_output_options` decorator (`utils.py`, sibling of `v4_output_options`). API-data commands keep `json` default. New reference commands should use this decorator.
34
39
 
35
40
  ## Key Conventions
36
41
 
@@ -50,7 +55,7 @@ Click group-of-groups. Each Yandex Direct API resource = one file in `direct_cli
50
55
  - runtime-deprecated methods (see `RUNTIME_DEPRECATED_METHODS`);
51
56
  - v4 methods that have no v5 WSDL (covered by `direct_cli/v4_contracts.py`);
52
57
  - custom non-RPC endpoints (e.g. `reports.get` — TSV stream);
53
- - methods explicitly covered by `tests/test_dry_run.py::test_<service>_<op>_payload`.
58
+ - methods explicitly covered by a `test_<service>_<op>_payload` test in the `tests/test_dry_run_*.py` module suite.
54
59
 
55
60
  A guard in `tests/test_api_coverage.py::test_dry_run_exclusions_have_no_helper_or_legacy_rationale` enforces this — any rationale outside those five categories that uses the banned phrasing is a mis-classification: write a `PAYLOAD_CASES` fixture instead. See post-mortem in issue #199.
56
61
 
@@ -78,6 +83,12 @@ Adding a new mutating command requires extending `COMMAND_WSDL_MAP` in `tests/te
78
83
 
79
84
  **Docs/cache freshness guard.** `direct_cli.reports_coverage.fetch_reports_spec` and `direct_cli.wsdl_coverage.fetch_wsdl` both refuse responses that look like a Yandex SmartCaptcha gate (markers `showcaptcha`, `smartcaptcha`, `<title>Captcha`) or are suspiciously short (<30 KB for HTML, <3 KB for WSDL). The matching tests `test_reports_cache_files_are_real_content` and `test_wsdl_cache_files_are_real_content` enforce the same invariant on committed cache files.
80
85
 
86
+ **`direct masters` cookie decryption (#634).** On macOS, Chrome's cookie AES key lives *only* in the login Keychain (`Chrome Safe Storage`) — never in `Local State`, whose `os_crypt.encrypted_key` field is populated on Windows only. `direct_cli/browser/_chrome_crypto.py` reads the Keychain password itself, derives the AES-128-CBC key (PBKDF2-HMAC-SHA1, Chromium's `saltysalt`/1003-iteration constants), and decrypts only the Yandex Direct cookies it needs — it does not copy the user's Chrome profile or rely on a real Chrome to decrypt anything. `direct_cli/browser/session.py::open_chrome_session` injects the result via Playwright's `add_cookies()` into a bundled Chromium context. The same marker-registry pattern used for captchas (`_captcha.py`) covers Yandex's login-page markers (`session.py::assert_authenticated`), so an expired/wrong-account session is reported explicitly instead of surfacing as a `networkidle` timeout.
87
+
88
+ **`direct playwright login`/`doctor` — persisted browser session.** `open_chrome_session` above decrypts from Chrome on every call; `direct playwright login` (`direct_cli/commands/browser_session.py`) instead decrypts once via `session.py::capture_storage_state` and persists the result as a Playwright `storage_state` file (`direct_cli/browser/store.py`, `~/.direct-cli/playwright/session.json`, `0600` inside `0700`, sibling of `auth.py`'s `AUTH_STORE_PATH`). `direct masters`' `_open_session` (`commands/masters.py`) then prefers this saved session (`session.py::open_saved_session`) over a fresh Keychain decrypt, falling back automatically when no session is saved, an explicit `--profile-dir`/`--chrome-profile` was passed, or the saved session turns out to be stale (`BrowserAuthError` triggers one retry via fresh decrypt). `direct playwright doctor` (`direct_cli/browser/diagnostics.py`) is a **read-only** diagnostic over the whole chain (playwright/cryptography installed, chromium downloaded, Chrome profile found, Keychain key derivable, cookies decryptable, saved session present/fresh) — it never logs in, never launches a browser of its own, and never writes to disk; that invariant is enforced by contract tests in `tests/test_playwright_session.py`.
89
+
90
+ **`direct masters list` reads the grid's JSON API, not its DOM (#639).** Live diagnosis found the campaigns grid (`https://direct.yandex.ru/dna/grid/campaigns/`) is a virtualized SPA that never renders a `a[href*='/wizard/campaigns/']` anchor, so the original DOM-scraping approach could never have worked. `direct_cli/browser/masters.py::fetch_masters_list` instead intercepts the grid's own data call — `POST /web-api/grid/api?operationName=GridCampaigns` — via `page.on("response", ...)`, replays it through `page.request.post(...)` (with the captured headers) to paginate past its 200-row `GRID_PAGE_LIMIT`, and never hand-assembles the GraphQL query itself (it is several KB of fragments that would drift out of sync with Yandex's schema). Rows created via Мастер кампаний have no distinguishing `type`/`metaType`/`__typename` — the one field that does discriminate them, confirmed live, is `source == "UAC"` (`MASTERS_SOURCE`). `status.primaryStatus` is filtered client-side via `STATUS_FILTERS` (`masters list --status`, default `not-archived`) because the grid's `status-filter` URL query parameter is silently ignored server-side. `masters` has **no `--login`/agency support**: passing your own login as Yandex's managed-client `ulogin` parameter produces HTTP 401 "Доступ ограничен" on the grid's data calls (confirmed live) — both `list` and `get` build URLs without `ulogin`, and `masters` is in `cli.py`'s `_NO_CREDENTIALS_GROUPS`.
91
+
81
92
  ## PyPI Release
82
93
 
83
94
  Release is two-phase on purpose. Yandex frequently rate-limits the docs host with a SmartCaptcha gateway, which is an external rate-limit on our IP — not evidence that an URL is gone. Mixing docs-health checks into the publish path made releases non-deterministic, so they live in a separate preflight script.
@@ -105,6 +116,8 @@ Builds dist artifacts, runs twine checks, uploads to TestPyPI + PyPI. Does **not
105
116
  ## Tests
106
117
 
107
118
  - **Unit** (`test_cli.py`, `test_comprehensive.py`) — no API calls, no token needed.
119
+ - **Dry-run payload tests** (`tests/test_dry_run_*.py`) — split by command type (issue #604): `test_dry_run.py` is the guard module (rationale + a `test_dry_run_module_is_importable` sanity check); `test_dry_run_shared.py` holds the `_dry_run`/`_read_dry_run`/`_rejected`/`_failing_run`/`_ids_csv`/`_write_jsonl` invocation helpers; `test_dry_run_<resource>.py` modules hold the per-service payload assertions. Add a new dry-run test to the owning module and import the helpers from `test_dry_run_shared`.
120
+ - **Parallel by default:** `addopts` runs the offline tier with `-n auto` (pytest-xdist) across CPU cores; the suite is process-parallel-safe (env/cwd via `monkeypatch`, filesystem via `tmp_path`, the shared orphan store only in the excluded live tiers). Use `pytest -n0` to run sequentially for `pdb`/`-s`/debugging. The live tiers (`integration`/`v4_live_read`/`integration_live_write`) are **not** parallel-safe (real API + shared `~/.direct-cli` orphan store + ordered resource lifecycles); a `pytest_xdist_auto_num_workers` hook in `tests/conftest.py` auto-resolves `-n auto` to a single worker whenever a live marker is selected, so an explicit `pytest -m integration_live_write` stays serial.
108
121
  - **Integration** (`test_integration.py`, `@pytest.mark.integration`) — require `.env` with `YANDEX_DIRECT_TOKEN` and `YANDEX_DIRECT_LOGIN`. Auto-skip if absent.
109
122
  - **Credential resolution in tests:** env vars/current working directory `.env` first, then active `direct auth` profile, then skip. This matches the safe CLI default for base env vs. active profile: a developer machine with an active profile must not silently hit production on a plain `pytest`.
110
123