direct-cli 0.3.15__tar.gz → 0.4.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (283) hide show
  1. {direct_cli-0.3.15 → direct_cli-0.4.0}/.github/workflows/claude.yml +1 -1
  2. {direct_cli-0.3.15 → direct_cli-0.4.0}/CHANGELOG.md +70 -0
  3. {direct_cli-0.3.15 → direct_cli-0.4.0}/PKG-INFO +12 -4
  4. {direct_cli-0.3.15 → direct_cli-0.4.0}/README.md +11 -3
  5. {direct_cli-0.3.15 → direct_cli-0.4.0}/direct_cli/_vendor/tapi_yandex_direct/v4/adapter.py +9 -3
  6. {direct_cli-0.3.15 → direct_cli-0.4.0}/direct_cli/cli.py +28 -0
  7. direct_cli-0.4.0/direct_cli/commands/v4events.py +232 -0
  8. {direct_cli-0.3.15 → direct_cli-0.4.0}/direct_cli/commands/v4finance.py +53 -21
  9. {direct_cli-0.3.15 → direct_cli-0.4.0}/direct_cli/commands/v4forecast.py +52 -3
  10. direct_cli-0.4.0/direct_cli/i18n.py +107 -0
  11. {direct_cli-0.3.15 → direct_cli-0.4.0}/direct_cli/v4/__init__.py +2 -2
  12. {direct_cli-0.3.15 → direct_cli-0.4.0}/direct_cli/v4_contracts.py +131 -42
  13. {direct_cli-0.3.15 → direct_cli-0.4.0}/direct_cli.egg-info/PKG-INFO +12 -4
  14. {direct_cli-0.3.15 → direct_cli-0.4.0}/direct_cli.egg-info/SOURCES.txt +47 -0
  15. direct_cli-0.4.0/docs/audits/API_COVERAGE.md +93 -0
  16. direct_cli-0.4.0/docs/audits/PROJECT_WIRE_SHAPE_AUDIT_2026-05-29.md +193 -0
  17. direct_cli-0.4.0/docs/audits/wire_shape.json +1394 -0
  18. {direct_cli-0.3.15 → direct_cli-0.4.0}/pyproject.toml +1 -1
  19. direct_cli-0.4.0/scripts/audit_wire_shape.py +791 -0
  20. {direct_cli-0.3.15 → direct_cli-0.4.0}/scripts/test_dangerous_commands.sh +2 -2
  21. {direct_cli-0.3.15 → direct_cli-0.4.0}/tests/API_COVERAGE.md +11 -6
  22. direct_cli-0.4.0/tests/cassettes/test_read_cassettes/test_read_command[adextensions_get].yaml +56 -0
  23. direct_cli-0.4.0/tests/cassettes/test_read_cassettes/test_read_command[adgroups_get].yaml +56 -0
  24. direct_cli-0.4.0/tests/cassettes/test_read_cassettes/test_read_command[adimages_get].yaml +66 -0
  25. direct_cli-0.4.0/tests/cassettes/test_read_cassettes/test_read_command[ads_get].yaml +56 -0
  26. direct_cli-0.4.0/tests/cassettes/test_read_cassettes/test_read_command[advideos_get].yaml +56 -0
  27. direct_cli-0.4.0/tests/cassettes/test_read_cassettes/test_read_command[audiencetargets_get].yaml +56 -0
  28. direct_cli-0.4.0/tests/cassettes/test_read_cassettes/test_read_command[bidmodifiers_get].yaml +56 -0
  29. direct_cli-0.4.0/tests/cassettes/test_read_cassettes/test_read_command[bids_get].yaml +56 -0
  30. direct_cli-0.4.0/tests/cassettes/test_read_cassettes/test_read_command[businesses_get].yaml +56 -0
  31. direct_cli-0.4.0/tests/cassettes/test_read_cassettes/test_read_command[campaigns_get].yaml +57 -0
  32. direct_cli-0.4.0/tests/cassettes/test_read_cassettes/test_read_command[changes_check].yaml +56 -0
  33. direct_cli-0.4.0/tests/cassettes/test_read_cassettes/test_read_command[changes_check_campaigns].yaml +56 -0
  34. direct_cli-0.4.0/tests/cassettes/test_read_cassettes/test_read_command[changes_check_dictionaries].yaml +56 -0
  35. direct_cli-0.4.0/tests/cassettes/test_read_cassettes/test_read_command[clients_get].yaml +56 -0
  36. direct_cli-0.4.0/tests/cassettes/test_read_cassettes/test_read_command[creatives_get].yaml +56 -0
  37. direct_cli-0.4.0/tests/cassettes/test_read_cassettes/test_read_command[dictionaries_get].yaml +11696 -0
  38. direct_cli-0.4.0/tests/cassettes/test_read_cassettes/test_read_command[dynamicads_get].yaml +56 -0
  39. direct_cli-0.4.0/tests/cassettes/test_read_cassettes/test_read_command[dynamicfeedadtargets_get].yaml +56 -0
  40. direct_cli-0.4.0/tests/cassettes/test_read_cassettes/test_read_command[feeds_get].yaml +56 -0
  41. direct_cli-0.4.0/tests/cassettes/test_read_cassettes/test_read_command[keywordbids_get].yaml +56 -0
  42. direct_cli-0.4.0/tests/cassettes/test_read_cassettes/test_read_command[keywords_get].yaml +56 -0
  43. direct_cli-0.4.0/tests/cassettes/test_read_cassettes/test_read_command[keywordsresearch_deduplicate].yaml +59 -0
  44. direct_cli-0.4.0/tests/cassettes/test_read_cassettes/test_read_command[keywordsresearch_has_search_volume].yaml +58 -0
  45. direct_cli-0.4.0/tests/cassettes/test_read_cassettes/test_read_command[leads_get].yaml +56 -0
  46. direct_cli-0.4.0/tests/cassettes/test_read_cassettes/test_read_command[negativekeywordsharedsets_get].yaml +56 -0
  47. direct_cli-0.4.0/tests/cassettes/test_read_cassettes/test_read_command[reports_get].yaml +64 -0
  48. direct_cli-0.4.0/tests/cassettes/test_read_cassettes/test_read_command[retargeting_get].yaml +56 -0
  49. direct_cli-0.4.0/tests/cassettes/test_read_cassettes/test_read_command[sitelinks_get].yaml +56 -0
  50. direct_cli-0.4.0/tests/cassettes/test_read_cassettes/test_read_command[smartadtargets_get].yaml +56 -0
  51. direct_cli-0.4.0/tests/cassettes/test_read_cassettes/test_read_command[strategies_get].yaml +66 -0
  52. direct_cli-0.4.0/tests/cassettes/test_read_cassettes/test_read_command[turbopages_get].yaml +56 -0
  53. direct_cli-0.4.0/tests/cassettes/test_read_cassettes/test_read_command[v4events_get_events_log].yaml +52 -0
  54. direct_cli-0.4.0/tests/cassettes/test_read_cassettes/test_read_command[v4finance_get_clients_units].yaml +52 -0
  55. direct_cli-0.4.0/tests/cassettes/test_read_cassettes/test_read_command[v4forecast_list].yaml +52 -0
  56. direct_cli-0.4.0/tests/cassettes/test_read_cassettes/test_read_command[v4goals_get_stat_goals].yaml +52 -0
  57. direct_cli-0.4.0/tests/cassettes/test_read_cassettes/test_read_command[v4tags_get_campaigns].yaml +52 -0
  58. direct_cli-0.4.0/tests/cassettes/test_read_cassettes/test_read_command[v4wordstat_list_reports].yaml +52 -0
  59. direct_cli-0.4.0/tests/cassettes/test_read_cassettes/test_read_command[vcards_get].yaml +57 -0
  60. direct_cli-0.4.0/tests/cassettes/test_read_cassettes/test_v4finance_check_payment_unknown_transaction.yaml +52 -0
  61. {direct_cli-0.3.15 → direct_cli-0.4.0}/tests/conftest.py +68 -17
  62. direct_cli-0.4.0/tests/test_audit_wire_shape.py +158 -0
  63. direct_cli-0.4.0/tests/test_i18n.py +118 -0
  64. direct_cli-0.4.0/tests/test_read_cassettes.py +252 -0
  65. {direct_cli-0.3.15 → direct_cli-0.4.0}/tests/test_v4_contracts.py +20 -5
  66. {direct_cli-0.3.15 → direct_cli-0.4.0}/tests/test_v4_runtime_shape.py +29 -3
  67. {direct_cli-0.3.15 → direct_cli-0.4.0}/tests/test_v4events.py +110 -0
  68. {direct_cli-0.3.15 → direct_cli-0.4.0}/tests/test_v4finance_money.py +81 -9
  69. {direct_cli-0.3.15 → direct_cli-0.4.0}/tests/test_v4forecast.py +68 -1
  70. direct_cli-0.3.15/direct_cli/commands/v4events.py +0 -120
  71. {direct_cli-0.3.15 → direct_cli-0.4.0}/.env.example +0 -0
  72. {direct_cli-0.3.15 → direct_cli-0.4.0}/.github/copilot-instructions.md +0 -0
  73. {direct_cli-0.3.15 → direct_cli-0.4.0}/.github/workflows/api-coverage.yml +0 -0
  74. {direct_cli-0.3.15 → direct_cli-0.4.0}/.github/workflows/quality.yml +0 -0
  75. {direct_cli-0.3.15 → direct_cli-0.4.0}/.gitignore +0 -0
  76. {direct_cli-0.3.15 → direct_cli-0.4.0}/AGENTS.md +0 -0
  77. {direct_cli-0.3.15 → direct_cli-0.4.0}/CLAUDE.md +0 -0
  78. {direct_cli-0.3.15 → direct_cli-0.4.0}/MANIFEST.in +0 -0
  79. {direct_cli-0.3.15 → direct_cli-0.4.0}/direct_cli/__init__.py +0 -0
  80. {direct_cli-0.3.15 → direct_cli-0.4.0}/direct_cli/_bidding_strategy.py +0 -0
  81. {direct_cli-0.3.15 → direct_cli-0.4.0}/direct_cli/_deprecated.py +0 -0
  82. {direct_cli-0.3.15 → direct_cli-0.4.0}/direct_cli/_smoke_probes.py +0 -0
  83. {direct_cli-0.3.15 → direct_cli-0.4.0}/direct_cli/_vendor/__init__.py +0 -0
  84. {direct_cli-0.3.15 → direct_cli-0.4.0}/direct_cli/_vendor/tapi_yandex_direct/__init__.py +0 -0
  85. {direct_cli-0.3.15 → direct_cli-0.4.0}/direct_cli/_vendor/tapi_yandex_direct/endpoints.py +0 -0
  86. {direct_cli-0.3.15 → direct_cli-0.4.0}/direct_cli/_vendor/tapi_yandex_direct/exceptions.py +0 -0
  87. {direct_cli-0.3.15 → direct_cli-0.4.0}/direct_cli/_vendor/tapi_yandex_direct/resource_mapping.py +0 -0
  88. {direct_cli-0.3.15 → direct_cli-0.4.0}/direct_cli/_vendor/tapi_yandex_direct/tapi_yandex_direct.py +0 -0
  89. {direct_cli-0.3.15 → direct_cli-0.4.0}/direct_cli/_vendor/tapi_yandex_direct/tapi_yandex_direct.pyi +0 -0
  90. {direct_cli-0.3.15 → direct_cli-0.4.0}/direct_cli/_vendor/tapi_yandex_direct/v4/__init__.py +0 -0
  91. {direct_cli-0.3.15 → direct_cli-0.4.0}/direct_cli/_vendor/tapi_yandex_direct/v4/adapter.pyi +0 -0
  92. {direct_cli-0.3.15 → direct_cli-0.4.0}/direct_cli/_vendor/tapi_yandex_direct/v4/resource_mapping.py +0 -0
  93. {direct_cli-0.3.15 → direct_cli-0.4.0}/direct_cli/api.py +0 -0
  94. {direct_cli-0.3.15 → direct_cli-0.4.0}/direct_cli/auth.py +0 -0
  95. {direct_cli-0.3.15 → direct_cli-0.4.0}/direct_cli/commands/__init__.py +0 -0
  96. {direct_cli-0.3.15 → direct_cli-0.4.0}/direct_cli/commands/adextensions.py +0 -0
  97. {direct_cli-0.3.15 → direct_cli-0.4.0}/direct_cli/commands/adgroups.py +0 -0
  98. {direct_cli-0.3.15 → direct_cli-0.4.0}/direct_cli/commands/adimages.py +0 -0
  99. {direct_cli-0.3.15 → direct_cli-0.4.0}/direct_cli/commands/ads.py +0 -0
  100. {direct_cli-0.3.15 → direct_cli-0.4.0}/direct_cli/commands/advideos.py +0 -0
  101. {direct_cli-0.3.15 → direct_cli-0.4.0}/direct_cli/commands/agencyclients.py +0 -0
  102. {direct_cli-0.3.15 → direct_cli-0.4.0}/direct_cli/commands/audiencetargets.py +0 -0
  103. {direct_cli-0.3.15 → direct_cli-0.4.0}/direct_cli/commands/auth.py +0 -0
  104. {direct_cli-0.3.15 → direct_cli-0.4.0}/direct_cli/commands/balance.py +0 -0
  105. {direct_cli-0.3.15 → direct_cli-0.4.0}/direct_cli/commands/bidmodifiers.py +0 -0
  106. {direct_cli-0.3.15 → direct_cli-0.4.0}/direct_cli/commands/bids.py +0 -0
  107. {direct_cli-0.3.15 → direct_cli-0.4.0}/direct_cli/commands/businesses.py +0 -0
  108. {direct_cli-0.3.15 → direct_cli-0.4.0}/direct_cli/commands/campaigns.py +0 -0
  109. {direct_cli-0.3.15 → direct_cli-0.4.0}/direct_cli/commands/changes.py +0 -0
  110. {direct_cli-0.3.15 → direct_cli-0.4.0}/direct_cli/commands/clients.py +0 -0
  111. {direct_cli-0.3.15 → direct_cli-0.4.0}/direct_cli/commands/creatives.py +0 -0
  112. {direct_cli-0.3.15 → direct_cli-0.4.0}/direct_cli/commands/dictionaries.py +0 -0
  113. {direct_cli-0.3.15 → direct_cli-0.4.0}/direct_cli/commands/dynamicads.py +0 -0
  114. {direct_cli-0.3.15 → direct_cli-0.4.0}/direct_cli/commands/dynamicfeedadtargets.py +0 -0
  115. {direct_cli-0.3.15 → direct_cli-0.4.0}/direct_cli/commands/feeds.py +0 -0
  116. {direct_cli-0.3.15 → direct_cli-0.4.0}/direct_cli/commands/keywordbids.py +0 -0
  117. {direct_cli-0.3.15 → direct_cli-0.4.0}/direct_cli/commands/keywords.py +0 -0
  118. {direct_cli-0.3.15 → direct_cli-0.4.0}/direct_cli/commands/keywordsresearch.py +0 -0
  119. {direct_cli-0.3.15 → direct_cli-0.4.0}/direct_cli/commands/leads.py +0 -0
  120. {direct_cli-0.3.15 → direct_cli-0.4.0}/direct_cli/commands/negativekeywordsharedsets.py +0 -0
  121. {direct_cli-0.3.15 → direct_cli-0.4.0}/direct_cli/commands/reports.py +0 -0
  122. {direct_cli-0.3.15 → direct_cli-0.4.0}/direct_cli/commands/retargeting.py +0 -0
  123. {direct_cli-0.3.15 → direct_cli-0.4.0}/direct_cli/commands/sitelinks.py +0 -0
  124. {direct_cli-0.3.15 → direct_cli-0.4.0}/direct_cli/commands/smartadtargets.py +0 -0
  125. {direct_cli-0.3.15 → direct_cli-0.4.0}/direct_cli/commands/strategies.py +0 -0
  126. {direct_cli-0.3.15 → direct_cli-0.4.0}/direct_cli/commands/turbopages.py +0 -0
  127. {direct_cli-0.3.15 → direct_cli-0.4.0}/direct_cli/commands/v4account.py +0 -0
  128. {direct_cli-0.3.15 → direct_cli-0.4.0}/direct_cli/commands/v4goals.py +0 -0
  129. {direct_cli-0.3.15 → direct_cli-0.4.0}/direct_cli/commands/v4shells.py +0 -0
  130. {direct_cli-0.3.15 → direct_cli-0.4.0}/direct_cli/commands/v4tags.py +0 -0
  131. {direct_cli-0.3.15 → direct_cli-0.4.0}/direct_cli/commands/v4wordstat.py +0 -0
  132. {direct_cli-0.3.15 → direct_cli-0.4.0}/direct_cli/commands/vcards.py +0 -0
  133. {direct_cli-0.3.15 → direct_cli-0.4.0}/direct_cli/output.py +0 -0
  134. {direct_cli-0.3.15 → direct_cli-0.4.0}/direct_cli/reports_coverage.py +0 -0
  135. {direct_cli-0.3.15 → direct_cli-0.4.0}/direct_cli/smoke_matrix.py +0 -0
  136. {direct_cli-0.3.15 → direct_cli-0.4.0}/direct_cli/utils.py +0 -0
  137. {direct_cli-0.3.15 → direct_cli-0.4.0}/direct_cli/v4/money.py +0 -0
  138. {direct_cli-0.3.15 → direct_cli-0.4.0}/direct_cli/wsdl_coverage.py +0 -0
  139. {direct_cli-0.3.15 → direct_cli-0.4.0}/direct_cli.egg-info/dependency_links.txt +0 -0
  140. {direct_cli-0.3.15 → direct_cli-0.4.0}/direct_cli.egg-info/entry_points.txt +0 -0
  141. {direct_cli-0.3.15 → direct_cli-0.4.0}/direct_cli.egg-info/requires.txt +0 -0
  142. {direct_cli-0.3.15 → direct_cli-0.4.0}/direct_cli.egg-info/top_level.txt +0 -0
  143. {direct_cli-0.3.15 → direct_cli-0.4.0}/docs/audits/issue-198-mutating-wsdl-audit.md +0 -0
  144. {direct_cli-0.3.15 → direct_cli-0.4.0}/docs/superpowers/plans/2026-04-12-issue-32-completion.md +0 -0
  145. {direct_cli-0.3.15 → direct_cli-0.4.0}/docs/superpowers/specs/2026-04-23-vendor-tapi-yandex-direct-design.md +0 -0
  146. {direct_cli-0.3.15 → direct_cli-0.4.0}/scripts/anonymize_cassettes.py +0 -0
  147. {direct_cli-0.3.15 → direct_cli-0.4.0}/scripts/build_api_coverage_checklist.py +0 -0
  148. {direct_cli-0.3.15 → direct_cli-0.4.0}/scripts/build_api_coverage_report.py +0 -0
  149. {direct_cli-0.3.15 → direct_cli-0.4.0}/scripts/build_wsdl_optional_field_audit.py +0 -0
  150. {direct_cli-0.3.15 → direct_cli-0.4.0}/scripts/check_all_docs_urls.py +0 -0
  151. {direct_cli-0.3.15 → direct_cli-0.4.0}/scripts/check_reports_drift.py +0 -0
  152. {direct_cli-0.3.15 → direct_cli-0.4.0}/scripts/check_wsdl_drift.py +0 -0
  153. {direct_cli-0.3.15 → direct_cli-0.4.0}/scripts/patch_vendor_imports.py +0 -0
  154. {direct_cli-0.3.15 → direct_cli-0.4.0}/scripts/preflight_check.sh +0 -0
  155. {direct_cli-0.3.15 → direct_cli-0.4.0}/scripts/refresh_reports_cache.py +0 -0
  156. {direct_cli-0.3.15 → direct_cli-0.4.0}/scripts/refresh_wsdl_cache.py +0 -0
  157. {direct_cli-0.3.15 → direct_cli-0.4.0}/scripts/release_pypi.sh +0 -0
  158. {direct_cli-0.3.15 → direct_cli-0.4.0}/scripts/sandbox_write_audit.py +0 -0
  159. {direct_cli-0.3.15 → direct_cli-0.4.0}/scripts/sandbox_write_live.py +0 -0
  160. {direct_cli-0.3.15 → direct_cli-0.4.0}/scripts/test_safe_commands.sh +0 -0
  161. {direct_cli-0.3.15 → direct_cli-0.4.0}/scripts/test_sandbox_write.sh +0 -0
  162. {direct_cli-0.3.15 → direct_cli-0.4.0}/scripts/update_vendor.sh +0 -0
  163. {direct_cli-0.3.15 → direct_cli-0.4.0}/setup.cfg +0 -0
  164. {direct_cli-0.3.15 → direct_cli-0.4.0}/setup.py +0 -0
  165. {direct_cli-0.3.15 → direct_cli-0.4.0}/tests/API_ISSUE_AUDIT.md +0 -0
  166. {direct_cli-0.3.15 → direct_cli-0.4.0}/tests/MANUAL_COVERAGE.md +0 -0
  167. {direct_cli-0.3.15 → direct_cli-0.4.0}/tests/WSDL_OPTIONAL_FIELD_AUDIT.md +0 -0
  168. {direct_cli-0.3.15 → direct_cli-0.4.0}/tests/__init__.py +0 -0
  169. {direct_cli-0.3.15 → direct_cli-0.4.0}/tests/_orphan_store.py +0 -0
  170. {direct_cli-0.3.15 → direct_cli-0.4.0}/tests/api_coverage_payloads.py +0 -0
  171. {direct_cli-0.3.15 → direct_cli-0.4.0}/tests/cassettes/test_integration_write/TestWriteAdExtensions.test_add_delete.yaml +0 -0
  172. {direct_cli-0.3.15 → direct_cli-0.4.0}/tests/cassettes/test_integration_write/TestWriteAdGroups.test_add_update_delete.yaml +0 -0
  173. {direct_cli-0.3.15 → direct_cli-0.4.0}/tests/cassettes/test_integration_write/TestWriteAdImages.test_add_delete.yaml +0 -0
  174. {direct_cli-0.3.15 → direct_cli-0.4.0}/tests/cassettes/test_integration_write/TestWriteAds.test_add_text_ad_update_delete.yaml +0 -0
  175. {direct_cli-0.3.15 → direct_cli-0.4.0}/tests/cassettes/test_integration_write/TestWriteAudienceTargets.test_add_delete.yaml +0 -0
  176. {direct_cli-0.3.15 → direct_cli-0.4.0}/tests/cassettes/test_integration_write/TestWriteBidModifiersAdd.test_add_delete_mobile.yaml +0 -0
  177. {direct_cli-0.3.15 → direct_cli-0.4.0}/tests/cassettes/test_integration_write/TestWriteBidModifiersSet.test_set_without_id_is_rejected.yaml +0 -0
  178. {direct_cli-0.3.15 → direct_cli-0.4.0}/tests/cassettes/test_integration_write/TestWriteBids.test_set_bid.yaml +0 -0
  179. {direct_cli-0.3.15 → direct_cli-0.4.0}/tests/cassettes/test_integration_write/TestWriteBidsRead.test_bids_get.yaml +0 -0
  180. {direct_cli-0.3.15 → direct_cli-0.4.0}/tests/cassettes/test_integration_write/TestWriteBidsRead.test_bids_set_auto.yaml +0 -0
  181. {direct_cli-0.3.15 → direct_cli-0.4.0}/tests/cassettes/test_integration_write/TestWriteCampaignDraftLifecycle.test_draft_create_get_delete.yaml +0 -0
  182. {direct_cli-0.3.15 → direct_cli-0.4.0}/tests/cassettes/test_integration_write/TestWriteCampaigns.test_campaign_lifecycle.yaml +0 -0
  183. {direct_cli-0.3.15 → direct_cli-0.4.0}/tests/cassettes/test_integration_write/TestWriteDynamicAds.test_add_update_delete.yaml +0 -0
  184. {direct_cli-0.3.15 → direct_cli-0.4.0}/tests/cassettes/test_integration_write/TestWriteFeeds.test_add_update_delete.yaml +0 -0
  185. {direct_cli-0.3.15 → direct_cli-0.4.0}/tests/cassettes/test_integration_write/TestWriteKeywordBids.test_set_keyword_bid.yaml +0 -0
  186. {direct_cli-0.3.15 → direct_cli-0.4.0}/tests/cassettes/test_integration_write/TestWriteKeywords.test_add_update_delete.yaml +0 -0
  187. {direct_cli-0.3.15 → direct_cli-0.4.0}/tests/cassettes/test_integration_write/TestWriteNegativeKeywordSharedSets.test_add_update_delete.yaml +0 -0
  188. {direct_cli-0.3.15 → direct_cli-0.4.0}/tests/cassettes/test_integration_write/TestWriteRetargeting.test_add_delete.yaml +0 -0
  189. {direct_cli-0.3.15 → direct_cli-0.4.0}/tests/cassettes/test_integration_write/TestWriteRetargetingUpdate.test_retargeting_update.yaml +0 -0
  190. {direct_cli-0.3.15 → direct_cli-0.4.0}/tests/cassettes/test_integration_write/TestWriteSitelinks.test_add_delete.yaml +0 -0
  191. {direct_cli-0.3.15 → direct_cli-0.4.0}/tests/cassettes/test_integration_write/TestWriteSmartAdTargets.test_add_update_delete.yaml +0 -0
  192. {direct_cli-0.3.15 → direct_cli-0.4.0}/tests/cassettes/test_integration_write/TestWriteStrategies.test_strategies_lifecycle.yaml +0 -0
  193. {direct_cli-0.3.15 → direct_cli-0.4.0}/tests/cassettes/test_integration_write/TestWriteVCards.test_add_delete.yaml +0 -0
  194. {direct_cli-0.3.15 → direct_cli-0.4.0}/tests/cassettes/test_v5_live_write/test_v5_live_draft_adgroups_add_update_delete.yaml +0 -0
  195. {direct_cli-0.3.15 → direct_cli-0.4.0}/tests/cassettes/test_v5_live_write/test_v5_live_draft_adimages_add_get_delete.yaml +0 -0
  196. {direct_cli-0.3.15 → direct_cli-0.4.0}/tests/cassettes/test_v5_live_write/test_v5_live_draft_ads_add_update_delete.yaml +0 -0
  197. {direct_cli-0.3.15 → direct_cli-0.4.0}/tests/cassettes/test_v5_live_write/test_v5_live_draft_ads_suspend_resume_archive_unarchive.yaml +0 -0
  198. {direct_cli-0.3.15 → direct_cli-0.4.0}/tests/cassettes/test_v5_live_write/test_v5_live_draft_advideos_add_get.yaml +0 -0
  199. {direct_cli-0.3.15 → direct_cli-0.4.0}/tests/cassettes/test_v5_live_write/test_v5_live_draft_audiencetargets_add_delete.yaml +0 -0
  200. {direct_cli-0.3.15 → direct_cli-0.4.0}/tests/cassettes/test_v5_live_write/test_v5_live_draft_audiencetargets_suspend_resume.yaml +0 -0
  201. {direct_cli-0.3.15 → direct_cli-0.4.0}/tests/cassettes/test_v5_live_write/test_v5_live_draft_bids_set.yaml +0 -0
  202. {direct_cli-0.3.15 → direct_cli-0.4.0}/tests/cassettes/test_v5_live_write/test_v5_live_draft_campaign_create_get_delete.yaml +0 -0
  203. {direct_cli-0.3.15 → direct_cli-0.4.0}/tests/cassettes/test_v5_live_write/test_v5_live_draft_creatives_chain_advideo_to_creative.yaml +0 -0
  204. {direct_cli-0.3.15 → direct_cli-0.4.0}/tests/cassettes/test_v5_live_write/test_v5_live_draft_dynamicads_add_delete.yaml +0 -0
  205. {direct_cli-0.3.15 → direct_cli-0.4.0}/tests/cassettes/test_v5_live_write/test_v5_live_draft_dynamicads_suspend_resume.yaml +0 -0
  206. {direct_cli-0.3.15 → direct_cli-0.4.0}/tests/cassettes/test_v5_live_write/test_v5_live_draft_keywordbids_set.yaml +0 -0
  207. {direct_cli-0.3.15 → direct_cli-0.4.0}/tests/cassettes/test_v5_live_write/test_v5_live_draft_keywords_add_update_delete.yaml +0 -0
  208. {direct_cli-0.3.15 → direct_cli-0.4.0}/tests/cassettes/test_v5_live_write/test_v5_live_draft_keywords_suspend_resume.yaml +0 -0
  209. {direct_cli-0.3.15 → direct_cli-0.4.0}/tests/cassettes/test_v5_live_write/test_v5_live_draft_sitelinks_add_get_delete.yaml +0 -0
  210. {direct_cli-0.3.15 → direct_cli-0.4.0}/tests/cassettes/test_v5_live_write/test_v5_live_draft_smartadtargets_add_update_delete.yaml +0 -0
  211. {direct_cli-0.3.15 → direct_cli-0.4.0}/tests/cassettes/test_v5_live_write/test_v5_live_draft_smartadtargets_suspend_resume.yaml +0 -0
  212. {direct_cli-0.3.15 → direct_cli-0.4.0}/tests/fixtures/test-video.mp4 +0 -0
  213. {direct_cli-0.3.15 → direct_cli-0.4.0}/tests/reports_cache/raw/fields-list.html +0 -0
  214. {direct_cli-0.3.15 → direct_cli-0.4.0}/tests/reports_cache/raw/headers.html +0 -0
  215. {direct_cli-0.3.15 → direct_cli-0.4.0}/tests/reports_cache/raw/period.html +0 -0
  216. {direct_cli-0.3.15 → direct_cli-0.4.0}/tests/reports_cache/raw/spec.html +0 -0
  217. {direct_cli-0.3.15 → direct_cli-0.4.0}/tests/reports_cache/raw/type.html +0 -0
  218. {direct_cli-0.3.15 → direct_cli-0.4.0}/tests/reports_cache/spec.json +0 -0
  219. {direct_cli-0.3.15 → direct_cli-0.4.0}/tests/test_api_coverage.py +0 -0
  220. {direct_cli-0.3.15 → direct_cli-0.4.0}/tests/test_auth_bw.py +0 -0
  221. {direct_cli-0.3.15 → direct_cli-0.4.0}/tests/test_auth_oauth.py +0 -0
  222. {direct_cli-0.3.15 → direct_cli-0.4.0}/tests/test_auth_op.py +0 -0
  223. {direct_cli-0.3.15 → direct_cli-0.4.0}/tests/test_auth_write_json.py +0 -0
  224. {direct_cli-0.3.15 → direct_cli-0.4.0}/tests/test_balance.py +0 -0
  225. {direct_cli-0.3.15 → direct_cli-0.4.0}/tests/test_changes.py +0 -0
  226. {direct_cli-0.3.15 → direct_cli-0.4.0}/tests/test_cli.py +0 -0
  227. {direct_cli-0.3.15 → direct_cli-0.4.0}/tests/test_cli_contract.py +0 -0
  228. {direct_cli-0.3.15 → direct_cli-0.4.0}/tests/test_comprehensive.py +0 -0
  229. {direct_cli-0.3.15 → direct_cli-0.4.0}/tests/test_dry_run.py +0 -0
  230. {direct_cli-0.3.15 → direct_cli-0.4.0}/tests/test_env_loading.py +0 -0
  231. {direct_cli-0.3.15 → direct_cli-0.4.0}/tests/test_integration.py +0 -0
  232. {direct_cli-0.3.15 → direct_cli-0.4.0}/tests/test_integration_write.py +0 -0
  233. {direct_cli-0.3.15 → direct_cli-0.4.0}/tests/test_low_coverage_payloads.py +0 -0
  234. {direct_cli-0.3.15 → direct_cli-0.4.0}/tests/test_reports_drift.py +0 -0
  235. {direct_cli-0.3.15 → direct_cli-0.4.0}/tests/test_reports_parsing.py +0 -0
  236. {direct_cli-0.3.15 → direct_cli-0.4.0}/tests/test_sandbox_write_audit.py +0 -0
  237. {direct_cli-0.3.15 → direct_cli-0.4.0}/tests/test_smoke_matrix.py +0 -0
  238. {direct_cli-0.3.15 → direct_cli-0.4.0}/tests/test_transport_contract.py +0 -0
  239. {direct_cli-0.3.15 → direct_cli-0.4.0}/tests/test_unknown_option_hints.py +0 -0
  240. {direct_cli-0.3.15 → direct_cli-0.4.0}/tests/test_v4_exit_codes.py +0 -0
  241. {direct_cli-0.3.15 → direct_cli-0.4.0}/tests/test_v4_foundation.py +0 -0
  242. {direct_cli-0.3.15 → direct_cli-0.4.0}/tests/test_v4_live_contracts.py +0 -0
  243. {direct_cli-0.3.15 → direct_cli-0.4.0}/tests/test_v4_safety.py +0 -0
  244. {direct_cli-0.3.15 → direct_cli-0.4.0}/tests/test_v4account.py +0 -0
  245. {direct_cli-0.3.15 → direct_cli-0.4.0}/tests/test_v4finance_read.py +0 -0
  246. {direct_cli-0.3.15 → direct_cli-0.4.0}/tests/test_v4goals.py +0 -0
  247. {direct_cli-0.3.15 → direct_cli-0.4.0}/tests/test_v4tags.py +0 -0
  248. {direct_cli-0.3.15 → direct_cli-0.4.0}/tests/test_v4wordstat.py +0 -0
  249. {direct_cli-0.3.15 → direct_cli-0.4.0}/tests/test_v5_live_write.py +0 -0
  250. {direct_cli-0.3.15 → direct_cli-0.4.0}/tests/test_vendor_imports.py +0 -0
  251. {direct_cli-0.3.15 → direct_cli-0.4.0}/tests/test_wsdl_parity_gate.py +0 -0
  252. {direct_cli-0.3.15 → direct_cli-0.4.0}/tests/wsdl_cache/adextensions.xml +0 -0
  253. {direct_cli-0.3.15 → direct_cli-0.4.0}/tests/wsdl_cache/adgroups.xml +0 -0
  254. {direct_cli-0.3.15 → direct_cli-0.4.0}/tests/wsdl_cache/adimages.xml +0 -0
  255. {direct_cli-0.3.15 → direct_cli-0.4.0}/tests/wsdl_cache/ads.xml +0 -0
  256. {direct_cli-0.3.15 → direct_cli-0.4.0}/tests/wsdl_cache/advideos.xml +0 -0
  257. {direct_cli-0.3.15 → direct_cli-0.4.0}/tests/wsdl_cache/agencyclients.xml +0 -0
  258. {direct_cli-0.3.15 → direct_cli-0.4.0}/tests/wsdl_cache/audiencetargets.xml +0 -0
  259. {direct_cli-0.3.15 → direct_cli-0.4.0}/tests/wsdl_cache/bidmodifiers.xml +0 -0
  260. {direct_cli-0.3.15 → direct_cli-0.4.0}/tests/wsdl_cache/bids.xml +0 -0
  261. {direct_cli-0.3.15 → direct_cli-0.4.0}/tests/wsdl_cache/businesses.xml +0 -0
  262. {direct_cli-0.3.15 → direct_cli-0.4.0}/tests/wsdl_cache/campaigns.xml +0 -0
  263. {direct_cli-0.3.15 → direct_cli-0.4.0}/tests/wsdl_cache/changes.xml +0 -0
  264. {direct_cli-0.3.15 → direct_cli-0.4.0}/tests/wsdl_cache/clients.xml +0 -0
  265. {direct_cli-0.3.15 → direct_cli-0.4.0}/tests/wsdl_cache/creatives.xml +0 -0
  266. {direct_cli-0.3.15 → direct_cli-0.4.0}/tests/wsdl_cache/dictionaries.xml +0 -0
  267. {direct_cli-0.3.15 → direct_cli-0.4.0}/tests/wsdl_cache/dynamicfeedadtargets.xml +0 -0
  268. {direct_cli-0.3.15 → direct_cli-0.4.0}/tests/wsdl_cache/dynamictextadtargets.xml +0 -0
  269. {direct_cli-0.3.15 → direct_cli-0.4.0}/tests/wsdl_cache/feeds.xml +0 -0
  270. {direct_cli-0.3.15 → direct_cli-0.4.0}/tests/wsdl_cache/imports/adextensiontypes.xsd +0 -0
  271. {direct_cli-0.3.15 → direct_cli-0.4.0}/tests/wsdl_cache/imports/general.xsd +0 -0
  272. {direct_cli-0.3.15 → direct_cli-0.4.0}/tests/wsdl_cache/imports/generalclients.xsd +0 -0
  273. {direct_cli-0.3.15 → direct_cli-0.4.0}/tests/wsdl_cache/keywordbids.xml +0 -0
  274. {direct_cli-0.3.15 → direct_cli-0.4.0}/tests/wsdl_cache/keywords.xml +0 -0
  275. {direct_cli-0.3.15 → direct_cli-0.4.0}/tests/wsdl_cache/keywordsresearch.xml +0 -0
  276. {direct_cli-0.3.15 → direct_cli-0.4.0}/tests/wsdl_cache/leads.xml +0 -0
  277. {direct_cli-0.3.15 → direct_cli-0.4.0}/tests/wsdl_cache/negativekeywordsharedsets.xml +0 -0
  278. {direct_cli-0.3.15 → direct_cli-0.4.0}/tests/wsdl_cache/retargetinglists.xml +0 -0
  279. {direct_cli-0.3.15 → direct_cli-0.4.0}/tests/wsdl_cache/sitelinks.xml +0 -0
  280. {direct_cli-0.3.15 → direct_cli-0.4.0}/tests/wsdl_cache/smartadtargets.xml +0 -0
  281. {direct_cli-0.3.15 → direct_cli-0.4.0}/tests/wsdl_cache/strategies.xml +0 -0
  282. {direct_cli-0.3.15 → direct_cli-0.4.0}/tests/wsdl_cache/turbopages.xml +0 -0
  283. {direct_cli-0.3.15 → direct_cli-0.4.0}/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,75 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.4.0
4
+
5
+ Milestone release closing the 0.4.0 roadmap (#123): typed Yandex Direct
6
+ API **v4 Live** CLI support and completion of the post-0.3.0 write-command
7
+ coverage gates. All public v4 input stays typed and canonical — no `--json`
8
+ passthrough — and mirrors `dg-v4/live/*` wire shapes 1:1.
9
+
10
+ **Added — typed v4 Live CLI:**
11
+
12
+ - v4 Live command foundation with typed Click groups and a `--dry-run`
13
+ seam that prints the `{method, param}` body before token/locale
14
+ enrichment (#124, closes #111 — typed CLI, not raw JSON passthrough).
15
+ - `v4finance` and `v4account` typed finance and shared-account commands
16
+ (#125).
17
+ - `v4goals`, `v4events`, `v4wordstat`, and `v4forecast` typed commands
18
+ (#126).
19
+ - `v4events get-events-log` and `v4forecast create-new-forecast` now expose
20
+ every documented input field (#456).
21
+ - Russian-default CLI help with English opt-in, starting with `v4finance`
22
+ (#458).
23
+
24
+ **Changed — write-command coverage gates:**
25
+
26
+ - Extended the WSDL schema gate to mutating operations; `keywordbids.set`
27
+ is now enum-validated against its WSDL `*FieldEnum` (#118).
28
+ - Per-method `WRITE_SANDBOX` integration coverage completed — zero
29
+ unexplained `NOT_COVERED` commands in `direct_cli/smoke_matrix.py`
30
+ (#122).
31
+ - Closed the remaining mutating `DRY_RUN_PAYLOAD_EXCLUSIONS`; every
32
+ declared WSDL operation now has a `PAYLOAD_CASES` fixture or a
33
+ documented technical exclusion (#127).
34
+
35
+ **Tests / tooling:**
36
+
37
+ - Offline VCR cassettes for all v5 read-only commands (#455).
38
+ - v4 Live read cassettes and a fix for an unbounded retry-loop (#457,
39
+ closes #454).
40
+ - Docs/wire-shape scanner with the 2026-05-29 sweep (#451).
41
+
42
+ `strict_parity_ok`, `live_model_parity_ok`, and `schema_parity_ok` all
43
+ report `true` in `scripts/build_api_coverage_report.py`.
44
+
45
+ ## 0.3.16
46
+
47
+ **BREAKING CHANGES (regression fix — reverts 0.3.15 wire-shape changes):**
48
+
49
+ - `direct v4finance transfer-money` now requires `--currency` again and
50
+ re-emits `Currency` on every `FromCampaigns` / `ToCampaigns` item.
51
+ The 0.3.15 removal verified against `dg-v4/reference/TransferMoney`
52
+ (legacy v4); the actual Live 4 docs at
53
+ `dg-v4/live/TransferMoney` define `PayCampElement` with
54
+ `CampaignID`, `Sum`, and `Currency`, and explicitly mark `Currency` as
55
+ obligatory in the Live 4 changelog. The CLI now matches the live
56
+ docs 1:1. See audit comment on #125 for the reproducible diff.
57
+ - `direct v4finance pay-campaigns` now requires `--currency` again and
58
+ re-emits `Currency` on every `Payments[]` item. Same root cause:
59
+ `dg-v4/reference/PayCampaigns` (legacy) lacks `Currency`,
60
+ `dg-v4/live/PayCampaigns` (Live 4) requires it.
61
+ - `direct v4finance pay-campaigns` accepts `--pay-method Overdraft`
62
+ again. The Live 4 changelog explicitly adds `Overdraft` for direct
63
+ advertisers (paired with `Bank` for agencies). Only `Bank` keeps the
64
+ `--contract-id` requirement.
65
+ - `direct v4finance create-invoice` now requires `--currency` again and
66
+ re-emits `Currency` on every `Payments[]` item, mirroring
67
+ `dg-v4/live/CreateInvoice`.
68
+
69
+ This release reverts the wire-shape changes shipped by PRs #441, #442,
70
+ #443 (which closed #432, #433, #434). The CLI lives in the `v4finance`
71
+ Live group and must mirror `dg-v4/live/*`, not `dg-v4/reference/*`.
72
+
3
73
  ## 0.3.15
4
74
 
5
75
  **BREAKING CHANGES:**
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: direct-cli
3
- Version: 0.3.15
3
+ Version: 0.4.0
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
@@ -232,10 +240,10 @@ is omitted. Dry-run output masks the financial token.
232
240
  ```bash
233
241
  direct v4finance get-clients-units --logins client-login,other-client --format table
234
242
  direct v4finance get-credit-limits --master-token MASTER_TOKEN --operation-num 123 --finance-login agency-login
235
- direct v4finance create-invoice --payment 123=100.50 --payment 456=25 --master-token MASTER_TOKEN --operation-num 124 --finance-login agency-login --dry-run
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
- direct v4finance transfer-money --from-campaign-id 123 --to-campaign-id 456 --amount 100.50 --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 --contract-id CONTRACT_ID --pay-method Bank --master-token MASTER_TOKEN --operation-num 123 --finance-login agency-login --dry-run
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
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
@@ -189,10 +197,10 @@ is omitted. Dry-run output masks the financial token.
189
197
  ```bash
190
198
  direct v4finance get-clients-units --logins client-login,other-client --format table
191
199
  direct v4finance get-credit-limits --master-token MASTER_TOKEN --operation-num 123 --finance-login agency-login
192
- direct v4finance create-invoice --payment 123=100.50 --payment 456=25 --master-token MASTER_TOKEN --operation-num 124 --finance-login agency-login --dry-run
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
- direct v4finance transfer-money --from-campaign-id 123 --to-campaign-id 456 --amount 100.50 --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 --contract-id CONTRACT_ID --pay-method Bank --master-token MASTER_TOKEN --operation-num 123 --finance-login agency-login --dry-run
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
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()