direct-cli 0.3.5__tar.gz → 0.3.7__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 (216) hide show
  1. {direct_cli-0.3.5 → direct_cli-0.3.7}/PKG-INFO +44 -13
  2. {direct_cli-0.3.5 → direct_cli-0.3.7}/README.md +43 -12
  3. {direct_cli-0.3.5 → direct_cli-0.3.7}/direct_cli/cli.py +4 -1
  4. {direct_cli-0.3.5 → direct_cli-0.3.7}/direct_cli/commands/auth.py +13 -2
  5. {direct_cli-0.3.5 → direct_cli-0.3.7}/direct_cli/commands/campaigns.py +106 -1
  6. direct_cli-0.3.7/direct_cli/commands/v4forecast.py +168 -0
  7. {direct_cli-0.3.5 → direct_cli-0.3.7}/direct_cli/commands/v4shells.py +0 -5
  8. direct_cli-0.3.7/direct_cli/commands/v4tags.py +263 -0
  9. {direct_cli-0.3.5 → direct_cli-0.3.7}/direct_cli/smoke_matrix.py +8 -0
  10. {direct_cli-0.3.5 → direct_cli-0.3.7}/direct_cli/v4_contracts.py +86 -24
  11. {direct_cli-0.3.5 → direct_cli-0.3.7}/direct_cli.egg-info/PKG-INFO +44 -13
  12. {direct_cli-0.3.5 → direct_cli-0.3.7}/direct_cli.egg-info/SOURCES.txt +4 -1
  13. {direct_cli-0.3.5 → direct_cli-0.3.7}/pyproject.toml +1 -1
  14. {direct_cli-0.3.5 → direct_cli-0.3.7}/scripts/sandbox_write_live.py +60 -3
  15. {direct_cli-0.3.5 → direct_cli-0.3.7}/scripts/test_safe_commands.sh +12 -0
  16. {direct_cli-0.3.5 → direct_cli-0.3.7}/tests/API_COVERAGE.md +3 -3
  17. {direct_cli-0.3.5 → direct_cli-0.3.7}/tests/api_coverage_payloads.py +8 -0
  18. {direct_cli-0.3.5 → direct_cli-0.3.7}/tests/test_auth_oauth.py +97 -7
  19. {direct_cli-0.3.5 → direct_cli-0.3.7}/tests/test_cli_contract.py +1 -0
  20. {direct_cli-0.3.5 → direct_cli-0.3.7}/tests/test_comprehensive.py +1 -0
  21. {direct_cli-0.3.5 → direct_cli-0.3.7}/tests/test_dry_run.py +124 -0
  22. {direct_cli-0.3.5 → direct_cli-0.3.7}/tests/test_smoke_matrix.py +75 -4
  23. {direct_cli-0.3.5 → direct_cli-0.3.7}/tests/test_v4_contracts.py +86 -0
  24. {direct_cli-0.3.5 → direct_cli-0.3.7}/tests/test_v4_foundation.py +2 -0
  25. {direct_cli-0.3.5 → direct_cli-0.3.7}/tests/test_v4_safety.py +14 -0
  26. direct_cli-0.3.7/tests/test_v4forecast.py +238 -0
  27. direct_cli-0.3.7/tests/test_v4tags.py +321 -0
  28. direct_cli-0.3.5/.github/workflows/claude-code-review.yml +0 -44
  29. {direct_cli-0.3.5 → direct_cli-0.3.7}/.env.example +0 -0
  30. {direct_cli-0.3.5 → direct_cli-0.3.7}/.github/copilot-instructions.md +0 -0
  31. {direct_cli-0.3.5 → direct_cli-0.3.7}/.github/workflows/api-coverage.yml +0 -0
  32. {direct_cli-0.3.5 → direct_cli-0.3.7}/.github/workflows/claude.yml +0 -0
  33. {direct_cli-0.3.5 → direct_cli-0.3.7}/.github/workflows/quality.yml +0 -0
  34. {direct_cli-0.3.5 → direct_cli-0.3.7}/.gitignore +0 -0
  35. {direct_cli-0.3.5 → direct_cli-0.3.7}/AGENTS.md +0 -0
  36. {direct_cli-0.3.5 → direct_cli-0.3.7}/CHANGELOG.md +0 -0
  37. {direct_cli-0.3.5 → direct_cli-0.3.7}/CLAUDE.md +0 -0
  38. {direct_cli-0.3.5 → direct_cli-0.3.7}/MANIFEST.in +0 -0
  39. {direct_cli-0.3.5 → direct_cli-0.3.7}/direct_cli/__init__.py +0 -0
  40. {direct_cli-0.3.5 → direct_cli-0.3.7}/direct_cli/_deprecated.py +0 -0
  41. {direct_cli-0.3.5 → direct_cli-0.3.7}/direct_cli/_smoke_probes.py +0 -0
  42. {direct_cli-0.3.5 → direct_cli-0.3.7}/direct_cli/_vendor/__init__.py +0 -0
  43. {direct_cli-0.3.5 → direct_cli-0.3.7}/direct_cli/_vendor/tapi_yandex_direct/__init__.py +0 -0
  44. {direct_cli-0.3.5 → direct_cli-0.3.7}/direct_cli/_vendor/tapi_yandex_direct/endpoints.py +0 -0
  45. {direct_cli-0.3.5 → direct_cli-0.3.7}/direct_cli/_vendor/tapi_yandex_direct/exceptions.py +0 -0
  46. {direct_cli-0.3.5 → direct_cli-0.3.7}/direct_cli/_vendor/tapi_yandex_direct/resource_mapping.py +0 -0
  47. {direct_cli-0.3.5 → direct_cli-0.3.7}/direct_cli/_vendor/tapi_yandex_direct/tapi_yandex_direct.py +0 -0
  48. {direct_cli-0.3.5 → direct_cli-0.3.7}/direct_cli/_vendor/tapi_yandex_direct/tapi_yandex_direct.pyi +0 -0
  49. {direct_cli-0.3.5 → direct_cli-0.3.7}/direct_cli/_vendor/tapi_yandex_direct/v4/__init__.py +0 -0
  50. {direct_cli-0.3.5 → direct_cli-0.3.7}/direct_cli/_vendor/tapi_yandex_direct/v4/adapter.py +0 -0
  51. {direct_cli-0.3.5 → direct_cli-0.3.7}/direct_cli/_vendor/tapi_yandex_direct/v4/adapter.pyi +0 -0
  52. {direct_cli-0.3.5 → direct_cli-0.3.7}/direct_cli/_vendor/tapi_yandex_direct/v4/resource_mapping.py +0 -0
  53. {direct_cli-0.3.5 → direct_cli-0.3.7}/direct_cli/api.py +0 -0
  54. {direct_cli-0.3.5 → direct_cli-0.3.7}/direct_cli/auth.py +0 -0
  55. {direct_cli-0.3.5 → direct_cli-0.3.7}/direct_cli/commands/__init__.py +0 -0
  56. {direct_cli-0.3.5 → direct_cli-0.3.7}/direct_cli/commands/adextensions.py +0 -0
  57. {direct_cli-0.3.5 → direct_cli-0.3.7}/direct_cli/commands/adgroups.py +0 -0
  58. {direct_cli-0.3.5 → direct_cli-0.3.7}/direct_cli/commands/adimages.py +0 -0
  59. {direct_cli-0.3.5 → direct_cli-0.3.7}/direct_cli/commands/ads.py +0 -0
  60. {direct_cli-0.3.5 → direct_cli-0.3.7}/direct_cli/commands/advideos.py +0 -0
  61. {direct_cli-0.3.5 → direct_cli-0.3.7}/direct_cli/commands/agencyclients.py +0 -0
  62. {direct_cli-0.3.5 → direct_cli-0.3.7}/direct_cli/commands/audiencetargets.py +0 -0
  63. {direct_cli-0.3.5 → direct_cli-0.3.7}/direct_cli/commands/balance.py +0 -0
  64. {direct_cli-0.3.5 → direct_cli-0.3.7}/direct_cli/commands/bidmodifiers.py +0 -0
  65. {direct_cli-0.3.5 → direct_cli-0.3.7}/direct_cli/commands/bids.py +0 -0
  66. {direct_cli-0.3.5 → direct_cli-0.3.7}/direct_cli/commands/businesses.py +0 -0
  67. {direct_cli-0.3.5 → direct_cli-0.3.7}/direct_cli/commands/changes.py +0 -0
  68. {direct_cli-0.3.5 → direct_cli-0.3.7}/direct_cli/commands/clients.py +0 -0
  69. {direct_cli-0.3.5 → direct_cli-0.3.7}/direct_cli/commands/creatives.py +0 -0
  70. {direct_cli-0.3.5 → direct_cli-0.3.7}/direct_cli/commands/dictionaries.py +0 -0
  71. {direct_cli-0.3.5 → direct_cli-0.3.7}/direct_cli/commands/dynamicads.py +0 -0
  72. {direct_cli-0.3.5 → direct_cli-0.3.7}/direct_cli/commands/dynamicfeedadtargets.py +0 -0
  73. {direct_cli-0.3.5 → direct_cli-0.3.7}/direct_cli/commands/feeds.py +0 -0
  74. {direct_cli-0.3.5 → direct_cli-0.3.7}/direct_cli/commands/keywordbids.py +0 -0
  75. {direct_cli-0.3.5 → direct_cli-0.3.7}/direct_cli/commands/keywords.py +0 -0
  76. {direct_cli-0.3.5 → direct_cli-0.3.7}/direct_cli/commands/keywordsresearch.py +0 -0
  77. {direct_cli-0.3.5 → direct_cli-0.3.7}/direct_cli/commands/leads.py +0 -0
  78. {direct_cli-0.3.5 → direct_cli-0.3.7}/direct_cli/commands/negativekeywordsharedsets.py +0 -0
  79. {direct_cli-0.3.5 → direct_cli-0.3.7}/direct_cli/commands/reports.py +0 -0
  80. {direct_cli-0.3.5 → direct_cli-0.3.7}/direct_cli/commands/retargeting.py +0 -0
  81. {direct_cli-0.3.5 → direct_cli-0.3.7}/direct_cli/commands/sitelinks.py +0 -0
  82. {direct_cli-0.3.5 → direct_cli-0.3.7}/direct_cli/commands/smartadtargets.py +0 -0
  83. {direct_cli-0.3.5 → direct_cli-0.3.7}/direct_cli/commands/strategies.py +0 -0
  84. {direct_cli-0.3.5 → direct_cli-0.3.7}/direct_cli/commands/turbopages.py +0 -0
  85. {direct_cli-0.3.5 → direct_cli-0.3.7}/direct_cli/commands/v4account.py +0 -0
  86. {direct_cli-0.3.5 → direct_cli-0.3.7}/direct_cli/commands/v4events.py +0 -0
  87. {direct_cli-0.3.5 → direct_cli-0.3.7}/direct_cli/commands/v4finance.py +0 -0
  88. {direct_cli-0.3.5 → direct_cli-0.3.7}/direct_cli/commands/v4goals.py +0 -0
  89. {direct_cli-0.3.5 → direct_cli-0.3.7}/direct_cli/commands/v4wordstat.py +0 -0
  90. {direct_cli-0.3.5 → direct_cli-0.3.7}/direct_cli/commands/vcards.py +0 -0
  91. {direct_cli-0.3.5 → direct_cli-0.3.7}/direct_cli/output.py +0 -0
  92. {direct_cli-0.3.5 → direct_cli-0.3.7}/direct_cli/reports_coverage.py +0 -0
  93. {direct_cli-0.3.5 → direct_cli-0.3.7}/direct_cli/utils.py +0 -0
  94. {direct_cli-0.3.5 → direct_cli-0.3.7}/direct_cli/v4/__init__.py +0 -0
  95. {direct_cli-0.3.5 → direct_cli-0.3.7}/direct_cli/v4/money.py +0 -0
  96. {direct_cli-0.3.5 → direct_cli-0.3.7}/direct_cli/wsdl_coverage.py +0 -0
  97. {direct_cli-0.3.5 → direct_cli-0.3.7}/direct_cli.egg-info/dependency_links.txt +0 -0
  98. {direct_cli-0.3.5 → direct_cli-0.3.7}/direct_cli.egg-info/entry_points.txt +0 -0
  99. {direct_cli-0.3.5 → direct_cli-0.3.7}/direct_cli.egg-info/requires.txt +0 -0
  100. {direct_cli-0.3.5 → direct_cli-0.3.7}/direct_cli.egg-info/top_level.txt +0 -0
  101. {direct_cli-0.3.5 → direct_cli-0.3.7}/docs/superpowers/plans/2026-04-12-issue-32-completion.md +0 -0
  102. {direct_cli-0.3.5 → direct_cli-0.3.7}/docs/superpowers/specs/2026-04-23-vendor-tapi-yandex-direct-design.md +0 -0
  103. {direct_cli-0.3.5 → direct_cli-0.3.7}/scripts/anonymize_cassettes.py +0 -0
  104. {direct_cli-0.3.5 → direct_cli-0.3.7}/scripts/build_api_coverage_checklist.py +0 -0
  105. {direct_cli-0.3.5 → direct_cli-0.3.7}/scripts/build_api_coverage_report.py +0 -0
  106. {direct_cli-0.3.5 → direct_cli-0.3.7}/scripts/check_reports_drift.py +0 -0
  107. {direct_cli-0.3.5 → direct_cli-0.3.7}/scripts/check_wsdl_drift.py +0 -0
  108. {direct_cli-0.3.5 → direct_cli-0.3.7}/scripts/patch_vendor_imports.py +0 -0
  109. {direct_cli-0.3.5 → direct_cli-0.3.7}/scripts/refresh_reports_cache.py +0 -0
  110. {direct_cli-0.3.5 → direct_cli-0.3.7}/scripts/refresh_wsdl_cache.py +0 -0
  111. {direct_cli-0.3.5 → direct_cli-0.3.7}/scripts/release_pypi.sh +0 -0
  112. {direct_cli-0.3.5 → direct_cli-0.3.7}/scripts/test_dangerous_commands.sh +0 -0
  113. {direct_cli-0.3.5 → direct_cli-0.3.7}/scripts/test_sandbox_write.sh +0 -0
  114. {direct_cli-0.3.5 → direct_cli-0.3.7}/scripts/update_vendor.sh +0 -0
  115. {direct_cli-0.3.5 → direct_cli-0.3.7}/setup.cfg +0 -0
  116. {direct_cli-0.3.5 → direct_cli-0.3.7}/setup.py +0 -0
  117. {direct_cli-0.3.5 → direct_cli-0.3.7}/tests/API_ISSUE_AUDIT.md +0 -0
  118. {direct_cli-0.3.5 → direct_cli-0.3.7}/tests/MANUAL_COVERAGE.md +0 -0
  119. {direct_cli-0.3.5 → direct_cli-0.3.7}/tests/__init__.py +0 -0
  120. {direct_cli-0.3.5 → direct_cli-0.3.7}/tests/cassettes/test_integration_live_write/test_live_draft_adgroups_add_update_delete.yaml +0 -0
  121. {direct_cli-0.3.5 → direct_cli-0.3.7}/tests/cassettes/test_integration_live_write/test_live_draft_adimages_add_get_delete.yaml +0 -0
  122. {direct_cli-0.3.5 → direct_cli-0.3.7}/tests/cassettes/test_integration_live_write/test_live_draft_ads_add_update_delete.yaml +0 -0
  123. {direct_cli-0.3.5 → direct_cli-0.3.7}/tests/cassettes/test_integration_live_write/test_live_draft_ads_suspend_resume_archive_unarchive.yaml +0 -0
  124. {direct_cli-0.3.5 → direct_cli-0.3.7}/tests/cassettes/test_integration_live_write/test_live_draft_advideos_add_get.yaml +0 -0
  125. {direct_cli-0.3.5 → direct_cli-0.3.7}/tests/cassettes/test_integration_live_write/test_live_draft_audiencetargets_add_delete.yaml +0 -0
  126. {direct_cli-0.3.5 → direct_cli-0.3.7}/tests/cassettes/test_integration_live_write/test_live_draft_audiencetargets_suspend_resume.yaml +0 -0
  127. {direct_cli-0.3.5 → direct_cli-0.3.7}/tests/cassettes/test_integration_live_write/test_live_draft_bids_set.yaml +0 -0
  128. {direct_cli-0.3.5 → direct_cli-0.3.7}/tests/cassettes/test_integration_live_write/test_live_draft_campaign_create_get_delete.yaml +0 -0
  129. {direct_cli-0.3.5 → direct_cli-0.3.7}/tests/cassettes/test_integration_live_write/test_live_draft_creatives_chain_advideo_to_creative.yaml +0 -0
  130. {direct_cli-0.3.5 → direct_cli-0.3.7}/tests/cassettes/test_integration_live_write/test_live_draft_dynamicads_add_delete.yaml +0 -0
  131. {direct_cli-0.3.5 → direct_cli-0.3.7}/tests/cassettes/test_integration_live_write/test_live_draft_dynamicads_suspend_resume.yaml +0 -0
  132. {direct_cli-0.3.5 → direct_cli-0.3.7}/tests/cassettes/test_integration_live_write/test_live_draft_keywordbids_set.yaml +0 -0
  133. {direct_cli-0.3.5 → direct_cli-0.3.7}/tests/cassettes/test_integration_live_write/test_live_draft_keywords_add_update_delete.yaml +0 -0
  134. {direct_cli-0.3.5 → direct_cli-0.3.7}/tests/cassettes/test_integration_live_write/test_live_draft_keywords_suspend_resume.yaml +0 -0
  135. {direct_cli-0.3.5 → direct_cli-0.3.7}/tests/cassettes/test_integration_live_write/test_live_draft_sitelinks_add_get_delete.yaml +0 -0
  136. {direct_cli-0.3.5 → direct_cli-0.3.7}/tests/cassettes/test_integration_live_write/test_live_draft_smartadtargets_add_update_delete.yaml +0 -0
  137. {direct_cli-0.3.5 → direct_cli-0.3.7}/tests/cassettes/test_integration_live_write/test_live_draft_smartadtargets_suspend_resume.yaml +0 -0
  138. {direct_cli-0.3.5 → direct_cli-0.3.7}/tests/cassettes/test_integration_write/TestWriteAdExtensions.test_add_delete.yaml +0 -0
  139. {direct_cli-0.3.5 → direct_cli-0.3.7}/tests/cassettes/test_integration_write/TestWriteAdGroups.test_add_update_delete.yaml +0 -0
  140. {direct_cli-0.3.5 → direct_cli-0.3.7}/tests/cassettes/test_integration_write/TestWriteAdImages.test_add_delete.yaml +0 -0
  141. {direct_cli-0.3.5 → direct_cli-0.3.7}/tests/cassettes/test_integration_write/TestWriteAds.test_add_text_ad_update_delete.yaml +0 -0
  142. {direct_cli-0.3.5 → direct_cli-0.3.7}/tests/cassettes/test_integration_write/TestWriteAudienceTargets.test_add_delete.yaml +0 -0
  143. {direct_cli-0.3.5 → direct_cli-0.3.7}/tests/cassettes/test_integration_write/TestWriteBidModifiersAdd.test_add_delete_mobile.yaml +0 -0
  144. {direct_cli-0.3.5 → direct_cli-0.3.7}/tests/cassettes/test_integration_write/TestWriteBidModifiersSet.test_set_without_id_is_rejected.yaml +0 -0
  145. {direct_cli-0.3.5 → direct_cli-0.3.7}/tests/cassettes/test_integration_write/TestWriteBids.test_set_bid.yaml +0 -0
  146. {direct_cli-0.3.5 → direct_cli-0.3.7}/tests/cassettes/test_integration_write/TestWriteCampaignDraftLifecycle.test_draft_create_get_delete.yaml +0 -0
  147. {direct_cli-0.3.5 → direct_cli-0.3.7}/tests/cassettes/test_integration_write/TestWriteCampaigns.test_campaign_lifecycle.yaml +0 -0
  148. {direct_cli-0.3.5 → direct_cli-0.3.7}/tests/cassettes/test_integration_write/TestWriteDynamicAds.test_add_update_delete.yaml +0 -0
  149. {direct_cli-0.3.5 → direct_cli-0.3.7}/tests/cassettes/test_integration_write/TestWriteFeeds.test_add_update_delete.yaml +0 -0
  150. {direct_cli-0.3.5 → direct_cli-0.3.7}/tests/cassettes/test_integration_write/TestWriteKeywordBids.test_set_keyword_bid.yaml +0 -0
  151. {direct_cli-0.3.5 → direct_cli-0.3.7}/tests/cassettes/test_integration_write/TestWriteKeywords.test_add_update_delete.yaml +0 -0
  152. {direct_cli-0.3.5 → direct_cli-0.3.7}/tests/cassettes/test_integration_write/TestWriteNegativeKeywordSharedSets.test_add_update_delete.yaml +0 -0
  153. {direct_cli-0.3.5 → direct_cli-0.3.7}/tests/cassettes/test_integration_write/TestWriteRetargeting.test_add_delete.yaml +0 -0
  154. {direct_cli-0.3.5 → direct_cli-0.3.7}/tests/cassettes/test_integration_write/TestWriteSitelinks.test_add_delete.yaml +0 -0
  155. {direct_cli-0.3.5 → direct_cli-0.3.7}/tests/cassettes/test_integration_write/TestWriteSmartAdTargets.test_add_update_delete.yaml +0 -0
  156. {direct_cli-0.3.5 → direct_cli-0.3.7}/tests/cassettes/test_integration_write/TestWriteVCards.test_add_delete.yaml +0 -0
  157. {direct_cli-0.3.5 → direct_cli-0.3.7}/tests/conftest.py +0 -0
  158. {direct_cli-0.3.5 → direct_cli-0.3.7}/tests/fixtures/test-video.mp4 +0 -0
  159. {direct_cli-0.3.5 → direct_cli-0.3.7}/tests/reports_cache/raw/fields-list.html +0 -0
  160. {direct_cli-0.3.5 → direct_cli-0.3.7}/tests/reports_cache/raw/headers.html +0 -0
  161. {direct_cli-0.3.5 → direct_cli-0.3.7}/tests/reports_cache/raw/period.html +0 -0
  162. {direct_cli-0.3.5 → direct_cli-0.3.7}/tests/reports_cache/raw/spec.html +0 -0
  163. {direct_cli-0.3.5 → direct_cli-0.3.7}/tests/reports_cache/raw/type.html +0 -0
  164. {direct_cli-0.3.5 → direct_cli-0.3.7}/tests/reports_cache/spec.json +0 -0
  165. {direct_cli-0.3.5 → direct_cli-0.3.7}/tests/test_api_coverage.py +0 -0
  166. {direct_cli-0.3.5 → direct_cli-0.3.7}/tests/test_auth_bw.py +0 -0
  167. {direct_cli-0.3.5 → direct_cli-0.3.7}/tests/test_auth_op.py +0 -0
  168. {direct_cli-0.3.5 → direct_cli-0.3.7}/tests/test_balance.py +0 -0
  169. {direct_cli-0.3.5 → direct_cli-0.3.7}/tests/test_cli.py +0 -0
  170. {direct_cli-0.3.5 → direct_cli-0.3.7}/tests/test_integration.py +0 -0
  171. {direct_cli-0.3.5 → direct_cli-0.3.7}/tests/test_integration_live_write.py +0 -0
  172. {direct_cli-0.3.5 → direct_cli-0.3.7}/tests/test_integration_write.py +0 -0
  173. {direct_cli-0.3.5 → direct_cli-0.3.7}/tests/test_low_coverage_payloads.py +0 -0
  174. {direct_cli-0.3.5 → direct_cli-0.3.7}/tests/test_reports_drift.py +0 -0
  175. {direct_cli-0.3.5 → direct_cli-0.3.7}/tests/test_reports_parsing.py +0 -0
  176. {direct_cli-0.3.5 → direct_cli-0.3.7}/tests/test_transport_contract.py +0 -0
  177. {direct_cli-0.3.5 → direct_cli-0.3.7}/tests/test_v4_live_contracts.py +0 -0
  178. {direct_cli-0.3.5 → direct_cli-0.3.7}/tests/test_v4account.py +0 -0
  179. {direct_cli-0.3.5 → direct_cli-0.3.7}/tests/test_v4events.py +0 -0
  180. {direct_cli-0.3.5 → direct_cli-0.3.7}/tests/test_v4finance_money.py +0 -0
  181. {direct_cli-0.3.5 → direct_cli-0.3.7}/tests/test_v4finance_read.py +0 -0
  182. {direct_cli-0.3.5 → direct_cli-0.3.7}/tests/test_v4goals.py +0 -0
  183. {direct_cli-0.3.5 → direct_cli-0.3.7}/tests/test_v4wordstat.py +0 -0
  184. {direct_cli-0.3.5 → direct_cli-0.3.7}/tests/test_vendor_imports.py +0 -0
  185. {direct_cli-0.3.5 → direct_cli-0.3.7}/tests/wsdl_cache/adextensions.xml +0 -0
  186. {direct_cli-0.3.5 → direct_cli-0.3.7}/tests/wsdl_cache/adgroups.xml +0 -0
  187. {direct_cli-0.3.5 → direct_cli-0.3.7}/tests/wsdl_cache/adimages.xml +0 -0
  188. {direct_cli-0.3.5 → direct_cli-0.3.7}/tests/wsdl_cache/ads.xml +0 -0
  189. {direct_cli-0.3.5 → direct_cli-0.3.7}/tests/wsdl_cache/advideos.xml +0 -0
  190. {direct_cli-0.3.5 → direct_cli-0.3.7}/tests/wsdl_cache/agencyclients.xml +0 -0
  191. {direct_cli-0.3.5 → direct_cli-0.3.7}/tests/wsdl_cache/audiencetargets.xml +0 -0
  192. {direct_cli-0.3.5 → direct_cli-0.3.7}/tests/wsdl_cache/bidmodifiers.xml +0 -0
  193. {direct_cli-0.3.5 → direct_cli-0.3.7}/tests/wsdl_cache/bids.xml +0 -0
  194. {direct_cli-0.3.5 → direct_cli-0.3.7}/tests/wsdl_cache/businesses.xml +0 -0
  195. {direct_cli-0.3.5 → direct_cli-0.3.7}/tests/wsdl_cache/campaigns.xml +0 -0
  196. {direct_cli-0.3.5 → direct_cli-0.3.7}/tests/wsdl_cache/changes.xml +0 -0
  197. {direct_cli-0.3.5 → direct_cli-0.3.7}/tests/wsdl_cache/clients.xml +0 -0
  198. {direct_cli-0.3.5 → direct_cli-0.3.7}/tests/wsdl_cache/creatives.xml +0 -0
  199. {direct_cli-0.3.5 → direct_cli-0.3.7}/tests/wsdl_cache/dictionaries.xml +0 -0
  200. {direct_cli-0.3.5 → direct_cli-0.3.7}/tests/wsdl_cache/dynamicfeedadtargets.xml +0 -0
  201. {direct_cli-0.3.5 → direct_cli-0.3.7}/tests/wsdl_cache/dynamictextadtargets.xml +0 -0
  202. {direct_cli-0.3.5 → direct_cli-0.3.7}/tests/wsdl_cache/feeds.xml +0 -0
  203. {direct_cli-0.3.5 → direct_cli-0.3.7}/tests/wsdl_cache/imports/adextensiontypes.xsd +0 -0
  204. {direct_cli-0.3.5 → direct_cli-0.3.7}/tests/wsdl_cache/imports/general.xsd +0 -0
  205. {direct_cli-0.3.5 → direct_cli-0.3.7}/tests/wsdl_cache/imports/generalclients.xsd +0 -0
  206. {direct_cli-0.3.5 → direct_cli-0.3.7}/tests/wsdl_cache/keywordbids.xml +0 -0
  207. {direct_cli-0.3.5 → direct_cli-0.3.7}/tests/wsdl_cache/keywords.xml +0 -0
  208. {direct_cli-0.3.5 → direct_cli-0.3.7}/tests/wsdl_cache/keywordsresearch.xml +0 -0
  209. {direct_cli-0.3.5 → direct_cli-0.3.7}/tests/wsdl_cache/leads.xml +0 -0
  210. {direct_cli-0.3.5 → direct_cli-0.3.7}/tests/wsdl_cache/negativekeywordsharedsets.xml +0 -0
  211. {direct_cli-0.3.5 → direct_cli-0.3.7}/tests/wsdl_cache/retargetinglists.xml +0 -0
  212. {direct_cli-0.3.5 → direct_cli-0.3.7}/tests/wsdl_cache/sitelinks.xml +0 -0
  213. {direct_cli-0.3.5 → direct_cli-0.3.7}/tests/wsdl_cache/smartadtargets.xml +0 -0
  214. {direct_cli-0.3.5 → direct_cli-0.3.7}/tests/wsdl_cache/strategies.xml +0 -0
  215. {direct_cli-0.3.5 → direct_cli-0.3.7}/tests/wsdl_cache/turbopages.xml +0 -0
  216. {direct_cli-0.3.5 → direct_cli-0.3.7}/tests/wsdl_cache/vcards.xml +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: direct-cli
3
- Version: 0.3.5
3
+ Version: 0.3.7
4
4
  Summary: Command-line interface for Yandex Direct API
5
5
  Author: axisrow
6
6
  License: MIT
@@ -88,7 +88,7 @@ direct auth login
88
88
  direct auth login --profile agency1
89
89
  direct auth login --profile agency1 --format json
90
90
  direct auth login --code abc123 --profile agency1
91
- printf '%s\n' abc123 | direct auth login --code-stdin --profile agency1
91
+ printf '%s\n' abc123 | direct auth login --code - --profile agency1
92
92
  direct auth list
93
93
  direct auth use --profile agency1
94
94
  direct auth status --profile agency1
@@ -101,8 +101,8 @@ Notes:
101
101
  - `--login` remains Direct client login.
102
102
  - Authorization is performed via `direct auth login`.
103
103
  - OAuth profiles store refresh tokens and refresh access tokens automatically.
104
- - In a non-interactive shell, run `direct auth login --profile NAME` first, then finish with `direct auth login --code-stdin --profile NAME` and pass the browser code on stdin.
105
- - `direct auth login --code CODE --profile NAME` remains supported for compatibility, but `--code-stdin` avoids exposing the code in process arguments.
104
+ - In a non-interactive shell, run `direct auth login --profile NAME` first, then finish with `direct auth login --code - --profile NAME` and pass the browser code on stdin.
105
+ - `direct auth login --code CODE --profile NAME` remains supported for compatibility, but automation should use `--code -` to avoid exposing the code in process arguments.
106
106
  - If the first non-interactive step includes `--client-secret`, the secret is remembered for the matching completion step.
107
107
  - If a profile already stores a confidential OAuth client, `direct auth login --code CODE --profile NAME` reuses the saved `client_id` and `client_secret`.
108
108
  - `direct auth login --oauth-token TOKEN` is a manual access-token import and does not auto-refresh.
@@ -158,6 +158,24 @@ direct v4goals get-retargeting-goals --campaign-ids 123,456 --format table
158
158
  direct v4goals get-stat-goals --campaign-ids 123 --dry-run
159
159
  ```
160
160
 
161
+ ### V4 Live Tags
162
+
163
+ Campaign tags are managed as `{TagID, Tag}` pairs. Use `TagID=0` to create a
164
+ new campaign tag. Banner/ad tags are assigned by campaign tag IDs. Update
165
+ methods replace the full tag list for the target campaign or banner, so pass
166
+ existing tags again if they must remain assigned. Ad group tags are filter-only
167
+ through `direct adgroups get --tag-ids/--tags`; this release does not add ad
168
+ group tag mutation commands.
169
+
170
+ ```bash
171
+ direct v4tags get-campaigns --campaign-ids 3193279,1634563
172
+ direct v4tags get-banners --banner-ids 2571700,2571745
173
+ direct v4tags get-banners --campaign-ids 3193279
174
+ direct v4tags update-campaigns --campaign-id 3193279 --tag 0=akapulko --tag 16590=orange --dry-run
175
+ direct v4tags update-banners --banner-ids 2571700,2571745 --tag-ids 16590,16734 --dry-run
176
+ direct v4tags update-banners --banner-ids 2571700 --clear-tags --dry-run
177
+ ```
178
+
161
179
  ### V4 Live Events
162
180
 
163
181
  ```bash
@@ -178,6 +196,19 @@ direct v4wordstat get-report --report-id 123 --format table
178
196
  direct v4wordstat delete-report --report-id 123
179
197
  ```
180
198
 
199
+ ### V4 Live Budget Forecasts
200
+
201
+ Budget forecasts are asynchronous. Direct CLI makes exactly one API call per
202
+ command and does not poll automatically; repeat `list` or `get` yourself until
203
+ the forecast is ready.
204
+
205
+ ```bash
206
+ direct v4forecast create --phrases "buy laptop,buy desktop" --geo-ids 213 --currency RUB
207
+ direct v4forecast list --format table
208
+ direct v4forecast get --forecast-id 123 --format table
209
+ direct v4forecast delete --forecast-id 123
210
+ ```
211
+
181
212
  ### V4 Live Finance
182
213
 
183
214
  Finance methods require an extra financial token for money operations. In the
@@ -562,9 +593,9 @@ Current command surface:
562
593
  | WSDL-backed API services | 29 |
563
594
  | Supported API services including Reports | 30 |
564
595
  | WSDL operations | 112 |
565
- | CLI groups including `auth` | 39 |
566
- | CLI subcommands including `auth` | 130 |
567
- | API CLI subcommands excluding `auth` | 126 |
596
+ | CLI groups including `auth` | 40 |
597
+ | CLI subcommands including `auth` | 144 |
598
+ | API CLI subcommands excluding `auth` | 140 |
568
599
 
569
600
  ### API Coverage And Drift Monitoring
570
601
 
@@ -750,7 +781,7 @@ direct auth login
750
781
  direct auth login --profile agency1
751
782
  direct auth login --profile agency1 --format json
752
783
  direct auth login --code abc123 --profile agency1
753
- printf '%s\n' abc123 | direct auth login --code-stdin --profile agency1
784
+ printf '%s\n' abc123 | direct auth login --code - --profile agency1
754
785
  direct auth list
755
786
  direct auth use --profile agency1
756
787
  direct auth status --profile agency1
@@ -759,8 +790,8 @@ direct --profile agency1 campaigns get
759
790
 
760
791
  Примечания:
761
792
  - OAuth profiles сохраняют refresh token и автоматически обновляют access token.
762
- - В non-interactive shell сначала выполните `direct auth login --profile NAME`, затем завершите через `direct auth login --code-stdin --profile NAME` и передайте browser code через stdin.
763
- - `direct auth login --code CODE --profile NAME` сохраняется для совместимости, но `--code-stdin` не раскрывает код в process arguments.
793
+ - В non-interactive shell сначала выполните `direct auth login --profile NAME`, затем завершите через `direct auth login --code - --profile NAME` и передайте browser code через stdin.
794
+ - `direct auth login --code CODE --profile NAME` сохраняется для совместимости, но автоматизация должна использовать `--code -`, чтобы не раскрывать код в process arguments.
764
795
  - Если первый non-interactive шаг включает `--client-secret`, secret запоминается для последующего completion step.
765
796
  - Если profile уже хранит confidential OAuth client, `direct auth login --code CODE --profile NAME` использует сохраненные `client_id` и `client_secret`.
766
797
  - `direct auth login --oauth-token TOKEN` импортирует access token вручную и не включает auto-refresh.
@@ -1183,9 +1214,9 @@ YANDEX_DIRECT_LIVE_WRITE=1 pytest -m integration_live_write -v --record-mode=rew
1183
1214
  | WSDL-backed API services | 29 |
1184
1215
  | API services с учётом Reports | 30 |
1185
1216
  | WSDL operations | 112 |
1186
- | CLI groups с `auth` | 39 |
1187
- | CLI subcommands с `auth` | 130 |
1188
- | API CLI subcommands без `auth` | 126 |
1217
+ | CLI groups с `auth` | 40 |
1218
+ | CLI subcommands с `auth` | 144 |
1219
+ | API CLI subcommands без `auth` | 140 |
1189
1220
 
1190
1221
  #### Live sandbox write smoke
1191
1222
 
@@ -45,7 +45,7 @@ direct auth login
45
45
  direct auth login --profile agency1
46
46
  direct auth login --profile agency1 --format json
47
47
  direct auth login --code abc123 --profile agency1
48
- printf '%s\n' abc123 | direct auth login --code-stdin --profile agency1
48
+ printf '%s\n' abc123 | direct auth login --code - --profile agency1
49
49
  direct auth list
50
50
  direct auth use --profile agency1
51
51
  direct auth status --profile agency1
@@ -58,8 +58,8 @@ Notes:
58
58
  - `--login` remains Direct client login.
59
59
  - Authorization is performed via `direct auth login`.
60
60
  - OAuth profiles store refresh tokens and refresh access tokens automatically.
61
- - In a non-interactive shell, run `direct auth login --profile NAME` first, then finish with `direct auth login --code-stdin --profile NAME` and pass the browser code on stdin.
62
- - `direct auth login --code CODE --profile NAME` remains supported for compatibility, but `--code-stdin` avoids exposing the code in process arguments.
61
+ - In a non-interactive shell, run `direct auth login --profile NAME` first, then finish with `direct auth login --code - --profile NAME` and pass the browser code on stdin.
62
+ - `direct auth login --code CODE --profile NAME` remains supported for compatibility, but automation should use `--code -` to avoid exposing the code in process arguments.
63
63
  - If the first non-interactive step includes `--client-secret`, the secret is remembered for the matching completion step.
64
64
  - If a profile already stores a confidential OAuth client, `direct auth login --code CODE --profile NAME` reuses the saved `client_id` and `client_secret`.
65
65
  - `direct auth login --oauth-token TOKEN` is a manual access-token import and does not auto-refresh.
@@ -115,6 +115,24 @@ direct v4goals get-retargeting-goals --campaign-ids 123,456 --format table
115
115
  direct v4goals get-stat-goals --campaign-ids 123 --dry-run
116
116
  ```
117
117
 
118
+ ### V4 Live Tags
119
+
120
+ Campaign tags are managed as `{TagID, Tag}` pairs. Use `TagID=0` to create a
121
+ new campaign tag. Banner/ad tags are assigned by campaign tag IDs. Update
122
+ methods replace the full tag list for the target campaign or banner, so pass
123
+ existing tags again if they must remain assigned. Ad group tags are filter-only
124
+ through `direct adgroups get --tag-ids/--tags`; this release does not add ad
125
+ group tag mutation commands.
126
+
127
+ ```bash
128
+ direct v4tags get-campaigns --campaign-ids 3193279,1634563
129
+ direct v4tags get-banners --banner-ids 2571700,2571745
130
+ direct v4tags get-banners --campaign-ids 3193279
131
+ direct v4tags update-campaigns --campaign-id 3193279 --tag 0=akapulko --tag 16590=orange --dry-run
132
+ direct v4tags update-banners --banner-ids 2571700,2571745 --tag-ids 16590,16734 --dry-run
133
+ direct v4tags update-banners --banner-ids 2571700 --clear-tags --dry-run
134
+ ```
135
+
118
136
  ### V4 Live Events
119
137
 
120
138
  ```bash
@@ -135,6 +153,19 @@ direct v4wordstat get-report --report-id 123 --format table
135
153
  direct v4wordstat delete-report --report-id 123
136
154
  ```
137
155
 
156
+ ### V4 Live Budget Forecasts
157
+
158
+ Budget forecasts are asynchronous. Direct CLI makes exactly one API call per
159
+ command and does not poll automatically; repeat `list` or `get` yourself until
160
+ the forecast is ready.
161
+
162
+ ```bash
163
+ direct v4forecast create --phrases "buy laptop,buy desktop" --geo-ids 213 --currency RUB
164
+ direct v4forecast list --format table
165
+ direct v4forecast get --forecast-id 123 --format table
166
+ direct v4forecast delete --forecast-id 123
167
+ ```
168
+
138
169
  ### V4 Live Finance
139
170
 
140
171
  Finance methods require an extra financial token for money operations. In the
@@ -519,9 +550,9 @@ Current command surface:
519
550
  | WSDL-backed API services | 29 |
520
551
  | Supported API services including Reports | 30 |
521
552
  | WSDL operations | 112 |
522
- | CLI groups including `auth` | 39 |
523
- | CLI subcommands including `auth` | 130 |
524
- | API CLI subcommands excluding `auth` | 126 |
553
+ | CLI groups including `auth` | 40 |
554
+ | CLI subcommands including `auth` | 144 |
555
+ | API CLI subcommands excluding `auth` | 140 |
525
556
 
526
557
  ### API Coverage And Drift Monitoring
527
558
 
@@ -707,7 +738,7 @@ direct auth login
707
738
  direct auth login --profile agency1
708
739
  direct auth login --profile agency1 --format json
709
740
  direct auth login --code abc123 --profile agency1
710
- printf '%s\n' abc123 | direct auth login --code-stdin --profile agency1
741
+ printf '%s\n' abc123 | direct auth login --code - --profile agency1
711
742
  direct auth list
712
743
  direct auth use --profile agency1
713
744
  direct auth status --profile agency1
@@ -716,8 +747,8 @@ direct --profile agency1 campaigns get
716
747
 
717
748
  Примечания:
718
749
  - OAuth profiles сохраняют refresh token и автоматически обновляют access token.
719
- - В non-interactive shell сначала выполните `direct auth login --profile NAME`, затем завершите через `direct auth login --code-stdin --profile NAME` и передайте browser code через stdin.
720
- - `direct auth login --code CODE --profile NAME` сохраняется для совместимости, но `--code-stdin` не раскрывает код в process arguments.
750
+ - В non-interactive shell сначала выполните `direct auth login --profile NAME`, затем завершите через `direct auth login --code - --profile NAME` и передайте browser code через stdin.
751
+ - `direct auth login --code CODE --profile NAME` сохраняется для совместимости, но автоматизация должна использовать `--code -`, чтобы не раскрывать код в process arguments.
721
752
  - Если первый non-interactive шаг включает `--client-secret`, secret запоминается для последующего completion step.
722
753
  - Если profile уже хранит confidential OAuth client, `direct auth login --code CODE --profile NAME` использует сохраненные `client_id` и `client_secret`.
723
754
  - `direct auth login --oauth-token TOKEN` импортирует access token вручную и не включает auto-refresh.
@@ -1140,9 +1171,9 @@ YANDEX_DIRECT_LIVE_WRITE=1 pytest -m integration_live_write -v --record-mode=rew
1140
1171
  | WSDL-backed API services | 29 |
1141
1172
  | API services с учётом Reports | 30 |
1142
1173
  | WSDL operations | 112 |
1143
- | CLI groups с `auth` | 39 |
1144
- | CLI subcommands с `auth` | 130 |
1145
- | API CLI subcommands без `auth` | 126 |
1174
+ | CLI groups с `auth` | 40 |
1175
+ | CLI subcommands с `auth` | 144 |
1176
+ | API CLI subcommands без `auth` | 140 |
1146
1177
 
1147
1178
  #### Live sandbox write smoke
1148
1179
 
@@ -43,10 +43,12 @@ from .commands.strategies import strategies
43
43
  from .commands.auth import auth
44
44
  from .commands.balance import balance
45
45
  from .commands.v4events import v4events
46
+ from .commands.v4forecast import v4forecast
46
47
  from .commands.v4finance import v4finance
47
48
  from .commands.v4account import v4account
48
- from .commands.v4shells import v4forecast, v4meta
49
+ from .commands.v4shells import v4meta
49
50
  from .commands.v4goals import v4goals
51
+ from .commands.v4tags import v4tags
50
52
  from .commands.v4wordstat import v4wordstat
51
53
 
52
54
  # Load .env file
@@ -186,6 +188,7 @@ for command in (
186
188
  v4goals,
187
189
  v4events,
188
190
  v4wordstat,
191
+ v4tags,
189
192
  v4forecast,
190
193
  v4meta,
191
194
  auth,
@@ -35,10 +35,14 @@ def auth():
35
35
 
36
36
  @auth.command()
37
37
  @click.option("--profile", default="default", show_default=True, help="Profile name")
38
- @click.option("--code", help="OAuth authorization code")
38
+ @click.option(
39
+ "--code",
40
+ help="OAuth authorization code; use '-' to read from stdin for automation",
41
+ )
39
42
  @click.option(
40
43
  "--code-stdin",
41
44
  is_flag=True,
45
+ hidden=True,
42
46
  help="Read OAuth authorization code from stdin",
43
47
  )
44
48
  @click.option("--oauth-token", help="Ready OAuth access token")
@@ -78,9 +82,16 @@ def login(
78
82
  "--code-stdin cannot be combined with --code, "
79
83
  "--oauth-token, or --start-pkce."
80
84
  )
85
+ code = "-"
86
+
87
+ if code == "-":
88
+ if oauth_token or start_pkce:
89
+ raise click.ClickException(
90
+ "--code - cannot be combined with --oauth-token or --start-pkce."
91
+ )
81
92
  code = sys.stdin.read().strip()
82
93
  if not code:
83
- raise click.ClickException("--code-stdin requires a code on stdin.")
94
+ raise click.ClickException("--code - requires a code on stdin.")
84
95
 
85
96
  if start_pkce:
86
97
  if code or oauth_token or client_secret:
@@ -2,6 +2,8 @@
2
2
  Campaigns commands
3
3
  """
4
4
 
5
+ from typing import List, Optional
6
+
5
7
  import click
6
8
 
7
9
  from ..api import create_client
@@ -13,6 +15,7 @@ from ..utils import (
13
15
  get_default_fields,
14
16
  MICRO_RUBLES,
15
17
  parse_ids,
18
+ parse_csv_strings,
16
19
  parse_setting_specs,
17
20
  )
18
21
 
@@ -22,6 +25,14 @@ def campaigns():
22
25
  """Manage campaigns"""
23
26
 
24
27
 
28
+ def _parse_csv_option(option_name: str, value: Optional[str]) -> Optional[List[str]]:
29
+ """Parse a CSV option and reject explicitly empty input."""
30
+ parsed = parse_csv_strings(value)
31
+ if value is not None and not parsed:
32
+ raise click.UsageError(f"{option_name} must contain at least one value")
33
+ return parsed
34
+
35
+
25
36
  @campaigns.command()
26
37
  @click.option("--ids", help="Comma-separated campaign IDs")
27
38
  @click.option("--status", help="Filter by status (ACTIVE, SUSPENDED, etc.)")
@@ -41,6 +52,40 @@ def campaigns():
41
52
  @click.option(
42
53
  "--fields", help="Comma-separated field names (default: all common fields)"
43
54
  )
55
+ @click.option("--text-campaign-fields", help="Comma-separated TextCampaignFieldNames")
56
+ @click.option(
57
+ "--text-campaign-search-strategy-placement-types-fields",
58
+ help="Comma-separated TextCampaignSearchStrategyPlacementTypesFieldNames",
59
+ )
60
+ @click.option(
61
+ "--mobile-app-campaign-fields",
62
+ help="Comma-separated MobileAppCampaignFieldNames",
63
+ )
64
+ @click.option(
65
+ "--dynamic-text-campaign-fields",
66
+ help="Comma-separated DynamicTextCampaignFieldNames",
67
+ )
68
+ @click.option(
69
+ "--dynamic-text-campaign-search-strategy-placement-types-fields",
70
+ help="Comma-separated DynamicTextCampaignSearchStrategyPlacementTypesFieldNames",
71
+ )
72
+ @click.option(
73
+ "--cpm-banner-campaign-fields",
74
+ help="Comma-separated CpmBannerCampaignFieldNames",
75
+ )
76
+ @click.option("--smart-campaign-fields", help="Comma-separated SmartCampaignFieldNames")
77
+ @click.option(
78
+ "--unified-campaign-fields",
79
+ help="Comma-separated UnifiedCampaignFieldNames",
80
+ )
81
+ @click.option(
82
+ "--unified-campaign-search-strategy-placement-types-fields",
83
+ help="Comma-separated UnifiedCampaignSearchStrategyPlacementTypesFieldNames",
84
+ )
85
+ @click.option(
86
+ "--unified-campaign-package-bidding-strategy-platforms-fields",
87
+ help="Comma-separated UnifiedCampaignPackageBiddingStrategyPlatformsFieldNames",
88
+ )
44
89
  @click.option("--dry-run", is_flag=True, help="Show request without sending")
45
90
  @click.pass_context
46
91
  def get(
@@ -56,6 +101,16 @@ def get(
56
101
  output_format,
57
102
  output,
58
103
  fields,
104
+ text_campaign_fields,
105
+ text_campaign_search_strategy_placement_types_fields,
106
+ mobile_app_campaign_fields,
107
+ dynamic_text_campaign_fields,
108
+ dynamic_text_campaign_search_strategy_placement_types_fields,
109
+ cpm_banner_campaign_fields,
110
+ smart_campaign_fields,
111
+ unified_campaign_fields,
112
+ unified_campaign_search_strategy_placement_types_fields,
113
+ unified_campaign_package_bidding_strategy_platforms_fields,
59
114
  dry_run,
60
115
  ):
61
116
  """Get campaigns"""
@@ -70,7 +125,11 @@ def get(
70
125
  )
71
126
 
72
127
  # Parse field names
73
- field_names = fields.split(",") if fields else get_default_fields("campaigns")
128
+ field_names = (
129
+ _parse_csv_option("--fields", fields)
130
+ if fields is not None
131
+ else get_default_fields("campaigns")
132
+ )
74
133
 
75
134
  # Build selection criteria
76
135
  criteria = build_selection_criteria(
@@ -86,6 +145,52 @@ def get(
86
145
  params = build_common_params(
87
146
  criteria=criteria, field_names=field_names, limit=limit
88
147
  )
148
+ selector_options = {
149
+ "TextCampaignFieldNames": (
150
+ "--text-campaign-fields",
151
+ text_campaign_fields,
152
+ ),
153
+ "TextCampaignSearchStrategyPlacementTypesFieldNames": (
154
+ "--text-campaign-search-strategy-placement-types-fields",
155
+ text_campaign_search_strategy_placement_types_fields,
156
+ ),
157
+ "MobileAppCampaignFieldNames": (
158
+ "--mobile-app-campaign-fields",
159
+ mobile_app_campaign_fields,
160
+ ),
161
+ "DynamicTextCampaignFieldNames": (
162
+ "--dynamic-text-campaign-fields",
163
+ dynamic_text_campaign_fields,
164
+ ),
165
+ "DynamicTextCampaignSearchStrategyPlacementTypesFieldNames": (
166
+ "--dynamic-text-campaign-search-strategy-placement-types-fields",
167
+ dynamic_text_campaign_search_strategy_placement_types_fields,
168
+ ),
169
+ "CpmBannerCampaignFieldNames": (
170
+ "--cpm-banner-campaign-fields",
171
+ cpm_banner_campaign_fields,
172
+ ),
173
+ "SmartCampaignFieldNames": (
174
+ "--smart-campaign-fields",
175
+ smart_campaign_fields,
176
+ ),
177
+ "UnifiedCampaignFieldNames": (
178
+ "--unified-campaign-fields",
179
+ unified_campaign_fields,
180
+ ),
181
+ "UnifiedCampaignSearchStrategyPlacementTypesFieldNames": (
182
+ "--unified-campaign-search-strategy-placement-types-fields",
183
+ unified_campaign_search_strategy_placement_types_fields,
184
+ ),
185
+ "UnifiedCampaignPackageBiddingStrategyPlatformsFieldNames": (
186
+ "--unified-campaign-package-bidding-strategy-platforms-fields",
187
+ unified_campaign_package_bidding_strategy_platforms_fields,
188
+ ),
189
+ }
190
+ for request_key, (option_name, value) in selector_options.items():
191
+ parsed = _parse_csv_option(option_name, value)
192
+ if parsed:
193
+ params[request_key] = parsed
89
194
 
90
195
  body = {"method": "get", "params": params}
91
196
 
@@ -0,0 +1,168 @@
1
+ """Yandex Direct v4 Live budget forecast commands."""
2
+
3
+ from typing import Optional
4
+
5
+ import click
6
+
7
+ from ..api import create_v4_client
8
+ from ..output import format_output, print_error
9
+ from ..utils import parse_csv_strings, parse_ids
10
+ from ..v4 import build_v4_body, call_v4
11
+ from ..v4_contracts import v4_method_contract
12
+ from .v4shells import V4_EPILOG
13
+
14
+
15
+ def _forecast_param(
16
+ phrases: str, geo_ids: Optional[str], currency: str
17
+ ) -> dict[str, object]:
18
+ """Build the v4 Live CreateNewForecast parameter."""
19
+ phrase_list = parse_csv_strings(phrases)
20
+ if not phrase_list:
21
+ raise click.UsageError("--phrases must not be empty")
22
+ if len(phrase_list) > 100:
23
+ raise click.UsageError("--phrases accepts at most 100 phrases")
24
+
25
+ param: dict[str, object] = {
26
+ "Phrases": phrase_list,
27
+ "Currency": currency,
28
+ }
29
+ if geo_ids:
30
+ try:
31
+ parsed_geo_ids = parse_ids(geo_ids)
32
+ except ValueError as exc:
33
+ raise click.UsageError(str(exc)) from exc
34
+ if parsed_geo_ids:
35
+ param["GeoID"] = parsed_geo_ids
36
+ return param
37
+
38
+
39
+ def _call_forecast(
40
+ ctx,
41
+ method: str,
42
+ param,
43
+ output_format: str,
44
+ output: Optional[str],
45
+ ) -> None:
46
+ """Call one v4 Live budget forecast method and print formatted output."""
47
+ try:
48
+ client = create_v4_client(
49
+ token=ctx.obj.get("token"),
50
+ login=ctx.obj.get("login"),
51
+ profile=ctx.obj.get("profile"),
52
+ sandbox=ctx.obj.get("sandbox"),
53
+ )
54
+ data = call_v4(client, method, param)
55
+ format_output(data, output_format, output)
56
+ except Exception as e:
57
+ print_error(str(e))
58
+ raise click.Abort()
59
+
60
+
61
+ @click.group(epilog=V4_EPILOG)
62
+ def v4forecast():
63
+ """Yandex Direct v4 Live budget forecast commands."""
64
+
65
+
66
+ @v4_method_contract("CreateNewForecast")
67
+ @v4forecast.command()
68
+ @click.option("--phrases", required=True, help="Comma-separated phrases, up to 100")
69
+ @click.option("--geo-ids", help="Comma-separated geo region IDs")
70
+ @click.option(
71
+ "--currency",
72
+ default="RUB",
73
+ show_default=True,
74
+ help="Forecast currency",
75
+ )
76
+ @click.option(
77
+ "--format",
78
+ "output_format",
79
+ default="json",
80
+ type=click.Choice(["json", "table", "csv", "tsv"]),
81
+ help="Output format",
82
+ )
83
+ @click.option("--output", help="Output file")
84
+ @click.option("--dry-run", is_flag=True, help="Show request without sending")
85
+ @click.pass_context
86
+ def create(ctx, phrases, geo_ids, currency, output_format, output, dry_run):
87
+ """Create a v4 Live budget forecast."""
88
+ param = _forecast_param(phrases, geo_ids, currency)
89
+ if dry_run:
90
+ format_output(build_v4_body("CreateNewForecast", param), "json", None)
91
+ return
92
+
93
+ _call_forecast(ctx, "CreateNewForecast", param, output_format, output)
94
+
95
+
96
+ @v4_method_contract("GetForecastList")
97
+ @v4forecast.command(name="list")
98
+ @click.option(
99
+ "--format",
100
+ "output_format",
101
+ default="json",
102
+ type=click.Choice(["json", "table", "csv", "tsv"]),
103
+ help="Output format",
104
+ )
105
+ @click.option("--output", help="Output file")
106
+ @click.option("--dry-run", is_flag=True, help="Show request without sending")
107
+ @click.pass_context
108
+ def list_forecasts(ctx, output_format, output, dry_run):
109
+ """List v4 Live budget forecasts."""
110
+ if dry_run:
111
+ format_output(build_v4_body("GetForecastList"), "json", None)
112
+ return
113
+
114
+ _call_forecast(ctx, "GetForecastList", None, output_format, output)
115
+
116
+
117
+ @v4_method_contract("GetForecast")
118
+ @v4forecast.command()
119
+ @click.option(
120
+ "--forecast-id",
121
+ required=True,
122
+ type=click.IntRange(min=1),
123
+ help="Forecast ID",
124
+ )
125
+ @click.option(
126
+ "--format",
127
+ "output_format",
128
+ default="json",
129
+ type=click.Choice(["json", "table", "csv", "tsv"]),
130
+ help="Output format",
131
+ )
132
+ @click.option("--output", help="Output file")
133
+ @click.option("--dry-run", is_flag=True, help="Show request without sending")
134
+ @click.pass_context
135
+ def get(ctx, forecast_id, output_format, output, dry_run):
136
+ """Get a ready v4 Live budget forecast."""
137
+ if dry_run:
138
+ format_output(build_v4_body("GetForecast", forecast_id), "json", None)
139
+ return
140
+
141
+ _call_forecast(ctx, "GetForecast", forecast_id, output_format, output)
142
+
143
+
144
+ @v4_method_contract("DeleteForecastReport")
145
+ @v4forecast.command()
146
+ @click.option(
147
+ "--forecast-id",
148
+ required=True,
149
+ type=click.IntRange(min=1),
150
+ help="Forecast ID",
151
+ )
152
+ @click.option(
153
+ "--format",
154
+ "output_format",
155
+ default="json",
156
+ type=click.Choice(["json", "table", "csv", "tsv"]),
157
+ help="Output format",
158
+ )
159
+ @click.option("--output", help="Output file")
160
+ @click.option("--dry-run", is_flag=True, help="Show request without sending")
161
+ @click.pass_context
162
+ def delete(ctx, forecast_id, output_format, output, dry_run):
163
+ """Delete a v4 Live budget forecast."""
164
+ if dry_run:
165
+ format_output(build_v4_body("DeleteForecastReport", forecast_id), "json", None)
166
+ return
167
+
168
+ _call_forecast(ctx, "DeleteForecastReport", forecast_id, output_format, output)
@@ -15,11 +15,6 @@ def v4wordstat():
15
15
  """Yandex Direct v4 Live wordstat commands."""
16
16
 
17
17
 
18
- @click.group(epilog=V4_EPILOG)
19
- def v4forecast():
20
- """Yandex Direct v4 Live forecast commands."""
21
-
22
-
23
18
  @click.group(epilog=V4_EPILOG)
24
19
  def v4meta():
25
20
  """Yandex Direct v4 Live metadata commands."""