direct-cli 0.3.5__tar.gz → 0.3.6__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 (212) hide show
  1. {direct_cli-0.3.5 → direct_cli-0.3.6}/PKG-INFO +7 -7
  2. {direct_cli-0.3.5 → direct_cli-0.3.6}/README.md +6 -6
  3. {direct_cli-0.3.5 → direct_cli-0.3.6}/direct_cli/commands/auth.py +13 -2
  4. {direct_cli-0.3.5 → direct_cli-0.3.6}/direct_cli/commands/campaigns.py +106 -1
  5. {direct_cli-0.3.5 → direct_cli-0.3.6}/direct_cli.egg-info/PKG-INFO +7 -7
  6. {direct_cli-0.3.5 → direct_cli-0.3.6}/pyproject.toml +1 -1
  7. {direct_cli-0.3.5 → direct_cli-0.3.6}/tests/test_auth_oauth.py +97 -7
  8. {direct_cli-0.3.5 → direct_cli-0.3.6}/tests/test_dry_run.py +124 -0
  9. {direct_cli-0.3.5 → direct_cli-0.3.6}/.env.example +0 -0
  10. {direct_cli-0.3.5 → direct_cli-0.3.6}/.github/copilot-instructions.md +0 -0
  11. {direct_cli-0.3.5 → direct_cli-0.3.6}/.github/workflows/api-coverage.yml +0 -0
  12. {direct_cli-0.3.5 → direct_cli-0.3.6}/.github/workflows/claude-code-review.yml +0 -0
  13. {direct_cli-0.3.5 → direct_cli-0.3.6}/.github/workflows/claude.yml +0 -0
  14. {direct_cli-0.3.5 → direct_cli-0.3.6}/.github/workflows/quality.yml +0 -0
  15. {direct_cli-0.3.5 → direct_cli-0.3.6}/.gitignore +0 -0
  16. {direct_cli-0.3.5 → direct_cli-0.3.6}/AGENTS.md +0 -0
  17. {direct_cli-0.3.5 → direct_cli-0.3.6}/CHANGELOG.md +0 -0
  18. {direct_cli-0.3.5 → direct_cli-0.3.6}/CLAUDE.md +0 -0
  19. {direct_cli-0.3.5 → direct_cli-0.3.6}/MANIFEST.in +0 -0
  20. {direct_cli-0.3.5 → direct_cli-0.3.6}/direct_cli/__init__.py +0 -0
  21. {direct_cli-0.3.5 → direct_cli-0.3.6}/direct_cli/_deprecated.py +0 -0
  22. {direct_cli-0.3.5 → direct_cli-0.3.6}/direct_cli/_smoke_probes.py +0 -0
  23. {direct_cli-0.3.5 → direct_cli-0.3.6}/direct_cli/_vendor/__init__.py +0 -0
  24. {direct_cli-0.3.5 → direct_cli-0.3.6}/direct_cli/_vendor/tapi_yandex_direct/__init__.py +0 -0
  25. {direct_cli-0.3.5 → direct_cli-0.3.6}/direct_cli/_vendor/tapi_yandex_direct/endpoints.py +0 -0
  26. {direct_cli-0.3.5 → direct_cli-0.3.6}/direct_cli/_vendor/tapi_yandex_direct/exceptions.py +0 -0
  27. {direct_cli-0.3.5 → direct_cli-0.3.6}/direct_cli/_vendor/tapi_yandex_direct/resource_mapping.py +0 -0
  28. {direct_cli-0.3.5 → direct_cli-0.3.6}/direct_cli/_vendor/tapi_yandex_direct/tapi_yandex_direct.py +0 -0
  29. {direct_cli-0.3.5 → direct_cli-0.3.6}/direct_cli/_vendor/tapi_yandex_direct/tapi_yandex_direct.pyi +0 -0
  30. {direct_cli-0.3.5 → direct_cli-0.3.6}/direct_cli/_vendor/tapi_yandex_direct/v4/__init__.py +0 -0
  31. {direct_cli-0.3.5 → direct_cli-0.3.6}/direct_cli/_vendor/tapi_yandex_direct/v4/adapter.py +0 -0
  32. {direct_cli-0.3.5 → direct_cli-0.3.6}/direct_cli/_vendor/tapi_yandex_direct/v4/adapter.pyi +0 -0
  33. {direct_cli-0.3.5 → direct_cli-0.3.6}/direct_cli/_vendor/tapi_yandex_direct/v4/resource_mapping.py +0 -0
  34. {direct_cli-0.3.5 → direct_cli-0.3.6}/direct_cli/api.py +0 -0
  35. {direct_cli-0.3.5 → direct_cli-0.3.6}/direct_cli/auth.py +0 -0
  36. {direct_cli-0.3.5 → direct_cli-0.3.6}/direct_cli/cli.py +0 -0
  37. {direct_cli-0.3.5 → direct_cli-0.3.6}/direct_cli/commands/__init__.py +0 -0
  38. {direct_cli-0.3.5 → direct_cli-0.3.6}/direct_cli/commands/adextensions.py +0 -0
  39. {direct_cli-0.3.5 → direct_cli-0.3.6}/direct_cli/commands/adgroups.py +0 -0
  40. {direct_cli-0.3.5 → direct_cli-0.3.6}/direct_cli/commands/adimages.py +0 -0
  41. {direct_cli-0.3.5 → direct_cli-0.3.6}/direct_cli/commands/ads.py +0 -0
  42. {direct_cli-0.3.5 → direct_cli-0.3.6}/direct_cli/commands/advideos.py +0 -0
  43. {direct_cli-0.3.5 → direct_cli-0.3.6}/direct_cli/commands/agencyclients.py +0 -0
  44. {direct_cli-0.3.5 → direct_cli-0.3.6}/direct_cli/commands/audiencetargets.py +0 -0
  45. {direct_cli-0.3.5 → direct_cli-0.3.6}/direct_cli/commands/balance.py +0 -0
  46. {direct_cli-0.3.5 → direct_cli-0.3.6}/direct_cli/commands/bidmodifiers.py +0 -0
  47. {direct_cli-0.3.5 → direct_cli-0.3.6}/direct_cli/commands/bids.py +0 -0
  48. {direct_cli-0.3.5 → direct_cli-0.3.6}/direct_cli/commands/businesses.py +0 -0
  49. {direct_cli-0.3.5 → direct_cli-0.3.6}/direct_cli/commands/changes.py +0 -0
  50. {direct_cli-0.3.5 → direct_cli-0.3.6}/direct_cli/commands/clients.py +0 -0
  51. {direct_cli-0.3.5 → direct_cli-0.3.6}/direct_cli/commands/creatives.py +0 -0
  52. {direct_cli-0.3.5 → direct_cli-0.3.6}/direct_cli/commands/dictionaries.py +0 -0
  53. {direct_cli-0.3.5 → direct_cli-0.3.6}/direct_cli/commands/dynamicads.py +0 -0
  54. {direct_cli-0.3.5 → direct_cli-0.3.6}/direct_cli/commands/dynamicfeedadtargets.py +0 -0
  55. {direct_cli-0.3.5 → direct_cli-0.3.6}/direct_cli/commands/feeds.py +0 -0
  56. {direct_cli-0.3.5 → direct_cli-0.3.6}/direct_cli/commands/keywordbids.py +0 -0
  57. {direct_cli-0.3.5 → direct_cli-0.3.6}/direct_cli/commands/keywords.py +0 -0
  58. {direct_cli-0.3.5 → direct_cli-0.3.6}/direct_cli/commands/keywordsresearch.py +0 -0
  59. {direct_cli-0.3.5 → direct_cli-0.3.6}/direct_cli/commands/leads.py +0 -0
  60. {direct_cli-0.3.5 → direct_cli-0.3.6}/direct_cli/commands/negativekeywordsharedsets.py +0 -0
  61. {direct_cli-0.3.5 → direct_cli-0.3.6}/direct_cli/commands/reports.py +0 -0
  62. {direct_cli-0.3.5 → direct_cli-0.3.6}/direct_cli/commands/retargeting.py +0 -0
  63. {direct_cli-0.3.5 → direct_cli-0.3.6}/direct_cli/commands/sitelinks.py +0 -0
  64. {direct_cli-0.3.5 → direct_cli-0.3.6}/direct_cli/commands/smartadtargets.py +0 -0
  65. {direct_cli-0.3.5 → direct_cli-0.3.6}/direct_cli/commands/strategies.py +0 -0
  66. {direct_cli-0.3.5 → direct_cli-0.3.6}/direct_cli/commands/turbopages.py +0 -0
  67. {direct_cli-0.3.5 → direct_cli-0.3.6}/direct_cli/commands/v4account.py +0 -0
  68. {direct_cli-0.3.5 → direct_cli-0.3.6}/direct_cli/commands/v4events.py +0 -0
  69. {direct_cli-0.3.5 → direct_cli-0.3.6}/direct_cli/commands/v4finance.py +0 -0
  70. {direct_cli-0.3.5 → direct_cli-0.3.6}/direct_cli/commands/v4goals.py +0 -0
  71. {direct_cli-0.3.5 → direct_cli-0.3.6}/direct_cli/commands/v4shells.py +0 -0
  72. {direct_cli-0.3.5 → direct_cli-0.3.6}/direct_cli/commands/v4wordstat.py +0 -0
  73. {direct_cli-0.3.5 → direct_cli-0.3.6}/direct_cli/commands/vcards.py +0 -0
  74. {direct_cli-0.3.5 → direct_cli-0.3.6}/direct_cli/output.py +0 -0
  75. {direct_cli-0.3.5 → direct_cli-0.3.6}/direct_cli/reports_coverage.py +0 -0
  76. {direct_cli-0.3.5 → direct_cli-0.3.6}/direct_cli/smoke_matrix.py +0 -0
  77. {direct_cli-0.3.5 → direct_cli-0.3.6}/direct_cli/utils.py +0 -0
  78. {direct_cli-0.3.5 → direct_cli-0.3.6}/direct_cli/v4/__init__.py +0 -0
  79. {direct_cli-0.3.5 → direct_cli-0.3.6}/direct_cli/v4/money.py +0 -0
  80. {direct_cli-0.3.5 → direct_cli-0.3.6}/direct_cli/v4_contracts.py +0 -0
  81. {direct_cli-0.3.5 → direct_cli-0.3.6}/direct_cli/wsdl_coverage.py +0 -0
  82. {direct_cli-0.3.5 → direct_cli-0.3.6}/direct_cli.egg-info/SOURCES.txt +0 -0
  83. {direct_cli-0.3.5 → direct_cli-0.3.6}/direct_cli.egg-info/dependency_links.txt +0 -0
  84. {direct_cli-0.3.5 → direct_cli-0.3.6}/direct_cli.egg-info/entry_points.txt +0 -0
  85. {direct_cli-0.3.5 → direct_cli-0.3.6}/direct_cli.egg-info/requires.txt +0 -0
  86. {direct_cli-0.3.5 → direct_cli-0.3.6}/direct_cli.egg-info/top_level.txt +0 -0
  87. {direct_cli-0.3.5 → direct_cli-0.3.6}/docs/superpowers/plans/2026-04-12-issue-32-completion.md +0 -0
  88. {direct_cli-0.3.5 → direct_cli-0.3.6}/docs/superpowers/specs/2026-04-23-vendor-tapi-yandex-direct-design.md +0 -0
  89. {direct_cli-0.3.5 → direct_cli-0.3.6}/scripts/anonymize_cassettes.py +0 -0
  90. {direct_cli-0.3.5 → direct_cli-0.3.6}/scripts/build_api_coverage_checklist.py +0 -0
  91. {direct_cli-0.3.5 → direct_cli-0.3.6}/scripts/build_api_coverage_report.py +0 -0
  92. {direct_cli-0.3.5 → direct_cli-0.3.6}/scripts/check_reports_drift.py +0 -0
  93. {direct_cli-0.3.5 → direct_cli-0.3.6}/scripts/check_wsdl_drift.py +0 -0
  94. {direct_cli-0.3.5 → direct_cli-0.3.6}/scripts/patch_vendor_imports.py +0 -0
  95. {direct_cli-0.3.5 → direct_cli-0.3.6}/scripts/refresh_reports_cache.py +0 -0
  96. {direct_cli-0.3.5 → direct_cli-0.3.6}/scripts/refresh_wsdl_cache.py +0 -0
  97. {direct_cli-0.3.5 → direct_cli-0.3.6}/scripts/release_pypi.sh +0 -0
  98. {direct_cli-0.3.5 → direct_cli-0.3.6}/scripts/sandbox_write_live.py +0 -0
  99. {direct_cli-0.3.5 → direct_cli-0.3.6}/scripts/test_dangerous_commands.sh +0 -0
  100. {direct_cli-0.3.5 → direct_cli-0.3.6}/scripts/test_safe_commands.sh +0 -0
  101. {direct_cli-0.3.5 → direct_cli-0.3.6}/scripts/test_sandbox_write.sh +0 -0
  102. {direct_cli-0.3.5 → direct_cli-0.3.6}/scripts/update_vendor.sh +0 -0
  103. {direct_cli-0.3.5 → direct_cli-0.3.6}/setup.cfg +0 -0
  104. {direct_cli-0.3.5 → direct_cli-0.3.6}/setup.py +0 -0
  105. {direct_cli-0.3.5 → direct_cli-0.3.6}/tests/API_COVERAGE.md +0 -0
  106. {direct_cli-0.3.5 → direct_cli-0.3.6}/tests/API_ISSUE_AUDIT.md +0 -0
  107. {direct_cli-0.3.5 → direct_cli-0.3.6}/tests/MANUAL_COVERAGE.md +0 -0
  108. {direct_cli-0.3.5 → direct_cli-0.3.6}/tests/__init__.py +0 -0
  109. {direct_cli-0.3.5 → direct_cli-0.3.6}/tests/api_coverage_payloads.py +0 -0
  110. {direct_cli-0.3.5 → direct_cli-0.3.6}/tests/cassettes/test_integration_live_write/test_live_draft_adgroups_add_update_delete.yaml +0 -0
  111. {direct_cli-0.3.5 → direct_cli-0.3.6}/tests/cassettes/test_integration_live_write/test_live_draft_adimages_add_get_delete.yaml +0 -0
  112. {direct_cli-0.3.5 → direct_cli-0.3.6}/tests/cassettes/test_integration_live_write/test_live_draft_ads_add_update_delete.yaml +0 -0
  113. {direct_cli-0.3.5 → direct_cli-0.3.6}/tests/cassettes/test_integration_live_write/test_live_draft_ads_suspend_resume_archive_unarchive.yaml +0 -0
  114. {direct_cli-0.3.5 → direct_cli-0.3.6}/tests/cassettes/test_integration_live_write/test_live_draft_advideos_add_get.yaml +0 -0
  115. {direct_cli-0.3.5 → direct_cli-0.3.6}/tests/cassettes/test_integration_live_write/test_live_draft_audiencetargets_add_delete.yaml +0 -0
  116. {direct_cli-0.3.5 → direct_cli-0.3.6}/tests/cassettes/test_integration_live_write/test_live_draft_audiencetargets_suspend_resume.yaml +0 -0
  117. {direct_cli-0.3.5 → direct_cli-0.3.6}/tests/cassettes/test_integration_live_write/test_live_draft_bids_set.yaml +0 -0
  118. {direct_cli-0.3.5 → direct_cli-0.3.6}/tests/cassettes/test_integration_live_write/test_live_draft_campaign_create_get_delete.yaml +0 -0
  119. {direct_cli-0.3.5 → direct_cli-0.3.6}/tests/cassettes/test_integration_live_write/test_live_draft_creatives_chain_advideo_to_creative.yaml +0 -0
  120. {direct_cli-0.3.5 → direct_cli-0.3.6}/tests/cassettes/test_integration_live_write/test_live_draft_dynamicads_add_delete.yaml +0 -0
  121. {direct_cli-0.3.5 → direct_cli-0.3.6}/tests/cassettes/test_integration_live_write/test_live_draft_dynamicads_suspend_resume.yaml +0 -0
  122. {direct_cli-0.3.5 → direct_cli-0.3.6}/tests/cassettes/test_integration_live_write/test_live_draft_keywordbids_set.yaml +0 -0
  123. {direct_cli-0.3.5 → direct_cli-0.3.6}/tests/cassettes/test_integration_live_write/test_live_draft_keywords_add_update_delete.yaml +0 -0
  124. {direct_cli-0.3.5 → direct_cli-0.3.6}/tests/cassettes/test_integration_live_write/test_live_draft_keywords_suspend_resume.yaml +0 -0
  125. {direct_cli-0.3.5 → direct_cli-0.3.6}/tests/cassettes/test_integration_live_write/test_live_draft_sitelinks_add_get_delete.yaml +0 -0
  126. {direct_cli-0.3.5 → direct_cli-0.3.6}/tests/cassettes/test_integration_live_write/test_live_draft_smartadtargets_add_update_delete.yaml +0 -0
  127. {direct_cli-0.3.5 → direct_cli-0.3.6}/tests/cassettes/test_integration_live_write/test_live_draft_smartadtargets_suspend_resume.yaml +0 -0
  128. {direct_cli-0.3.5 → direct_cli-0.3.6}/tests/cassettes/test_integration_write/TestWriteAdExtensions.test_add_delete.yaml +0 -0
  129. {direct_cli-0.3.5 → direct_cli-0.3.6}/tests/cassettes/test_integration_write/TestWriteAdGroups.test_add_update_delete.yaml +0 -0
  130. {direct_cli-0.3.5 → direct_cli-0.3.6}/tests/cassettes/test_integration_write/TestWriteAdImages.test_add_delete.yaml +0 -0
  131. {direct_cli-0.3.5 → direct_cli-0.3.6}/tests/cassettes/test_integration_write/TestWriteAds.test_add_text_ad_update_delete.yaml +0 -0
  132. {direct_cli-0.3.5 → direct_cli-0.3.6}/tests/cassettes/test_integration_write/TestWriteAudienceTargets.test_add_delete.yaml +0 -0
  133. {direct_cli-0.3.5 → direct_cli-0.3.6}/tests/cassettes/test_integration_write/TestWriteBidModifiersAdd.test_add_delete_mobile.yaml +0 -0
  134. {direct_cli-0.3.5 → direct_cli-0.3.6}/tests/cassettes/test_integration_write/TestWriteBidModifiersSet.test_set_without_id_is_rejected.yaml +0 -0
  135. {direct_cli-0.3.5 → direct_cli-0.3.6}/tests/cassettes/test_integration_write/TestWriteBids.test_set_bid.yaml +0 -0
  136. {direct_cli-0.3.5 → direct_cli-0.3.6}/tests/cassettes/test_integration_write/TestWriteCampaignDraftLifecycle.test_draft_create_get_delete.yaml +0 -0
  137. {direct_cli-0.3.5 → direct_cli-0.3.6}/tests/cassettes/test_integration_write/TestWriteCampaigns.test_campaign_lifecycle.yaml +0 -0
  138. {direct_cli-0.3.5 → direct_cli-0.3.6}/tests/cassettes/test_integration_write/TestWriteDynamicAds.test_add_update_delete.yaml +0 -0
  139. {direct_cli-0.3.5 → direct_cli-0.3.6}/tests/cassettes/test_integration_write/TestWriteFeeds.test_add_update_delete.yaml +0 -0
  140. {direct_cli-0.3.5 → direct_cli-0.3.6}/tests/cassettes/test_integration_write/TestWriteKeywordBids.test_set_keyword_bid.yaml +0 -0
  141. {direct_cli-0.3.5 → direct_cli-0.3.6}/tests/cassettes/test_integration_write/TestWriteKeywords.test_add_update_delete.yaml +0 -0
  142. {direct_cli-0.3.5 → direct_cli-0.3.6}/tests/cassettes/test_integration_write/TestWriteNegativeKeywordSharedSets.test_add_update_delete.yaml +0 -0
  143. {direct_cli-0.3.5 → direct_cli-0.3.6}/tests/cassettes/test_integration_write/TestWriteRetargeting.test_add_delete.yaml +0 -0
  144. {direct_cli-0.3.5 → direct_cli-0.3.6}/tests/cassettes/test_integration_write/TestWriteSitelinks.test_add_delete.yaml +0 -0
  145. {direct_cli-0.3.5 → direct_cli-0.3.6}/tests/cassettes/test_integration_write/TestWriteSmartAdTargets.test_add_update_delete.yaml +0 -0
  146. {direct_cli-0.3.5 → direct_cli-0.3.6}/tests/cassettes/test_integration_write/TestWriteVCards.test_add_delete.yaml +0 -0
  147. {direct_cli-0.3.5 → direct_cli-0.3.6}/tests/conftest.py +0 -0
  148. {direct_cli-0.3.5 → direct_cli-0.3.6}/tests/fixtures/test-video.mp4 +0 -0
  149. {direct_cli-0.3.5 → direct_cli-0.3.6}/tests/reports_cache/raw/fields-list.html +0 -0
  150. {direct_cli-0.3.5 → direct_cli-0.3.6}/tests/reports_cache/raw/headers.html +0 -0
  151. {direct_cli-0.3.5 → direct_cli-0.3.6}/tests/reports_cache/raw/period.html +0 -0
  152. {direct_cli-0.3.5 → direct_cli-0.3.6}/tests/reports_cache/raw/spec.html +0 -0
  153. {direct_cli-0.3.5 → direct_cli-0.3.6}/tests/reports_cache/raw/type.html +0 -0
  154. {direct_cli-0.3.5 → direct_cli-0.3.6}/tests/reports_cache/spec.json +0 -0
  155. {direct_cli-0.3.5 → direct_cli-0.3.6}/tests/test_api_coverage.py +0 -0
  156. {direct_cli-0.3.5 → direct_cli-0.3.6}/tests/test_auth_bw.py +0 -0
  157. {direct_cli-0.3.5 → direct_cli-0.3.6}/tests/test_auth_op.py +0 -0
  158. {direct_cli-0.3.5 → direct_cli-0.3.6}/tests/test_balance.py +0 -0
  159. {direct_cli-0.3.5 → direct_cli-0.3.6}/tests/test_cli.py +0 -0
  160. {direct_cli-0.3.5 → direct_cli-0.3.6}/tests/test_cli_contract.py +0 -0
  161. {direct_cli-0.3.5 → direct_cli-0.3.6}/tests/test_comprehensive.py +0 -0
  162. {direct_cli-0.3.5 → direct_cli-0.3.6}/tests/test_integration.py +0 -0
  163. {direct_cli-0.3.5 → direct_cli-0.3.6}/tests/test_integration_live_write.py +0 -0
  164. {direct_cli-0.3.5 → direct_cli-0.3.6}/tests/test_integration_write.py +0 -0
  165. {direct_cli-0.3.5 → direct_cli-0.3.6}/tests/test_low_coverage_payloads.py +0 -0
  166. {direct_cli-0.3.5 → direct_cli-0.3.6}/tests/test_reports_drift.py +0 -0
  167. {direct_cli-0.3.5 → direct_cli-0.3.6}/tests/test_reports_parsing.py +0 -0
  168. {direct_cli-0.3.5 → direct_cli-0.3.6}/tests/test_smoke_matrix.py +0 -0
  169. {direct_cli-0.3.5 → direct_cli-0.3.6}/tests/test_transport_contract.py +0 -0
  170. {direct_cli-0.3.5 → direct_cli-0.3.6}/tests/test_v4_contracts.py +0 -0
  171. {direct_cli-0.3.5 → direct_cli-0.3.6}/tests/test_v4_foundation.py +0 -0
  172. {direct_cli-0.3.5 → direct_cli-0.3.6}/tests/test_v4_live_contracts.py +0 -0
  173. {direct_cli-0.3.5 → direct_cli-0.3.6}/tests/test_v4_safety.py +0 -0
  174. {direct_cli-0.3.5 → direct_cli-0.3.6}/tests/test_v4account.py +0 -0
  175. {direct_cli-0.3.5 → direct_cli-0.3.6}/tests/test_v4events.py +0 -0
  176. {direct_cli-0.3.5 → direct_cli-0.3.6}/tests/test_v4finance_money.py +0 -0
  177. {direct_cli-0.3.5 → direct_cli-0.3.6}/tests/test_v4finance_read.py +0 -0
  178. {direct_cli-0.3.5 → direct_cli-0.3.6}/tests/test_v4goals.py +0 -0
  179. {direct_cli-0.3.5 → direct_cli-0.3.6}/tests/test_v4wordstat.py +0 -0
  180. {direct_cli-0.3.5 → direct_cli-0.3.6}/tests/test_vendor_imports.py +0 -0
  181. {direct_cli-0.3.5 → direct_cli-0.3.6}/tests/wsdl_cache/adextensions.xml +0 -0
  182. {direct_cli-0.3.5 → direct_cli-0.3.6}/tests/wsdl_cache/adgroups.xml +0 -0
  183. {direct_cli-0.3.5 → direct_cli-0.3.6}/tests/wsdl_cache/adimages.xml +0 -0
  184. {direct_cli-0.3.5 → direct_cli-0.3.6}/tests/wsdl_cache/ads.xml +0 -0
  185. {direct_cli-0.3.5 → direct_cli-0.3.6}/tests/wsdl_cache/advideos.xml +0 -0
  186. {direct_cli-0.3.5 → direct_cli-0.3.6}/tests/wsdl_cache/agencyclients.xml +0 -0
  187. {direct_cli-0.3.5 → direct_cli-0.3.6}/tests/wsdl_cache/audiencetargets.xml +0 -0
  188. {direct_cli-0.3.5 → direct_cli-0.3.6}/tests/wsdl_cache/bidmodifiers.xml +0 -0
  189. {direct_cli-0.3.5 → direct_cli-0.3.6}/tests/wsdl_cache/bids.xml +0 -0
  190. {direct_cli-0.3.5 → direct_cli-0.3.6}/tests/wsdl_cache/businesses.xml +0 -0
  191. {direct_cli-0.3.5 → direct_cli-0.3.6}/tests/wsdl_cache/campaigns.xml +0 -0
  192. {direct_cli-0.3.5 → direct_cli-0.3.6}/tests/wsdl_cache/changes.xml +0 -0
  193. {direct_cli-0.3.5 → direct_cli-0.3.6}/tests/wsdl_cache/clients.xml +0 -0
  194. {direct_cli-0.3.5 → direct_cli-0.3.6}/tests/wsdl_cache/creatives.xml +0 -0
  195. {direct_cli-0.3.5 → direct_cli-0.3.6}/tests/wsdl_cache/dictionaries.xml +0 -0
  196. {direct_cli-0.3.5 → direct_cli-0.3.6}/tests/wsdl_cache/dynamicfeedadtargets.xml +0 -0
  197. {direct_cli-0.3.5 → direct_cli-0.3.6}/tests/wsdl_cache/dynamictextadtargets.xml +0 -0
  198. {direct_cli-0.3.5 → direct_cli-0.3.6}/tests/wsdl_cache/feeds.xml +0 -0
  199. {direct_cli-0.3.5 → direct_cli-0.3.6}/tests/wsdl_cache/imports/adextensiontypes.xsd +0 -0
  200. {direct_cli-0.3.5 → direct_cli-0.3.6}/tests/wsdl_cache/imports/general.xsd +0 -0
  201. {direct_cli-0.3.5 → direct_cli-0.3.6}/tests/wsdl_cache/imports/generalclients.xsd +0 -0
  202. {direct_cli-0.3.5 → direct_cli-0.3.6}/tests/wsdl_cache/keywordbids.xml +0 -0
  203. {direct_cli-0.3.5 → direct_cli-0.3.6}/tests/wsdl_cache/keywords.xml +0 -0
  204. {direct_cli-0.3.5 → direct_cli-0.3.6}/tests/wsdl_cache/keywordsresearch.xml +0 -0
  205. {direct_cli-0.3.5 → direct_cli-0.3.6}/tests/wsdl_cache/leads.xml +0 -0
  206. {direct_cli-0.3.5 → direct_cli-0.3.6}/tests/wsdl_cache/negativekeywordsharedsets.xml +0 -0
  207. {direct_cli-0.3.5 → direct_cli-0.3.6}/tests/wsdl_cache/retargetinglists.xml +0 -0
  208. {direct_cli-0.3.5 → direct_cli-0.3.6}/tests/wsdl_cache/sitelinks.xml +0 -0
  209. {direct_cli-0.3.5 → direct_cli-0.3.6}/tests/wsdl_cache/smartadtargets.xml +0 -0
  210. {direct_cli-0.3.5 → direct_cli-0.3.6}/tests/wsdl_cache/strategies.xml +0 -0
  211. {direct_cli-0.3.5 → direct_cli-0.3.6}/tests/wsdl_cache/turbopages.xml +0 -0
  212. {direct_cli-0.3.5 → direct_cli-0.3.6}/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.6
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.
@@ -750,7 +750,7 @@ direct auth login
750
750
  direct auth login --profile agency1
751
751
  direct auth login --profile agency1 --format json
752
752
  direct auth login --code abc123 --profile agency1
753
- printf '%s\n' abc123 | direct auth login --code-stdin --profile agency1
753
+ printf '%s\n' abc123 | direct auth login --code - --profile agency1
754
754
  direct auth list
755
755
  direct auth use --profile agency1
756
756
  direct auth status --profile agency1
@@ -759,8 +759,8 @@ direct --profile agency1 campaigns get
759
759
 
760
760
  Примечания:
761
761
  - 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.
762
+ - В non-interactive shell сначала выполните `direct auth login --profile NAME`, затем завершите через `direct auth login --code - --profile NAME` и передайте browser code через stdin.
763
+ - `direct auth login --code CODE --profile NAME` сохраняется для совместимости, но автоматизация должна использовать `--code -`, чтобы не раскрывать код в process arguments.
764
764
  - Если первый non-interactive шаг включает `--client-secret`, secret запоминается для последующего completion step.
765
765
  - Если profile уже хранит confidential OAuth client, `direct auth login --code CODE --profile NAME` использует сохраненные `client_id` и `client_secret`.
766
766
  - `direct auth login --oauth-token TOKEN` импортирует access token вручную и не включает auto-refresh.
@@ -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.
@@ -707,7 +707,7 @@ direct auth login
707
707
  direct auth login --profile agency1
708
708
  direct auth login --profile agency1 --format json
709
709
  direct auth login --code abc123 --profile agency1
710
- printf '%s\n' abc123 | direct auth login --code-stdin --profile agency1
710
+ printf '%s\n' abc123 | direct auth login --code - --profile agency1
711
711
  direct auth list
712
712
  direct auth use --profile agency1
713
713
  direct auth status --profile agency1
@@ -716,8 +716,8 @@ direct --profile agency1 campaigns get
716
716
 
717
717
  Примечания:
718
718
  - 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.
719
+ - В non-interactive shell сначала выполните `direct auth login --profile NAME`, затем завершите через `direct auth login --code - --profile NAME` и передайте browser code через stdin.
720
+ - `direct auth login --code CODE --profile NAME` сохраняется для совместимости, но автоматизация должна использовать `--code -`, чтобы не раскрывать код в process arguments.
721
721
  - Если первый non-interactive шаг включает `--client-secret`, secret запоминается для последующего completion step.
722
722
  - Если profile уже хранит confidential OAuth client, `direct auth login --code CODE --profile NAME` использует сохраненные `client_id` и `client_secret`.
723
723
  - `direct auth login --oauth-token TOKEN` импортирует access token вручную и не включает auto-refresh.
@@ -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
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: direct-cli
3
- Version: 0.3.5
3
+ Version: 0.3.6
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.
@@ -750,7 +750,7 @@ direct auth login
750
750
  direct auth login --profile agency1
751
751
  direct auth login --profile agency1 --format json
752
752
  direct auth login --code abc123 --profile agency1
753
- printf '%s\n' abc123 | direct auth login --code-stdin --profile agency1
753
+ printf '%s\n' abc123 | direct auth login --code - --profile agency1
754
754
  direct auth list
755
755
  direct auth use --profile agency1
756
756
  direct auth status --profile agency1
@@ -759,8 +759,8 @@ direct --profile agency1 campaigns get
759
759
 
760
760
  Примечания:
761
761
  - 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.
762
+ - В non-interactive shell сначала выполните `direct auth login --profile NAME`, затем завершите через `direct auth login --code - --profile NAME` и передайте browser code через stdin.
763
+ - `direct auth login --code CODE --profile NAME` сохраняется для совместимости, но автоматизация должна использовать `--code -`, чтобы не раскрывать код в process arguments.
764
764
  - Если первый non-interactive шаг включает `--client-secret`, secret запоминается для последующего completion step.
765
765
  - Если profile уже хранит confidential OAuth client, `direct auth login --code CODE --profile NAME` использует сохраненные `client_id` и `client_secret`.
766
766
  - `direct auth login --oauth-token TOKEN` импортирует access token вручную и не включает auto-refresh.
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "direct-cli"
7
- version = "0.3.5"
7
+ version = "0.3.6"
8
8
  description = "Command-line interface for Yandex Direct API"
9
9
  readme = "README.md"
10
10
  license = {text = "MIT"}
@@ -460,7 +460,7 @@ class TestAuthOAuth:
460
460
  },
461
461
  )
462
462
  @patch("direct_cli.commands.auth.time.time", return_value=1000.0)
463
- def test_auth_login_code_stdin_uses_pending_pkce_state(
463
+ def test_auth_login_code_dash_uses_pending_pkce_state(
464
464
  self, mock_time, mock_exchange, isolated_auth_store
465
465
  ):
466
466
  save_auth_store(
@@ -483,7 +483,7 @@ class TestAuthOAuth:
483
483
 
484
484
  result = runner.invoke(
485
485
  cli,
486
- ["auth", "login", "--profile", "agency1", "--code-stdin"],
486
+ ["auth", "login", "--profile", "agency1", "--code", "-"],
487
487
  input="abc123\n",
488
488
  )
489
489
 
@@ -501,19 +501,108 @@ class TestAuthOAuth:
501
501
  assert profile["token"] == "y0_pkce"
502
502
  assert profile["login"] == "client-login"
503
503
 
504
- def test_auth_login_code_stdin_requires_input(self, isolated_auth_store):
504
+ @patch(
505
+ "direct_cli.commands.auth.exchange_oauth_code",
506
+ return_value={
507
+ "access_token": "y0_pkce",
508
+ "refresh_token": "r1",
509
+ "expires_in": 3600,
510
+ },
511
+ )
512
+ @patch("direct_cli.commands.auth.time.time", return_value=1000.0)
513
+ def test_auth_login_code_stdin_alias_uses_pending_pkce_state(
514
+ self, mock_time, mock_exchange, isolated_auth_store
515
+ ):
516
+ save_auth_store(
517
+ {
518
+ "profiles": {},
519
+ "active_profile": None,
520
+ "pending_pkce": {
521
+ "agency1": {
522
+ "type": "pkce",
523
+ "client_id": "cid",
524
+ "code_verifier": "ver",
525
+ "login": "client-login",
526
+ "created_at": 900.0,
527
+ "expires_at": 1500.0,
528
+ }
529
+ },
530
+ }
531
+ )
505
532
  runner = CliRunner()
506
533
 
507
534
  result = runner.invoke(
508
535
  cli,
509
536
  ["auth", "login", "--profile", "agency1", "--code-stdin"],
537
+ input="abc123\n",
538
+ )
539
+
540
+ assert result.exit_code == 0
541
+ assert "abc123" not in result.output
542
+ mock_exchange.assert_called_once_with(
543
+ code="abc123",
544
+ client_id="cid",
545
+ client_secret=None,
546
+ code_verifier="ver",
547
+ )
548
+
549
+ def test_auth_login_code_dash_requires_input(self, isolated_auth_store):
550
+ runner = CliRunner()
551
+
552
+ result = runner.invoke(
553
+ cli,
554
+ ["auth", "login", "--profile", "agency1", "--code", "-"],
510
555
  input="\n",
511
556
  )
512
557
 
513
558
  assert result.exit_code != 0
514
- assert "--code-stdin requires a code on stdin" in result.output
559
+ assert "--code - requires a code on stdin" in result.output
515
560
 
516
- def test_auth_login_code_stdin_conflicts_with_code(self, isolated_auth_store):
561
+ def test_auth_login_code_dash_conflicts_before_reading_stdin(
562
+ self, isolated_auth_store
563
+ ):
564
+ runner = CliRunner()
565
+
566
+ result = runner.invoke(
567
+ cli,
568
+ [
569
+ "auth",
570
+ "login",
571
+ "--profile",
572
+ "agency1",
573
+ "--code",
574
+ "-",
575
+ "--start-pkce",
576
+ ],
577
+ )
578
+
579
+ assert result.exit_code != 0
580
+ assert "--code - cannot be combined" in result.output
581
+
582
+ def test_auth_login_code_dash_oauth_token_conflict_before_reading_stdin(
583
+ self, isolated_auth_store
584
+ ):
585
+ runner = CliRunner()
586
+
587
+ result = runner.invoke(
588
+ cli,
589
+ [
590
+ "auth",
591
+ "login",
592
+ "--profile",
593
+ "agency1",
594
+ "--code",
595
+ "-",
596
+ "--oauth-token",
597
+ "token",
598
+ ],
599
+ )
600
+
601
+ assert result.exit_code != 0
602
+ assert "--code - cannot be combined" in result.output
603
+ assert "--code - requires a code on stdin" not in result.output
604
+
605
+ def test_auth_login_code_stdin_alias_conflicts_with_code(self, isolated_auth_store):
517
606
  runner = CliRunner()
518
607
 
519
608
  result = runner.invoke(
@@ -542,7 +631,7 @@ class TestAuthOAuth:
542
631
  },
543
632
  )
544
633
  @patch("direct_cli.commands.auth.time.time", return_value=1000.0)
545
- def test_auth_login_code_stdin_custom_app(
634
+ def test_auth_login_code_dash_custom_app(
546
635
  self, mock_time, mock_exchange, isolated_auth_store
547
636
  ):
548
637
  runner = CliRunner()
@@ -554,7 +643,8 @@ class TestAuthOAuth:
554
643
  "login",
555
644
  "--profile",
556
645
  "agency1",
557
- "--code-stdin",
646
+ "--code",
647
+ "-",
558
648
  "--client-id",
559
649
  "cid",
560
650
  "--client-secret",
@@ -326,6 +326,130 @@ def test_advideos_get_ids_required():
326
326
  assert "Missing option '--ids'" in result.output
327
327
 
328
328
 
329
+ def test_campaigns_get_text_campaign_fields_dry_run():
330
+ body = _read_dry_run(
331
+ "campaigns",
332
+ "get",
333
+ "--fields",
334
+ "Id,Name,State",
335
+ "--text-campaign-fields",
336
+ "BiddingStrategy",
337
+ )
338
+
339
+ assert body["params"]["FieldNames"] == ["Id", "Name", "State"]
340
+ assert body["params"]["TextCampaignFieldNames"] == ["BiddingStrategy"]
341
+
342
+
343
+ def test_campaigns_get_campaign_specific_fields_dry_run():
344
+ body = _read_dry_run(
345
+ "campaigns",
346
+ "get",
347
+ "--text-campaign-fields",
348
+ "BiddingStrategy,PriorityGoals",
349
+ "--mobile-app-campaign-fields",
350
+ "Settings,BiddingStrategy",
351
+ "--dynamic-text-campaign-fields",
352
+ "BiddingStrategy,Settings",
353
+ "--cpm-banner-campaign-fields",
354
+ "BiddingStrategy,Settings",
355
+ "--smart-campaign-fields",
356
+ "BiddingStrategy,Settings",
357
+ "--unified-campaign-fields",
358
+ "BiddingStrategy,PriorityGoals",
359
+ )
360
+
361
+ params = body["params"]
362
+ assert params["TextCampaignFieldNames"] == ["BiddingStrategy", "PriorityGoals"]
363
+ assert params["MobileAppCampaignFieldNames"] == ["Settings", "BiddingStrategy"]
364
+ assert params["DynamicTextCampaignFieldNames"] == ["BiddingStrategy", "Settings"]
365
+ assert params["CpmBannerCampaignFieldNames"] == ["BiddingStrategy", "Settings"]
366
+ assert params["SmartCampaignFieldNames"] == ["BiddingStrategy", "Settings"]
367
+ assert params["UnifiedCampaignFieldNames"] == ["BiddingStrategy", "PriorityGoals"]
368
+
369
+
370
+ def test_campaigns_get_strategy_placement_fields_dry_run():
371
+ body = _read_dry_run(
372
+ "campaigns",
373
+ "get",
374
+ "--text-campaign-search-strategy-placement-types-fields",
375
+ "SearchResults,ProductGallery",
376
+ "--dynamic-text-campaign-search-strategy-placement-types-fields",
377
+ "SearchResults,DynamicPlaces",
378
+ "--unified-campaign-search-strategy-placement-types-fields",
379
+ "SearchResults,Maps,SearchOrganizationList",
380
+ "--unified-campaign-package-bidding-strategy-platforms-fields",
381
+ "SearchResult,Network",
382
+ )
383
+
384
+ params = body["params"]
385
+ assert params["TextCampaignSearchStrategyPlacementTypesFieldNames"] == [
386
+ "SearchResults",
387
+ "ProductGallery",
388
+ ]
389
+ assert params["DynamicTextCampaignSearchStrategyPlacementTypesFieldNames"] == [
390
+ "SearchResults",
391
+ "DynamicPlaces",
392
+ ]
393
+ assert params["UnifiedCampaignSearchStrategyPlacementTypesFieldNames"] == [
394
+ "SearchResults",
395
+ "Maps",
396
+ "SearchOrganizationList",
397
+ ]
398
+ assert params["UnifiedCampaignPackageBiddingStrategyPlatformsFieldNames"] == [
399
+ "SearchResult",
400
+ "Network",
401
+ ]
402
+
403
+
404
+ def test_campaigns_get_omits_campaign_specific_fields_by_default():
405
+ body = _read_dry_run("campaigns", "get", "--fields", "Id,Name,State")
406
+
407
+ omitted_keys = {
408
+ "TextCampaignFieldNames",
409
+ "TextCampaignSearchStrategyPlacementTypesFieldNames",
410
+ "MobileAppCampaignFieldNames",
411
+ "DynamicTextCampaignFieldNames",
412
+ "DynamicTextCampaignSearchStrategyPlacementTypesFieldNames",
413
+ "CpmBannerCampaignFieldNames",
414
+ "SmartCampaignFieldNames",
415
+ "UnifiedCampaignFieldNames",
416
+ "UnifiedCampaignSearchStrategyPlacementTypesFieldNames",
417
+ "UnifiedCampaignPackageBiddingStrategyPlatformsFieldNames",
418
+ }
419
+ assert body["params"]["FieldNames"] == ["Id", "Name", "State"]
420
+ assert omitted_keys.isdisjoint(body["params"])
421
+
422
+
423
+ def test_campaigns_get_rejects_empty_fields_csv():
424
+ result = CliRunner().invoke(
425
+ cli,
426
+ ["campaigns", "get", "--fields", ",", "--dry-run"],
427
+ env={"YANDEX_DIRECT_TOKEN": "test-token", "YANDEX_DIRECT_LOGIN": ""},
428
+ )
429
+
430
+ assert result.exit_code != 0
431
+ assert "--fields must contain at least one value" in result.output
432
+
433
+
434
+ def test_campaigns_get_rejects_empty_campaign_specific_fields_csv():
435
+ result = CliRunner().invoke(
436
+ cli,
437
+ [
438
+ "campaigns",
439
+ "get",
440
+ "--fields",
441
+ "Id",
442
+ "--text-campaign-fields",
443
+ ",",
444
+ "--dry-run",
445
+ ],
446
+ env={"YANDEX_DIRECT_TOKEN": "test-token", "YANDEX_DIRECT_LOGIN": ""},
447
+ )
448
+
449
+ assert result.exit_code != 0
450
+ assert "--text-campaign-fields must contain at least one value" in result.output
451
+
452
+
329
453
  def _reports_get_result(*extra_args: str) -> Result:
330
454
  return CliRunner().invoke(
331
455
  cli,
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes