direct-cli 0.2.10__tar.gz → 0.2.11__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 (174) hide show
  1. {direct_cli-0.2.10 → direct_cli-0.2.11}/PKG-INFO +58 -1
  2. {direct_cli-0.2.10 → direct_cli-0.2.11}/README.md +57 -0
  3. {direct_cli-0.2.10 → direct_cli-0.2.11}/direct_cli/cli.py +46 -31
  4. {direct_cli-0.2.10 → direct_cli-0.2.11}/direct_cli/commands/agencyclients.py +32 -14
  5. {direct_cli-0.2.10 → direct_cli-0.2.11}/direct_cli/reports_coverage.py +15 -5
  6. {direct_cli-0.2.10 → direct_cli-0.2.11}/direct_cli/utils.py +7 -0
  7. {direct_cli-0.2.10 → direct_cli-0.2.11}/direct_cli.egg-info/PKG-INFO +58 -1
  8. {direct_cli-0.2.10 → direct_cli-0.2.11}/pyproject.toml +1 -1
  9. {direct_cli-0.2.10 → direct_cli-0.2.11}/scripts/test_safe_commands.sh +1 -3
  10. {direct_cli-0.2.10 → direct_cli-0.2.11}/tests/API_COVERAGE.md +14 -0
  11. {direct_cli-0.2.10 → direct_cli-0.2.11}/tests/MANUAL_COVERAGE.md +7 -4
  12. {direct_cli-0.2.10 → direct_cli-0.2.11}/tests/test_api_coverage.py +16 -0
  13. {direct_cli-0.2.10 → direct_cli-0.2.11}/tests/test_cli.py +24 -1
  14. {direct_cli-0.2.10 → direct_cli-0.2.11}/tests/test_dry_run.py +8 -4
  15. {direct_cli-0.2.10 → direct_cli-0.2.11}/tests/test_integration.py +208 -16
  16. {direct_cli-0.2.10 → direct_cli-0.2.11}/tests/test_integration_write.py +4 -0
  17. {direct_cli-0.2.10 → direct_cli-0.2.11}/.env.example +0 -0
  18. {direct_cli-0.2.10 → direct_cli-0.2.11}/.github/copilot-instructions.md +0 -0
  19. {direct_cli-0.2.10 → direct_cli-0.2.11}/.github/workflows/api-coverage.yml +0 -0
  20. {direct_cli-0.2.10 → direct_cli-0.2.11}/.github/workflows/claude-code-review.yml +0 -0
  21. {direct_cli-0.2.10 → direct_cli-0.2.11}/.github/workflows/claude.yml +0 -0
  22. {direct_cli-0.2.10 → direct_cli-0.2.11}/.gitignore +0 -0
  23. {direct_cli-0.2.10 → direct_cli-0.2.11}/AGENTS.md +0 -0
  24. {direct_cli-0.2.10 → direct_cli-0.2.11}/CLAUDE.md +0 -0
  25. {direct_cli-0.2.10 → direct_cli-0.2.11}/MANIFEST.in +0 -0
  26. {direct_cli-0.2.10 → direct_cli-0.2.11}/direct_cli/__init__.py +0 -0
  27. {direct_cli-0.2.10 → direct_cli-0.2.11}/direct_cli/_deprecated.py +0 -0
  28. {direct_cli-0.2.10 → direct_cli-0.2.11}/direct_cli/_vendor/__init__.py +0 -0
  29. {direct_cli-0.2.10 → direct_cli-0.2.11}/direct_cli/_vendor/tapi_yandex_direct/__init__.py +0 -0
  30. {direct_cli-0.2.10 → direct_cli-0.2.11}/direct_cli/_vendor/tapi_yandex_direct/exceptions.py +0 -0
  31. {direct_cli-0.2.10 → direct_cli-0.2.11}/direct_cli/_vendor/tapi_yandex_direct/resource_mapping.py +0 -0
  32. {direct_cli-0.2.10 → direct_cli-0.2.11}/direct_cli/_vendor/tapi_yandex_direct/tapi_yandex_direct.py +0 -0
  33. {direct_cli-0.2.10 → direct_cli-0.2.11}/direct_cli/api.py +0 -0
  34. {direct_cli-0.2.10 → direct_cli-0.2.11}/direct_cli/auth.py +0 -0
  35. {direct_cli-0.2.10 → direct_cli-0.2.11}/direct_cli/commands/__init__.py +0 -0
  36. {direct_cli-0.2.10 → direct_cli-0.2.11}/direct_cli/commands/adextensions.py +0 -0
  37. {direct_cli-0.2.10 → direct_cli-0.2.11}/direct_cli/commands/adgroups.py +0 -0
  38. {direct_cli-0.2.10 → direct_cli-0.2.11}/direct_cli/commands/adimages.py +0 -0
  39. {direct_cli-0.2.10 → direct_cli-0.2.11}/direct_cli/commands/ads.py +0 -0
  40. {direct_cli-0.2.10 → direct_cli-0.2.11}/direct_cli/commands/advideos.py +0 -0
  41. {direct_cli-0.2.10 → direct_cli-0.2.11}/direct_cli/commands/audiencetargets.py +0 -0
  42. {direct_cli-0.2.10 → direct_cli-0.2.11}/direct_cli/commands/auth.py +0 -0
  43. {direct_cli-0.2.10 → direct_cli-0.2.11}/direct_cli/commands/bidmodifiers.py +0 -0
  44. {direct_cli-0.2.10 → direct_cli-0.2.11}/direct_cli/commands/bids.py +0 -0
  45. {direct_cli-0.2.10 → direct_cli-0.2.11}/direct_cli/commands/businesses.py +0 -0
  46. {direct_cli-0.2.10 → direct_cli-0.2.11}/direct_cli/commands/campaigns.py +0 -0
  47. {direct_cli-0.2.10 → direct_cli-0.2.11}/direct_cli/commands/changes.py +0 -0
  48. {direct_cli-0.2.10 → direct_cli-0.2.11}/direct_cli/commands/clients.py +0 -0
  49. {direct_cli-0.2.10 → direct_cli-0.2.11}/direct_cli/commands/creatives.py +0 -0
  50. {direct_cli-0.2.10 → direct_cli-0.2.11}/direct_cli/commands/dictionaries.py +0 -0
  51. {direct_cli-0.2.10 → direct_cli-0.2.11}/direct_cli/commands/dynamicads.py +0 -0
  52. {direct_cli-0.2.10 → direct_cli-0.2.11}/direct_cli/commands/dynamicfeedadtargets.py +0 -0
  53. {direct_cli-0.2.10 → direct_cli-0.2.11}/direct_cli/commands/feeds.py +0 -0
  54. {direct_cli-0.2.10 → direct_cli-0.2.11}/direct_cli/commands/keywordbids.py +0 -0
  55. {direct_cli-0.2.10 → direct_cli-0.2.11}/direct_cli/commands/keywords.py +0 -0
  56. {direct_cli-0.2.10 → direct_cli-0.2.11}/direct_cli/commands/keywordsresearch.py +0 -0
  57. {direct_cli-0.2.10 → direct_cli-0.2.11}/direct_cli/commands/leads.py +0 -0
  58. {direct_cli-0.2.10 → direct_cli-0.2.11}/direct_cli/commands/negativekeywordsharedsets.py +0 -0
  59. {direct_cli-0.2.10 → direct_cli-0.2.11}/direct_cli/commands/reports.py +0 -0
  60. {direct_cli-0.2.10 → direct_cli-0.2.11}/direct_cli/commands/retargeting.py +0 -0
  61. {direct_cli-0.2.10 → direct_cli-0.2.11}/direct_cli/commands/sitelinks.py +0 -0
  62. {direct_cli-0.2.10 → direct_cli-0.2.11}/direct_cli/commands/smartadtargets.py +0 -0
  63. {direct_cli-0.2.10 → direct_cli-0.2.11}/direct_cli/commands/strategies.py +0 -0
  64. {direct_cli-0.2.10 → direct_cli-0.2.11}/direct_cli/commands/turbopages.py +0 -0
  65. {direct_cli-0.2.10 → direct_cli-0.2.11}/direct_cli/commands/vcards.py +0 -0
  66. {direct_cli-0.2.10 → direct_cli-0.2.11}/direct_cli/output.py +0 -0
  67. {direct_cli-0.2.10 → direct_cli-0.2.11}/direct_cli/smoke_matrix.py +0 -0
  68. {direct_cli-0.2.10 → direct_cli-0.2.11}/direct_cli/wsdl_coverage.py +0 -0
  69. {direct_cli-0.2.10 → direct_cli-0.2.11}/direct_cli.egg-info/SOURCES.txt +0 -0
  70. {direct_cli-0.2.10 → direct_cli-0.2.11}/direct_cli.egg-info/dependency_links.txt +0 -0
  71. {direct_cli-0.2.10 → direct_cli-0.2.11}/direct_cli.egg-info/entry_points.txt +0 -0
  72. {direct_cli-0.2.10 → direct_cli-0.2.11}/direct_cli.egg-info/requires.txt +0 -0
  73. {direct_cli-0.2.10 → direct_cli-0.2.11}/direct_cli.egg-info/top_level.txt +0 -0
  74. {direct_cli-0.2.10 → direct_cli-0.2.11}/docs/superpowers/plans/2026-04-12-issue-32-completion.md +0 -0
  75. {direct_cli-0.2.10 → direct_cli-0.2.11}/docs/superpowers/specs/2026-04-23-vendor-tapi-yandex-direct-design.md +0 -0
  76. {direct_cli-0.2.10 → direct_cli-0.2.11}/scripts/anonymize_cassettes.py +0 -0
  77. {direct_cli-0.2.10 → direct_cli-0.2.11}/scripts/build_api_coverage_checklist.py +0 -0
  78. {direct_cli-0.2.10 → direct_cli-0.2.11}/scripts/build_api_coverage_report.py +0 -0
  79. {direct_cli-0.2.10 → direct_cli-0.2.11}/scripts/check_reports_drift.py +0 -0
  80. {direct_cli-0.2.10 → direct_cli-0.2.11}/scripts/check_wsdl_drift.py +0 -0
  81. {direct_cli-0.2.10 → direct_cli-0.2.11}/scripts/patch_vendor_imports.py +0 -0
  82. {direct_cli-0.2.10 → direct_cli-0.2.11}/scripts/refresh_reports_cache.py +0 -0
  83. {direct_cli-0.2.10 → direct_cli-0.2.11}/scripts/refresh_wsdl_cache.py +0 -0
  84. {direct_cli-0.2.10 → direct_cli-0.2.11}/scripts/release_pypi.sh +0 -0
  85. {direct_cli-0.2.10 → direct_cli-0.2.11}/scripts/sandbox_write_live.py +0 -0
  86. {direct_cli-0.2.10 → direct_cli-0.2.11}/scripts/test_dangerous_commands.sh +0 -0
  87. {direct_cli-0.2.10 → direct_cli-0.2.11}/scripts/test_sandbox_write.sh +0 -0
  88. {direct_cli-0.2.10 → direct_cli-0.2.11}/scripts/update_vendor.sh +0 -0
  89. {direct_cli-0.2.10 → direct_cli-0.2.11}/setup.cfg +0 -0
  90. {direct_cli-0.2.10 → direct_cli-0.2.11}/setup.py +0 -0
  91. {direct_cli-0.2.10 → direct_cli-0.2.11}/tests/API_ISSUE_AUDIT.md +0 -0
  92. {direct_cli-0.2.10 → direct_cli-0.2.11}/tests/__init__.py +0 -0
  93. {direct_cli-0.2.10 → direct_cli-0.2.11}/tests/cassettes/test_integration_live_write/test_live_draft_adgroups_add_update_delete.yaml +0 -0
  94. {direct_cli-0.2.10 → direct_cli-0.2.11}/tests/cassettes/test_integration_live_write/test_live_draft_adimages_add_get_delete.yaml +0 -0
  95. {direct_cli-0.2.10 → direct_cli-0.2.11}/tests/cassettes/test_integration_live_write/test_live_draft_ads_add_update_delete.yaml +0 -0
  96. {direct_cli-0.2.10 → direct_cli-0.2.11}/tests/cassettes/test_integration_live_write/test_live_draft_ads_suspend_resume_archive_unarchive.yaml +0 -0
  97. {direct_cli-0.2.10 → direct_cli-0.2.11}/tests/cassettes/test_integration_live_write/test_live_draft_advideos_add_get.yaml +0 -0
  98. {direct_cli-0.2.10 → direct_cli-0.2.11}/tests/cassettes/test_integration_live_write/test_live_draft_audiencetargets_add_delete.yaml +0 -0
  99. {direct_cli-0.2.10 → direct_cli-0.2.11}/tests/cassettes/test_integration_live_write/test_live_draft_audiencetargets_suspend_resume.yaml +0 -0
  100. {direct_cli-0.2.10 → direct_cli-0.2.11}/tests/cassettes/test_integration_live_write/test_live_draft_bids_set.yaml +0 -0
  101. {direct_cli-0.2.10 → direct_cli-0.2.11}/tests/cassettes/test_integration_live_write/test_live_draft_campaign_create_get_delete.yaml +0 -0
  102. {direct_cli-0.2.10 → direct_cli-0.2.11}/tests/cassettes/test_integration_live_write/test_live_draft_creatives_chain_advideo_to_creative.yaml +0 -0
  103. {direct_cli-0.2.10 → direct_cli-0.2.11}/tests/cassettes/test_integration_live_write/test_live_draft_dynamicads_add_delete.yaml +0 -0
  104. {direct_cli-0.2.10 → direct_cli-0.2.11}/tests/cassettes/test_integration_live_write/test_live_draft_dynamicads_suspend_resume.yaml +0 -0
  105. {direct_cli-0.2.10 → direct_cli-0.2.11}/tests/cassettes/test_integration_live_write/test_live_draft_keywordbids_set.yaml +0 -0
  106. {direct_cli-0.2.10 → direct_cli-0.2.11}/tests/cassettes/test_integration_live_write/test_live_draft_keywords_add_update_delete.yaml +0 -0
  107. {direct_cli-0.2.10 → direct_cli-0.2.11}/tests/cassettes/test_integration_live_write/test_live_draft_keywords_suspend_resume.yaml +0 -0
  108. {direct_cli-0.2.10 → direct_cli-0.2.11}/tests/cassettes/test_integration_live_write/test_live_draft_sitelinks_add_get_delete.yaml +0 -0
  109. {direct_cli-0.2.10 → direct_cli-0.2.11}/tests/cassettes/test_integration_live_write/test_live_draft_smartadtargets_add_update_delete.yaml +0 -0
  110. {direct_cli-0.2.10 → direct_cli-0.2.11}/tests/cassettes/test_integration_live_write/test_live_draft_smartadtargets_suspend_resume.yaml +0 -0
  111. {direct_cli-0.2.10 → direct_cli-0.2.11}/tests/cassettes/test_integration_write/TestWriteAdExtensions.test_add_delete.yaml +0 -0
  112. {direct_cli-0.2.10 → direct_cli-0.2.11}/tests/cassettes/test_integration_write/TestWriteAdGroups.test_add_update_delete.yaml +0 -0
  113. {direct_cli-0.2.10 → direct_cli-0.2.11}/tests/cassettes/test_integration_write/TestWriteAdImages.test_add_delete.yaml +0 -0
  114. {direct_cli-0.2.10 → direct_cli-0.2.11}/tests/cassettes/test_integration_write/TestWriteAds.test_add_text_ad_update_delete.yaml +0 -0
  115. {direct_cli-0.2.10 → direct_cli-0.2.11}/tests/cassettes/test_integration_write/TestWriteAudienceTargets.test_add_delete.yaml +0 -0
  116. {direct_cli-0.2.10 → direct_cli-0.2.11}/tests/cassettes/test_integration_write/TestWriteBidModifiersAdd.test_add_delete_mobile.yaml +0 -0
  117. {direct_cli-0.2.10 → direct_cli-0.2.11}/tests/cassettes/test_integration_write/TestWriteBidModifiersSet.test_set_without_id_is_rejected.yaml +0 -0
  118. {direct_cli-0.2.10 → direct_cli-0.2.11}/tests/cassettes/test_integration_write/TestWriteBids.test_set_bid.yaml +0 -0
  119. {direct_cli-0.2.10 → direct_cli-0.2.11}/tests/cassettes/test_integration_write/TestWriteCampaignDraftLifecycle.test_draft_create_get_delete.yaml +0 -0
  120. {direct_cli-0.2.10 → direct_cli-0.2.11}/tests/cassettes/test_integration_write/TestWriteCampaigns.test_campaign_lifecycle.yaml +0 -0
  121. {direct_cli-0.2.10 → direct_cli-0.2.11}/tests/cassettes/test_integration_write/TestWriteDynamicAds.test_add_update_delete.yaml +0 -0
  122. {direct_cli-0.2.10 → direct_cli-0.2.11}/tests/cassettes/test_integration_write/TestWriteFeeds.test_add_update_delete.yaml +0 -0
  123. {direct_cli-0.2.10 → direct_cli-0.2.11}/tests/cassettes/test_integration_write/TestWriteKeywordBids.test_set_keyword_bid.yaml +0 -0
  124. {direct_cli-0.2.10 → direct_cli-0.2.11}/tests/cassettes/test_integration_write/TestWriteKeywords.test_add_update_delete.yaml +0 -0
  125. {direct_cli-0.2.10 → direct_cli-0.2.11}/tests/cassettes/test_integration_write/TestWriteNegativeKeywordSharedSets.test_add_update_delete.yaml +0 -0
  126. {direct_cli-0.2.10 → direct_cli-0.2.11}/tests/cassettes/test_integration_write/TestWriteRetargeting.test_add_delete.yaml +0 -0
  127. {direct_cli-0.2.10 → direct_cli-0.2.11}/tests/cassettes/test_integration_write/TestWriteSitelinks.test_add_delete.yaml +0 -0
  128. {direct_cli-0.2.10 → direct_cli-0.2.11}/tests/cassettes/test_integration_write/TestWriteSmartAdTargets.test_add_update_delete.yaml +0 -0
  129. {direct_cli-0.2.10 → direct_cli-0.2.11}/tests/cassettes/test_integration_write/TestWriteVCards.test_add_delete.yaml +0 -0
  130. {direct_cli-0.2.10 → direct_cli-0.2.11}/tests/conftest.py +0 -0
  131. {direct_cli-0.2.10 → direct_cli-0.2.11}/tests/fixtures/test-video.mp4 +0 -0
  132. {direct_cli-0.2.10 → direct_cli-0.2.11}/tests/reports_cache/raw/fields-list.html +0 -0
  133. {direct_cli-0.2.10 → direct_cli-0.2.11}/tests/reports_cache/raw/headers.html +0 -0
  134. {direct_cli-0.2.10 → direct_cli-0.2.11}/tests/reports_cache/raw/spec.html +0 -0
  135. {direct_cli-0.2.10 → direct_cli-0.2.11}/tests/reports_cache/raw/type.html +0 -0
  136. {direct_cli-0.2.10 → direct_cli-0.2.11}/tests/reports_cache/spec.json +0 -0
  137. {direct_cli-0.2.10 → direct_cli-0.2.11}/tests/test_auth_bw.py +0 -0
  138. {direct_cli-0.2.10 → direct_cli-0.2.11}/tests/test_auth_oauth.py +0 -0
  139. {direct_cli-0.2.10 → direct_cli-0.2.11}/tests/test_auth_op.py +0 -0
  140. {direct_cli-0.2.10 → direct_cli-0.2.11}/tests/test_comprehensive.py +0 -0
  141. {direct_cli-0.2.10 → direct_cli-0.2.11}/tests/test_integration_live_write.py +0 -0
  142. {direct_cli-0.2.10 → direct_cli-0.2.11}/tests/test_reports_drift.py +0 -0
  143. {direct_cli-0.2.10 → direct_cli-0.2.11}/tests/test_smoke_matrix.py +0 -0
  144. {direct_cli-0.2.10 → direct_cli-0.2.11}/tests/test_transport_contract.py +0 -0
  145. {direct_cli-0.2.10 → direct_cli-0.2.11}/tests/test_vendor_imports.py +0 -0
  146. {direct_cli-0.2.10 → direct_cli-0.2.11}/tests/wsdl_cache/adextensions.xml +0 -0
  147. {direct_cli-0.2.10 → direct_cli-0.2.11}/tests/wsdl_cache/adgroups.xml +0 -0
  148. {direct_cli-0.2.10 → direct_cli-0.2.11}/tests/wsdl_cache/adimages.xml +0 -0
  149. {direct_cli-0.2.10 → direct_cli-0.2.11}/tests/wsdl_cache/ads.xml +0 -0
  150. {direct_cli-0.2.10 → direct_cli-0.2.11}/tests/wsdl_cache/advideos.xml +0 -0
  151. {direct_cli-0.2.10 → direct_cli-0.2.11}/tests/wsdl_cache/agencyclients.xml +0 -0
  152. {direct_cli-0.2.10 → direct_cli-0.2.11}/tests/wsdl_cache/audiencetargets.xml +0 -0
  153. {direct_cli-0.2.10 → direct_cli-0.2.11}/tests/wsdl_cache/bidmodifiers.xml +0 -0
  154. {direct_cli-0.2.10 → direct_cli-0.2.11}/tests/wsdl_cache/bids.xml +0 -0
  155. {direct_cli-0.2.10 → direct_cli-0.2.11}/tests/wsdl_cache/businesses.xml +0 -0
  156. {direct_cli-0.2.10 → direct_cli-0.2.11}/tests/wsdl_cache/campaigns.xml +0 -0
  157. {direct_cli-0.2.10 → direct_cli-0.2.11}/tests/wsdl_cache/changes.xml +0 -0
  158. {direct_cli-0.2.10 → direct_cli-0.2.11}/tests/wsdl_cache/clients.xml +0 -0
  159. {direct_cli-0.2.10 → direct_cli-0.2.11}/tests/wsdl_cache/creatives.xml +0 -0
  160. {direct_cli-0.2.10 → direct_cli-0.2.11}/tests/wsdl_cache/dictionaries.xml +0 -0
  161. {direct_cli-0.2.10 → direct_cli-0.2.11}/tests/wsdl_cache/dynamicfeedadtargets.xml +0 -0
  162. {direct_cli-0.2.10 → direct_cli-0.2.11}/tests/wsdl_cache/dynamictextadtargets.xml +0 -0
  163. {direct_cli-0.2.10 → direct_cli-0.2.11}/tests/wsdl_cache/feeds.xml +0 -0
  164. {direct_cli-0.2.10 → direct_cli-0.2.11}/tests/wsdl_cache/keywordbids.xml +0 -0
  165. {direct_cli-0.2.10 → direct_cli-0.2.11}/tests/wsdl_cache/keywords.xml +0 -0
  166. {direct_cli-0.2.10 → direct_cli-0.2.11}/tests/wsdl_cache/keywordsresearch.xml +0 -0
  167. {direct_cli-0.2.10 → direct_cli-0.2.11}/tests/wsdl_cache/leads.xml +0 -0
  168. {direct_cli-0.2.10 → direct_cli-0.2.11}/tests/wsdl_cache/negativekeywordsharedsets.xml +0 -0
  169. {direct_cli-0.2.10 → direct_cli-0.2.11}/tests/wsdl_cache/retargetinglists.xml +0 -0
  170. {direct_cli-0.2.10 → direct_cli-0.2.11}/tests/wsdl_cache/sitelinks.xml +0 -0
  171. {direct_cli-0.2.10 → direct_cli-0.2.11}/tests/wsdl_cache/smartadtargets.xml +0 -0
  172. {direct_cli-0.2.10 → direct_cli-0.2.11}/tests/wsdl_cache/strategies.xml +0 -0
  173. {direct_cli-0.2.10 → direct_cli-0.2.11}/tests/wsdl_cache/turbopages.xml +0 -0
  174. {direct_cli-0.2.10 → direct_cli-0.2.11}/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.2.10
3
+ Version: 0.2.11
4
4
  Summary: Command-line interface for Yandex Direct API
5
5
  Author: axisrow
6
6
  License: MIT
@@ -97,6 +97,23 @@ Notes:
97
97
  - Authorization is performed via `direct auth login`.
98
98
  - Alias `auth_login` is not supported.
99
99
 
100
+ Credential resolution priority:
101
+
102
+ | Priority | Source | Example |
103
+ |----------|--------|---------|
104
+ | 1 | Explicit CLI options | `direct --token TOKEN --login LOGIN campaigns get` |
105
+ | 2 | OAuth profile storage | `direct --profile agency1 campaigns get` |
106
+ | 3 | Profile-specific env vars | `YANDEX_DIRECT_TOKEN_AGENCY1`, `YANDEX_DIRECT_LOGIN_AGENCY1` |
107
+ | 4 | Base env vars or project `.env` | `YANDEX_DIRECT_TOKEN`, `YANDEX_DIRECT_LOGIN` |
108
+ | 5 | 1Password references | `--op-token-ref`, `YANDEX_DIRECT_OP_TOKEN_REF` |
109
+ | 6 | Bitwarden references | `--bw-token-ref`, `YANDEX_DIRECT_BW_TOKEN_REF` |
110
+
111
+ The project `.env` file is loaded automatically. If a profile is selected
112
+ with `--profile` or `direct auth use --profile NAME`, Direct CLI does not
113
+ fall back to base `YANDEX_DIRECT_LOGIN`; this prevents mixing a profile token
114
+ with a login from the project `.env`. For multi-account setups, prefer OAuth
115
+ profiles or profile-specific env vars instead of base credentials.
116
+
100
117
  Install with `pip install direct-cli`, then run commands with `direct`.
101
118
  Invoking the deprecated `direct-cli` entrypoint exits with
102
119
  `use direct instead of direct-cli`.
@@ -634,6 +651,45 @@ YANDEX_DIRECT_LOGIN=ваш_логин_на_яндексе
634
651
  direct --token ВАШ_ТОКЕН --login ВАШ_ЛОГИН campaigns get
635
652
  ```
636
653
 
654
+ Используйте профильные credentials из `.env`:
655
+
656
+ ```env
657
+ YANDEX_DIRECT_TOKEN_AGENCY1=token-1
658
+ YANDEX_DIRECT_LOGIN_AGENCY1=client-login-1
659
+ YANDEX_DIRECT_TOKEN_AGENCY2=token-2
660
+ YANDEX_DIRECT_LOGIN_AGENCY2=client-login-2
661
+ ```
662
+
663
+ OAuth и profile-команды:
664
+
665
+ ```bash
666
+ direct auth login
667
+ direct auth login --profile agency1
668
+ direct auth login --code abc123 --profile agency1
669
+ direct auth login --oauth-token y0_example --profile agency1
670
+ direct auth list
671
+ direct auth use --profile agency1
672
+ direct auth status --profile agency1
673
+ direct --profile agency1 campaigns get
674
+ ```
675
+
676
+ Порядок выбора credentials:
677
+
678
+ | Приоритет | Источник | Пример |
679
+ |-----------|----------|--------|
680
+ | 1 | Явные CLI-опции | `direct --token TOKEN --login LOGIN campaigns get` |
681
+ | 2 | OAuth profile storage | `direct --profile agency1 campaigns get` |
682
+ | 3 | Профильные env vars | `YANDEX_DIRECT_TOKEN_AGENCY1`, `YANDEX_DIRECT_LOGIN_AGENCY1` |
683
+ | 4 | Базовые env vars или project `.env` | `YANDEX_DIRECT_TOKEN`, `YANDEX_DIRECT_LOGIN` |
684
+ | 5 | 1Password references | `--op-token-ref`, `YANDEX_DIRECT_OP_TOKEN_REF` |
685
+ | 6 | Bitwarden references | `--bw-token-ref`, `YANDEX_DIRECT_BW_TOKEN_REF` |
686
+
687
+ Файл `.env` в проекте загружается автоматически. Если профиль выбран через
688
+ `--profile` или `direct auth use --profile NAME`, Direct CLI не подставляет
689
+ base `YANDEX_DIRECT_LOGIN`; это защищает от смешивания токена из профиля с
690
+ логином из project `.env`. Для нескольких аккаунтов используйте OAuth profiles
691
+ или профильные env vars, а не базовые credentials.
692
+
637
693
  Установка остаётся через `pip install direct-cli`, а запуск команд теперь идет
638
694
  через `direct`. Вызов deprecated entrypoint `direct-cli` завершается ошибкой с
639
695
  подсказкой `use direct instead of direct-cli`.
@@ -644,6 +700,7 @@ direct --token ВАШ_ТОКЕН --login ВАШ_ЛОГИН campaigns get
644
700
  |-------|----------|
645
701
  | `--token` | OAuth-токен доступа к API |
646
702
  | `--login` | Direct client login |
703
+ | `--profile` | Имя credential profile |
647
704
  | `--sandbox` | Использовать тестовое API (песочница) |
648
705
 
649
706
  ### Использование
@@ -58,6 +58,23 @@ Notes:
58
58
  - Authorization is performed via `direct auth login`.
59
59
  - Alias `auth_login` is not supported.
60
60
 
61
+ Credential resolution priority:
62
+
63
+ | Priority | Source | Example |
64
+ |----------|--------|---------|
65
+ | 1 | Explicit CLI options | `direct --token TOKEN --login LOGIN campaigns get` |
66
+ | 2 | OAuth profile storage | `direct --profile agency1 campaigns get` |
67
+ | 3 | Profile-specific env vars | `YANDEX_DIRECT_TOKEN_AGENCY1`, `YANDEX_DIRECT_LOGIN_AGENCY1` |
68
+ | 4 | Base env vars or project `.env` | `YANDEX_DIRECT_TOKEN`, `YANDEX_DIRECT_LOGIN` |
69
+ | 5 | 1Password references | `--op-token-ref`, `YANDEX_DIRECT_OP_TOKEN_REF` |
70
+ | 6 | Bitwarden references | `--bw-token-ref`, `YANDEX_DIRECT_BW_TOKEN_REF` |
71
+
72
+ The project `.env` file is loaded automatically. If a profile is selected
73
+ with `--profile` or `direct auth use --profile NAME`, Direct CLI does not
74
+ fall back to base `YANDEX_DIRECT_LOGIN`; this prevents mixing a profile token
75
+ with a login from the project `.env`. For multi-account setups, prefer OAuth
76
+ profiles or profile-specific env vars instead of base credentials.
77
+
61
78
  Install with `pip install direct-cli`, then run commands with `direct`.
62
79
  Invoking the deprecated `direct-cli` entrypoint exits with
63
80
  `use direct instead of direct-cli`.
@@ -595,6 +612,45 @@ YANDEX_DIRECT_LOGIN=ваш_логин_на_яндексе
595
612
  direct --token ВАШ_ТОКЕН --login ВАШ_ЛОГИН campaigns get
596
613
  ```
597
614
 
615
+ Используйте профильные credentials из `.env`:
616
+
617
+ ```env
618
+ YANDEX_DIRECT_TOKEN_AGENCY1=token-1
619
+ YANDEX_DIRECT_LOGIN_AGENCY1=client-login-1
620
+ YANDEX_DIRECT_TOKEN_AGENCY2=token-2
621
+ YANDEX_DIRECT_LOGIN_AGENCY2=client-login-2
622
+ ```
623
+
624
+ OAuth и profile-команды:
625
+
626
+ ```bash
627
+ direct auth login
628
+ direct auth login --profile agency1
629
+ direct auth login --code abc123 --profile agency1
630
+ direct auth login --oauth-token y0_example --profile agency1
631
+ direct auth list
632
+ direct auth use --profile agency1
633
+ direct auth status --profile agency1
634
+ direct --profile agency1 campaigns get
635
+ ```
636
+
637
+ Порядок выбора credentials:
638
+
639
+ | Приоритет | Источник | Пример |
640
+ |-----------|----------|--------|
641
+ | 1 | Явные CLI-опции | `direct --token TOKEN --login LOGIN campaigns get` |
642
+ | 2 | OAuth profile storage | `direct --profile agency1 campaigns get` |
643
+ | 3 | Профильные env vars | `YANDEX_DIRECT_TOKEN_AGENCY1`, `YANDEX_DIRECT_LOGIN_AGENCY1` |
644
+ | 4 | Базовые env vars или project `.env` | `YANDEX_DIRECT_TOKEN`, `YANDEX_DIRECT_LOGIN` |
645
+ | 5 | 1Password references | `--op-token-ref`, `YANDEX_DIRECT_OP_TOKEN_REF` |
646
+ | 6 | Bitwarden references | `--bw-token-ref`, `YANDEX_DIRECT_BW_TOKEN_REF` |
647
+
648
+ Файл `.env` в проекте загружается автоматически. Если профиль выбран через
649
+ `--profile` или `direct auth use --profile NAME`, Direct CLI не подставляет
650
+ base `YANDEX_DIRECT_LOGIN`; это защищает от смешивания токена из профиля с
651
+ логином из project `.env`. Для нескольких аккаунтов используйте OAuth profiles
652
+ или профильные env vars, а не базовые credentials.
653
+
598
654
  Установка остаётся через `pip install direct-cli`, а запуск команд теперь идет
599
655
  через `direct`. Вызов deprecated entrypoint `direct-cli` завершается ошибкой с
600
656
  подсказкой `use direct instead of direct-cli`.
@@ -605,6 +661,7 @@ direct --token ВАШ_ТОКЕН --login ВАШ_ЛОГИН campaigns get
605
661
  |-------|----------|
606
662
  | `--token` | OAuth-токен доступа к API |
607
663
  | `--login` | Direct client login |
664
+ | `--profile` | Имя credential profile |
608
665
  | `--sandbox` | Использовать тестовое API (песочница) |
609
666
 
610
667
  ### Использование
@@ -8,6 +8,7 @@ from dotenv import load_dotenv
8
8
 
9
9
  from . import __version__
10
10
  from .auth import get_active_profile, get_credentials
11
+ from .utils import get_docs_url
11
12
 
12
13
  from .commands.campaigns import campaigns
13
14
  from .commands.adgroups import adgroups
@@ -129,38 +130,52 @@ def cli(
129
130
  ctx.obj["login"] = login
130
131
 
131
132
 
133
+ def _register_command(command: click.Command) -> None:
134
+ """Register a command and append mapped documentation URL to group help."""
135
+ docs_url = get_docs_url(command.name or "")
136
+ if docs_url:
137
+ docs_line = f"\b\nDocumentation: {docs_url}"
138
+ command.epilog = (
139
+ f"{command.epilog}\n\n{docs_line}" if command.epilog else docs_line
140
+ )
141
+ cli.add_command(command)
142
+
143
+
132
144
  # Register all commands
133
- cli.add_command(campaigns)
134
- cli.add_command(adgroups)
135
- cli.add_command(ads)
136
- cli.add_command(keywords)
137
- cli.add_command(keywordbids)
138
- cli.add_command(bids)
139
- cli.add_command(bidmodifiers)
140
- cli.add_command(audiencetargets)
141
- cli.add_command(retargeting)
142
- cli.add_command(creatives)
143
- cli.add_command(adimages)
144
- cli.add_command(adextensions)
145
- cli.add_command(sitelinks)
146
- cli.add_command(vcards)
147
- cli.add_command(leads)
148
- cli.add_command(clients)
149
- cli.add_command(agencyclients)
150
- cli.add_command(dictionaries)
151
- cli.add_command(changes)
152
- cli.add_command(reports)
153
- cli.add_command(turbopages)
154
- cli.add_command(negativekeywordsharedsets)
155
- cli.add_command(feeds)
156
- cli.add_command(smartadtargets)
157
- cli.add_command(businesses)
158
- cli.add_command(keywordsresearch)
159
- cli.add_command(dynamicads)
160
- cli.add_command(advideos)
161
- cli.add_command(dynamicfeedadtargets)
162
- cli.add_command(strategies)
163
- cli.add_command(auth)
145
+ for command in (
146
+ campaigns,
147
+ adgroups,
148
+ ads,
149
+ keywords,
150
+ keywordbids,
151
+ bids,
152
+ bidmodifiers,
153
+ audiencetargets,
154
+ retargeting,
155
+ creatives,
156
+ adimages,
157
+ adextensions,
158
+ sitelinks,
159
+ vcards,
160
+ leads,
161
+ clients,
162
+ agencyclients,
163
+ dictionaries,
164
+ changes,
165
+ reports,
166
+ turbopages,
167
+ negativekeywordsharedsets,
168
+ feeds,
169
+ smartadtargets,
170
+ businesses,
171
+ keywordsresearch,
172
+ dynamicads,
173
+ advideos,
174
+ dynamicfeedadtargets,
175
+ strategies,
176
+ auth,
177
+ ):
178
+ _register_command(command)
164
179
 
165
180
 
166
181
  if __name__ == "__main__":
@@ -6,7 +6,7 @@ import click
6
6
 
7
7
  from ..api import create_client
8
8
  from ..output import format_output, print_error
9
- from ..utils import get_default_fields, parse_ids
9
+ from ..utils import get_default_fields
10
10
 
11
11
 
12
12
  def _build_notification(
@@ -21,10 +21,24 @@ def _build_notification(
21
21
  notification["Email"] = notification_email
22
22
  if notification_lang:
23
23
  notification["Lang"] = notification_lang
24
- if send_account_news is not None:
25
- notification["SendAccountNews"] = "YES" if send_account_news else "NO"
26
- if send_warnings is not None:
27
- notification["SendWarnings"] = "YES" if send_warnings else "NO"
24
+ if notification_email:
25
+ subscriptions = []
26
+ if send_account_news is not None:
27
+ subscriptions.append(
28
+ {
29
+ "Option": "RECEIVE_RECOMMENDATIONS",
30
+ "Value": "YES" if send_account_news else "NO",
31
+ }
32
+ )
33
+ if send_warnings is not None:
34
+ subscriptions.append(
35
+ {
36
+ "Option": "TRACK_POSITION_CHANGES",
37
+ "Value": "YES" if send_warnings else "NO",
38
+ }
39
+ )
40
+ if subscriptions:
41
+ notification["EmailSubscriptions"] = subscriptions
28
42
  return notification
29
43
 
30
44
 
@@ -34,14 +48,21 @@ def agencyclients():
34
48
 
35
49
 
36
50
  @agencyclients.command()
37
- @click.option("--ids", help="Comma-separated client IDs")
51
+ @click.option("--logins", help="Comma-separated client logins")
52
+ @click.option(
53
+ "--archived",
54
+ type=click.Choice(["YES", "NO"]),
55
+ default="NO",
56
+ show_default=True,
57
+ help="Filter archived clients",
58
+ )
38
59
  @click.option("--limit", type=int, help="Limit number of results")
39
60
  @click.option("--fetch-all", is_flag=True, help="Fetch all pages")
40
61
  @click.option("--format", "output_format", default="json", help="Output format")
41
62
  @click.option("--output", help="Output file")
42
63
  @click.option("--fields", help="Comma-separated field names")
43
64
  @click.pass_context
44
- def get(ctx, ids, limit, fetch_all, output_format, output, fields):
65
+ def get(ctx, logins, archived, limit, fetch_all, output_format, output, fields):
45
66
  """Get agency clients"""
46
67
  try:
47
68
  client = create_client(
@@ -52,14 +73,11 @@ def get(ctx, ids, limit, fetch_all, output_format, output, fields):
52
73
 
53
74
  field_names = fields.split(",") if fields else get_default_fields("clients")
54
75
 
55
- criteria = {}
56
- if ids:
57
- criteria["ClientIds"] = parse_ids(ids)
58
-
59
- params = {"FieldNames": field_names}
76
+ criteria = {"Archived": archived}
77
+ if logins:
78
+ criteria["Logins"] = [login.strip() for login in logins.split(",")]
60
79
 
61
- if criteria:
62
- params["SelectionCriteria"] = criteria
80
+ params = {"SelectionCriteria": criteria, "FieldNames": field_names}
63
81
 
64
82
  if limit:
65
83
  params["Page"] = {"Limit": limit}
@@ -11,12 +11,22 @@ import json
11
11
  import re
12
12
  from pathlib import Path
13
13
 
14
- # Real working URLs (yandex.ru, static HTML with content in page text)
14
+ from .utils import get_docs_pages
15
+
16
+
17
+ _REPORTS_DOCS_PAGES = get_docs_pages("reports")
18
+ _REQUIRED_DOCS_KEYS = ("type", "period", "fields-list", "headers")
19
+ _missing_docs_keys = [k for k in _REQUIRED_DOCS_KEYS if k not in _REPORTS_DOCS_PAGES]
20
+ if _missing_docs_keys:
21
+ raise RuntimeError(
22
+ "reports docs_pages mapping missing required keys "
23
+ f"{_missing_docs_keys}; vendored resource_mapping.py is out of sync"
24
+ )
15
25
  REPORTS_SPEC_URLS: dict[str, str] = {
16
- "type": "https://yandex.ru/dev/direct/doc/reports/type.html",
17
- "spec": "https://yandex.ru/dev/direct/doc/reports/period.html",
18
- "fields-list": "https://yandex.ru/dev/direct/doc/reports/fields-list.html",
19
- "headers": "https://yandex.ru/dev/direct/doc/reports/headers.html",
26
+ "type": _REPORTS_DOCS_PAGES["type"],
27
+ "spec": _REPORTS_DOCS_PAGES["period"],
28
+ "fields-list": _REPORTS_DOCS_PAGES["fields-list"],
29
+ "headers": _REPORTS_DOCS_PAGES["headers"],
20
30
  }
21
31
 
22
32
  REPORTS_CACHE_DIR = Path(__file__).resolve().parent.parent / "tests" / "reports_cache"
@@ -296,3 +296,10 @@ def get_docs_url(service: str) -> Optional[str]:
296
296
  """Return documentation URL for a service from tapi resource mapping."""
297
297
  entry = RESOURCE_MAPPING_V5.get(service)
298
298
  return entry.get("docs") if entry else None
299
+
300
+
301
+ def get_docs_pages(service: str) -> Dict[str, str]:
302
+ """Return documentation page URLs for a service from tapi resource mapping."""
303
+ entry = RESOURCE_MAPPING_V5.get(service)
304
+ docs_pages = entry.get("docs_pages") if entry else None
305
+ return dict(docs_pages) if docs_pages else {}
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: direct-cli
3
- Version: 0.2.10
3
+ Version: 0.2.11
4
4
  Summary: Command-line interface for Yandex Direct API
5
5
  Author: axisrow
6
6
  License: MIT
@@ -97,6 +97,23 @@ Notes:
97
97
  - Authorization is performed via `direct auth login`.
98
98
  - Alias `auth_login` is not supported.
99
99
 
100
+ Credential resolution priority:
101
+
102
+ | Priority | Source | Example |
103
+ |----------|--------|---------|
104
+ | 1 | Explicit CLI options | `direct --token TOKEN --login LOGIN campaigns get` |
105
+ | 2 | OAuth profile storage | `direct --profile agency1 campaigns get` |
106
+ | 3 | Profile-specific env vars | `YANDEX_DIRECT_TOKEN_AGENCY1`, `YANDEX_DIRECT_LOGIN_AGENCY1` |
107
+ | 4 | Base env vars or project `.env` | `YANDEX_DIRECT_TOKEN`, `YANDEX_DIRECT_LOGIN` |
108
+ | 5 | 1Password references | `--op-token-ref`, `YANDEX_DIRECT_OP_TOKEN_REF` |
109
+ | 6 | Bitwarden references | `--bw-token-ref`, `YANDEX_DIRECT_BW_TOKEN_REF` |
110
+
111
+ The project `.env` file is loaded automatically. If a profile is selected
112
+ with `--profile` or `direct auth use --profile NAME`, Direct CLI does not
113
+ fall back to base `YANDEX_DIRECT_LOGIN`; this prevents mixing a profile token
114
+ with a login from the project `.env`. For multi-account setups, prefer OAuth
115
+ profiles or profile-specific env vars instead of base credentials.
116
+
100
117
  Install with `pip install direct-cli`, then run commands with `direct`.
101
118
  Invoking the deprecated `direct-cli` entrypoint exits with
102
119
  `use direct instead of direct-cli`.
@@ -634,6 +651,45 @@ YANDEX_DIRECT_LOGIN=ваш_логин_на_яндексе
634
651
  direct --token ВАШ_ТОКЕН --login ВАШ_ЛОГИН campaigns get
635
652
  ```
636
653
 
654
+ Используйте профильные credentials из `.env`:
655
+
656
+ ```env
657
+ YANDEX_DIRECT_TOKEN_AGENCY1=token-1
658
+ YANDEX_DIRECT_LOGIN_AGENCY1=client-login-1
659
+ YANDEX_DIRECT_TOKEN_AGENCY2=token-2
660
+ YANDEX_DIRECT_LOGIN_AGENCY2=client-login-2
661
+ ```
662
+
663
+ OAuth и profile-команды:
664
+
665
+ ```bash
666
+ direct auth login
667
+ direct auth login --profile agency1
668
+ direct auth login --code abc123 --profile agency1
669
+ direct auth login --oauth-token y0_example --profile agency1
670
+ direct auth list
671
+ direct auth use --profile agency1
672
+ direct auth status --profile agency1
673
+ direct --profile agency1 campaigns get
674
+ ```
675
+
676
+ Порядок выбора credentials:
677
+
678
+ | Приоритет | Источник | Пример |
679
+ |-----------|----------|--------|
680
+ | 1 | Явные CLI-опции | `direct --token TOKEN --login LOGIN campaigns get` |
681
+ | 2 | OAuth profile storage | `direct --profile agency1 campaigns get` |
682
+ | 3 | Профильные env vars | `YANDEX_DIRECT_TOKEN_AGENCY1`, `YANDEX_DIRECT_LOGIN_AGENCY1` |
683
+ | 4 | Базовые env vars или project `.env` | `YANDEX_DIRECT_TOKEN`, `YANDEX_DIRECT_LOGIN` |
684
+ | 5 | 1Password references | `--op-token-ref`, `YANDEX_DIRECT_OP_TOKEN_REF` |
685
+ | 6 | Bitwarden references | `--bw-token-ref`, `YANDEX_DIRECT_BW_TOKEN_REF` |
686
+
687
+ Файл `.env` в проекте загружается автоматически. Если профиль выбран через
688
+ `--profile` или `direct auth use --profile NAME`, Direct CLI не подставляет
689
+ base `YANDEX_DIRECT_LOGIN`; это защищает от смешивания токена из профиля с
690
+ логином из project `.env`. Для нескольких аккаунтов используйте OAuth profiles
691
+ или профильные env vars, а не базовые credentials.
692
+
637
693
  Установка остаётся через `pip install direct-cli`, а запуск команд теперь идет
638
694
  через `direct`. Вызов deprecated entrypoint `direct-cli` завершается ошибкой с
639
695
  подсказкой `use direct instead of direct-cli`.
@@ -644,6 +700,7 @@ direct --token ВАШ_ТОКЕН --login ВАШ_ЛОГИН campaigns get
644
700
  |-------|----------|
645
701
  | `--token` | OAuth-токен доступа к API |
646
702
  | `--login` | Direct client login |
703
+ | `--profile` | Имя credential profile |
647
704
  | `--sandbox` | Использовать тестовое API (песочница) |
648
705
 
649
706
  ### Использование
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "direct-cli"
7
- version = "0.2.10"
7
+ version = "0.2.11"
8
8
  description = "Command-line interface for Yandex Direct API"
9
9
  readme = "README.md"
10
10
  license = {text = "MIT"}
@@ -165,9 +165,7 @@ run_test "sitelinks get --ids (env auth)" direct sitelinks get --ids 1
165
165
  run_test "vcards get --ids (env auth)" direct vcards get --ids 1
166
166
  run_test "leads get --turbo-page-ids (env auth)" direct leads get --turbo-page-ids 1 --limit 1
167
167
  run_test "clients get (env auth)" direct clients get
168
- # agencyclients requires agency account tracked in #73
169
- echo -e " ${CYAN}[BUG #73]${RESET} agencyclients get — требует агентский аккаунт (sandbox)"
170
- ((KNOWN++)) || true
168
+ skip_bug 73 "agencyclients get требует агентский аккаунт (sandbox)"
171
169
  run_test "feeds get --ids (env auth)" direct feeds get --ids 1
172
170
  run_test "creatives get (env auth)" direct creatives get
173
171
  # businesses requires Ids/Name/Url in SelectionCriteria
@@ -105,6 +105,20 @@ Sandbox re-check is not useful — this is an account-tier limitation.
105
105
  Testing strategy: manual-only on an account where DYNAMIC_TEXT_CAMPAIGN and
106
106
  SMART_CAMPAIGN are enabled. See `tests/MANUAL_COVERAGE.md`.
107
107
 
108
+ ### Category C — account-permission limited (code 3001)
109
+
110
+ The endpoint is available, but the current sandbox agency account does not
111
+ have rights to create agency clients. This differs from read-only
112
+ `agencyclients get`, which is covered against sandbox.
113
+
114
+ | Scenario | Symptom | Error code | Test class |
115
+ |---|---|---|---|
116
+ | agencyclients add-passport-organization | no rights to create clients | 3001 | manual-only |
117
+
118
+ Testing strategy: keep `agencyclients get` in read-only integration coverage;
119
+ keep agency-client creation manual-only unless a sandbox agency account with
120
+ client-creation rights is available.
121
+
108
122
  Originally classified in
109
123
  [#28 issuecomment-4275359621](https://github.com/axisrow/direct-cli/issues/28#issuecomment-4275359621)
110
124
  and
@@ -14,9 +14,12 @@ account requirements, or external dependencies.
14
14
  ## Account-Scoped Operations
15
15
 
16
16
  - **agencyclients add/update/delete** — requires an agency-type account.
17
- Non-agency accounts receive 403.
17
+ Non-agency accounts receive 403. The current sandbox agency account can
18
+ read agency clients, but creation returns error 3001: "No rights to create
19
+ clients".
18
20
  - **agencyclients add-passport-organization** — creates a real Passport
19
- organization linked to the account.
21
+ organization linked to the account. The current sandbox agency account is
22
+ sandbox-limited for this operation with error 3001.
20
23
  - **agencyclients add-passport-organization-member** — sends an invitation
21
24
  email to an external user.
22
25
 
@@ -64,8 +67,8 @@ Live tests skip gracefully when the API returns error 3500.
64
67
  |---|---|---|
65
68
  | ads moderate | Irreversible | Moderate |
66
69
  | campaigns/ads suspend/resume (live) | Traffic impact | High |
67
- | agencyclients add/update/delete | Account type | None (403) |
68
- | agencyclients add-passport-organization* | External state | Moderate |
70
+ | agencyclients add/update/delete | Account type / sandbox rights (403 or 3001) | None (skip) |
71
+ | agencyclients add-passport-organization* | External state / sandbox rights (3001) | Moderate |
69
72
  | bids/keywordbids/bidmodifiers set | Financial | High |
70
73
  | dynamicads (all) | Account type (3500) | None (skip) |
71
74
  | smartadtargets (all) | Account type (3500) | None (skip) |
@@ -1119,6 +1119,22 @@ class TestApiCoverage:
1119
1119
  class TestReportsCoverage:
1120
1120
  """Tests for Reports API spec snapshot and CLI parity."""
1121
1121
 
1122
+ def test_reports_spec_urls_come_from_resource_mapping(self):
1123
+ """Reports docs URLs must be derived from the vendored resource mapping."""
1124
+ from direct_cli._vendor.tapi_yandex_direct.resource_mapping import (
1125
+ RESOURCE_MAPPING_V5,
1126
+ )
1127
+ from direct_cli.reports_coverage import REPORTS_SPEC_URLS
1128
+
1129
+ docs_pages = RESOURCE_MAPPING_V5["reports"]["docs_pages"]
1130
+ assert REPORTS_SPEC_URLS == {
1131
+ "type": docs_pages["type"],
1132
+ "spec": docs_pages["period"],
1133
+ "fields-list": docs_pages["fields-list"],
1134
+ "headers": docs_pages["headers"],
1135
+ }
1136
+ assert all(not url.endswith(".html") for url in REPORTS_SPEC_URLS.values())
1137
+
1122
1138
  def test_reports_cache_files_exist(self):
1123
1139
  """All 4 raw HTML files and spec.json must be committed."""
1124
1140
  from direct_cli.reports_coverage import REPORTS_CACHE_DIR
@@ -12,8 +12,10 @@ from unittest.mock import patch
12
12
 
13
13
  from click.testing import CliRunner
14
14
 
15
- from direct_cli.cli import cli
15
+ from direct_cli._vendor.tapi_yandex_direct.resource_mapping import RESOURCE_MAPPING_V5
16
16
  from direct_cli._deprecated import DEPRECATED_ENTRYPOINT_MESSAGE, deprecated_main
17
+ from direct_cli.cli import cli
18
+ from direct_cli.utils import get_docs_url
17
19
 
18
20
 
19
21
  class TestCLI(unittest.TestCase):
@@ -42,6 +44,7 @@ class TestCLI(unittest.TestCase):
42
44
  self.assertEqual(result.exit_code, 0)
43
45
  self.assertIn("Manage campaigns", result.output)
44
46
  self.assertIn("Usage: direct campaigns", result.output)
47
+ self.assertIn(f"Documentation: {get_docs_url('campaigns')}", result.output)
45
48
 
46
49
  def test_adgroups_help(self):
47
50
  """Test adgroups help"""
@@ -60,6 +63,26 @@ class TestCLI(unittest.TestCase):
60
63
  result = self.runner.invoke(cli, ["reports", "--help"])
61
64
  self.assertEqual(result.exit_code, 0)
62
65
  self.assertIn("Generate and manage reports", result.output)
66
+ self.assertIn(f"Documentation: {get_docs_url('reports')}", result.output)
67
+
68
+ def test_registered_mapped_groups_show_docs_url(self):
69
+ """Registered groups from resource mapping show their documentation URL."""
70
+ mapped_groups = sorted(set(cli.commands) & set(RESOURCE_MAPPING_V5))
71
+ self.assertTrue(mapped_groups)
72
+ for group in mapped_groups:
73
+ with self.subTest(group=group):
74
+ result = self.runner.invoke(cli, [group, "--help"])
75
+ self.assertEqual(result.exit_code, 0)
76
+ self.assertIn(
77
+ f"Documentation: {get_docs_url(group)}",
78
+ result.output,
79
+ )
80
+
81
+ def test_auth_help_has_no_docs_url(self):
82
+ """Auth is not a Yandex Direct API resource and has no docs epilog."""
83
+ result = self.runner.invoke(cli, ["auth", "--help"])
84
+ self.assertEqual(result.exit_code, 0)
85
+ self.assertNotIn("Documentation:", result.output)
63
86
 
64
87
  def test_canonical_groups_in_help(self):
65
88
  """Test canonical transport groups"""
@@ -1378,8 +1378,10 @@ def test_agencyclients_add_builds_notification_from_typed_flags():
1378
1378
  "Notification": {
1379
1379
  "Email": "ops@example.com",
1380
1380
  "Lang": "RU",
1381
- "SendAccountNews": "YES",
1382
- "SendWarnings": "NO",
1381
+ "EmailSubscriptions": [
1382
+ {"Option": "RECEIVE_RECOMMENDATIONS", "Value": "YES"},
1383
+ {"Option": "TRACK_POSITION_CHANGES", "Value": "NO"},
1384
+ ],
1383
1385
  },
1384
1386
  }
1385
1387
  body = _dry_run(
@@ -1584,8 +1586,10 @@ def test_agencyclients_add_passport_organization_payload():
1584
1586
  "Notification": {
1585
1587
  "Email": "ops@example.com",
1586
1588
  "Lang": "EN",
1587
- "SendAccountNews": "NO",
1588
- "SendWarnings": "YES",
1589
+ "EmailSubscriptions": [
1590
+ {"Option": "RECEIVE_RECOMMENDATIONS", "Value": "NO"},
1591
+ {"Option": "TRACK_POSITION_CHANGES", "Value": "YES"},
1592
+ ],
1589
1593
  },
1590
1594
  }
1591
1595