direct-cli 0.2.5__tar.gz → 0.2.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 (135) hide show
  1. direct_cli-0.2.7/.github/workflows/api-coverage.yml +152 -0
  2. {direct_cli-0.2.5 → direct_cli-0.2.7}/AGENTS.md +116 -0
  3. direct_cli-0.2.7/PKG-INFO +921 -0
  4. direct_cli-0.2.7/README.md +884 -0
  5. {direct_cli-0.2.5 → direct_cli-0.2.7}/direct_cli/api.py +18 -5
  6. {direct_cli-0.2.5 → direct_cli-0.2.7}/direct_cli/cli.py +2 -5
  7. {direct_cli-0.2.5 → direct_cli-0.2.7}/direct_cli/commands/adextensions.py +17 -36
  8. {direct_cli-0.2.5 → direct_cli-0.2.7}/direct_cli/commands/adgroups.py +57 -44
  9. {direct_cli-0.2.5 → direct_cli-0.2.7}/direct_cli/commands/adimages.py +32 -14
  10. {direct_cli-0.2.5 → direct_cli-0.2.7}/direct_cli/commands/ads.py +104 -63
  11. direct_cli-0.2.5/direct_cli/commands/agencyclients.py → direct_cli-0.2.7/direct_cli/commands/advideos.py +36 -35
  12. direct_cli-0.2.7/direct_cli/commands/agencyclients.py +318 -0
  13. {direct_cli-0.2.5 → direct_cli-0.2.7}/direct_cli/commands/audiencetargets.py +115 -36
  14. {direct_cli-0.2.5 → direct_cli-0.2.7}/direct_cli/commands/bidmodifiers.py +84 -41
  15. {direct_cli-0.2.5 → direct_cli-0.2.7}/direct_cli/commands/bids.py +72 -14
  16. {direct_cli-0.2.5 → direct_cli-0.2.7}/direct_cli/commands/campaigns.py +163 -67
  17. {direct_cli-0.2.5 → direct_cli-0.2.7}/direct_cli/commands/changes.py +9 -9
  18. {direct_cli-0.2.5 → direct_cli-0.2.7}/direct_cli/commands/clients.py +18 -10
  19. {direct_cli-0.2.5 → direct_cli-0.2.7}/direct_cli/commands/creatives.py +30 -4
  20. direct_cli-0.2.7/direct_cli/commands/dictionaries.py +103 -0
  21. direct_cli-0.2.7/direct_cli/commands/dynamicads.py +256 -0
  22. {direct_cli-0.2.5 → direct_cli-0.2.7}/direct_cli/commands/feeds.py +11 -59
  23. direct_cli-0.2.7/direct_cli/commands/keywordbids.py +190 -0
  24. {direct_cli-0.2.5 → direct_cli-0.2.7}/direct_cli/commands/keywords.py +61 -51
  25. {direct_cli-0.2.5 → direct_cli-0.2.7}/direct_cli/commands/keywordsresearch.py +0 -33
  26. {direct_cli-0.2.5 → direct_cli-0.2.7}/direct_cli/commands/leads.py +0 -3
  27. {direct_cli-0.2.5 → direct_cli-0.2.7}/direct_cli/commands/negativekeywordsharedsets.py +10 -15
  28. direct_cli-0.2.7/direct_cli/commands/reports.py +387 -0
  29. {direct_cli-0.2.5 → direct_cli-0.2.7}/direct_cli/commands/retargeting.py +76 -24
  30. {direct_cli-0.2.5 → direct_cli-0.2.7}/direct_cli/commands/sitelinks.py +22 -11
  31. direct_cli-0.2.7/direct_cli/commands/smartadtargets.py +358 -0
  32. {direct_cli-0.2.5 → direct_cli-0.2.7}/direct_cli/commands/turbopages.py +0 -3
  33. {direct_cli-0.2.5 → direct_cli-0.2.7}/direct_cli/commands/vcards.py +82 -10
  34. direct_cli-0.2.7/direct_cli/reports_coverage.py +242 -0
  35. direct_cli-0.2.7/direct_cli/utils.py +266 -0
  36. direct_cli-0.2.7/direct_cli/wsdl_coverage.py +334 -0
  37. direct_cli-0.2.7/direct_cli.egg-info/PKG-INFO +921 -0
  38. {direct_cli-0.2.5 → direct_cli-0.2.7}/direct_cli.egg-info/SOURCES.txt +45 -1
  39. {direct_cli-0.2.5 → direct_cli-0.2.7}/direct_cli.egg-info/requires.txt +3 -0
  40. direct_cli-0.2.7/docs/superpowers/plans/2026-04-12-issue-32-completion.md +548 -0
  41. {direct_cli-0.2.5 → direct_cli-0.2.7}/pyproject.toml +5 -1
  42. direct_cli-0.2.7/scripts/build_api_coverage_report.py +108 -0
  43. direct_cli-0.2.7/scripts/check_reports_drift.py +136 -0
  44. direct_cli-0.2.7/scripts/check_wsdl_drift.py +62 -0
  45. direct_cli-0.2.7/scripts/refresh_reports_cache.py +44 -0
  46. direct_cli-0.2.7/scripts/refresh_wsdl_cache.py +39 -0
  47. {direct_cli-0.2.5 → direct_cli-0.2.7}/tests/conftest.py +9 -33
  48. direct_cli-0.2.7/tests/reports_cache/raw/fields-list.html +3316 -0
  49. direct_cli-0.2.7/tests/reports_cache/raw/headers.html +88 -0
  50. direct_cli-0.2.7/tests/reports_cache/raw/spec.html +119 -0
  51. direct_cli-0.2.7/tests/reports_cache/raw/type.html +279 -0
  52. direct_cli-0.2.7/tests/reports_cache/spec.json +583 -0
  53. direct_cli-0.2.7/tests/test_api_coverage.py +1220 -0
  54. direct_cli-0.2.7/tests/test_cli.py +167 -0
  55. {direct_cli-0.2.5 → direct_cli-0.2.7}/tests/test_comprehensive.py +1 -3
  56. {direct_cli-0.2.5 → direct_cli-0.2.7}/tests/test_dry_run.py +852 -263
  57. {direct_cli-0.2.5 → direct_cli-0.2.7}/tests/test_integration.py +61 -0
  58. {direct_cli-0.2.5 → direct_cli-0.2.7}/tests/test_integration_write.py +85 -123
  59. direct_cli-0.2.7/tests/test_reports_drift.py +102 -0
  60. direct_cli-0.2.7/tests/wsdl_cache/adextensions.xml +189 -0
  61. direct_cli-0.2.7/tests/wsdl_cache/adgroups.xml +551 -0
  62. direct_cli-0.2.7/tests/wsdl_cache/adimages.xml +270 -0
  63. direct_cli-0.2.7/tests/wsdl_cache/ads.xml +1622 -0
  64. direct_cli-0.2.7/tests/wsdl_cache/advideos.xml +153 -0
  65. direct_cli-0.2.7/tests/wsdl_cache/agencyclients.xml +310 -0
  66. direct_cli-0.2.7/tests/wsdl_cache/audiencetargets.xml +339 -0
  67. direct_cli-0.2.7/tests/wsdl_cache/bidmodifiers.xml +536 -0
  68. direct_cli-0.2.7/tests/wsdl_cache/bids.xml +262 -0
  69. direct_cli-0.2.7/tests/wsdl_cache/businesses.xml +106 -0
  70. direct_cli-0.2.7/tests/wsdl_cache/campaigns.xml +2809 -0
  71. direct_cli-0.2.7/tests/wsdl_cache/changes.xml +190 -0
  72. direct_cli-0.2.7/tests/wsdl_cache/clients.xml +132 -0
  73. direct_cli-0.2.7/tests/wsdl_cache/creatives.xml +239 -0
  74. direct_cli-0.2.7/tests/wsdl_cache/dictionaries.xml +327 -0
  75. direct_cli-0.2.7/tests/wsdl_cache/dynamictextadtargets.xml +338 -0
  76. direct_cli-0.2.7/tests/wsdl_cache/feeds.xml +339 -0
  77. direct_cli-0.2.7/tests/wsdl_cache/keywordbids.xml +291 -0
  78. direct_cli-0.2.7/tests/wsdl_cache/keywords.xml +362 -0
  79. direct_cli-0.2.7/tests/wsdl_cache/keywordsresearch.xml +174 -0
  80. direct_cli-0.2.7/tests/wsdl_cache/leads.xml +107 -0
  81. direct_cli-0.2.7/tests/wsdl_cache/negativekeywordsharedsets.xml +232 -0
  82. direct_cli-0.2.7/tests/wsdl_cache/retargetinglists.xml +283 -0
  83. direct_cli-0.2.7/tests/wsdl_cache/sitelinks.xml +189 -0
  84. direct_cli-0.2.7/tests/wsdl_cache/smartadtargets.xml +395 -0
  85. direct_cli-0.2.7/tests/wsdl_cache/turbopages.xml +103 -0
  86. direct_cli-0.2.7/tests/wsdl_cache/vcards.xml +238 -0
  87. direct_cli-0.2.5/PKG-INFO +0 -551
  88. direct_cli-0.2.5/README.md +0 -517
  89. direct_cli-0.2.5/direct_cli/commands/dictionaries.py +0 -65
  90. direct_cli-0.2.5/direct_cli/commands/dynamicads.py +0 -168
  91. direct_cli-0.2.5/direct_cli/commands/keywordbids.py +0 -110
  92. direct_cli-0.2.5/direct_cli/commands/reports.py +0 -133
  93. direct_cli-0.2.5/direct_cli/commands/smartadtargets.py +0 -208
  94. direct_cli-0.2.5/direct_cli/utils.py +0 -120
  95. direct_cli-0.2.5/direct_cli.egg-info/PKG-INFO +0 -551
  96. direct_cli-0.2.5/tests/test_cli.py +0 -116
  97. {direct_cli-0.2.5 → direct_cli-0.2.7}/.env.example +0 -0
  98. {direct_cli-0.2.5 → direct_cli-0.2.7}/.github/copilot-instructions.md +0 -0
  99. {direct_cli-0.2.5 → direct_cli-0.2.7}/.github/workflows/claude-code-review.yml +0 -0
  100. {direct_cli-0.2.5 → direct_cli-0.2.7}/.github/workflows/claude.yml +0 -0
  101. {direct_cli-0.2.5 → direct_cli-0.2.7}/.gitignore +0 -0
  102. {direct_cli-0.2.5 → direct_cli-0.2.7}/CLAUDE.md +0 -0
  103. {direct_cli-0.2.5 → direct_cli-0.2.7}/MANIFEST.in +0 -0
  104. {direct_cli-0.2.5 → direct_cli-0.2.7}/direct_cli/__init__.py +0 -0
  105. {direct_cli-0.2.5 → direct_cli-0.2.7}/direct_cli/auth.py +0 -0
  106. {direct_cli-0.2.5 → direct_cli-0.2.7}/direct_cli/commands/__init__.py +0 -0
  107. {direct_cli-0.2.5 → direct_cli-0.2.7}/direct_cli/commands/businesses.py +0 -0
  108. {direct_cli-0.2.5 → direct_cli-0.2.7}/direct_cli/output.py +0 -0
  109. {direct_cli-0.2.5 → direct_cli-0.2.7}/direct_cli.egg-info/dependency_links.txt +0 -0
  110. {direct_cli-0.2.5 → direct_cli-0.2.7}/direct_cli.egg-info/entry_points.txt +0 -0
  111. {direct_cli-0.2.5 → direct_cli-0.2.7}/direct_cli.egg-info/top_level.txt +0 -0
  112. {direct_cli-0.2.5 → direct_cli-0.2.7}/scripts/release_pypi.sh +0 -0
  113. {direct_cli-0.2.5 → direct_cli-0.2.7}/setup.cfg +0 -0
  114. {direct_cli-0.2.5 → direct_cli-0.2.7}/setup.py +0 -0
  115. {direct_cli-0.2.5 → direct_cli-0.2.7}/tests/__init__.py +0 -0
  116. {direct_cli-0.2.5 → direct_cli-0.2.7}/tests/cassettes/test_integration_write/TestWriteAdExtensions.test_add_delete.yaml +0 -0
  117. {direct_cli-0.2.5 → direct_cli-0.2.7}/tests/cassettes/test_integration_write/TestWriteAdGroups.test_add_update_delete.yaml +0 -0
  118. {direct_cli-0.2.5 → direct_cli-0.2.7}/tests/cassettes/test_integration_write/TestWriteAdImages.test_add_delete.yaml +0 -0
  119. {direct_cli-0.2.5 → direct_cli-0.2.7}/tests/cassettes/test_integration_write/TestWriteAds.test_add_text_ad_update_delete.yaml +0 -0
  120. {direct_cli-0.2.5 → direct_cli-0.2.7}/tests/cassettes/test_integration_write/TestWriteAudienceTargets.test_add_delete.yaml +0 -0
  121. {direct_cli-0.2.5 → direct_cli-0.2.7}/tests/cassettes/test_integration_write/TestWriteBidModifiersAdd.test_add_delete_mobile.yaml +0 -0
  122. {direct_cli-0.2.5 → direct_cli-0.2.7}/tests/cassettes/test_integration_write/TestWriteBidModifiersSet.test_set_without_id_is_rejected.yaml +0 -0
  123. {direct_cli-0.2.5 → direct_cli-0.2.7}/tests/cassettes/test_integration_write/TestWriteBids.test_set_bid.yaml +0 -0
  124. {direct_cli-0.2.5 → direct_cli-0.2.7}/tests/cassettes/test_integration_write/TestWriteCampaigns.test_campaign_lifecycle.yaml +0 -0
  125. {direct_cli-0.2.5 → direct_cli-0.2.7}/tests/cassettes/test_integration_write/TestWriteDynamicAds.test_add_update_delete.yaml +0 -0
  126. {direct_cli-0.2.5 → direct_cli-0.2.7}/tests/cassettes/test_integration_write/TestWriteFeeds.test_add_update_delete.yaml +0 -0
  127. {direct_cli-0.2.5 → direct_cli-0.2.7}/tests/cassettes/test_integration_write/TestWriteKeywordBids.test_set_keyword_bid.yaml +0 -0
  128. {direct_cli-0.2.5 → direct_cli-0.2.7}/tests/cassettes/test_integration_write/TestWriteKeywords.test_add_update_delete.yaml +0 -0
  129. {direct_cli-0.2.5 → direct_cli-0.2.7}/tests/cassettes/test_integration_write/TestWriteNegativeKeywordSharedSets.test_add_update_delete.yaml +0 -0
  130. {direct_cli-0.2.5 → direct_cli-0.2.7}/tests/cassettes/test_integration_write/TestWriteRetargeting.test_add_delete.yaml +0 -0
  131. {direct_cli-0.2.5 → direct_cli-0.2.7}/tests/cassettes/test_integration_write/TestWriteSitelinks.test_add_delete.yaml +0 -0
  132. {direct_cli-0.2.5 → direct_cli-0.2.7}/tests/cassettes/test_integration_write/TestWriteSmartAdTargets.test_add_update_delete.yaml +0 -0
  133. {direct_cli-0.2.5 → direct_cli-0.2.7}/tests/cassettes/test_integration_write/TestWriteVCards.test_add_delete.yaml +0 -0
  134. {direct_cli-0.2.5 → direct_cli-0.2.7}/tests/test_auth_bw.py +0 -0
  135. {direct_cli-0.2.5 → direct_cli-0.2.7}/tests/test_auth_op.py +0 -0
@@ -0,0 +1,152 @@
1
+ name: API Coverage
2
+
3
+ on:
4
+ push:
5
+ branches: ["main"]
6
+ pull_request:
7
+ schedule:
8
+ - cron: "0 6 * * 1"
9
+ workflow_dispatch:
10
+
11
+ jobs:
12
+ test-and-report:
13
+ runs-on: ubuntu-latest
14
+ strategy:
15
+ fail-fast: false
16
+ matrix:
17
+ python-version: ["3.9", "3.11", "3.13"]
18
+ steps:
19
+ - name: Checkout repository
20
+ uses: actions/checkout@v4
21
+
22
+ - name: Set up Python
23
+ uses: actions/setup-python@v5
24
+ with:
25
+ python-version: ${{ matrix.python-version }}
26
+
27
+ - name: Install package with dev dependencies
28
+ run: |
29
+ python -m pip install --upgrade pip
30
+ pip install -e ".[dev]"
31
+
32
+ - name: Run fast coverage suites
33
+ run: |
34
+ pytest -q tests/test_api_coverage.py tests/test_dry_run.py tests/test_cli.py tests/test_comprehensive.py tests/test_reports_drift.py
35
+
36
+ - name: Build API coverage report
37
+ run: |
38
+ python scripts/build_api_coverage_report.py > api_coverage_report.json
39
+
40
+ - name: Publish API coverage summary
41
+ run: |
42
+ python - <<'PY' >> "$GITHUB_STEP_SUMMARY"
43
+ import json
44
+ from pathlib import Path
45
+
46
+ report = json.loads(Path("api_coverage_report.json").read_text())
47
+ summary = report["summary"]
48
+ print("## API coverage summary")
49
+ print()
50
+ print(f"- Services checked: {summary['services_checked']}")
51
+ print(f"- Missing methods: {summary['missing_service_methods']}")
52
+ print(f"- Unexpected methods: {summary['unexpected_service_methods']}")
53
+ print(f"- Strict parity OK: {summary['strict_parity_ok']}")
54
+ print(f"- Non-WSDL services: {len(report['non_wsdl_services'])}")
55
+ print(f"- CLI helpers: {len(report['cli_helpers'])}")
56
+ PY
57
+
58
+ - name: Upload API coverage report
59
+ uses: actions/upload-artifact@v4
60
+ with:
61
+ name: api-coverage-report-py${{ matrix.python-version }}
62
+ path: api_coverage_report.json
63
+
64
+ monitor-live-wsdl:
65
+ if: github.event_name == 'schedule' || github.event_name == 'workflow_dispatch'
66
+ runs-on: ubuntu-latest
67
+ steps:
68
+ - name: Checkout repository
69
+ uses: actions/checkout@v4
70
+
71
+ - name: Set up Python
72
+ uses: actions/setup-python@v5
73
+ with:
74
+ python-version: "3.11"
75
+
76
+ - name: Install package with dev dependencies
77
+ run: |
78
+ python -m pip install --upgrade pip
79
+ pip install -e ".[dev]"
80
+
81
+ - name: Check live WSDL drift
82
+ run: |
83
+ python scripts/check_wsdl_drift.py > wsdl_drift_report.json
84
+
85
+ - name: Publish WSDL drift summary
86
+ if: always()
87
+ run: |
88
+ python - <<'PY' >> "$GITHUB_STEP_SUMMARY"
89
+ import json
90
+ from pathlib import Path
91
+
92
+ report = json.loads(Path("wsdl_drift_report.json").read_text())
93
+ print("## WSDL drift summary")
94
+ print()
95
+ print(f"- Services checked: {report['services_checked']}")
96
+ print(f"- Missing cache files: {report['missing_cache_count']}")
97
+ print(f"- Drifted services: {report['drift_count']}")
98
+ for item in report["drift"][:10]:
99
+ print(f"- Drift: {item['service']}")
100
+ PY
101
+
102
+ - name: Upload WSDL drift report
103
+ if: always()
104
+ uses: actions/upload-artifact@v4
105
+ with:
106
+ name: wsdl-drift-report
107
+ path: wsdl_drift_report.json
108
+
109
+ monitor-live-reports:
110
+ if: github.event_name == 'schedule' || github.event_name == 'workflow_dispatch'
111
+ runs-on: ubuntu-latest
112
+ steps:
113
+ - name: Checkout repository
114
+ uses: actions/checkout@v4
115
+
116
+ - name: Set up Python
117
+ uses: actions/setup-python@v5
118
+ with:
119
+ python-version: "3.11"
120
+
121
+ - name: Install package with dev dependencies
122
+ run: |
123
+ python -m pip install --upgrade pip
124
+ pip install -e ".[dev]"
125
+
126
+ - name: Check live reports drift
127
+ run: |
128
+ python scripts/check_reports_drift.py > reports_drift_report.json
129
+
130
+ - name: Publish reports drift summary
131
+ if: always()
132
+ run: |
133
+ python - <<'PY' >> "$GITHUB_STEP_SUMMARY"
134
+ import json
135
+ from pathlib import Path
136
+
137
+ report = json.loads(Path("reports_drift_report.json").read_text())
138
+ print("## Reports drift summary")
139
+ print()
140
+ print(f"- Sources checked: {report.get('sources_checked', 0)}")
141
+ print(f"- Missing cache files: {report.get('missing_cache_count', 0)}")
142
+ print(f"- Drifted sections: {report.get('drift_count', 0)}")
143
+ for item in report.get("drift", [])[:10]:
144
+ print(f"- Drift: {item['section']}")
145
+ PY
146
+
147
+ - name: Upload reports drift report
148
+ if: always()
149
+ uses: actions/upload-artifact@v4
150
+ with:
151
+ name: reports-drift-report
152
+ path: reports_drift_report.json
@@ -6,6 +6,122 @@ This document provides essential information for AI coding agents working on the
6
6
 
7
7
  Direct CLI is a command-line interface for the Yandex Direct API, built with Python and Click. It provides commands for managing campaigns, ad groups, ads, keywords, reports, and other Yandex Direct resources.
8
8
 
9
+ ## CLI Convention
10
+
11
+ The current CLI convention is defined as follows.
12
+
13
+ ### CLI Contract
14
+
15
+ The canonical command shape is:
16
+
17
+ ```bash
18
+ direct <group> <command> [flags]
19
+ ```
20
+
21
+ Naming rules:
22
+
23
+ - `group`:
24
+ - lowercase ASCII only
25
+ - no underscores
26
+ - multiword groups are concatenated
27
+ - examples: `dynamicads`, `smartadtargets`, `negativekeywordsharedsets`
28
+
29
+ - `command`:
30
+ - lowercase only
31
+ - multiword commands use kebab-case
32
+ - examples: `get`, `set-bids`, `check-campaigns`, `has-search-volume`
33
+
34
+ ### Input Rules
35
+
36
+ - All user-facing input must be passed only through typed CLI flags.
37
+ - `--json` is not part of the public CLI contract.
38
+ - User-facing parameters must not be passed through `--json`.
39
+ - The CLI must not accept `SelectionCriteria`, nested payloads, update payloads, bidding rules, or any other user-facing command input through `--json`.
40
+ - Typed flags and JSON blobs must not be mixed as part of one public command contract.
41
+ - If the API requires a complex object, the CLI must expose explicit flags or subcommands instead of forwarding raw JSON.
42
+
43
+ ### Command Formatting Rules
44
+
45
+ - Every canonical CLI command must be written strictly on a single line.
46
+ - Multi-line command formatting is not allowed.
47
+ - Shell line continuation using `\` is forbidden in canonical documentation, help text, tests, and examples.
48
+
49
+ Allowed:
50
+
51
+ ```bash
52
+ direct dictionaries get-geo-regions --region-ids 225,187 --fields GeoRegionId,GeoRegionName
53
+ ```
54
+
55
+ Not allowed:
56
+
57
+ ```bash
58
+ direct dictionaries get-geo-regions \
59
+ --region-ids 225,187 \
60
+ --fields GeoRegionId,GeoRegionName
61
+ ```
62
+
63
+ ### Flag Design Rules
64
+
65
+ - List inputs use comma-separated CLI syntax where appropriate.
66
+ - Money and bid values stay human-readable in CLI input and are converted internally to the API wire format.
67
+ - Selector fields remain explicit flags, for example:
68
+ - `--id`
69
+ - `--campaign-id`
70
+ - `--adgroup-id`
71
+ - Nested API structures must be projected into typed flags instead of blob JSON.
72
+ - Help text must not advertise JSON as an alternative input path.
73
+
74
+ ### Datetime Rules
75
+
76
+ - Datetime parameters must be passed in the format `YYYY-MM-DDTHH:MM:SS`.
77
+ - Datetime values must be passed as a single shell token.
78
+ - Canonical examples must not use timezone suffixes like `Z`.
79
+ - Canonical examples must not use quoted space-separated datetime values.
80
+
81
+ Use:
82
+
83
+ ```bash
84
+ direct changes check-campaigns --timestamp 2026-04-14T00:00:00
85
+ ```
86
+
87
+ Do not use:
88
+
89
+ ```bash
90
+ direct changes check-campaigns --timestamp 2026-04-14T00:00:00Z
91
+ direct changes check-campaigns --timestamp "2026-04-14 00:00:00"
92
+ ```
93
+
94
+ ### Documentation Contract
95
+
96
+ - `README` must use only canonical syntax.
97
+ - `README` must use only single-line command examples.
98
+ - Canonical examples must not contain `--json`.
99
+ - Help output and tests must enforce the same contract.
100
+
101
+ ### Examples
102
+
103
+ Valid canonical examples:
104
+
105
+ ```bash
106
+ direct campaigns get --ids 1,2,3
107
+ direct changes check-campaigns --timestamp 2026-04-14T00:00:00
108
+ direct keywordsresearch has-search-volume --keywords "buy laptop,buy desktop"
109
+ direct dynamicads set-bids --id 789 --bid 12.5
110
+ direct dictionaries get-geo-regions --region-ids 225 --fields GeoRegionId,GeoRegionName
111
+ ```
112
+
113
+ Invalid examples:
114
+
115
+ ```bash
116
+ direct dictionaries get-geo-regions --json '{"GeoRegionIds":[225]}' --fields GeoRegionId,GeoRegionName
117
+ direct dynamicads set-bids --id 789 --bid 12.5 --json '{"StrategyPriority":"HIGH"}'
118
+ direct dictionaries get-geo-regions \
119
+ --region-ids 225 \
120
+ --fields GeoRegionId,GeoRegionName
121
+ direct changes check-campaigns --timestamp 2026-04-14T00:00:00Z
122
+ direct changes check-campaigns --timestamp "2026-04-14 00:00:00"
123
+ ```
124
+
9
125
  ## Build, Test, and Lint Commands
10
126
 
11
127
  ### Installation