direct-cli 0.3.8__tar.gz → 0.3.10__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 (234) hide show
  1. direct_cli-0.3.10/CHANGELOG.md +170 -0
  2. {direct_cli-0.3.8 → direct_cli-0.3.10}/CLAUDE.md +9 -0
  3. {direct_cli-0.3.8 → direct_cli-0.3.10}/PKG-INFO +136 -9
  4. {direct_cli-0.3.8 → direct_cli-0.3.10}/README.md +135 -8
  5. {direct_cli-0.3.8 → direct_cli-0.3.10}/direct_cli/auth.py +7 -1
  6. {direct_cli-0.3.8 → direct_cli-0.3.10}/direct_cli/cli.py +14 -1
  7. {direct_cli-0.3.8 → direct_cli-0.3.10}/direct_cli/commands/adgroups.py +53 -9
  8. {direct_cli-0.3.8 → direct_cli-0.3.10}/direct_cli/commands/ads.py +271 -39
  9. {direct_cli-0.3.8 → direct_cli-0.3.10}/direct_cli/commands/bidmodifiers.py +93 -45
  10. {direct_cli-0.3.8 → direct_cli-0.3.10}/direct_cli/commands/bids.py +5 -4
  11. direct_cli-0.3.10/direct_cli/commands/campaigns.py +1061 -0
  12. direct_cli-0.3.10/direct_cli/commands/changes.py +175 -0
  13. {direct_cli-0.3.8 → direct_cli-0.3.10}/direct_cli/commands/dynamicads.py +4 -4
  14. {direct_cli-0.3.8 → direct_cli-0.3.10}/direct_cli/commands/keywordbids.py +6 -0
  15. direct_cli-0.3.10/direct_cli/commands/keywords.py +639 -0
  16. direct_cli-0.3.10/direct_cli/commands/sitelinks.py +249 -0
  17. {direct_cli-0.3.8 → direct_cli-0.3.10}/direct_cli/commands/strategies.py +198 -24
  18. {direct_cli-0.3.8 → direct_cli-0.3.10}/direct_cli/output.py +72 -2
  19. {direct_cli-0.3.8 → direct_cli-0.3.10}/direct_cli/utils.py +78 -3
  20. direct_cli-0.3.10/direct_cli/v4/__init__.py +57 -0
  21. {direct_cli-0.3.8 → direct_cli-0.3.10}/direct_cli/v4_contracts.py +33 -0
  22. {direct_cli-0.3.8 → direct_cli-0.3.10}/direct_cli/wsdl_coverage.py +31 -0
  23. {direct_cli-0.3.8 → direct_cli-0.3.10}/direct_cli.egg-info/PKG-INFO +136 -9
  24. {direct_cli-0.3.8 → direct_cli-0.3.10}/direct_cli.egg-info/SOURCES.txt +7 -0
  25. direct_cli-0.3.10/docs/audits/issue-198-mutating-wsdl-audit.md +148 -0
  26. {direct_cli-0.3.8 → direct_cli-0.3.10}/pyproject.toml +1 -1
  27. direct_cli-0.3.10/scripts/sandbox_write_audit.py +198 -0
  28. {direct_cli-0.3.8 → direct_cli-0.3.10}/scripts/sandbox_write_live.py +143 -14
  29. {direct_cli-0.3.8 → direct_cli-0.3.10}/scripts/test_sandbox_write.sh +7 -1
  30. {direct_cli-0.3.8 → direct_cli-0.3.10}/tests/api_coverage_payloads.py +143 -0
  31. direct_cli-0.3.10/tests/cassettes/test_integration_write/TestWriteFeeds.test_add_update_delete.yaml +166 -0
  32. {direct_cli-0.3.8 → direct_cli-0.3.10}/tests/cassettes/test_integration_write/TestWriteSmartAdTargets.test_add_update_delete.yaml +16 -16
  33. {direct_cli-0.3.8 → direct_cli-0.3.10}/tests/conftest.py +38 -9
  34. {direct_cli-0.3.8 → direct_cli-0.3.10}/tests/test_api_coverage.py +31 -2
  35. direct_cli-0.3.10/tests/test_auth_write_json.py +96 -0
  36. direct_cli-0.3.10/tests/test_changes.py +179 -0
  37. {direct_cli-0.3.8 → direct_cli-0.3.10}/tests/test_cli.py +271 -0
  38. {direct_cli-0.3.8 → direct_cli-0.3.10}/tests/test_dry_run.py +1787 -105
  39. {direct_cli-0.3.8 → direct_cli-0.3.10}/tests/test_integration_write.py +39 -21
  40. {direct_cli-0.3.8 → direct_cli-0.3.10}/tests/test_low_coverage_payloads.py +131 -15
  41. direct_cli-0.3.10/tests/test_sandbox_write_audit.py +67 -0
  42. {direct_cli-0.3.8 → direct_cli-0.3.10}/tests/test_smoke_matrix.py +202 -0
  43. direct_cli-0.3.10/tests/test_v4_runtime_shape.py +188 -0
  44. direct_cli-0.3.10/tests/test_wsdl_parity_gate.py +735 -0
  45. direct_cli-0.3.8/CHANGELOG.md +0 -43
  46. direct_cli-0.3.8/direct_cli/commands/campaigns.py +0 -558
  47. direct_cli-0.3.8/direct_cli/commands/changes.py +0 -104
  48. direct_cli-0.3.8/direct_cli/commands/keywords.py +0 -322
  49. direct_cli-0.3.8/direct_cli/commands/sitelinks.py +0 -134
  50. direct_cli-0.3.8/direct_cli/v4/__init__.py +0 -17
  51. direct_cli-0.3.8/tests/cassettes/test_integration_write/TestWriteFeeds.test_add_update_delete.yaml +0 -58
  52. {direct_cli-0.3.8 → direct_cli-0.3.10}/.env.example +0 -0
  53. {direct_cli-0.3.8 → direct_cli-0.3.10}/.github/copilot-instructions.md +0 -0
  54. {direct_cli-0.3.8 → direct_cli-0.3.10}/.github/workflows/api-coverage.yml +0 -0
  55. {direct_cli-0.3.8 → direct_cli-0.3.10}/.github/workflows/claude.yml +0 -0
  56. {direct_cli-0.3.8 → direct_cli-0.3.10}/.github/workflows/quality.yml +0 -0
  57. {direct_cli-0.3.8 → direct_cli-0.3.10}/.gitignore +0 -0
  58. {direct_cli-0.3.8 → direct_cli-0.3.10}/AGENTS.md +0 -0
  59. {direct_cli-0.3.8 → direct_cli-0.3.10}/MANIFEST.in +0 -0
  60. {direct_cli-0.3.8 → direct_cli-0.3.10}/direct_cli/__init__.py +0 -0
  61. {direct_cli-0.3.8 → direct_cli-0.3.10}/direct_cli/_deprecated.py +0 -0
  62. {direct_cli-0.3.8 → direct_cli-0.3.10}/direct_cli/_smoke_probes.py +0 -0
  63. {direct_cli-0.3.8 → direct_cli-0.3.10}/direct_cli/_vendor/__init__.py +0 -0
  64. {direct_cli-0.3.8 → direct_cli-0.3.10}/direct_cli/_vendor/tapi_yandex_direct/__init__.py +0 -0
  65. {direct_cli-0.3.8 → direct_cli-0.3.10}/direct_cli/_vendor/tapi_yandex_direct/endpoints.py +0 -0
  66. {direct_cli-0.3.8 → direct_cli-0.3.10}/direct_cli/_vendor/tapi_yandex_direct/exceptions.py +0 -0
  67. {direct_cli-0.3.8 → direct_cli-0.3.10}/direct_cli/_vendor/tapi_yandex_direct/resource_mapping.py +0 -0
  68. {direct_cli-0.3.8 → direct_cli-0.3.10}/direct_cli/_vendor/tapi_yandex_direct/tapi_yandex_direct.py +0 -0
  69. {direct_cli-0.3.8 → direct_cli-0.3.10}/direct_cli/_vendor/tapi_yandex_direct/tapi_yandex_direct.pyi +0 -0
  70. {direct_cli-0.3.8 → direct_cli-0.3.10}/direct_cli/_vendor/tapi_yandex_direct/v4/__init__.py +0 -0
  71. {direct_cli-0.3.8 → direct_cli-0.3.10}/direct_cli/_vendor/tapi_yandex_direct/v4/adapter.py +0 -0
  72. {direct_cli-0.3.8 → direct_cli-0.3.10}/direct_cli/_vendor/tapi_yandex_direct/v4/adapter.pyi +0 -0
  73. {direct_cli-0.3.8 → direct_cli-0.3.10}/direct_cli/_vendor/tapi_yandex_direct/v4/resource_mapping.py +0 -0
  74. {direct_cli-0.3.8 → direct_cli-0.3.10}/direct_cli/api.py +0 -0
  75. {direct_cli-0.3.8 → direct_cli-0.3.10}/direct_cli/commands/__init__.py +0 -0
  76. {direct_cli-0.3.8 → direct_cli-0.3.10}/direct_cli/commands/adextensions.py +0 -0
  77. {direct_cli-0.3.8 → direct_cli-0.3.10}/direct_cli/commands/adimages.py +0 -0
  78. {direct_cli-0.3.8 → direct_cli-0.3.10}/direct_cli/commands/advideos.py +0 -0
  79. {direct_cli-0.3.8 → direct_cli-0.3.10}/direct_cli/commands/agencyclients.py +0 -0
  80. {direct_cli-0.3.8 → direct_cli-0.3.10}/direct_cli/commands/audiencetargets.py +0 -0
  81. {direct_cli-0.3.8 → direct_cli-0.3.10}/direct_cli/commands/auth.py +0 -0
  82. {direct_cli-0.3.8 → direct_cli-0.3.10}/direct_cli/commands/balance.py +0 -0
  83. {direct_cli-0.3.8 → direct_cli-0.3.10}/direct_cli/commands/businesses.py +0 -0
  84. {direct_cli-0.3.8 → direct_cli-0.3.10}/direct_cli/commands/clients.py +0 -0
  85. {direct_cli-0.3.8 → direct_cli-0.3.10}/direct_cli/commands/creatives.py +0 -0
  86. {direct_cli-0.3.8 → direct_cli-0.3.10}/direct_cli/commands/dictionaries.py +0 -0
  87. {direct_cli-0.3.8 → direct_cli-0.3.10}/direct_cli/commands/dynamicfeedadtargets.py +0 -0
  88. {direct_cli-0.3.8 → direct_cli-0.3.10}/direct_cli/commands/feeds.py +0 -0
  89. {direct_cli-0.3.8 → direct_cli-0.3.10}/direct_cli/commands/keywordsresearch.py +0 -0
  90. {direct_cli-0.3.8 → direct_cli-0.3.10}/direct_cli/commands/leads.py +0 -0
  91. {direct_cli-0.3.8 → direct_cli-0.3.10}/direct_cli/commands/negativekeywordsharedsets.py +0 -0
  92. {direct_cli-0.3.8 → direct_cli-0.3.10}/direct_cli/commands/reports.py +0 -0
  93. {direct_cli-0.3.8 → direct_cli-0.3.10}/direct_cli/commands/retargeting.py +0 -0
  94. {direct_cli-0.3.8 → direct_cli-0.3.10}/direct_cli/commands/smartadtargets.py +0 -0
  95. {direct_cli-0.3.8 → direct_cli-0.3.10}/direct_cli/commands/turbopages.py +0 -0
  96. {direct_cli-0.3.8 → direct_cli-0.3.10}/direct_cli/commands/v4account.py +0 -0
  97. {direct_cli-0.3.8 → direct_cli-0.3.10}/direct_cli/commands/v4events.py +0 -0
  98. {direct_cli-0.3.8 → direct_cli-0.3.10}/direct_cli/commands/v4finance.py +0 -0
  99. {direct_cli-0.3.8 → direct_cli-0.3.10}/direct_cli/commands/v4forecast.py +0 -0
  100. {direct_cli-0.3.8 → direct_cli-0.3.10}/direct_cli/commands/v4goals.py +0 -0
  101. {direct_cli-0.3.8 → direct_cli-0.3.10}/direct_cli/commands/v4shells.py +0 -0
  102. {direct_cli-0.3.8 → direct_cli-0.3.10}/direct_cli/commands/v4tags.py +0 -0
  103. {direct_cli-0.3.8 → direct_cli-0.3.10}/direct_cli/commands/v4wordstat.py +0 -0
  104. {direct_cli-0.3.8 → direct_cli-0.3.10}/direct_cli/commands/vcards.py +0 -0
  105. {direct_cli-0.3.8 → direct_cli-0.3.10}/direct_cli/reports_coverage.py +0 -0
  106. {direct_cli-0.3.8 → direct_cli-0.3.10}/direct_cli/smoke_matrix.py +0 -0
  107. {direct_cli-0.3.8 → direct_cli-0.3.10}/direct_cli/v4/money.py +0 -0
  108. {direct_cli-0.3.8 → direct_cli-0.3.10}/direct_cli.egg-info/dependency_links.txt +0 -0
  109. {direct_cli-0.3.8 → direct_cli-0.3.10}/direct_cli.egg-info/entry_points.txt +0 -0
  110. {direct_cli-0.3.8 → direct_cli-0.3.10}/direct_cli.egg-info/requires.txt +0 -0
  111. {direct_cli-0.3.8 → direct_cli-0.3.10}/direct_cli.egg-info/top_level.txt +0 -0
  112. {direct_cli-0.3.8 → direct_cli-0.3.10}/docs/superpowers/plans/2026-04-12-issue-32-completion.md +0 -0
  113. {direct_cli-0.3.8 → direct_cli-0.3.10}/docs/superpowers/specs/2026-04-23-vendor-tapi-yandex-direct-design.md +0 -0
  114. {direct_cli-0.3.8 → direct_cli-0.3.10}/scripts/anonymize_cassettes.py +0 -0
  115. {direct_cli-0.3.8 → direct_cli-0.3.10}/scripts/build_api_coverage_checklist.py +0 -0
  116. {direct_cli-0.3.8 → direct_cli-0.3.10}/scripts/build_api_coverage_report.py +0 -0
  117. {direct_cli-0.3.8 → direct_cli-0.3.10}/scripts/check_reports_drift.py +0 -0
  118. {direct_cli-0.3.8 → direct_cli-0.3.10}/scripts/check_wsdl_drift.py +0 -0
  119. {direct_cli-0.3.8 → direct_cli-0.3.10}/scripts/patch_vendor_imports.py +0 -0
  120. {direct_cli-0.3.8 → direct_cli-0.3.10}/scripts/refresh_reports_cache.py +0 -0
  121. {direct_cli-0.3.8 → direct_cli-0.3.10}/scripts/refresh_wsdl_cache.py +0 -0
  122. {direct_cli-0.3.8 → direct_cli-0.3.10}/scripts/release_pypi.sh +0 -0
  123. {direct_cli-0.3.8 → direct_cli-0.3.10}/scripts/test_dangerous_commands.sh +0 -0
  124. {direct_cli-0.3.8 → direct_cli-0.3.10}/scripts/test_safe_commands.sh +0 -0
  125. {direct_cli-0.3.8 → direct_cli-0.3.10}/scripts/update_vendor.sh +0 -0
  126. {direct_cli-0.3.8 → direct_cli-0.3.10}/setup.cfg +0 -0
  127. {direct_cli-0.3.8 → direct_cli-0.3.10}/setup.py +0 -0
  128. {direct_cli-0.3.8 → direct_cli-0.3.10}/tests/API_COVERAGE.md +0 -0
  129. {direct_cli-0.3.8 → direct_cli-0.3.10}/tests/API_ISSUE_AUDIT.md +0 -0
  130. {direct_cli-0.3.8 → direct_cli-0.3.10}/tests/MANUAL_COVERAGE.md +0 -0
  131. {direct_cli-0.3.8 → direct_cli-0.3.10}/tests/__init__.py +0 -0
  132. {direct_cli-0.3.8 → direct_cli-0.3.10}/tests/_orphan_store.py +0 -0
  133. {direct_cli-0.3.8 → direct_cli-0.3.10}/tests/cassettes/test_integration_write/TestWriteAdExtensions.test_add_delete.yaml +0 -0
  134. {direct_cli-0.3.8 → direct_cli-0.3.10}/tests/cassettes/test_integration_write/TestWriteAdGroups.test_add_update_delete.yaml +0 -0
  135. {direct_cli-0.3.8 → direct_cli-0.3.10}/tests/cassettes/test_integration_write/TestWriteAdImages.test_add_delete.yaml +0 -0
  136. {direct_cli-0.3.8 → direct_cli-0.3.10}/tests/cassettes/test_integration_write/TestWriteAds.test_add_text_ad_update_delete.yaml +0 -0
  137. {direct_cli-0.3.8 → direct_cli-0.3.10}/tests/cassettes/test_integration_write/TestWriteAudienceTargets.test_add_delete.yaml +0 -0
  138. {direct_cli-0.3.8 → direct_cli-0.3.10}/tests/cassettes/test_integration_write/TestWriteBidModifiersAdd.test_add_delete_mobile.yaml +0 -0
  139. {direct_cli-0.3.8 → direct_cli-0.3.10}/tests/cassettes/test_integration_write/TestWriteBidModifiersSet.test_set_without_id_is_rejected.yaml +0 -0
  140. {direct_cli-0.3.8 → direct_cli-0.3.10}/tests/cassettes/test_integration_write/TestWriteBids.test_set_bid.yaml +0 -0
  141. {direct_cli-0.3.8 → direct_cli-0.3.10}/tests/cassettes/test_integration_write/TestWriteBidsRead.test_bids_get.yaml +0 -0
  142. {direct_cli-0.3.8 → direct_cli-0.3.10}/tests/cassettes/test_integration_write/TestWriteBidsRead.test_bids_set_auto.yaml +0 -0
  143. {direct_cli-0.3.8 → direct_cli-0.3.10}/tests/cassettes/test_integration_write/TestWriteCampaignDraftLifecycle.test_draft_create_get_delete.yaml +0 -0
  144. {direct_cli-0.3.8 → direct_cli-0.3.10}/tests/cassettes/test_integration_write/TestWriteCampaigns.test_campaign_lifecycle.yaml +0 -0
  145. {direct_cli-0.3.8 → direct_cli-0.3.10}/tests/cassettes/test_integration_write/TestWriteDynamicAds.test_add_update_delete.yaml +0 -0
  146. {direct_cli-0.3.8 → direct_cli-0.3.10}/tests/cassettes/test_integration_write/TestWriteKeywordBids.test_set_keyword_bid.yaml +0 -0
  147. {direct_cli-0.3.8 → direct_cli-0.3.10}/tests/cassettes/test_integration_write/TestWriteKeywords.test_add_update_delete.yaml +0 -0
  148. {direct_cli-0.3.8 → direct_cli-0.3.10}/tests/cassettes/test_integration_write/TestWriteNegativeKeywordSharedSets.test_add_update_delete.yaml +0 -0
  149. {direct_cli-0.3.8 → direct_cli-0.3.10}/tests/cassettes/test_integration_write/TestWriteRetargeting.test_add_delete.yaml +0 -0
  150. {direct_cli-0.3.8 → direct_cli-0.3.10}/tests/cassettes/test_integration_write/TestWriteRetargetingUpdate.test_retargeting_update.yaml +0 -0
  151. {direct_cli-0.3.8 → direct_cli-0.3.10}/tests/cassettes/test_integration_write/TestWriteSitelinks.test_add_delete.yaml +0 -0
  152. {direct_cli-0.3.8 → direct_cli-0.3.10}/tests/cassettes/test_integration_write/TestWriteStrategies.test_strategies_lifecycle.yaml +0 -0
  153. {direct_cli-0.3.8 → direct_cli-0.3.10}/tests/cassettes/test_integration_write/TestWriteVCards.test_add_delete.yaml +0 -0
  154. {direct_cli-0.3.8 → direct_cli-0.3.10}/tests/cassettes/test_v5_live_write/test_v5_live_draft_adgroups_add_update_delete.yaml +0 -0
  155. {direct_cli-0.3.8 → direct_cli-0.3.10}/tests/cassettes/test_v5_live_write/test_v5_live_draft_adimages_add_get_delete.yaml +0 -0
  156. {direct_cli-0.3.8 → direct_cli-0.3.10}/tests/cassettes/test_v5_live_write/test_v5_live_draft_ads_add_update_delete.yaml +0 -0
  157. {direct_cli-0.3.8 → direct_cli-0.3.10}/tests/cassettes/test_v5_live_write/test_v5_live_draft_ads_suspend_resume_archive_unarchive.yaml +0 -0
  158. {direct_cli-0.3.8 → direct_cli-0.3.10}/tests/cassettes/test_v5_live_write/test_v5_live_draft_advideos_add_get.yaml +0 -0
  159. {direct_cli-0.3.8 → direct_cli-0.3.10}/tests/cassettes/test_v5_live_write/test_v5_live_draft_audiencetargets_add_delete.yaml +0 -0
  160. {direct_cli-0.3.8 → direct_cli-0.3.10}/tests/cassettes/test_v5_live_write/test_v5_live_draft_audiencetargets_suspend_resume.yaml +0 -0
  161. {direct_cli-0.3.8 → direct_cli-0.3.10}/tests/cassettes/test_v5_live_write/test_v5_live_draft_bids_set.yaml +0 -0
  162. {direct_cli-0.3.8 → direct_cli-0.3.10}/tests/cassettes/test_v5_live_write/test_v5_live_draft_campaign_create_get_delete.yaml +0 -0
  163. {direct_cli-0.3.8 → direct_cli-0.3.10}/tests/cassettes/test_v5_live_write/test_v5_live_draft_creatives_chain_advideo_to_creative.yaml +0 -0
  164. {direct_cli-0.3.8 → direct_cli-0.3.10}/tests/cassettes/test_v5_live_write/test_v5_live_draft_dynamicads_add_delete.yaml +0 -0
  165. {direct_cli-0.3.8 → direct_cli-0.3.10}/tests/cassettes/test_v5_live_write/test_v5_live_draft_dynamicads_suspend_resume.yaml +0 -0
  166. {direct_cli-0.3.8 → direct_cli-0.3.10}/tests/cassettes/test_v5_live_write/test_v5_live_draft_keywordbids_set.yaml +0 -0
  167. {direct_cli-0.3.8 → direct_cli-0.3.10}/tests/cassettes/test_v5_live_write/test_v5_live_draft_keywords_add_update_delete.yaml +0 -0
  168. {direct_cli-0.3.8 → direct_cli-0.3.10}/tests/cassettes/test_v5_live_write/test_v5_live_draft_keywords_suspend_resume.yaml +0 -0
  169. {direct_cli-0.3.8 → direct_cli-0.3.10}/tests/cassettes/test_v5_live_write/test_v5_live_draft_sitelinks_add_get_delete.yaml +0 -0
  170. {direct_cli-0.3.8 → direct_cli-0.3.10}/tests/cassettes/test_v5_live_write/test_v5_live_draft_smartadtargets_add_update_delete.yaml +0 -0
  171. {direct_cli-0.3.8 → direct_cli-0.3.10}/tests/cassettes/test_v5_live_write/test_v5_live_draft_smartadtargets_suspend_resume.yaml +0 -0
  172. {direct_cli-0.3.8 → direct_cli-0.3.10}/tests/fixtures/test-video.mp4 +0 -0
  173. {direct_cli-0.3.8 → direct_cli-0.3.10}/tests/reports_cache/raw/fields-list.html +0 -0
  174. {direct_cli-0.3.8 → direct_cli-0.3.10}/tests/reports_cache/raw/headers.html +0 -0
  175. {direct_cli-0.3.8 → direct_cli-0.3.10}/tests/reports_cache/raw/period.html +0 -0
  176. {direct_cli-0.3.8 → direct_cli-0.3.10}/tests/reports_cache/raw/spec.html +0 -0
  177. {direct_cli-0.3.8 → direct_cli-0.3.10}/tests/reports_cache/raw/type.html +0 -0
  178. {direct_cli-0.3.8 → direct_cli-0.3.10}/tests/reports_cache/spec.json +0 -0
  179. {direct_cli-0.3.8 → direct_cli-0.3.10}/tests/test_auth_bw.py +0 -0
  180. {direct_cli-0.3.8 → direct_cli-0.3.10}/tests/test_auth_oauth.py +0 -0
  181. {direct_cli-0.3.8 → direct_cli-0.3.10}/tests/test_auth_op.py +0 -0
  182. {direct_cli-0.3.8 → direct_cli-0.3.10}/tests/test_balance.py +0 -0
  183. {direct_cli-0.3.8 → direct_cli-0.3.10}/tests/test_cli_contract.py +0 -0
  184. {direct_cli-0.3.8 → direct_cli-0.3.10}/tests/test_comprehensive.py +0 -0
  185. {direct_cli-0.3.8 → direct_cli-0.3.10}/tests/test_integration.py +0 -0
  186. {direct_cli-0.3.8 → direct_cli-0.3.10}/tests/test_reports_drift.py +0 -0
  187. {direct_cli-0.3.8 → direct_cli-0.3.10}/tests/test_reports_parsing.py +0 -0
  188. {direct_cli-0.3.8 → direct_cli-0.3.10}/tests/test_transport_contract.py +0 -0
  189. {direct_cli-0.3.8 → direct_cli-0.3.10}/tests/test_v4_contracts.py +0 -0
  190. {direct_cli-0.3.8 → direct_cli-0.3.10}/tests/test_v4_foundation.py +0 -0
  191. {direct_cli-0.3.8 → direct_cli-0.3.10}/tests/test_v4_live_contracts.py +0 -0
  192. {direct_cli-0.3.8 → direct_cli-0.3.10}/tests/test_v4_safety.py +0 -0
  193. {direct_cli-0.3.8 → direct_cli-0.3.10}/tests/test_v4account.py +0 -0
  194. {direct_cli-0.3.8 → direct_cli-0.3.10}/tests/test_v4events.py +0 -0
  195. {direct_cli-0.3.8 → direct_cli-0.3.10}/tests/test_v4finance_money.py +0 -0
  196. {direct_cli-0.3.8 → direct_cli-0.3.10}/tests/test_v4finance_read.py +0 -0
  197. {direct_cli-0.3.8 → direct_cli-0.3.10}/tests/test_v4forecast.py +0 -0
  198. {direct_cli-0.3.8 → direct_cli-0.3.10}/tests/test_v4goals.py +0 -0
  199. {direct_cli-0.3.8 → direct_cli-0.3.10}/tests/test_v4tags.py +0 -0
  200. {direct_cli-0.3.8 → direct_cli-0.3.10}/tests/test_v4wordstat.py +0 -0
  201. {direct_cli-0.3.8 → direct_cli-0.3.10}/tests/test_v5_live_write.py +0 -0
  202. {direct_cli-0.3.8 → direct_cli-0.3.10}/tests/test_vendor_imports.py +0 -0
  203. {direct_cli-0.3.8 → direct_cli-0.3.10}/tests/wsdl_cache/adextensions.xml +0 -0
  204. {direct_cli-0.3.8 → direct_cli-0.3.10}/tests/wsdl_cache/adgroups.xml +0 -0
  205. {direct_cli-0.3.8 → direct_cli-0.3.10}/tests/wsdl_cache/adimages.xml +0 -0
  206. {direct_cli-0.3.8 → direct_cli-0.3.10}/tests/wsdl_cache/ads.xml +0 -0
  207. {direct_cli-0.3.8 → direct_cli-0.3.10}/tests/wsdl_cache/advideos.xml +0 -0
  208. {direct_cli-0.3.8 → direct_cli-0.3.10}/tests/wsdl_cache/agencyclients.xml +0 -0
  209. {direct_cli-0.3.8 → direct_cli-0.3.10}/tests/wsdl_cache/audiencetargets.xml +0 -0
  210. {direct_cli-0.3.8 → direct_cli-0.3.10}/tests/wsdl_cache/bidmodifiers.xml +0 -0
  211. {direct_cli-0.3.8 → direct_cli-0.3.10}/tests/wsdl_cache/bids.xml +0 -0
  212. {direct_cli-0.3.8 → direct_cli-0.3.10}/tests/wsdl_cache/businesses.xml +0 -0
  213. {direct_cli-0.3.8 → direct_cli-0.3.10}/tests/wsdl_cache/campaigns.xml +0 -0
  214. {direct_cli-0.3.8 → direct_cli-0.3.10}/tests/wsdl_cache/changes.xml +0 -0
  215. {direct_cli-0.3.8 → direct_cli-0.3.10}/tests/wsdl_cache/clients.xml +0 -0
  216. {direct_cli-0.3.8 → direct_cli-0.3.10}/tests/wsdl_cache/creatives.xml +0 -0
  217. {direct_cli-0.3.8 → direct_cli-0.3.10}/tests/wsdl_cache/dictionaries.xml +0 -0
  218. {direct_cli-0.3.8 → direct_cli-0.3.10}/tests/wsdl_cache/dynamicfeedadtargets.xml +0 -0
  219. {direct_cli-0.3.8 → direct_cli-0.3.10}/tests/wsdl_cache/dynamictextadtargets.xml +0 -0
  220. {direct_cli-0.3.8 → direct_cli-0.3.10}/tests/wsdl_cache/feeds.xml +0 -0
  221. {direct_cli-0.3.8 → direct_cli-0.3.10}/tests/wsdl_cache/imports/adextensiontypes.xsd +0 -0
  222. {direct_cli-0.3.8 → direct_cli-0.3.10}/tests/wsdl_cache/imports/general.xsd +0 -0
  223. {direct_cli-0.3.8 → direct_cli-0.3.10}/tests/wsdl_cache/imports/generalclients.xsd +0 -0
  224. {direct_cli-0.3.8 → direct_cli-0.3.10}/tests/wsdl_cache/keywordbids.xml +0 -0
  225. {direct_cli-0.3.8 → direct_cli-0.3.10}/tests/wsdl_cache/keywords.xml +0 -0
  226. {direct_cli-0.3.8 → direct_cli-0.3.10}/tests/wsdl_cache/keywordsresearch.xml +0 -0
  227. {direct_cli-0.3.8 → direct_cli-0.3.10}/tests/wsdl_cache/leads.xml +0 -0
  228. {direct_cli-0.3.8 → direct_cli-0.3.10}/tests/wsdl_cache/negativekeywordsharedsets.xml +0 -0
  229. {direct_cli-0.3.8 → direct_cli-0.3.10}/tests/wsdl_cache/retargetinglists.xml +0 -0
  230. {direct_cli-0.3.8 → direct_cli-0.3.10}/tests/wsdl_cache/sitelinks.xml +0 -0
  231. {direct_cli-0.3.8 → direct_cli-0.3.10}/tests/wsdl_cache/smartadtargets.xml +0 -0
  232. {direct_cli-0.3.8 → direct_cli-0.3.10}/tests/wsdl_cache/strategies.xml +0 -0
  233. {direct_cli-0.3.8 → direct_cli-0.3.10}/tests/wsdl_cache/turbopages.xml +0 -0
  234. {direct_cli-0.3.8 → direct_cli-0.3.10}/tests/wsdl_cache/vcards.xml +0 -0
@@ -0,0 +1,170 @@
1
+ # Changelog
2
+
3
+ ## 0.3.10
4
+
5
+ **Added:**
6
+
7
+ - `direct changes check` now exposes all three mutually-exclusive ID
8
+ filters from the WSDL — `--campaign-ids` (≤3000), `--ad-group-ids`
9
+ (≤10 000) and `--ad-ids` (≤50 000); exactly one is required and the
10
+ mutex is enforced via `click.UsageError` (exit code 2) before any
11
+ request is built. `--fields` is now validated against the
12
+ `CheckFieldEnum` (`CampaignIds`, `AdGroupIds`, `AdIds`,
13
+ `CampaignsStat`); unknown values, empty / comma-only inputs and the
14
+ WSDL `minOccurs=1` violation are caught up-front. Refs: Closes #228.
15
+ - `direct sitelinks add` accepts `\|` as a literal pipe inside
16
+ `--sitelink` spec strings, so UTM templates like
17
+ `cid|{campaign_id}|gid|{gbid}` survive parsing. Two new structural
18
+ sources mirror the `keywords.add` #218 pattern:
19
+ `--sitelink-json '<JSON-array>'` (inline) and
20
+ `--sitelinks-from-file <path.jsonl>` (one object per line); sources
21
+ are mutually exclusive. Unknown JSON keys are rejected with the
22
+ offending key surfaced (no silent data loss), and missing
23
+ `Title`/`Href` rows are rejected with the row index. Refs:
24
+ Closes #221, Closes #220.
25
+ - `direct v4 *` commands now validate request body shape against
26
+ `V4_METHOD_CONTRACTS` before sending. Documented param shapes
27
+ (`PARAM_ARRAY` / `PARAM_OBJECT` / `PARAM_OPTIONAL_OBJECT` /
28
+ `PARAM_SCALAR`) raise `click.UsageError` on mismatch — the request
29
+ never reaches the network. Undocumented-shape methods are split by
30
+ contract safety: `SAFETY_READ` (e.g. `GetKeywordsSuggestion`)
31
+ emits a stderr warning and proceeds; `SAFETY_WRITE` /
32
+ `SAFETY_DANGEROUS` (e.g. `PayCampaignsByCard`) fail-closed with a
33
+ remediation pointer to `V4_METHOD_CONTRACTS`. Refs: Closes #182.
34
+ - Regression tests that lock down subtype validation invariants from
35
+ the `#210` umbrella repro matrix. Nine new `SILENT_LOSS_PROBES` in
36
+ `tests/test_wsdl_parity_gate.py` cover per-type rejection across
37
+ `campaigns add`, `adgroups add`, `ads add`, `bidmodifiers add` and
38
+ `strategies add` (test-only — the corrected rejection behavior was
39
+ shipped earlier in 0.3.9 via #198 audit follow-up PRs). Three new
40
+ non-regression tests in `tests/test_dry_run.py` lock down
41
+ `strategies update` field aliases (`AverageCpcPerFilter →
42
+ FilterAverageCpc`, `PayForConversion → Cpa`) and confirm that
43
+ `AverageCpa` update without `--goal-id` stays WSDL-valid
44
+ (`GoalId` is `minOccurs=0` on update). Refs: Closes #210.
45
+
46
+ **Fixed:**
47
+
48
+ - `direct keywords add` in bulk mode (`--from-file` / `--keywords-json`,
49
+ shipped in 0.3.9 / #218) now surfaces per-item `Errors` instead of
50
+ swallowing them and exiting 0 with raw JSON. The per-chunk loop now
51
+ calls `raise_for_api_result_errors` and the final response goes
52
+ through `format_output`, so the 8800 Client-Login guidance and the
53
+ full `Errors` payload propagate through the existing exception
54
+ handler. The partial-success diagnostic ("these keywords were
55
+ already created in Yandex Direct") only lists items Yandex actually
56
+ accepted. Refs: Closes #211.
57
+ - `direct_cli/auth.py::_write_json` no longer leaks a file descriptor
58
+ when `chmod` fails between `tempfile.mkstemp` and `os.fdopen`.
59
+ Descriptor ownership is now tracked via a sentinel; cleanup errors
60
+ in `os.close` / `os.unlink` use `contextlib.suppress(OSError)` so
61
+ the original exception is preserved. Refs: Closes #154.
62
+
63
+ ## 0.3.9
64
+
65
+ **Added:**
66
+
67
+ - `direct keywords add` now supports batch mode via `--from-file PATH`
68
+ (JSONL, one keyword object per line) or `--keywords-json '[…]'`
69
+ (inline JSON array). The CLI splits input into chunks of 10 — the
70
+ Yandex Direct API limit for `keywords.add` documented at
71
+ https://yandex.ru/dev/direct/doc/dg/objects/keyword.html — preserves
72
+ input order, and merges `AddResults` from every chunk into a single
73
+ response. Item-level errors do not abort the batch. If a chunk-level
74
+ exception breaks the loop, already-created Ids are printed to stderr
75
+ with a "Partial success before failure" header so a retry doesn't
76
+ duplicate them. Pre-flight warning when any AdGroupId in the input
77
+ exceeds the per-ad-group limit of 200 keywords (the API rejects the
78
+ excess with per-item errors; warning surfaces this before any chunk
79
+ is sent). Row keys use WSDL CamelCase (`Keyword`, `AdGroupId`,
80
+ `Bid`, `ContextBid`, `UserParam1`, `UserParam2`); unknown keys are
81
+ rejected with the row number, and JSON booleans are explicitly
82
+ rejected to prevent silent `True → 1` coercion. `--adgroup-id` is
83
+ optional in batch mode and acts as a default, overridable per row.
84
+ `--dry-run` prints the first chunk's payload alongside
85
+ `{chunks, totalItems, chunkSize}`. Single-item mode (`--keyword`)
86
+ is unchanged (#203).
87
+ - `direct campaigns add` typed flags for CPA strategies and
88
+ cross-cutting `CampaignAddItem` fields: `--goal-id` (single
89
+ Metrika goal), `--crr` (CRR percentage for
90
+ `PAY_FOR_CONVERSION_CRR`),
91
+ `--priority-goals goal_id:value,…` (multi-goal CPA via
92
+ WSDL `PriorityGoalsArray`), `--average-cpa MICRO_RUBLES`,
93
+ `--bid-ceiling MICRO_RUBLES`, `--counter-ids`
94
+ (TextCampaign/DynamicTextCampaign), `--notification JSON`
95
+ (`CampaignBase.Notification` with `SmsSettings`/`EmailSettings`
96
+ shape validation), `--time-targeting JSON`
97
+ (`CampaignAddItem.TimeTargeting` with `HolidaysSchedule`
98
+ shape validation). Strategy-subtype compatibility is enforced
99
+ via `UsageError` at CLI level both ways: WSDL-incompatible flags
100
+ are rejected (e.g. `--average-cpa` for `HIGHEST_POSITION`,
101
+ `--crr` outside `PAY_FOR_CONVERSION_CRR`,
102
+ `--bid-ceiling` for `PayForConversionCrr` /
103
+ `PayForConversionMultipleGoals`), and WSDL `minOccurs=1`
104
+ fields are demanded up-front (e.g. picking `AVERAGE_CPA`
105
+ without `--average-cpa`+`--goal-id`, or `PAY_FOR_CONVERSION_CRR`
106
+ without `--crr`+`--goal-id`, or `*_MULTIPLE_GOALS` without
107
+ `--priority-goals`, all fail at the CLI instead of the API).
108
+ Closes #204.
109
+
110
+ **Notes:**
111
+
112
+ - Issue #204 also requested `--goals` (array) and
113
+ `--network-settings`; both were dropped after WSDL audit. Yandex
114
+ `Strategy*Add` complex types declare only scalar `GoalId`, so
115
+ multi-goal CPA is shipped through `--priority-goals` instead
116
+ (correct WSDL path: `TextCampaign.PriorityGoals.Items[].GoalId/Value`).
117
+ No `NetworkSettings` field exists on `CampaignAddItem` /
118
+ `TextCampaignAddItem` / `DynamicTextCampaignAddItem` /
119
+ `SmartCampaignAddItem` in the current `campaigns.xml` WSDL.
120
+
121
+ **Fixed:**
122
+
123
+ - Refreshed `TestWriteFeeds` and `TestWriteSmartAdTargets` VCR cassettes against a real sandbox, dropped the `_FEED_REGRESSION_PATTERNS` skip workaround, and updated `sandbox_feed` / `sandbox_smart_adgroup` fixtures to pass the now-WSDL-required `--business-type RETAIL` (FeedAddItem) and `--counter-id` (SmartCampaignAddItem). Tests now skip only on genuine sandbox limitations, not on the missing-option proxy that the workaround papered over (#206, fallout from #201). Test invocation now also passes `--login` and prefers env vars over an active `direct auth` profile, matching the inversion documented in CLAUDE.md.
124
+ - WSDL parity gate now fails fast when `COMMAND_WSDL_MAP` points at a container that does not exist in the WSDL request schema. The previous skip-on-empty-required-list silently masked typo'd container names (#206, Copilot follow-up from #205).
125
+ - `WSDL_FIELD_TO_CLI_OPTION` no longer references the non-existent generic `--file` flag. `SourceType` maps to `{--url}` and `ImageData` maps to `{--image-data, --image-file}`, matching the real CLI surface (#206, Copilot follow-up from #205).
126
+ - `direct bidmodifiers set --help` no longer advertises the rejected `--campaign-id`/`--type` legacy path; the rejection now happens via an eager Click callback (same pattern as deprecated `keywords update` options), preserving the existing `UsageError` message for regression coverage (#206, Copilot follow-up from #214).
127
+
128
+ **Refs:** Closes issues #122, #138, #198, #202, #203, #204, #206, #207.
129
+
130
+ ## 0.3.8
131
+
132
+ **BREAKING CHANGES:**
133
+
134
+ - `direct ads update` now requires `--type {TEXT_AD,TEXT_IMAGE_AD,MOBILE_APP_AD}`. Scripts that called `ads update` with only field flags will fail with `Missing option '--type'`. Mirrors the WSDL one-of choice between TextAd/TextImageAd/MobileAppAd update subtypes (PR #197).
135
+ - `direct ads add --type TEXT_IMAGE_AD` rejects `--title/--text` (TEXT_IMAGE_AD has no such WSDL fields). `direct ads update --status` rejected — use `ads suspend/resume/archive/unarchive` for status changes (PR #190).
136
+ - `direct ads add --type MOBILE_APP_AD --href` rejected — MobileAppAd uses `--tracking-url`, not `--href` (PR #196).
137
+ - `direct feeds add` now requires `--business-type {RETAIL,HOTELS,REALTY,AUTOMOBILES,FLIGHTS,OTHER}`. Mirrors WSDL FeedAddItem.BusinessType (minOccurs=1) (PR #201).
138
+
139
+ **Schema gate — mutating ops parity:**
140
+
141
+ - Extended the WSDL `*FieldNames` schema gate (introduced for `get` in 0.3.7) to mutating operations (`add/update/set/setBids/lifecycle`). Added per-operation waiver granularity via `SCHEMA_GATE_OPERATION_WAIVERS` (PR #181).
142
+ - Promoted dynamicads, bidmodifiers add/set, adimages/advideos/vcards add (media payloads), adextensions/retargeting/feeds.add typed fixtures to `PAYLOAD_CASES` (PRs #184, #185, #187, #188).
143
+ - Added MOBILE_APP_AD branch to `ads add` mirroring WSDL `MobileAppAdAdd` (PR #190).
144
+ - `bidmodifiers.delete` correctly classified as a real destructive WSDL operation and added to schema gate (PR #194); the earlier "Helper/legacy surface" rationale was a mis-classification — see post-mortem in #199 / PR #200.
145
+
146
+ **Strict WSDL parity policy:**
147
+
148
+ - Documented "Strict WSDL parity" principle in `CLAUDE.md`: `DRY_RUN_PAYLOAD_EXCLUSIONS` may only contain entries from five legitimate categories (read-path `*.get`, runtime-deprecated, v4-not-in-v5-wsdl, custom non-RPC endpoints, methods covered by `tests/test_dry_run.py`). New guard test `test_dry_run_exclusions_have_no_helper_or_legacy_rationale` fails CI if any rationale uses banned phrases (PR #200).
149
+
150
+ **Integration test coverage:**
151
+
152
+ - Added read-only sandbox integration tests for `changes`, `keywordsresearch`, `balance` (PR #186).
153
+ - Added v5 write integration coverage for `strategies` lifecycle, `retargeting update`, `bids get/set-auto`, plus `auth status/list` read-only tests (PR #189).
154
+ - Re-recorded TestWriteBidsRead cassettes against live API and rewrote host to sandbox so the bids endpoints get real coverage in replay mode (PR #193).
155
+
156
+ **CI infrastructure:**
157
+
158
+ - Switched Claude code-review GitHub Action from default (Sonnet 4.5) to Claude Opus 4.7 for deeper PR review (PR #192).
159
+
160
+ **Refs:** Closes issues #118, #136, #137, #175, #176, #180, #183, #191, #199.
161
+
162
+ ## 0.3.3
163
+
164
+ **BREAKING CHANGE:** OAuth profiles created before 0.3.3 (without `refresh_token` and `expires_at`) are no longer accepted. Any such profile will fail immediately with an "incomplete profile" error. Run `direct auth login --profile <name>` to re-authenticate and create a valid 0.3.3 profile.
165
+
166
+ - Added refresh token persistence for OAuth profiles.
167
+ - Added automatic OAuth access token refresh before expiry.
168
+ - Added `expires_in` details to `direct auth status`.
169
+ - Added JSON output for `direct auth status`.
170
+ - Kept `direct auth login --oauth-token` as a manual access-token import without auto-refresh.
@@ -52,6 +52,15 @@ Click group-of-groups. Each Yandex Direct API resource = one file in `direct_cli
52
52
 
53
53
  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.
54
54
 
55
+ **WSDL parity gate:** `tests/test_wsdl_parity_gate.py` runs four invariant checks across every `add`/`update`/`set` command in `WRITE_SANDBOX`:
56
+
57
+ 1. *Empty subtype no-op* — a mutating command with only the resource ID must refuse to send the payload (no silent no-op on the live API).
58
+ 2. *Silent data loss* — a typed flag that does not belong to the chosen `--type` must raise `UsageError`, not be dropped.
59
+ 3. *WSDL `minOccurs=1` not validated* — every required WSDL item field must be enforced either via Click `required=True` *or* a documented `UsageError` body check (listed in `INTERNAL_VALIDATION`).
60
+ 4. *Strategy enum drift* — `STRATEGY_TYPES` (`direct_cli/commands/strategies.py`) must equal the subtype-of-one field names in `StrategyAddItem`.
61
+
62
+ Adding a new mutating command requires extending `COMMAND_WSDL_MAP` in `tests/test_wsdl_parity_gate.py` (the coverage test fails otherwise) and, if the WSDL request has a non-mechanical field name, also `WSDL_FIELD_TO_CLI_OPTION`. Tracked in issue #198.
63
+
55
64
  **SelectionCriteria:** Resources like `adgroups`, `ads`, `keywords` require at least one of `Ids`, `CampaignIds`, or `AdGroupIds` — otherwise API error 4001.
56
65
 
57
66
  **Error handling:** All commands wrap API calls in `try/except Exception` → `print_error(str(e))` + `raise click.Abort()`.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: direct-cli
3
- Version: 0.3.8
3
+ Version: 0.3.10
4
4
  Summary: Command-line interface for Yandex Direct API
5
5
  Author: axisrow
6
6
  License: MIT
@@ -384,6 +384,15 @@ direct campaigns add --name "My Campaign" --start-date 2024-02-01 --type TEXT_CA
384
384
  direct campaigns add --name "Dynamic Campaign" --start-date 2024-02-01 --type DYNAMIC_TEXT_CAMPAIGN --setting ADD_METRICA_TAG=NO --search-strategy HIGHEST_POSITION --network-strategy SERVING_OFF --dry-run
385
385
  direct campaigns add --name "Smart Campaign" --start-date 2024-02-01 --type SMART_CAMPAIGN --network-strategy AVERAGE_CPC_PER_FILTER --filter-average-cpc 1000000 --counter-id 123 --dry-run
386
386
 
387
+ # CPA strategy (single goal): --goal-id required, --average-cpa / --bid-ceiling are micro-rubles
388
+ direct campaigns add --name "CPA Campaign" --start-date 2026-06-01 --type TEXT_CAMPAIGN --search-strategy AVERAGE_CPA --network-strategy SERVING_OFF --goal-id 1234567 --average-cpa 500000000 --bid-ceiling 1000000000 --counter-ids 111,222 --dry-run
389
+
390
+ # Multi-goal CPA via PriorityGoals (goal_id:value pairs, WSDL PriorityGoalsItem)
391
+ direct campaigns add --name "Multi-Goal CPA" --start-date 2026-06-01 --type TEXT_CAMPAIGN --search-strategy AVERAGE_CPA_MULTIPLE_GOALS --network-strategy SERVING_OFF --priority-goals 1234567:80,9876543:20 --bid-ceiling 1000000000 --dry-run
392
+
393
+ # Notification (Sms/Email) and TimeTargeting accept JSON with WSDL CamelCase keys
394
+ direct campaigns add --name "Notify+Schedule" --start-date 2026-06-01 --type TEXT_CAMPAIGN --search-strategy HIGHEST_POSITION --network-strategy SERVING_OFF --notification '{"EmailSettings":{"Email":"ops@example.com","SendWarnings":"YES"}}' --time-targeting '{"Schedule":["1A0123456789ABCDEFGHIJKL"],"ConsiderWorkingWeekends":"YES"}' --dry-run
395
+
387
396
  # Update / lifecycle
388
397
  direct campaigns update --id 12345 --name "New Name" --status SUSPENDED --budget 100000000 --start-date 2024-02-10 --end-date 2024-03-01
389
398
  direct campaigns suspend --id 12345
@@ -410,11 +419,21 @@ direct adgroups delete --id 67890
410
419
  direct ads get --campaign-ids 1,2,3
411
420
  direct ads get --adgroup-ids 45678 --format table
412
421
  direct ads add --adgroup-id 12345 --type TEXT_AD --title "Title" --text "Ad text" --href "https://example.com" --dry-run
413
- direct ads add --adgroup-id 12345 --type TEXT_IMAGE_AD --image-hash abcdefghijklmnopqrst --href "https://example.com" --title "Banner" --text "Image ad" --dry-run
414
- direct ads update --id 99999 --status PAUSED --title "New Title" --text "New text" --href "https://example.com" --image-hash abcdefghijklmnopqrst
422
+ direct ads add --adgroup-id 12345 --type TEXT_AD --title "Title" --text "Ad text" --href "https://example.com" --title2 "Second headline" --display-url-path "deals" --mobile YES --vcard-id 111 --sitelink-set-id 222 --turbo-page-id 333 --ad-extensions "444,555" --dry-run
423
+ direct ads add --adgroup-id 12345 --type TEXT_IMAGE_AD --image-hash abcdefghijklmnopqrst --href "https://example.com" --turbo-page-id 555 --dry-run
424
+ direct ads update --id 99999 --type TEXT_AD --title "New Title" --text "New text" --href "https://example.com"
425
+ direct ads update --id 99999 --type TEXT_AD --image-hash abcdefghijklmnopqrst
426
+ direct ads update --id 99999 --type TEXT_AD --title2 "New second headline" --vcard-id 222
415
427
  direct ads delete --id 99999
416
428
  ```
417
429
 
430
+ Available TEXT_AD typed flags for `ads add` / `ads update`: `--title`, `--text`,
431
+ `--href`, `--image-hash`, `--title2`, `--display-url-path`, `--vcard-id`,
432
+ `--sitelink-set-id`, `--turbo-page-id`. `--mobile` (default `NO`) and
433
+ `--ad-extensions` are `ads add`-only — `TextAdUpdate` does not contain `Mobile`,
434
+ and ad-extension updates go through the `CalloutSetting` WSDL field, which is
435
+ not yet exposed by the CLI. TEXT_IMAGE_AD additionally accepts `--turbo-page-id`.
436
+
418
437
  #### Keywords
419
438
 
420
439
  ```bash
@@ -424,6 +443,33 @@ direct keywords update --id 88888 --keyword "updated keyword text"
424
443
  direct keywords delete --id 88888
425
444
  ```
426
445
 
446
+ **Batch keyword upload** (CLI auto-chunks to the API limit of 10 per request):
447
+
448
+ ```bash
449
+ # From a JSONL file (one keyword object per line)
450
+ direct keywords add --adgroup-id 12345 --from-file keywords.jsonl
451
+
452
+ # Inline JSON array
453
+ direct keywords add --keywords-json '[{"Keyword":"buy laptop","Bid":10000000},{"Keyword":"buy desktop"}]'
454
+ ```
455
+
456
+ Example `keywords.jsonl`:
457
+
458
+ ```jsonl
459
+ {"Keyword":"buy laptop","Bid":10000000,"UserParam1":"src=ad1"}
460
+ {"Keyword":"buy desktop","ContextBid":5000000}
461
+ {"Keyword":"купить ноутбук","AdGroupId":99999}
462
+ ```
463
+
464
+ - Row keys use WSDL CamelCase: `Keyword`, `AdGroupId`, `Bid`, `ContextBid`, `UserParam1`, `UserParam2`.
465
+ - `--adgroup-id` provides the default group ID; rows can override it via per-row `AdGroupId`.
466
+ - Each effective row must resolve `Keyword` and `AdGroupId`; unknown fields are rejected with the row number.
467
+ - API limit: 10 items per `keywords.add` request — see [Yandex Direct docs](https://yandex.ru/dev/direct/doc/dg/objects/keyword.html). The CLI sends as many chunks as needed and merges `AddResults`.
468
+ - API limit: 200 keywords per ad group. The CLI prints a warning if any `AdGroupId` in the input exceeds it; the API rejects the excess as per-item errors.
469
+ - Item-level errors from the API do not abort the batch; the merged output includes successes and per-item errors.
470
+ - If a chunk fails with a network-level error mid-batch, already-created Ids are printed to stderr (`Partial success before failure`) so a retry doesn't duplicate them.
471
+ - `--dry-run` shows the first chunk's payload plus `{chunks, totalItems, chunkSize}`.
472
+
427
473
  #### Reports
428
474
 
429
475
  ```bash
@@ -480,8 +526,8 @@ direct dynamicads set-bids --id 789 --bid 12500000 --context-bid 9000000 --prior
480
526
 
481
527
  # Shared bidding strategies
482
528
  direct strategies get --limit 5
483
- direct strategies add --name "Shared Clicks" --type WbMaximumClicks --spend-limit 1000000000 --average-cpc 30000000 --dry-run
484
- direct strategies update --id 42 --type WbMaximumClicks --average-cpc 35000000 --dry-run
529
+ direct strategies add --name "Shared Clicks" --type WbMaximumClicks --weekly-spend-limit 1000000000 --bid-ceiling 30000000 --dry-run
530
+ direct strategies update --id 42 --type WbMaximumClicks --weekly-spend-limit 35000000 --dry-run
485
531
  direct strategies archive --id 42 --dry-run
486
532
 
487
533
  # Dynamic feed ad targets
@@ -558,6 +604,17 @@ Use `--dry-run` on `add` / `update` commands to preview the API request before s
558
604
  direct campaigns add --name "Test" --start-date 2024-01-01 --dry-run
559
605
  ```
560
606
 
607
+ ### API Errors
608
+
609
+ Yandex Direct can return a successful HTTP response that still contains
610
+ item-level `Errors` for one object. Direct CLI treats those responses as
611
+ failed operations: it exits non-zero and prints the error code, message, and
612
+ details.
613
+
614
+ Code `8800` with `Object not found` usually means the object is not available
615
+ under the current `Client-Login` or account. Check the selected `--login`,
616
+ `YANDEX_DIRECT_LOGIN`, or auth profile before retrying.
617
+
561
618
  ### Testing
562
619
 
563
620
  Four tiers of tests live under `tests/`:
@@ -666,6 +723,12 @@ For `v4account` sandbox smoke, `enable-shared-account` uses
666
723
  `account-management` requires `YANDEX_DIRECT_V4ACCOUNT_ACCOUNT_ID`; without it
667
724
  the runner reports `NOT_COVERED` for that command.
668
725
 
726
+ `clients.update` is opt-in because it mutates client-level account metadata.
727
+ Set `YANDEX_DIRECT_CLIENTS_UPDATE_LOGIN` to an expendable sandbox
728
+ `Client-Login`; the runner passes it through `--login` and updates only
729
+ `ClientInfo` with a unique smoke marker. Without that variable, the runner
730
+ reports `NOT_COVERED` for `clients.update`.
731
+
669
732
  #### Re-recording write cassettes
670
733
 
671
734
  The `integration_write` pytest tier still replays stored write-test traffic
@@ -1011,6 +1074,15 @@ direct campaigns add --name "Моя кампания" --start-date 2024-02-01 --
1011
1074
  direct campaigns add --name "Динамическая кампания" --start-date 2024-02-01 --type DYNAMIC_TEXT_CAMPAIGN --setting ADD_METRICA_TAG=NO --search-strategy HIGHEST_POSITION --network-strategy SERVING_OFF --dry-run
1012
1075
  direct campaigns add --name "Смарт-кампания" --start-date 2024-02-01 --type SMART_CAMPAIGN --network-strategy AVERAGE_CPC_PER_FILTER --filter-average-cpc 1000000 --counter-id 123 --dry-run
1013
1076
 
1077
+ # CPA-стратегия (одна цель): --goal-id обязателен, --average-cpa/--bid-ceiling — micro-рубли
1078
+ direct campaigns add --name "CPA-кампания" --start-date 2026-06-01 --type TEXT_CAMPAIGN --search-strategy AVERAGE_CPA --network-strategy SERVING_OFF --goal-id 1234567 --average-cpa 500000000 --bid-ceiling 1000000000 --counter-ids 111,222 --dry-run
1079
+
1080
+ # Мульти-целевой CPA через PriorityGoals (пары goal_id:value, WSDL PriorityGoalsItem)
1081
+ direct campaigns add --name "Мульти-целевой CPA" --start-date 2026-06-01 --type TEXT_CAMPAIGN --search-strategy AVERAGE_CPA_MULTIPLE_GOALS --network-strategy SERVING_OFF --priority-goals 1234567:80,9876543:20 --bid-ceiling 1000000000 --dry-run
1082
+
1083
+ # Notification (Sms/Email) и TimeTargeting принимают JSON с CamelCase ключами WSDL
1084
+ direct campaigns add --name "Уведомления+Расписание" --start-date 2026-06-01 --type TEXT_CAMPAIGN --search-strategy HIGHEST_POSITION --network-strategy SERVING_OFF --notification '{"EmailSettings":{"Email":"ops@example.com","SendWarnings":"YES"}}' --time-targeting '{"Schedule":["1A0123456789ABCDEFGHIJKL"],"ConsiderWorkingWeekends":"YES"}' --dry-run
1085
+
1014
1086
  # Обновление и управление статусом
1015
1087
  direct campaigns update --id 12345 --name "Новое название" --status SUSPENDED --budget 100000000 --start-date 2024-02-10 --end-date 2024-03-01
1016
1088
  direct campaigns suspend --id 12345
@@ -1037,11 +1109,22 @@ direct adgroups delete --id 67890
1037
1109
  direct ads get --campaign-ids 1,2,3
1038
1110
  direct ads get --adgroup-ids 45678 --format table
1039
1111
  direct ads add --adgroup-id 12345 --type TEXT_AD --title "Заголовок" --text "Текст объявления" --href "https://example.com" --dry-run
1040
- direct ads add --adgroup-id 12345 --type TEXT_IMAGE_AD --image-hash abcdefghijklmnopqrst --href "https://example.com" --title "Баннер" --text "Имиджевое объявление" --dry-run
1041
- direct ads update --id 99999 --status PAUSED --title "Новый заголовок" --text "Новый текст" --href "https://example.com" --image-hash abcdefghijklmnopqrst
1112
+ direct ads add --adgroup-id 12345 --type TEXT_AD --title "Заголовок" --text "Текст" --href "https://example.com" --title2 "Второй заголовок" --display-url-path "deals" --mobile YES --vcard-id 111 --sitelink-set-id 222 --turbo-page-id 333 --ad-extensions "444,555" --dry-run
1113
+ direct ads add --adgroup-id 12345 --type TEXT_IMAGE_AD --image-hash abcdefghijklmnopqrst --href "https://example.com" --turbo-page-id 555 --dry-run
1114
+ direct ads update --id 99999 --type TEXT_AD --title "Новый заголовок" --text "Новый текст" --href "https://example.com"
1115
+ direct ads update --id 99999 --type TEXT_AD --image-hash abcdefghijklmnopqrst
1116
+ direct ads update --id 99999 --type TEXT_AD --title2 "Новый второй заголовок" --vcard-id 222
1042
1117
  direct ads delete --id 99999
1043
1118
  ```
1044
1119
 
1120
+ Доступные типизированные флаги TEXT_AD для `ads add` / `ads update`:
1121
+ `--title`, `--text`, `--href`, `--image-hash`, `--title2`, `--display-url-path`,
1122
+ `--vcard-id`, `--sitelink-set-id`, `--turbo-page-id`. `--mobile`
1123
+ (default `NO`) и `--ad-extensions` доступны только в `ads add` — WSDL
1124
+ `TextAdUpdate` не содержит `Mobile`, а обновление расширений идёт через поле
1125
+ `CalloutSetting`, которое пока не покрыто CLI. Для TEXT_IMAGE_AD дополнительно
1126
+ доступен `--turbo-page-id`.
1127
+
1045
1128
  #### Ключевые слова
1046
1129
 
1047
1130
  ```bash
@@ -1051,6 +1134,33 @@ direct keywords update --id 88888 --keyword "updated keyword text"
1051
1134
  direct keywords delete --id 88888
1052
1135
  ```
1053
1136
 
1137
+ **Пакетная загрузка ключевых слов** (CLI автоматически режет на куски по API-лимиту 10/запрос):
1138
+
1139
+ ```bash
1140
+ # Из JSONL-файла (по одному объекту ключевого слова на строку)
1141
+ direct keywords add --adgroup-id 12345 --from-file keywords.jsonl
1142
+
1143
+ # Inline JSON-массив
1144
+ direct keywords add --keywords-json '[{"Keyword":"купить ноутбук","Bid":10000000},{"Keyword":"купить ПК"}]'
1145
+ ```
1146
+
1147
+ Пример `keywords.jsonl`:
1148
+
1149
+ ```jsonl
1150
+ {"Keyword":"купить ноутбук","Bid":10000000,"UserParam1":"src=ad1"}
1151
+ {"Keyword":"купить ПК","ContextBid":5000000}
1152
+ {"Keyword":"buy laptop","AdGroupId":99999}
1153
+ ```
1154
+
1155
+ - Ключи строки — WSDL CamelCase: `Keyword`, `AdGroupId`, `Bid`, `ContextBid`, `UserParam1`, `UserParam2`.
1156
+ - `--adgroup-id` задаёт значение по умолчанию; в строке можно переопределить через `AdGroupId`.
1157
+ - В каждой строке должны разрешаться `Keyword` и `AdGroupId`; неизвестные поля отклоняются с указанием номера строки.
1158
+ - API-лимит: 10 элементов на запрос `keywords.add` — см. [документацию Yandex Direct](https://yandex.ru/dev/direct/doc/dg/objects/keyword.html). CLI отправит нужное число чанков и склеит `AddResults`.
1159
+ - API-лимит: 200 ключевых слов на одну группу объявлений. CLI печатает предупреждение, если в каком-то `AdGroupId` во входе их больше; API отклонит излишек item-level ошибками.
1160
+ - Item-level ошибки от API не прерывают batch; объединённый вывод содержит и успешные Id, и ошибки.
1161
+ - При сетевой ошибке в середине batch уже созданные Id выводятся в stderr (`Partial success before failure`), чтобы при retry не возникли дубли.
1162
+ - `--dry-run` показывает payload первого чанка плюс `{chunks, totalItems, chunkSize}`.
1163
+
1054
1164
  #### Отчёты
1055
1165
 
1056
1166
  ```bash
@@ -1107,8 +1217,8 @@ direct dynamicads set-bids --id 789 --bid 12500000 --context-bid 9000000 --prior
1107
1217
 
1108
1218
  # Общие стратегии ставок
1109
1219
  direct strategies get --limit 5
1110
- direct strategies add --name "Общая стратегия" --type WbMaximumClicks --spend-limit 1000000000 --average-cpc 30000000 --dry-run
1111
- direct strategies update --id 42 --type WbMaximumClicks --average-cpc 35000000 --dry-run
1220
+ direct strategies add --name "Общая стратегия" --type WbMaximumClicks --weekly-spend-limit 1000000000 --bid-ceiling 30000000 --dry-run
1221
+ direct strategies update --id 42 --type WbMaximumClicks --weekly-spend-limit 35000000 --dry-run
1112
1222
  direct strategies archive --id 42 --dry-run
1113
1223
 
1114
1224
  # Динамические таргеты по фиду
@@ -1186,6 +1296,17 @@ direct campaigns get --fetch-all # все страницы
1186
1296
  direct campaigns add --name "Тест" --start-date 2024-01-01 --dry-run
1187
1297
  ```
1188
1298
 
1299
+ ### Ошибки API
1300
+
1301
+ Яндекс Директ может вернуть успешный HTTP-ответ, внутри которого есть
1302
+ item-level `Errors` для конкретного объекта. Direct CLI считает такой ответ
1303
+ ошибкой операции: команда завершается с ненулевым кодом и печатает код ошибки,
1304
+ сообщение и детали.
1305
+
1306
+ Код `8800` с `Object not found` обычно означает, что объект недоступен в
1307
+ текущем `Client-Login` или аккаунте. Перед повтором проверьте выбранный
1308
+ `--login`, `YANDEX_DIRECT_LOGIN` или auth profile.
1309
+
1189
1310
  ### Тестирование
1190
1311
 
1191
1312
  В `tests/` четыре уровня тестов:
@@ -1262,6 +1383,12 @@ sandbox-токен не нужен.
1262
1383
  Для `account-management` нужна переменная
1263
1384
  `YANDEX_DIRECT_V4ACCOUNT_ACCOUNT_ID`; без неё runner покажет `NOT_COVERED`.
1264
1385
 
1386
+ `clients.update` включается только явно, потому что меняет client-level
1387
+ metadata аккаунта. Укажите `YANDEX_DIRECT_CLIENTS_UPDATE_LOGIN` с disposable
1388
+ sandbox `Client-Login`; runner передаст его через `--login` и изменит только
1389
+ `ClientInfo` на уникальный smoke marker. Без этой переменной runner покажет
1390
+ `NOT_COVERED` для `clients.update`.
1391
+
1265
1392
  #### Перезапись write-кассет
1266
1393
 
1267
1394
  Уровень `integration_write` в pytest всё ещё воспроизводит сохранённый