direct-cli 0.3.14__tar.gz → 0.3.16__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 (283) hide show
  1. {direct_cli-0.3.14 → direct_cli-0.3.16}/.github/workflows/claude.yml +1 -1
  2. {direct_cli-0.3.14 → direct_cli-0.3.16}/CHANGELOG.md +55 -0
  3. {direct_cli-0.3.14 → direct_cli-0.3.16}/PKG-INFO +10 -2
  4. {direct_cli-0.3.14 → direct_cli-0.3.16}/README.md +9 -1
  5. {direct_cli-0.3.14 → direct_cli-0.3.16}/direct_cli/_vendor/tapi_yandex_direct/v4/adapter.py +9 -3
  6. {direct_cli-0.3.14 → direct_cli-0.3.16}/direct_cli/cli.py +28 -0
  7. direct_cli-0.3.16/direct_cli/commands/v4events.py +232 -0
  8. {direct_cli-0.3.14 → direct_cli-0.3.16}/direct_cli/commands/v4finance.py +39 -38
  9. {direct_cli-0.3.14 → direct_cli-0.3.16}/direct_cli/commands/v4forecast.py +52 -3
  10. direct_cli-0.3.16/direct_cli/i18n.py +107 -0
  11. {direct_cli-0.3.14 → direct_cli-0.3.16}/direct_cli/v4/__init__.py +2 -2
  12. {direct_cli-0.3.14 → direct_cli-0.3.16}/direct_cli/v4_contracts.py +134 -35
  13. {direct_cli-0.3.14 → direct_cli-0.3.16}/direct_cli.egg-info/PKG-INFO +10 -2
  14. {direct_cli-0.3.14 → direct_cli-0.3.16}/direct_cli.egg-info/SOURCES.txt +48 -0
  15. direct_cli-0.3.16/docs/audits/API_COVERAGE.md +93 -0
  16. direct_cli-0.3.16/docs/audits/PROJECT_WIRE_SHAPE_AUDIT_2026-05-29.md +193 -0
  17. direct_cli-0.3.16/docs/audits/wire_shape.json +1394 -0
  18. {direct_cli-0.3.14 → direct_cli-0.3.16}/pyproject.toml +1 -1
  19. direct_cli-0.3.16/scripts/audit_wire_shape.py +791 -0
  20. direct_cli-0.3.16/scripts/preflight_check.sh +62 -0
  21. {direct_cli-0.3.14 → direct_cli-0.3.16}/scripts/test_dangerous_commands.sh +2 -2
  22. {direct_cli-0.3.14 → direct_cli-0.3.16}/tests/API_COVERAGE.md +11 -5
  23. direct_cli-0.3.16/tests/cassettes/test_read_cassettes/test_read_command[adextensions_get].yaml +56 -0
  24. direct_cli-0.3.16/tests/cassettes/test_read_cassettes/test_read_command[adgroups_get].yaml +56 -0
  25. direct_cli-0.3.16/tests/cassettes/test_read_cassettes/test_read_command[adimages_get].yaml +66 -0
  26. direct_cli-0.3.16/tests/cassettes/test_read_cassettes/test_read_command[ads_get].yaml +56 -0
  27. direct_cli-0.3.16/tests/cassettes/test_read_cassettes/test_read_command[advideos_get].yaml +56 -0
  28. direct_cli-0.3.16/tests/cassettes/test_read_cassettes/test_read_command[audiencetargets_get].yaml +56 -0
  29. direct_cli-0.3.16/tests/cassettes/test_read_cassettes/test_read_command[bidmodifiers_get].yaml +56 -0
  30. direct_cli-0.3.16/tests/cassettes/test_read_cassettes/test_read_command[bids_get].yaml +56 -0
  31. direct_cli-0.3.16/tests/cassettes/test_read_cassettes/test_read_command[businesses_get].yaml +56 -0
  32. direct_cli-0.3.16/tests/cassettes/test_read_cassettes/test_read_command[campaigns_get].yaml +57 -0
  33. direct_cli-0.3.16/tests/cassettes/test_read_cassettes/test_read_command[changes_check].yaml +56 -0
  34. direct_cli-0.3.16/tests/cassettes/test_read_cassettes/test_read_command[changes_check_campaigns].yaml +56 -0
  35. direct_cli-0.3.16/tests/cassettes/test_read_cassettes/test_read_command[changes_check_dictionaries].yaml +56 -0
  36. direct_cli-0.3.16/tests/cassettes/test_read_cassettes/test_read_command[clients_get].yaml +56 -0
  37. direct_cli-0.3.16/tests/cassettes/test_read_cassettes/test_read_command[creatives_get].yaml +56 -0
  38. direct_cli-0.3.16/tests/cassettes/test_read_cassettes/test_read_command[dictionaries_get].yaml +11696 -0
  39. direct_cli-0.3.16/tests/cassettes/test_read_cassettes/test_read_command[dynamicads_get].yaml +56 -0
  40. direct_cli-0.3.16/tests/cassettes/test_read_cassettes/test_read_command[dynamicfeedadtargets_get].yaml +56 -0
  41. direct_cli-0.3.16/tests/cassettes/test_read_cassettes/test_read_command[feeds_get].yaml +56 -0
  42. direct_cli-0.3.16/tests/cassettes/test_read_cassettes/test_read_command[keywordbids_get].yaml +56 -0
  43. direct_cli-0.3.16/tests/cassettes/test_read_cassettes/test_read_command[keywords_get].yaml +56 -0
  44. direct_cli-0.3.16/tests/cassettes/test_read_cassettes/test_read_command[keywordsresearch_deduplicate].yaml +59 -0
  45. direct_cli-0.3.16/tests/cassettes/test_read_cassettes/test_read_command[keywordsresearch_has_search_volume].yaml +58 -0
  46. direct_cli-0.3.16/tests/cassettes/test_read_cassettes/test_read_command[leads_get].yaml +56 -0
  47. direct_cli-0.3.16/tests/cassettes/test_read_cassettes/test_read_command[negativekeywordsharedsets_get].yaml +56 -0
  48. direct_cli-0.3.16/tests/cassettes/test_read_cassettes/test_read_command[reports_get].yaml +64 -0
  49. direct_cli-0.3.16/tests/cassettes/test_read_cassettes/test_read_command[retargeting_get].yaml +56 -0
  50. direct_cli-0.3.16/tests/cassettes/test_read_cassettes/test_read_command[sitelinks_get].yaml +56 -0
  51. direct_cli-0.3.16/tests/cassettes/test_read_cassettes/test_read_command[smartadtargets_get].yaml +56 -0
  52. direct_cli-0.3.16/tests/cassettes/test_read_cassettes/test_read_command[strategies_get].yaml +66 -0
  53. direct_cli-0.3.16/tests/cassettes/test_read_cassettes/test_read_command[turbopages_get].yaml +56 -0
  54. direct_cli-0.3.16/tests/cassettes/test_read_cassettes/test_read_command[v4events_get_events_log].yaml +52 -0
  55. direct_cli-0.3.16/tests/cassettes/test_read_cassettes/test_read_command[v4finance_get_clients_units].yaml +52 -0
  56. direct_cli-0.3.16/tests/cassettes/test_read_cassettes/test_read_command[v4forecast_list].yaml +52 -0
  57. direct_cli-0.3.16/tests/cassettes/test_read_cassettes/test_read_command[v4goals_get_stat_goals].yaml +52 -0
  58. direct_cli-0.3.16/tests/cassettes/test_read_cassettes/test_read_command[v4tags_get_campaigns].yaml +52 -0
  59. direct_cli-0.3.16/tests/cassettes/test_read_cassettes/test_read_command[v4wordstat_list_reports].yaml +52 -0
  60. direct_cli-0.3.16/tests/cassettes/test_read_cassettes/test_read_command[vcards_get].yaml +57 -0
  61. direct_cli-0.3.16/tests/cassettes/test_read_cassettes/test_v4finance_check_payment_unknown_transaction.yaml +52 -0
  62. {direct_cli-0.3.14 → direct_cli-0.3.16}/tests/conftest.py +68 -17
  63. direct_cli-0.3.16/tests/test_audit_wire_shape.py +158 -0
  64. direct_cli-0.3.16/tests/test_i18n.py +118 -0
  65. direct_cli-0.3.16/tests/test_read_cassettes.py +252 -0
  66. {direct_cli-0.3.14 → direct_cli-0.3.16}/tests/test_v4_contracts.py +16 -1
  67. {direct_cli-0.3.14 → direct_cli-0.3.16}/tests/test_v4_runtime_shape.py +29 -3
  68. {direct_cli-0.3.14 → direct_cli-0.3.16}/tests/test_v4events.py +110 -0
  69. {direct_cli-0.3.14 → direct_cli-0.3.16}/tests/test_v4finance_money.py +80 -13
  70. {direct_cli-0.3.14 → direct_cli-0.3.16}/tests/test_v4forecast.py +68 -1
  71. direct_cli-0.3.14/direct_cli/commands/v4events.py +0 -120
  72. {direct_cli-0.3.14 → direct_cli-0.3.16}/.env.example +0 -0
  73. {direct_cli-0.3.14 → direct_cli-0.3.16}/.github/copilot-instructions.md +0 -0
  74. {direct_cli-0.3.14 → direct_cli-0.3.16}/.github/workflows/api-coverage.yml +0 -0
  75. {direct_cli-0.3.14 → direct_cli-0.3.16}/.github/workflows/quality.yml +0 -0
  76. {direct_cli-0.3.14 → direct_cli-0.3.16}/.gitignore +0 -0
  77. {direct_cli-0.3.14 → direct_cli-0.3.16}/AGENTS.md +0 -0
  78. {direct_cli-0.3.14 → direct_cli-0.3.16}/CLAUDE.md +0 -0
  79. {direct_cli-0.3.14 → direct_cli-0.3.16}/MANIFEST.in +0 -0
  80. {direct_cli-0.3.14 → direct_cli-0.3.16}/direct_cli/__init__.py +0 -0
  81. {direct_cli-0.3.14 → direct_cli-0.3.16}/direct_cli/_bidding_strategy.py +0 -0
  82. {direct_cli-0.3.14 → direct_cli-0.3.16}/direct_cli/_deprecated.py +0 -0
  83. {direct_cli-0.3.14 → direct_cli-0.3.16}/direct_cli/_smoke_probes.py +0 -0
  84. {direct_cli-0.3.14 → direct_cli-0.3.16}/direct_cli/_vendor/__init__.py +0 -0
  85. {direct_cli-0.3.14 → direct_cli-0.3.16}/direct_cli/_vendor/tapi_yandex_direct/__init__.py +0 -0
  86. {direct_cli-0.3.14 → direct_cli-0.3.16}/direct_cli/_vendor/tapi_yandex_direct/endpoints.py +0 -0
  87. {direct_cli-0.3.14 → direct_cli-0.3.16}/direct_cli/_vendor/tapi_yandex_direct/exceptions.py +0 -0
  88. {direct_cli-0.3.14 → direct_cli-0.3.16}/direct_cli/_vendor/tapi_yandex_direct/resource_mapping.py +0 -0
  89. {direct_cli-0.3.14 → direct_cli-0.3.16}/direct_cli/_vendor/tapi_yandex_direct/tapi_yandex_direct.py +0 -0
  90. {direct_cli-0.3.14 → direct_cli-0.3.16}/direct_cli/_vendor/tapi_yandex_direct/tapi_yandex_direct.pyi +0 -0
  91. {direct_cli-0.3.14 → direct_cli-0.3.16}/direct_cli/_vendor/tapi_yandex_direct/v4/__init__.py +0 -0
  92. {direct_cli-0.3.14 → direct_cli-0.3.16}/direct_cli/_vendor/tapi_yandex_direct/v4/adapter.pyi +0 -0
  93. {direct_cli-0.3.14 → direct_cli-0.3.16}/direct_cli/_vendor/tapi_yandex_direct/v4/resource_mapping.py +0 -0
  94. {direct_cli-0.3.14 → direct_cli-0.3.16}/direct_cli/api.py +0 -0
  95. {direct_cli-0.3.14 → direct_cli-0.3.16}/direct_cli/auth.py +0 -0
  96. {direct_cli-0.3.14 → direct_cli-0.3.16}/direct_cli/commands/__init__.py +0 -0
  97. {direct_cli-0.3.14 → direct_cli-0.3.16}/direct_cli/commands/adextensions.py +0 -0
  98. {direct_cli-0.3.14 → direct_cli-0.3.16}/direct_cli/commands/adgroups.py +0 -0
  99. {direct_cli-0.3.14 → direct_cli-0.3.16}/direct_cli/commands/adimages.py +0 -0
  100. {direct_cli-0.3.14 → direct_cli-0.3.16}/direct_cli/commands/ads.py +0 -0
  101. {direct_cli-0.3.14 → direct_cli-0.3.16}/direct_cli/commands/advideos.py +0 -0
  102. {direct_cli-0.3.14 → direct_cli-0.3.16}/direct_cli/commands/agencyclients.py +0 -0
  103. {direct_cli-0.3.14 → direct_cli-0.3.16}/direct_cli/commands/audiencetargets.py +0 -0
  104. {direct_cli-0.3.14 → direct_cli-0.3.16}/direct_cli/commands/auth.py +0 -0
  105. {direct_cli-0.3.14 → direct_cli-0.3.16}/direct_cli/commands/balance.py +0 -0
  106. {direct_cli-0.3.14 → direct_cli-0.3.16}/direct_cli/commands/bidmodifiers.py +0 -0
  107. {direct_cli-0.3.14 → direct_cli-0.3.16}/direct_cli/commands/bids.py +0 -0
  108. {direct_cli-0.3.14 → direct_cli-0.3.16}/direct_cli/commands/businesses.py +0 -0
  109. {direct_cli-0.3.14 → direct_cli-0.3.16}/direct_cli/commands/campaigns.py +0 -0
  110. {direct_cli-0.3.14 → direct_cli-0.3.16}/direct_cli/commands/changes.py +0 -0
  111. {direct_cli-0.3.14 → direct_cli-0.3.16}/direct_cli/commands/clients.py +0 -0
  112. {direct_cli-0.3.14 → direct_cli-0.3.16}/direct_cli/commands/creatives.py +0 -0
  113. {direct_cli-0.3.14 → direct_cli-0.3.16}/direct_cli/commands/dictionaries.py +0 -0
  114. {direct_cli-0.3.14 → direct_cli-0.3.16}/direct_cli/commands/dynamicads.py +0 -0
  115. {direct_cli-0.3.14 → direct_cli-0.3.16}/direct_cli/commands/dynamicfeedadtargets.py +0 -0
  116. {direct_cli-0.3.14 → direct_cli-0.3.16}/direct_cli/commands/feeds.py +0 -0
  117. {direct_cli-0.3.14 → direct_cli-0.3.16}/direct_cli/commands/keywordbids.py +0 -0
  118. {direct_cli-0.3.14 → direct_cli-0.3.16}/direct_cli/commands/keywords.py +0 -0
  119. {direct_cli-0.3.14 → direct_cli-0.3.16}/direct_cli/commands/keywordsresearch.py +0 -0
  120. {direct_cli-0.3.14 → direct_cli-0.3.16}/direct_cli/commands/leads.py +0 -0
  121. {direct_cli-0.3.14 → direct_cli-0.3.16}/direct_cli/commands/negativekeywordsharedsets.py +0 -0
  122. {direct_cli-0.3.14 → direct_cli-0.3.16}/direct_cli/commands/reports.py +0 -0
  123. {direct_cli-0.3.14 → direct_cli-0.3.16}/direct_cli/commands/retargeting.py +0 -0
  124. {direct_cli-0.3.14 → direct_cli-0.3.16}/direct_cli/commands/sitelinks.py +0 -0
  125. {direct_cli-0.3.14 → direct_cli-0.3.16}/direct_cli/commands/smartadtargets.py +0 -0
  126. {direct_cli-0.3.14 → direct_cli-0.3.16}/direct_cli/commands/strategies.py +0 -0
  127. {direct_cli-0.3.14 → direct_cli-0.3.16}/direct_cli/commands/turbopages.py +0 -0
  128. {direct_cli-0.3.14 → direct_cli-0.3.16}/direct_cli/commands/v4account.py +0 -0
  129. {direct_cli-0.3.14 → direct_cli-0.3.16}/direct_cli/commands/v4goals.py +0 -0
  130. {direct_cli-0.3.14 → direct_cli-0.3.16}/direct_cli/commands/v4shells.py +0 -0
  131. {direct_cli-0.3.14 → direct_cli-0.3.16}/direct_cli/commands/v4tags.py +0 -0
  132. {direct_cli-0.3.14 → direct_cli-0.3.16}/direct_cli/commands/v4wordstat.py +0 -0
  133. {direct_cli-0.3.14 → direct_cli-0.3.16}/direct_cli/commands/vcards.py +0 -0
  134. {direct_cli-0.3.14 → direct_cli-0.3.16}/direct_cli/output.py +0 -0
  135. {direct_cli-0.3.14 → direct_cli-0.3.16}/direct_cli/reports_coverage.py +0 -0
  136. {direct_cli-0.3.14 → direct_cli-0.3.16}/direct_cli/smoke_matrix.py +0 -0
  137. {direct_cli-0.3.14 → direct_cli-0.3.16}/direct_cli/utils.py +0 -0
  138. {direct_cli-0.3.14 → direct_cli-0.3.16}/direct_cli/v4/money.py +0 -0
  139. {direct_cli-0.3.14 → direct_cli-0.3.16}/direct_cli/wsdl_coverage.py +0 -0
  140. {direct_cli-0.3.14 → direct_cli-0.3.16}/direct_cli.egg-info/dependency_links.txt +0 -0
  141. {direct_cli-0.3.14 → direct_cli-0.3.16}/direct_cli.egg-info/entry_points.txt +0 -0
  142. {direct_cli-0.3.14 → direct_cli-0.3.16}/direct_cli.egg-info/requires.txt +0 -0
  143. {direct_cli-0.3.14 → direct_cli-0.3.16}/direct_cli.egg-info/top_level.txt +0 -0
  144. {direct_cli-0.3.14 → direct_cli-0.3.16}/docs/audits/issue-198-mutating-wsdl-audit.md +0 -0
  145. {direct_cli-0.3.14 → direct_cli-0.3.16}/docs/superpowers/plans/2026-04-12-issue-32-completion.md +0 -0
  146. {direct_cli-0.3.14 → direct_cli-0.3.16}/docs/superpowers/specs/2026-04-23-vendor-tapi-yandex-direct-design.md +0 -0
  147. {direct_cli-0.3.14 → direct_cli-0.3.16}/scripts/anonymize_cassettes.py +0 -0
  148. {direct_cli-0.3.14 → direct_cli-0.3.16}/scripts/build_api_coverage_checklist.py +0 -0
  149. {direct_cli-0.3.14 → direct_cli-0.3.16}/scripts/build_api_coverage_report.py +0 -0
  150. {direct_cli-0.3.14 → direct_cli-0.3.16}/scripts/build_wsdl_optional_field_audit.py +0 -0
  151. {direct_cli-0.3.14 → direct_cli-0.3.16}/scripts/check_all_docs_urls.py +0 -0
  152. {direct_cli-0.3.14 → direct_cli-0.3.16}/scripts/check_reports_drift.py +0 -0
  153. {direct_cli-0.3.14 → direct_cli-0.3.16}/scripts/check_wsdl_drift.py +0 -0
  154. {direct_cli-0.3.14 → direct_cli-0.3.16}/scripts/patch_vendor_imports.py +0 -0
  155. {direct_cli-0.3.14 → direct_cli-0.3.16}/scripts/refresh_reports_cache.py +0 -0
  156. {direct_cli-0.3.14 → direct_cli-0.3.16}/scripts/refresh_wsdl_cache.py +0 -0
  157. {direct_cli-0.3.14 → direct_cli-0.3.16}/scripts/release_pypi.sh +0 -0
  158. {direct_cli-0.3.14 → direct_cli-0.3.16}/scripts/sandbox_write_audit.py +0 -0
  159. {direct_cli-0.3.14 → direct_cli-0.3.16}/scripts/sandbox_write_live.py +0 -0
  160. {direct_cli-0.3.14 → direct_cli-0.3.16}/scripts/test_safe_commands.sh +0 -0
  161. {direct_cli-0.3.14 → direct_cli-0.3.16}/scripts/test_sandbox_write.sh +0 -0
  162. {direct_cli-0.3.14 → direct_cli-0.3.16}/scripts/update_vendor.sh +0 -0
  163. {direct_cli-0.3.14 → direct_cli-0.3.16}/setup.cfg +0 -0
  164. {direct_cli-0.3.14 → direct_cli-0.3.16}/setup.py +0 -0
  165. {direct_cli-0.3.14 → direct_cli-0.3.16}/tests/API_ISSUE_AUDIT.md +0 -0
  166. {direct_cli-0.3.14 → direct_cli-0.3.16}/tests/MANUAL_COVERAGE.md +0 -0
  167. {direct_cli-0.3.14 → direct_cli-0.3.16}/tests/WSDL_OPTIONAL_FIELD_AUDIT.md +0 -0
  168. {direct_cli-0.3.14 → direct_cli-0.3.16}/tests/__init__.py +0 -0
  169. {direct_cli-0.3.14 → direct_cli-0.3.16}/tests/_orphan_store.py +0 -0
  170. {direct_cli-0.3.14 → direct_cli-0.3.16}/tests/api_coverage_payloads.py +0 -0
  171. {direct_cli-0.3.14 → direct_cli-0.3.16}/tests/cassettes/test_integration_write/TestWriteAdExtensions.test_add_delete.yaml +0 -0
  172. {direct_cli-0.3.14 → direct_cli-0.3.16}/tests/cassettes/test_integration_write/TestWriteAdGroups.test_add_update_delete.yaml +0 -0
  173. {direct_cli-0.3.14 → direct_cli-0.3.16}/tests/cassettes/test_integration_write/TestWriteAdImages.test_add_delete.yaml +0 -0
  174. {direct_cli-0.3.14 → direct_cli-0.3.16}/tests/cassettes/test_integration_write/TestWriteAds.test_add_text_ad_update_delete.yaml +0 -0
  175. {direct_cli-0.3.14 → direct_cli-0.3.16}/tests/cassettes/test_integration_write/TestWriteAudienceTargets.test_add_delete.yaml +0 -0
  176. {direct_cli-0.3.14 → direct_cli-0.3.16}/tests/cassettes/test_integration_write/TestWriteBidModifiersAdd.test_add_delete_mobile.yaml +0 -0
  177. {direct_cli-0.3.14 → direct_cli-0.3.16}/tests/cassettes/test_integration_write/TestWriteBidModifiersSet.test_set_without_id_is_rejected.yaml +0 -0
  178. {direct_cli-0.3.14 → direct_cli-0.3.16}/tests/cassettes/test_integration_write/TestWriteBids.test_set_bid.yaml +0 -0
  179. {direct_cli-0.3.14 → direct_cli-0.3.16}/tests/cassettes/test_integration_write/TestWriteBidsRead.test_bids_get.yaml +0 -0
  180. {direct_cli-0.3.14 → direct_cli-0.3.16}/tests/cassettes/test_integration_write/TestWriteBidsRead.test_bids_set_auto.yaml +0 -0
  181. {direct_cli-0.3.14 → direct_cli-0.3.16}/tests/cassettes/test_integration_write/TestWriteCampaignDraftLifecycle.test_draft_create_get_delete.yaml +0 -0
  182. {direct_cli-0.3.14 → direct_cli-0.3.16}/tests/cassettes/test_integration_write/TestWriteCampaigns.test_campaign_lifecycle.yaml +0 -0
  183. {direct_cli-0.3.14 → direct_cli-0.3.16}/tests/cassettes/test_integration_write/TestWriteDynamicAds.test_add_update_delete.yaml +0 -0
  184. {direct_cli-0.3.14 → direct_cli-0.3.16}/tests/cassettes/test_integration_write/TestWriteFeeds.test_add_update_delete.yaml +0 -0
  185. {direct_cli-0.3.14 → direct_cli-0.3.16}/tests/cassettes/test_integration_write/TestWriteKeywordBids.test_set_keyword_bid.yaml +0 -0
  186. {direct_cli-0.3.14 → direct_cli-0.3.16}/tests/cassettes/test_integration_write/TestWriteKeywords.test_add_update_delete.yaml +0 -0
  187. {direct_cli-0.3.14 → direct_cli-0.3.16}/tests/cassettes/test_integration_write/TestWriteNegativeKeywordSharedSets.test_add_update_delete.yaml +0 -0
  188. {direct_cli-0.3.14 → direct_cli-0.3.16}/tests/cassettes/test_integration_write/TestWriteRetargeting.test_add_delete.yaml +0 -0
  189. {direct_cli-0.3.14 → direct_cli-0.3.16}/tests/cassettes/test_integration_write/TestWriteRetargetingUpdate.test_retargeting_update.yaml +0 -0
  190. {direct_cli-0.3.14 → direct_cli-0.3.16}/tests/cassettes/test_integration_write/TestWriteSitelinks.test_add_delete.yaml +0 -0
  191. {direct_cli-0.3.14 → direct_cli-0.3.16}/tests/cassettes/test_integration_write/TestWriteSmartAdTargets.test_add_update_delete.yaml +0 -0
  192. {direct_cli-0.3.14 → direct_cli-0.3.16}/tests/cassettes/test_integration_write/TestWriteStrategies.test_strategies_lifecycle.yaml +0 -0
  193. {direct_cli-0.3.14 → direct_cli-0.3.16}/tests/cassettes/test_integration_write/TestWriteVCards.test_add_delete.yaml +0 -0
  194. {direct_cli-0.3.14 → direct_cli-0.3.16}/tests/cassettes/test_v5_live_write/test_v5_live_draft_adgroups_add_update_delete.yaml +0 -0
  195. {direct_cli-0.3.14 → direct_cli-0.3.16}/tests/cassettes/test_v5_live_write/test_v5_live_draft_adimages_add_get_delete.yaml +0 -0
  196. {direct_cli-0.3.14 → direct_cli-0.3.16}/tests/cassettes/test_v5_live_write/test_v5_live_draft_ads_add_update_delete.yaml +0 -0
  197. {direct_cli-0.3.14 → direct_cli-0.3.16}/tests/cassettes/test_v5_live_write/test_v5_live_draft_ads_suspend_resume_archive_unarchive.yaml +0 -0
  198. {direct_cli-0.3.14 → direct_cli-0.3.16}/tests/cassettes/test_v5_live_write/test_v5_live_draft_advideos_add_get.yaml +0 -0
  199. {direct_cli-0.3.14 → direct_cli-0.3.16}/tests/cassettes/test_v5_live_write/test_v5_live_draft_audiencetargets_add_delete.yaml +0 -0
  200. {direct_cli-0.3.14 → direct_cli-0.3.16}/tests/cassettes/test_v5_live_write/test_v5_live_draft_audiencetargets_suspend_resume.yaml +0 -0
  201. {direct_cli-0.3.14 → direct_cli-0.3.16}/tests/cassettes/test_v5_live_write/test_v5_live_draft_bids_set.yaml +0 -0
  202. {direct_cli-0.3.14 → direct_cli-0.3.16}/tests/cassettes/test_v5_live_write/test_v5_live_draft_campaign_create_get_delete.yaml +0 -0
  203. {direct_cli-0.3.14 → direct_cli-0.3.16}/tests/cassettes/test_v5_live_write/test_v5_live_draft_creatives_chain_advideo_to_creative.yaml +0 -0
  204. {direct_cli-0.3.14 → direct_cli-0.3.16}/tests/cassettes/test_v5_live_write/test_v5_live_draft_dynamicads_add_delete.yaml +0 -0
  205. {direct_cli-0.3.14 → direct_cli-0.3.16}/tests/cassettes/test_v5_live_write/test_v5_live_draft_dynamicads_suspend_resume.yaml +0 -0
  206. {direct_cli-0.3.14 → direct_cli-0.3.16}/tests/cassettes/test_v5_live_write/test_v5_live_draft_keywordbids_set.yaml +0 -0
  207. {direct_cli-0.3.14 → direct_cli-0.3.16}/tests/cassettes/test_v5_live_write/test_v5_live_draft_keywords_add_update_delete.yaml +0 -0
  208. {direct_cli-0.3.14 → direct_cli-0.3.16}/tests/cassettes/test_v5_live_write/test_v5_live_draft_keywords_suspend_resume.yaml +0 -0
  209. {direct_cli-0.3.14 → direct_cli-0.3.16}/tests/cassettes/test_v5_live_write/test_v5_live_draft_sitelinks_add_get_delete.yaml +0 -0
  210. {direct_cli-0.3.14 → direct_cli-0.3.16}/tests/cassettes/test_v5_live_write/test_v5_live_draft_smartadtargets_add_update_delete.yaml +0 -0
  211. {direct_cli-0.3.14 → direct_cli-0.3.16}/tests/cassettes/test_v5_live_write/test_v5_live_draft_smartadtargets_suspend_resume.yaml +0 -0
  212. {direct_cli-0.3.14 → direct_cli-0.3.16}/tests/fixtures/test-video.mp4 +0 -0
  213. {direct_cli-0.3.14 → direct_cli-0.3.16}/tests/reports_cache/raw/fields-list.html +0 -0
  214. {direct_cli-0.3.14 → direct_cli-0.3.16}/tests/reports_cache/raw/headers.html +0 -0
  215. {direct_cli-0.3.14 → direct_cli-0.3.16}/tests/reports_cache/raw/period.html +0 -0
  216. {direct_cli-0.3.14 → direct_cli-0.3.16}/tests/reports_cache/raw/spec.html +0 -0
  217. {direct_cli-0.3.14 → direct_cli-0.3.16}/tests/reports_cache/raw/type.html +0 -0
  218. {direct_cli-0.3.14 → direct_cli-0.3.16}/tests/reports_cache/spec.json +0 -0
  219. {direct_cli-0.3.14 → direct_cli-0.3.16}/tests/test_api_coverage.py +0 -0
  220. {direct_cli-0.3.14 → direct_cli-0.3.16}/tests/test_auth_bw.py +0 -0
  221. {direct_cli-0.3.14 → direct_cli-0.3.16}/tests/test_auth_oauth.py +0 -0
  222. {direct_cli-0.3.14 → direct_cli-0.3.16}/tests/test_auth_op.py +0 -0
  223. {direct_cli-0.3.14 → direct_cli-0.3.16}/tests/test_auth_write_json.py +0 -0
  224. {direct_cli-0.3.14 → direct_cli-0.3.16}/tests/test_balance.py +0 -0
  225. {direct_cli-0.3.14 → direct_cli-0.3.16}/tests/test_changes.py +0 -0
  226. {direct_cli-0.3.14 → direct_cli-0.3.16}/tests/test_cli.py +0 -0
  227. {direct_cli-0.3.14 → direct_cli-0.3.16}/tests/test_cli_contract.py +0 -0
  228. {direct_cli-0.3.14 → direct_cli-0.3.16}/tests/test_comprehensive.py +0 -0
  229. {direct_cli-0.3.14 → direct_cli-0.3.16}/tests/test_dry_run.py +0 -0
  230. {direct_cli-0.3.14 → direct_cli-0.3.16}/tests/test_env_loading.py +0 -0
  231. {direct_cli-0.3.14 → direct_cli-0.3.16}/tests/test_integration.py +0 -0
  232. {direct_cli-0.3.14 → direct_cli-0.3.16}/tests/test_integration_write.py +0 -0
  233. {direct_cli-0.3.14 → direct_cli-0.3.16}/tests/test_low_coverage_payloads.py +0 -0
  234. {direct_cli-0.3.14 → direct_cli-0.3.16}/tests/test_reports_drift.py +0 -0
  235. {direct_cli-0.3.14 → direct_cli-0.3.16}/tests/test_reports_parsing.py +0 -0
  236. {direct_cli-0.3.14 → direct_cli-0.3.16}/tests/test_sandbox_write_audit.py +0 -0
  237. {direct_cli-0.3.14 → direct_cli-0.3.16}/tests/test_smoke_matrix.py +0 -0
  238. {direct_cli-0.3.14 → direct_cli-0.3.16}/tests/test_transport_contract.py +0 -0
  239. {direct_cli-0.3.14 → direct_cli-0.3.16}/tests/test_unknown_option_hints.py +0 -0
  240. {direct_cli-0.3.14 → direct_cli-0.3.16}/tests/test_v4_exit_codes.py +0 -0
  241. {direct_cli-0.3.14 → direct_cli-0.3.16}/tests/test_v4_foundation.py +0 -0
  242. {direct_cli-0.3.14 → direct_cli-0.3.16}/tests/test_v4_live_contracts.py +0 -0
  243. {direct_cli-0.3.14 → direct_cli-0.3.16}/tests/test_v4_safety.py +0 -0
  244. {direct_cli-0.3.14 → direct_cli-0.3.16}/tests/test_v4account.py +0 -0
  245. {direct_cli-0.3.14 → direct_cli-0.3.16}/tests/test_v4finance_read.py +0 -0
  246. {direct_cli-0.3.14 → direct_cli-0.3.16}/tests/test_v4goals.py +0 -0
  247. {direct_cli-0.3.14 → direct_cli-0.3.16}/tests/test_v4tags.py +0 -0
  248. {direct_cli-0.3.14 → direct_cli-0.3.16}/tests/test_v4wordstat.py +0 -0
  249. {direct_cli-0.3.14 → direct_cli-0.3.16}/tests/test_v5_live_write.py +0 -0
  250. {direct_cli-0.3.14 → direct_cli-0.3.16}/tests/test_vendor_imports.py +0 -0
  251. {direct_cli-0.3.14 → direct_cli-0.3.16}/tests/test_wsdl_parity_gate.py +0 -0
  252. {direct_cli-0.3.14 → direct_cli-0.3.16}/tests/wsdl_cache/adextensions.xml +0 -0
  253. {direct_cli-0.3.14 → direct_cli-0.3.16}/tests/wsdl_cache/adgroups.xml +0 -0
  254. {direct_cli-0.3.14 → direct_cli-0.3.16}/tests/wsdl_cache/adimages.xml +0 -0
  255. {direct_cli-0.3.14 → direct_cli-0.3.16}/tests/wsdl_cache/ads.xml +0 -0
  256. {direct_cli-0.3.14 → direct_cli-0.3.16}/tests/wsdl_cache/advideos.xml +0 -0
  257. {direct_cli-0.3.14 → direct_cli-0.3.16}/tests/wsdl_cache/agencyclients.xml +0 -0
  258. {direct_cli-0.3.14 → direct_cli-0.3.16}/tests/wsdl_cache/audiencetargets.xml +0 -0
  259. {direct_cli-0.3.14 → direct_cli-0.3.16}/tests/wsdl_cache/bidmodifiers.xml +0 -0
  260. {direct_cli-0.3.14 → direct_cli-0.3.16}/tests/wsdl_cache/bids.xml +0 -0
  261. {direct_cli-0.3.14 → direct_cli-0.3.16}/tests/wsdl_cache/businesses.xml +0 -0
  262. {direct_cli-0.3.14 → direct_cli-0.3.16}/tests/wsdl_cache/campaigns.xml +0 -0
  263. {direct_cli-0.3.14 → direct_cli-0.3.16}/tests/wsdl_cache/changes.xml +0 -0
  264. {direct_cli-0.3.14 → direct_cli-0.3.16}/tests/wsdl_cache/clients.xml +0 -0
  265. {direct_cli-0.3.14 → direct_cli-0.3.16}/tests/wsdl_cache/creatives.xml +0 -0
  266. {direct_cli-0.3.14 → direct_cli-0.3.16}/tests/wsdl_cache/dictionaries.xml +0 -0
  267. {direct_cli-0.3.14 → direct_cli-0.3.16}/tests/wsdl_cache/dynamicfeedadtargets.xml +0 -0
  268. {direct_cli-0.3.14 → direct_cli-0.3.16}/tests/wsdl_cache/dynamictextadtargets.xml +0 -0
  269. {direct_cli-0.3.14 → direct_cli-0.3.16}/tests/wsdl_cache/feeds.xml +0 -0
  270. {direct_cli-0.3.14 → direct_cli-0.3.16}/tests/wsdl_cache/imports/adextensiontypes.xsd +0 -0
  271. {direct_cli-0.3.14 → direct_cli-0.3.16}/tests/wsdl_cache/imports/general.xsd +0 -0
  272. {direct_cli-0.3.14 → direct_cli-0.3.16}/tests/wsdl_cache/imports/generalclients.xsd +0 -0
  273. {direct_cli-0.3.14 → direct_cli-0.3.16}/tests/wsdl_cache/keywordbids.xml +0 -0
  274. {direct_cli-0.3.14 → direct_cli-0.3.16}/tests/wsdl_cache/keywords.xml +0 -0
  275. {direct_cli-0.3.14 → direct_cli-0.3.16}/tests/wsdl_cache/keywordsresearch.xml +0 -0
  276. {direct_cli-0.3.14 → direct_cli-0.3.16}/tests/wsdl_cache/leads.xml +0 -0
  277. {direct_cli-0.3.14 → direct_cli-0.3.16}/tests/wsdl_cache/negativekeywordsharedsets.xml +0 -0
  278. {direct_cli-0.3.14 → direct_cli-0.3.16}/tests/wsdl_cache/retargetinglists.xml +0 -0
  279. {direct_cli-0.3.14 → direct_cli-0.3.16}/tests/wsdl_cache/sitelinks.xml +0 -0
  280. {direct_cli-0.3.14 → direct_cli-0.3.16}/tests/wsdl_cache/smartadtargets.xml +0 -0
  281. {direct_cli-0.3.14 → direct_cli-0.3.16}/tests/wsdl_cache/strategies.xml +0 -0
  282. {direct_cli-0.3.14 → direct_cli-0.3.16}/tests/wsdl_cache/turbopages.xml +0 -0
  283. {direct_cli-0.3.14 → direct_cli-0.3.16}/tests/wsdl_cache/vcards.xml +0 -0
@@ -46,5 +46,5 @@ jobs:
46
46
  # Optional: Add claude_args to customize behavior and configuration
47
47
  # See https://github.com/anthropics/claude-code-action/blob/main/docs/usage.md
48
48
  # or https://code.claude.com/docs/en/cli-reference for available options
49
- claude_args: "--model claude-opus-4-7"
49
+ claude_args: "--model claude-opus-4-8"
50
50
 
@@ -1,5 +1,60 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.3.16
4
+
5
+ **BREAKING CHANGES (regression fix — reverts 0.3.15 wire-shape changes):**
6
+
7
+ - `direct v4finance transfer-money` now requires `--currency` again and
8
+ re-emits `Currency` on every `FromCampaigns` / `ToCampaigns` item.
9
+ The 0.3.15 removal verified against `dg-v4/reference/TransferMoney`
10
+ (legacy v4); the actual Live 4 docs at
11
+ `dg-v4/live/TransferMoney` define `PayCampElement` with
12
+ `CampaignID`, `Sum`, and `Currency`, and explicitly mark `Currency` as
13
+ obligatory in the Live 4 changelog. The CLI now matches the live
14
+ docs 1:1. See audit comment on #125 for the reproducible diff.
15
+ - `direct v4finance pay-campaigns` now requires `--currency` again and
16
+ re-emits `Currency` on every `Payments[]` item. Same root cause:
17
+ `dg-v4/reference/PayCampaigns` (legacy) lacks `Currency`,
18
+ `dg-v4/live/PayCampaigns` (Live 4) requires it.
19
+ - `direct v4finance pay-campaigns` accepts `--pay-method Overdraft`
20
+ again. The Live 4 changelog explicitly adds `Overdraft` for direct
21
+ advertisers (paired with `Bank` for agencies). Only `Bank` keeps the
22
+ `--contract-id` requirement.
23
+ - `direct v4finance create-invoice` now requires `--currency` again and
24
+ re-emits `Currency` on every `Payments[]` item, mirroring
25
+ `dg-v4/live/CreateInvoice`.
26
+
27
+ This release reverts the wire-shape changes shipped by PRs #441, #442,
28
+ #443 (which closed #432, #433, #434). The CLI lives in the `v4finance`
29
+ Live group and must mirror `dg-v4/live/*`, not `dg-v4/reference/*`.
30
+
31
+ ## 0.3.15
32
+
33
+ **BREAKING CHANGES:**
34
+
35
+ - `direct v4finance transfer-money` no longer accepts `--currency`, and
36
+ the wire-body no longer carries `Currency` on `FromCampaigns`/
37
+ `ToCampaigns` items. The official v4 docs
38
+ (`dg-v4/reference/TransferMoney`) define `PayCampElement` with only
39
+ `CampaignID` and `Sum`; `Sum` is in conventional units. The CLI now
40
+ matches the docs 1:1. Closes #432.
41
+ - `direct v4finance pay-campaigns` no longer accepts `--currency`. The
42
+ v4 documentation (`dg-v4/reference/PayCampaigns`) defines
43
+ `PayCampElement` with only `CampaignID` and `Sum` — `Currency` is not
44
+ part of the wire-body and was never forwarded to the API. The option
45
+ is removed entirely to make the CLI surface 1:1 with the docs.
46
+ - `direct v4finance pay-campaigns` no longer accepts `--pay-method
47
+ Overdraft`. The v4 documentation
48
+ (`dg-v4/reference/PayCampaigns#PayMethod`) lists only `"Bank"` as a
49
+ supported value; `Overdraft` was a historical undocumented value
50
+ retained by the CLI for sandbox flow. Strict 1:1 docs alignment
51
+ drops it.
52
+ - `direct v4finance create-invoice` no longer accepts `--currency`. The
53
+ v4 documentation (`dg-v4/reference/CreateInvoice`) defines
54
+ `PayCampElement` with only `CampaignID` and `Sum` — `Currency` is not
55
+ part of the wire-body and was never forwarded to the API. The option
56
+ is removed entirely to make the CLI surface 1:1 with the docs.
57
+
3
58
  ## 0.3.14
4
59
 
5
60
  **Fixed:**
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: direct-cli
3
- Version: 0.3.14
3
+ Version: 0.3.16
4
4
  Summary: Command-line interface for Yandex Direct API
5
5
  Author: axisrow
6
6
  License: MIT
@@ -154,6 +154,12 @@ direct balance --logins client-login --dry-run
154
154
  | `--login` | Direct client login |
155
155
  | `--profile` | Credential profile name |
156
156
  | `--sandbox` | Use sandbox API |
157
+ | `--locale` | Help/message language: `ru` (default) or `en`. Also set via `YANDEX_DIRECT_CLI_LOCALE`. Command and flag names are unchanged. |
158
+
159
+ Help text is shown in Russian by default. Switch to English with
160
+ `direct --locale en ...` or `export YANDEX_DIRECT_CLI_LOCALE=en`. For example,
161
+ `direct v4finance --help` shows the financial master-token setup steps using
162
+ the locale-appropriate Yandex Direct UI labels.
157
163
 
158
164
  ### V4 Live Goals
159
165
 
@@ -186,6 +192,7 @@ direct v4tags update-banners --banner-ids 2571700 --clear-tags --dry-run
186
192
  ```bash
187
193
  direct v4events get-events-log --from 2026-04-14T00:00:00 --to 2026-04-15T00:00:00
188
194
  direct v4events get-events-log --from 2026-04-14T00:00:00 --to 2026-04-15T00:00:00 --currency RUB --limit 100 --offset 0 --format table
195
+ direct v4events get-events-log --from 2026-04-14T00:00:00 --to 2026-04-15T00:00:00 --last-event-only Yes --with-text-description Yes --filter-campaign-ids 123,456 --filter-event-type MoneyOut,MoneyIn
189
196
  ```
190
197
 
191
198
  ### V4 Live Wordstat Reports
@@ -209,6 +216,7 @@ the forecast is ready.
209
216
 
210
217
  ```bash
211
218
  direct v4forecast create --phrases "buy laptop,buy desktop" --geo-ids 213 --currency RUB
219
+ direct v4forecast create --phrases "buy laptop" --geo-ids 213 --auction-bids Yes --common-minus-words "used,broken"
212
220
  direct v4forecast list --format table
213
221
  direct v4forecast get --forecast-id 123 --format table
214
222
  direct v4forecast delete --forecast-id 123
@@ -235,7 +243,7 @@ direct v4finance get-credit-limits --master-token MASTER_TOKEN --operation-num 1
235
243
  direct v4finance create-invoice --payment 123=100.50 --payment 456=25 --currency RUB --master-token MASTER_TOKEN --operation-num 124 --finance-login agency-login --dry-run
236
244
  direct v4finance check-payment --custom-transaction-id A123456789012345678901234567890B
237
245
  direct v4finance transfer-money --from-campaign-id 123 --to-campaign-id 456 --amount 100.50 --currency RUB --master-token MASTER_TOKEN --operation-num 123 --finance-login agency-login --dry-run
238
- direct v4finance pay-campaigns --campaign-ids 123,456 --amount 100.50 --currency RUB --contract-id CONTRACT_ID --pay-method Bank --master-token MASTER_TOKEN --operation-num 123 --finance-login agency-login --dry-run
246
+ direct v4finance pay-campaigns --campaign-ids 123,456 --amount 100.50 --contract-id CONTRACT_ID --pay-method Bank --currency RUB --master-token MASTER_TOKEN --operation-num 123 --finance-login agency-login --dry-run
239
247
  ```
240
248
 
241
249
  ### V4 Live Shared Account
@@ -111,6 +111,12 @@ direct balance --logins client-login --dry-run
111
111
  | `--login` | Direct client login |
112
112
  | `--profile` | Credential profile name |
113
113
  | `--sandbox` | Use sandbox API |
114
+ | `--locale` | Help/message language: `ru` (default) or `en`. Also set via `YANDEX_DIRECT_CLI_LOCALE`. Command and flag names are unchanged. |
115
+
116
+ Help text is shown in Russian by default. Switch to English with
117
+ `direct --locale en ...` or `export YANDEX_DIRECT_CLI_LOCALE=en`. For example,
118
+ `direct v4finance --help` shows the financial master-token setup steps using
119
+ the locale-appropriate Yandex Direct UI labels.
114
120
 
115
121
  ### V4 Live Goals
116
122
 
@@ -143,6 +149,7 @@ direct v4tags update-banners --banner-ids 2571700 --clear-tags --dry-run
143
149
  ```bash
144
150
  direct v4events get-events-log --from 2026-04-14T00:00:00 --to 2026-04-15T00:00:00
145
151
  direct v4events get-events-log --from 2026-04-14T00:00:00 --to 2026-04-15T00:00:00 --currency RUB --limit 100 --offset 0 --format table
152
+ direct v4events get-events-log --from 2026-04-14T00:00:00 --to 2026-04-15T00:00:00 --last-event-only Yes --with-text-description Yes --filter-campaign-ids 123,456 --filter-event-type MoneyOut,MoneyIn
146
153
  ```
147
154
 
148
155
  ### V4 Live Wordstat Reports
@@ -166,6 +173,7 @@ the forecast is ready.
166
173
 
167
174
  ```bash
168
175
  direct v4forecast create --phrases "buy laptop,buy desktop" --geo-ids 213 --currency RUB
176
+ direct v4forecast create --phrases "buy laptop" --geo-ids 213 --auction-bids Yes --common-minus-words "used,broken"
169
177
  direct v4forecast list --format table
170
178
  direct v4forecast get --forecast-id 123 --format table
171
179
  direct v4forecast delete --forecast-id 123
@@ -192,7 +200,7 @@ direct v4finance get-credit-limits --master-token MASTER_TOKEN --operation-num 1
192
200
  direct v4finance create-invoice --payment 123=100.50 --payment 456=25 --currency RUB --master-token MASTER_TOKEN --operation-num 124 --finance-login agency-login --dry-run
193
201
  direct v4finance check-payment --custom-transaction-id A123456789012345678901234567890B
194
202
  direct v4finance transfer-money --from-campaign-id 123 --to-campaign-id 456 --amount 100.50 --currency RUB --master-token MASTER_TOKEN --operation-num 123 --finance-login agency-login --dry-run
195
- direct v4finance pay-campaigns --campaign-ids 123,456 --amount 100.50 --currency RUB --contract-id CONTRACT_ID --pay-method Bank --master-token MASTER_TOKEN --operation-num 123 --finance-login agency-login --dry-run
203
+ direct v4finance pay-campaigns --campaign-ids 123,456 --amount 100.50 --contract-id CONTRACT_ID --pay-method Bank --currency RUB --master-token MASTER_TOKEN --operation-num 123 --finance-login agency-login --dry-run
196
204
  ```
197
205
 
198
206
  ### V4 Live Shared Account
@@ -176,9 +176,15 @@ class V4LiveClientAdapter(JSONAdapterMixin, TapiAdapter):
176
176
  code = 0
177
177
 
178
178
  if code in (54, 55) and api_params.get("retry_if_exceeded_limit", True):
179
- logger.warning("v4 Live limit exceeded (code=%s), retry in 10s", code)
180
- time.sleep(10)
181
- return True
179
+ # Bound the limit-exceeded retries with the same attempt budget as
180
+ # the server-error branch below. Without this guard the loop is
181
+ # infinite (sleep 10s, retry, forever) whenever the API keeps
182
+ # returning code 54/55 — which hangs the caller indefinitely.
183
+ if repeat_number < api_params.get("retries_if_server_error", 5):
184
+ logger.warning("v4 Live limit exceeded (code=%s), retry in 10s", code)
185
+ time.sleep(10)
186
+ return True
187
+ return False
182
188
 
183
189
  if code in (52, 1000, 1001, 1002) or response.status_code == 500:
184
190
  if repeat_number < api_params.get("retries_if_server_error", 5):
@@ -8,6 +8,11 @@ from click.core import ParameterSource
8
8
 
9
9
  from . import __version__
10
10
  from .auth import get_active_profile, get_credentials, load_env_file
11
+ from .i18n import (
12
+ LOCALE_ENV_VAR,
13
+ resolve_locale,
14
+ t,
15
+ )
11
16
  from .utils import get_docs_url
12
17
 
13
18
  from .commands.campaigns import campaigns
@@ -155,6 +160,21 @@ class DirectCliGroup(_NoSuchOptionHintMixin, click.Group):
155
160
 
156
161
  command_class = DirectCliCommand
157
162
 
163
+ def format_epilog(self, ctx, formatter):
164
+ """Render a locale-aware epilog when the group opts into i18n.
165
+
166
+ A group sets ``localized_epilog_key`` (a key in ``i18n.CATALOG``) and,
167
+ optionally, ``localized_epilog_suffix`` (an already-built tail appended
168
+ verbatim so single-sourced text such as ``V4_EPILOG`` is not
169
+ duplicated). Groups without the attribute keep their static epilog.
170
+ """
171
+ key = getattr(self, "localized_epilog_key", None)
172
+ if key:
173
+ text = t(key, resolve_locale(ctx))
174
+ suffix = getattr(self, "localized_epilog_suffix", None)
175
+ self.epilog = f"{text}\n\n{suffix}" if suffix else text
176
+ super().format_epilog(ctx, formatter)
177
+
158
178
 
159
179
  def _apply_directcli_classes(command: click.Command) -> None:
160
180
  """Recursively retype *command* (and subcommands) so every node in the
@@ -182,6 +202,12 @@ def _apply_directcli_classes(command: click.Command) -> None:
182
202
  @click.option("--login", envvar="YANDEX_DIRECT_LOGIN", help="Client login")
183
203
  @click.option("--profile", help="Credential profile name")
184
204
  @click.option("--sandbox", is_flag=True, help="Use sandbox API")
205
+ @click.option(
206
+ "--locale",
207
+ envvar=LOCALE_ENV_VAR,
208
+ default=None,
209
+ help="Language for help and messages (ru or en; default: ru)",
210
+ )
185
211
  @click.option(
186
212
  "--op-token-ref",
187
213
  envvar="YANDEX_DIRECT_OP_TOKEN_REF",
@@ -209,6 +235,7 @@ def cli(
209
235
  login,
210
236
  profile,
211
237
  sandbox,
238
+ locale,
212
239
  op_token_ref,
213
240
  op_login_ref,
214
241
  bw_token_ref,
@@ -218,6 +245,7 @@ def cli(
218
245
  ctx.ensure_object(dict)
219
246
  ctx.obj["sandbox"] = sandbox
220
247
  ctx.obj["profile"] = profile
248
+ ctx.obj["locale"] = locale
221
249
  active_profile = None
222
250
  if ctx.invoked_subcommand != "auth":
223
251
  active_profile = get_active_profile()
@@ -0,0 +1,232 @@
1
+ """Yandex Direct v4 Live events commands."""
2
+
3
+ import re
4
+ from datetime import datetime
5
+ from typing import Optional
6
+
7
+ import click
8
+
9
+ from ..api import create_v4_client
10
+ from ..output import format_output, print_error
11
+ from ..utils import parse_csv_strings, parse_ids
12
+ from ..v4 import build_v4_body, call_v4
13
+ from ..v4_contracts import v4_method_contract
14
+ from .v4shells import V4_EPILOG
15
+
16
+ V4_DATETIME_FORMAT = "%Y-%m-%dT%H:%M:%S"
17
+ V4_DATETIME_RE = re.compile(r"\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}")
18
+
19
+ # Documented GetEventsLogFilter.EventType enum (dg-v4/live/GetEventsLog).
20
+ EVENT_TYPES = [
21
+ "BannerModerated",
22
+ "CampaignFinished",
23
+ "LowCTR",
24
+ "MoneyOut",
25
+ "MoneyWarning",
26
+ "MoneyIn",
27
+ "PausedByDayBudget",
28
+ "WarnMinPrice",
29
+ "WarnPlace",
30
+ ]
31
+
32
+
33
+ def _events_log_filter(
34
+ campaign_ids: Optional[str],
35
+ banner_ids: Optional[str],
36
+ phrase_ids: Optional[str],
37
+ account_ids: Optional[str],
38
+ event_type: Optional[str],
39
+ ) -> Optional[dict]:
40
+ """Build the GetEventsLogFilter object; None when no filter is provided."""
41
+ filter_obj: dict = {}
42
+ for option_name, raw, key in (
43
+ ("--filter-campaign-ids", campaign_ids, "CampaignIDS"),
44
+ ("--filter-banner-ids", banner_ids, "BannerIDS"),
45
+ ("--filter-phrase-ids", phrase_ids, "PhraseIDS"),
46
+ ("--filter-account-ids", account_ids, "AccountIDS"),
47
+ ):
48
+ if not raw:
49
+ continue
50
+ try:
51
+ parsed = parse_ids(raw)
52
+ except ValueError as exc:
53
+ raise click.UsageError(f"{option_name}: {exc}") from exc
54
+ if parsed:
55
+ filter_obj[key] = parsed
56
+ if event_type:
57
+ types = parse_csv_strings(event_type)
58
+ if types:
59
+ unknown = [value for value in types if value not in EVENT_TYPES]
60
+ if unknown:
61
+ raise click.UsageError(
62
+ "--filter-event-type has unknown values: "
63
+ f"{', '.join(unknown)}. Valid values: {', '.join(EVENT_TYPES)}"
64
+ )
65
+ filter_obj["EventType"] = types
66
+ return filter_obj or None
67
+
68
+
69
+ def _parse_v4_datetime(value: str, option_name: str) -> datetime:
70
+ """Parse a strict v4 Live CLI datetime token."""
71
+ if not V4_DATETIME_RE.fullmatch(value):
72
+ raise click.UsageError(
73
+ f"Invalid {option_name}: {value}. Expected YYYY-MM-DDTHH:MM:SS"
74
+ )
75
+ try:
76
+ return datetime.strptime(value, V4_DATETIME_FORMAT)
77
+ except ValueError:
78
+ raise click.UsageError(
79
+ f"Invalid {option_name}: {value}. Expected YYYY-MM-DDTHH:MM:SS"
80
+ )
81
+
82
+
83
+ def _events_log_param(
84
+ timestamp_from: str,
85
+ timestamp_to: str,
86
+ currency: str,
87
+ limit: Optional[int],
88
+ offset: Optional[int],
89
+ last_event_only: Optional[str] = None,
90
+ with_text_description: Optional[str] = None,
91
+ logins: Optional[str] = None,
92
+ campaign_ids: Optional[str] = None,
93
+ banner_ids: Optional[str] = None,
94
+ phrase_ids: Optional[str] = None,
95
+ account_ids: Optional[str] = None,
96
+ event_type: Optional[str] = None,
97
+ ) -> dict:
98
+ """Build the v4 Live GetEventsLog parameter."""
99
+ from_dt = _parse_v4_datetime(timestamp_from, "--from")
100
+ to_dt = _parse_v4_datetime(timestamp_to, "--to")
101
+ if from_dt > to_dt:
102
+ raise click.UsageError("--from must be earlier than or equal to --to")
103
+
104
+ param = {
105
+ "TimestampFrom": timestamp_from,
106
+ "TimestampTo": timestamp_to,
107
+ "Currency": currency,
108
+ }
109
+ if last_event_only:
110
+ param["LastEventOnly"] = last_event_only
111
+ if with_text_description:
112
+ param["WithTextDescription"] = with_text_description
113
+ if logins:
114
+ login_list = parse_csv_strings(logins)
115
+ if login_list:
116
+ param["Logins"] = login_list
117
+ filter_obj = _events_log_filter(
118
+ campaign_ids, banner_ids, phrase_ids, account_ids, event_type
119
+ )
120
+ if filter_obj is not None:
121
+ param["Filter"] = filter_obj
122
+ if limit is not None:
123
+ param["Limit"] = limit
124
+ if offset is not None:
125
+ param["Offset"] = offset
126
+ return param
127
+
128
+
129
+ @click.group(epilog=V4_EPILOG)
130
+ def v4events():
131
+ """Yandex Direct v4 Live events commands."""
132
+
133
+
134
+ @v4_method_contract("GetEventsLog")
135
+ @v4events.command(name="get-events-log")
136
+ @click.option(
137
+ "--from",
138
+ "timestamp_from",
139
+ required=True,
140
+ help="Start timestamp in YYYY-MM-DDTHH:MM:SS format",
141
+ )
142
+ @click.option(
143
+ "--to",
144
+ "timestamp_to",
145
+ required=True,
146
+ help="End timestamp in YYYY-MM-DDTHH:MM:SS format",
147
+ )
148
+ @click.option(
149
+ "--last-event-only",
150
+ type=click.Choice(["Yes", "No"]),
151
+ help="Return only the latest record per event type — Yes/No",
152
+ )
153
+ @click.option(
154
+ "--with-text-description",
155
+ type=click.Choice(["Yes", "No"]),
156
+ help="Include event text descriptions in the response — Yes/No",
157
+ )
158
+ @click.option("--currency", default="RUB", show_default=True, help="Currency")
159
+ @click.option("--logins", help="Comma-separated client logins")
160
+ @click.option("--filter-campaign-ids", help="Filter: comma-separated campaign IDs")
161
+ @click.option("--filter-banner-ids", help="Filter: comma-separated banner IDs")
162
+ @click.option("--filter-phrase-ids", help="Filter: comma-separated phrase IDs")
163
+ @click.option("--filter-account-ids", help="Filter: comma-separated account IDs")
164
+ @click.option(
165
+ "--filter-event-type",
166
+ help="Filter: comma-separated event types (" + ", ".join(EVENT_TYPES) + ")",
167
+ )
168
+ @click.option("--limit", type=click.IntRange(min=0), help="Result limit")
169
+ @click.option("--offset", type=click.IntRange(min=0), help="Result offset")
170
+ @click.option(
171
+ "--format",
172
+ "output_format",
173
+ default="json",
174
+ type=click.Choice(["json", "table", "csv", "tsv"]),
175
+ help="Output format",
176
+ )
177
+ @click.option("--output", help="Output file")
178
+ @click.option("--dry-run", is_flag=True, help="Show request without sending")
179
+ @click.pass_context
180
+ def get_events_log(
181
+ ctx,
182
+ timestamp_from,
183
+ timestamp_to,
184
+ last_event_only,
185
+ with_text_description,
186
+ currency,
187
+ logins,
188
+ filter_campaign_ids,
189
+ filter_banner_ids,
190
+ filter_phrase_ids,
191
+ filter_account_ids,
192
+ filter_event_type,
193
+ limit,
194
+ offset,
195
+ output_format,
196
+ output,
197
+ dry_run,
198
+ ):
199
+ """Get v4 Live events log entries."""
200
+ param = _events_log_param(
201
+ timestamp_from,
202
+ timestamp_to,
203
+ currency,
204
+ limit,
205
+ offset,
206
+ last_event_only=last_event_only,
207
+ with_text_description=with_text_description,
208
+ logins=logins,
209
+ campaign_ids=filter_campaign_ids,
210
+ banner_ids=filter_banner_ids,
211
+ phrase_ids=filter_phrase_ids,
212
+ account_ids=filter_account_ids,
213
+ event_type=filter_event_type,
214
+ )
215
+ if dry_run:
216
+ format_output(build_v4_body("GetEventsLog", param), "json", None)
217
+ return
218
+
219
+ try:
220
+ client = create_v4_client(
221
+ token=ctx.obj.get("token"),
222
+ login=ctx.obj.get("login"),
223
+ profile=ctx.obj.get("profile"),
224
+ sandbox=ctx.obj.get("sandbox"),
225
+ )
226
+ data = call_v4(client, "GetEventsLog", param)
227
+ format_output(data, output_format, output)
228
+ except click.ClickException:
229
+ raise
230
+ except Exception as e:
231
+ print_error(str(e))
232
+ raise click.Abort()
@@ -6,6 +6,7 @@ from typing import Any, Optional
6
6
  import click
7
7
 
8
8
  from ..api import create_v4_client
9
+ from ..i18n import LocalizedOption
9
10
  from ..output import format_output, print_error
10
11
  from ..utils import parse_csv_strings, parse_ids
11
12
  from ..v4 import build_v4_body, call_v4
@@ -15,8 +16,8 @@ from .v4shells import V4_EPILOG
15
16
 
16
17
  FINANCE_TOKEN_MASK = "<redacted>"
17
18
  CUSTOM_TRANSACTION_ID_RE = re.compile(r"[A-Za-z0-9]{32}")
18
- V4_FINANCE_CURRENCIES = ["RUB", "USD", "EUR", "BYN", "KZT", "TRY", "UAH", "CHF"]
19
19
  V4_PAY_METHODS = ["Bank", "Overdraft"]
20
+ V4_FINANCE_CURRENCIES = ("RUB", "CHF", "EUR", "KZT", "TRY", "UAH", "USD", "BYN")
20
21
  FINANCE_HELP_EPILOG = (
21
22
  "To issue a master token in the Yandex Direct UI, open Tools -> API -> "
22
23
  "Financial operations, enable the 'Allow financial operations' checkbox, "
@@ -39,9 +40,9 @@ def _invoice_payments_param(payments: tuple[str, ...], currency: str) -> dict:
39
40
  if not payments:
40
41
  raise click.UsageError("--payment is required")
41
42
 
43
+ normalized_currency = currency.upper()
42
44
  parsed_payments = []
43
45
  seen_campaign_ids = set()
44
- normalized_currency = currency.upper()
45
46
  for payment in payments:
46
47
  spec = (payment or "").strip()
47
48
  if "=" not in spec:
@@ -159,6 +160,13 @@ def v4finance():
159
160
  """Yandex Direct v4 Live finance commands."""
160
161
 
161
162
 
163
+ # Localize the group epilog at render time (Russian by default, English via
164
+ # --locale en / YANDEX_DIRECT_CLI_LOCALE). The V4_EPILOG tail is appended
165
+ # verbatim so its single-sourced docs URL is not duplicated. See i18n.py.
166
+ v4finance.localized_epilog_key = "v4finance.master_token_setup"
167
+ v4finance.localized_epilog_suffix = V4_EPILOG
168
+
169
+
162
170
  @v4_method_contract("GetClientsUnits")
163
171
  @v4finance.command(name="get-clients-units")
164
172
  @click.option("--logins", required=True, help="Comma-separated client logins")
@@ -205,11 +213,9 @@ def get_clients_units(ctx, logins, output_format, output, dry_run):
205
213
  )
206
214
  @click.option(
207
215
  "--master-token",
216
+ cls=LocalizedOption,
217
+ help_key="v4finance.master_token_option",
208
218
  envvar="YANDEX_DIRECT_MASTER_TOKEN",
209
- help=(
210
- "Financial master token issued after enabling and saving financial "
211
- "operations in Tools -> API -> Financial operations"
212
- ),
213
219
  )
214
220
  @click.option(
215
221
  "--operation-num",
@@ -335,10 +341,9 @@ def check_payment(
335
341
  )
336
342
  @click.option(
337
343
  "--currency",
338
- default="RUB",
339
- show_default=True,
344
+ required=True,
340
345
  type=click.Choice(V4_FINANCE_CURRENCIES, case_sensitive=False),
341
- help="Payment currency",
346
+ help="Payment currency (RUB/CHF/EUR/KZT/TRY/UAH/USD/BYN)",
342
347
  )
343
348
  @click.option(
344
349
  "--finance-token",
@@ -347,11 +352,9 @@ def check_payment(
347
352
  )
348
353
  @click.option(
349
354
  "--master-token",
355
+ cls=LocalizedOption,
356
+ help_key="v4finance.master_token_option",
350
357
  envvar="YANDEX_DIRECT_MASTER_TOKEN",
351
- help=(
352
- "Financial master token issued after enabling and saving financial "
353
- "operations in Tools -> API -> Financial operations"
354
- ),
355
358
  )
356
359
  @click.option(
357
360
  "--operation-num",
@@ -440,10 +443,9 @@ def create_invoice(
440
443
  @click.option("--amount", required=True, help="Positive amount, for example 100.50")
441
444
  @click.option(
442
445
  "--currency",
443
- default="RUB",
444
- show_default=True,
446
+ required=True,
445
447
  type=click.Choice(V4_FINANCE_CURRENCIES, case_sensitive=False),
446
- help="Payment currency",
448
+ help="Transfer currency (RUB/CHF/EUR/KZT/TRY/UAH/USD/BYN)",
447
449
  )
448
450
  @click.option(
449
451
  "--finance-token",
@@ -452,11 +454,9 @@ def create_invoice(
452
454
  )
453
455
  @click.option(
454
456
  "--master-token",
457
+ cls=LocalizedOption,
458
+ help_key="v4finance.master_token_option",
455
459
  envvar="YANDEX_DIRECT_MASTER_TOKEN",
456
- help=(
457
- "Financial master token issued after enabling and saving financial "
458
- "operations in Tools -> API -> Financial operations"
459
- ),
460
460
  )
461
461
  @click.option(
462
462
  "--operation-num",
@@ -498,20 +498,20 @@ def transfer_money(
498
498
  ctx.obj.get("login"),
499
499
  )
500
500
  parsed_amount = parse_v4_money_sum(amount)
501
- currency = currency.upper()
501
+ normalized_currency = currency.upper()
502
502
  param = {
503
503
  "FromCampaigns": [
504
504
  {
505
505
  "CampaignID": from_campaign_id,
506
506
  "Sum": parsed_amount,
507
- "Currency": currency,
507
+ "Currency": normalized_currency,
508
508
  },
509
509
  ],
510
510
  "ToCampaigns": [
511
511
  {
512
512
  "CampaignID": to_campaign_id,
513
513
  "Sum": parsed_amount,
514
- "Currency": currency,
514
+ "Currency": normalized_currency,
515
515
  },
516
516
  ],
517
517
  }
@@ -531,19 +531,18 @@ def transfer_money(
531
531
  help="Comma-separated campaign IDs to pay",
532
532
  )
533
533
  @click.option("--amount", required=True, help="Positive amount, for example 100.50")
534
- @click.option(
535
- "--currency",
536
- default="RUB",
537
- show_default=True,
538
- type=click.Choice(V4_FINANCE_CURRENCIES, case_sensitive=False),
539
- help="Payment currency",
540
- )
541
534
  @click.option("--contract-id", help="Agency contract ID; required for Bank")
542
535
  @click.option(
543
536
  "--pay-method",
544
537
  required=True,
545
538
  type=click.Choice(V4_PAY_METHODS, case_sensitive=False),
546
- help="Payment method",
539
+ help="Payment method: Bank (agency credit) or Overdraft (direct advertiser)",
540
+ )
541
+ @click.option(
542
+ "--currency",
543
+ required=True,
544
+ type=click.Choice(V4_FINANCE_CURRENCIES, case_sensitive=False),
545
+ help="Payment currency (RUB/CHF/EUR/KZT/TRY/UAH/USD/BYN)",
547
546
  )
548
547
  @click.option(
549
548
  "--finance-token",
@@ -552,11 +551,9 @@ def transfer_money(
552
551
  )
553
552
  @click.option(
554
553
  "--master-token",
554
+ cls=LocalizedOption,
555
+ help_key="v4finance.master_token_option",
555
556
  envvar="YANDEX_DIRECT_MASTER_TOKEN",
556
- help=(
557
- "Financial master token issued after enabling and saving financial "
558
- "operations in Tools -> API -> Financial operations"
559
- ),
560
557
  )
561
558
  @click.option(
562
559
  "--operation-num",
@@ -579,9 +576,9 @@ def pay_campaigns(
579
576
  ctx,
580
577
  campaign_ids,
581
578
  amount,
582
- currency,
583
579
  contract_id,
584
580
  pay_method,
581
+ currency,
585
582
  finance_token,
586
583
  master_token,
587
584
  operation_num,
@@ -600,14 +597,18 @@ def pay_campaigns(
600
597
  )
601
598
  parsed_amount = parse_v4_money_sum(amount)
602
599
  parsed_campaign_ids = _campaign_ids_param(campaign_ids)
603
- currency = currency.upper()
604
600
  pay_method = _non_empty_option(pay_method, "--pay-method")
601
+ normalized_currency = currency.upper()
605
602
  contract_id = (contract_id or "").strip()
606
603
  if pay_method == "Bank" and not contract_id:
607
604
  raise click.UsageError("--contract-id is required when --pay-method Bank")
608
605
  param = {
609
606
  "Payments": [
610
- {"CampaignID": campaign_id, "Sum": parsed_amount, "Currency": currency}
607
+ {
608
+ "CampaignID": campaign_id,
609
+ "Sum": parsed_amount,
610
+ "Currency": normalized_currency,
611
+ }
611
612
  for campaign_id in parsed_campaign_ids
612
613
  ],
613
614
  "PayMethod": pay_method,