direct-cli 0.5.2__tar.gz → 0.5.3__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.
- {direct_cli-0.5.2 → direct_cli-0.5.3}/.github/workflows/quality.yml +3 -0
- {direct_cli-0.5.2 → direct_cli-0.5.3}/CHANGELOG.md +574 -3
- {direct_cli-0.5.2 → direct_cli-0.5.3}/PKG-INFO +98 -2
- {direct_cli-0.5.2 → direct_cli-0.5.3}/README.md +96 -1
- {direct_cli-0.5.2 → direct_cli-0.5.3}/direct_cli/auth.py +3 -10
- {direct_cli-0.5.2 → direct_cli-0.5.3}/direct_cli/browser/__init__.py +4 -2
- {direct_cli-0.5.2 → direct_cli-0.5.3}/direct_cli/browser/_chrome_crypto.py +35 -0
- direct_cli-0.5.3/direct_cli/browser/change_history.py +360 -0
- {direct_cli-0.5.2 → direct_cli-0.5.3}/direct_cli/browser/masters.py +2254 -314
- direct_cli-0.5.3/direct_cli/browser/session.py +1224 -0
- {direct_cli-0.5.2 → direct_cli-0.5.3}/direct_cli/cli.py +32 -15
- {direct_cli-0.5.2 → direct_cli-0.5.3}/direct_cli/commands/_lifecycle.py +1 -1
- {direct_cli-0.5.2 → direct_cli-0.5.3}/direct_cli/commands/auth.py +3 -6
- {direct_cli-0.5.2 → direct_cli-0.5.3}/direct_cli/commands/balance.py +3 -1
- {direct_cli-0.5.2 → direct_cli-0.5.3}/direct_cli/commands/bidmodifiers.py +35 -4
- {direct_cli-0.5.2 → direct_cli-0.5.3}/direct_cli/commands/browser_session.py +6 -1
- {direct_cli-0.5.2 → direct_cli-0.5.3}/direct_cli/commands/clients.py +20 -1
- {direct_cli-0.5.2 → direct_cli-0.5.3}/direct_cli/commands/dictionaries.py +57 -3
- direct_cli-0.5.3/direct_cli/commands/history.py +154 -0
- {direct_cli-0.5.2 → direct_cli-0.5.3}/direct_cli/commands/masters.py +1410 -53
- {direct_cli-0.5.2 → direct_cli-0.5.3}/direct_cli/commands/sitelinks.py +1 -0
- {direct_cli-0.5.2 → direct_cli-0.5.3}/direct_cli/commands/strategies.py +3 -1
- {direct_cli-0.5.2 → direct_cli-0.5.3}/direct_cli/commands/v4events.py +2 -2
- {direct_cli-0.5.2 → direct_cli-0.5.3}/direct_cli/commands/v4finance.py +5 -2
- {direct_cli-0.5.2 → direct_cli-0.5.3}/direct_cli/commands/v4wordstat.py +2 -2
- {direct_cli-0.5.2 → direct_cli-0.5.3}/direct_cli/i18n.py +28 -2
- {direct_cli-0.5.2 → direct_cli-0.5.3}/direct_cli/output.py +74 -1
- {direct_cli-0.5.2 → direct_cli-0.5.3}/direct_cli/smoke_matrix.py +15 -0
- {direct_cli-0.5.2 → direct_cli-0.5.3}/direct_cli/translations/bidmodifiers.json +3 -1
- {direct_cli-0.5.2 → direct_cli-0.5.3}/direct_cli/translations/clients.json +2 -1
- direct_cli-0.5.3/direct_cli/translations/dictionaries.json +14 -0
- {direct_cli-0.5.2 → direct_cli-0.5.3}/direct_cli/v4/parse.py +5 -4
- {direct_cli-0.5.2 → direct_cli-0.5.3}/direct_cli/wsdl_coverage.py +13 -1
- {direct_cli-0.5.2 → direct_cli-0.5.3}/direct_cli.egg-info/PKG-INFO +98 -2
- {direct_cli-0.5.2 → direct_cli-0.5.3}/direct_cli.egg-info/SOURCES.txt +7 -0
- {direct_cli-0.5.2 → direct_cli-0.5.3}/direct_cli.egg-info/requires.txt +1 -0
- {direct_cli-0.5.2 → direct_cli-0.5.3}/pyproject.toml +23 -1
- {direct_cli-0.5.2 → direct_cli-0.5.3}/scripts/audit_wire_shape.py +1 -1
- {direct_cli-0.5.2 → direct_cli-0.5.3}/scripts/build_api_coverage_checklist.py +3 -1
- {direct_cli-0.5.2 → direct_cli-0.5.3}/scripts/build_api_coverage_report.py +7 -12
- direct_cli-0.5.3/scripts/recon_812_video_upload.py +382 -0
- direct_cli-0.5.3/scripts/recon_871_bidmodifiers_enum.py +118 -0
- {direct_cli-0.5.2 → direct_cli-0.5.3}/scripts/sandbox_write_audit.py +2 -3
- {direct_cli-0.5.2 → direct_cli-0.5.3}/setup.cfg +1 -0
- {direct_cli-0.5.2 → direct_cli-0.5.3}/tests/WSDL_OPTIONAL_FIELD_AUDIT.md +2 -1
- direct_cli-0.5.3/tests/_cli_tree.py +19 -0
- {direct_cli-0.5.2 → direct_cli-0.5.3}/tests/api_coverage_payloads.py +6 -0
- {direct_cli-0.5.2 → direct_cli-0.5.3}/tests/conftest.py +7 -2
- direct_cli-0.5.3/tests/fixtures/user_action_log.json +85 -0
- {direct_cli-0.5.2 → direct_cli-0.5.3}/tests/test_adgroups_build_adgroup_object.py +54 -54
- {direct_cli-0.5.2 → direct_cli-0.5.3}/tests/test_adgroups_build_adgroup_update_object.py +14 -14
- {direct_cli-0.5.2 → direct_cli-0.5.3}/tests/test_ads_build_ad_object.py +46 -42
- {direct_cli-0.5.2 → direct_cli-0.5.3}/tests/test_ads_build_ad_update_object.py +24 -20
- {direct_cli-0.5.2 → direct_cli-0.5.3}/tests/test_api_coverage.py +10 -5
- {direct_cli-0.5.2 → direct_cli-0.5.3}/tests/test_audit_wire_shape.py +1 -1
- {direct_cli-0.5.2 → direct_cli-0.5.3}/tests/test_auth_oauth.py +2 -1
- {direct_cli-0.5.2 → direct_cli-0.5.3}/tests/test_balance.py +2 -1
- direct_cli-0.5.3/tests/test_change_history.py +653 -0
- {direct_cli-0.5.2 → direct_cli-0.5.3}/tests/test_chrome_crypto.py +41 -0
- {direct_cli-0.5.2 → direct_cli-0.5.3}/tests/test_cli.py +75 -0
- {direct_cli-0.5.2 → direct_cli-0.5.3}/tests/test_comprehensive.py +1 -0
- {direct_cli-0.5.2 → direct_cli-0.5.3}/tests/test_dry_run_bids.py +64 -0
- {direct_cli-0.5.2 → direct_cli-0.5.3}/tests/test_dry_run_clients.py +14 -0
- {direct_cli-0.5.2 → direct_cli-0.5.3}/tests/test_i18n.py +18 -2
- {direct_cli-0.5.2 → direct_cli-0.5.3}/tests/test_integration_write.py +2 -2
- {direct_cli-0.5.2 → direct_cli-0.5.3}/tests/test_low_coverage_payloads.py +57 -0
- {direct_cli-0.5.2 → direct_cli-0.5.3}/tests/test_masters.py +20764 -15541
- direct_cli-0.5.3/tests/test_playwright_session.py +706 -0
- {direct_cli-0.5.2 → direct_cli-0.5.3}/tests/test_reports_parsing.py +3 -1
- {direct_cli-0.5.2 → direct_cli-0.5.3}/tests/test_smoke_matrix.py +3 -2
- {direct_cli-0.5.2 → direct_cli-0.5.3}/tests/test_transport_contract.py +6 -2
- {direct_cli-0.5.2 → direct_cli-0.5.3}/tests/test_v4_contracts.py +6 -2
- {direct_cli-0.5.2 → direct_cli-0.5.3}/tests/test_v4_output_options.py +1 -0
- {direct_cli-0.5.2 → direct_cli-0.5.3}/tests/test_v4account.py +3 -2
- {direct_cli-0.5.2 → direct_cli-0.5.3}/tests/test_v4adimage.py +2 -1
- {direct_cli-0.5.2 → direct_cli-0.5.3}/tests/test_v4events.py +2 -1
- {direct_cli-0.5.2 → direct_cli-0.5.3}/tests/test_v4finance_money.py +2 -1
- {direct_cli-0.5.2 → direct_cli-0.5.3}/tests/test_v4finance_read.py +2 -1
- {direct_cli-0.5.2 → direct_cli-0.5.3}/tests/test_v4forecast.py +2 -1
- {direct_cli-0.5.2 → direct_cli-0.5.3}/tests/test_v4goals.py +2 -1
- {direct_cli-0.5.2 → direct_cli-0.5.3}/tests/test_v4keywords.py +2 -1
- {direct_cli-0.5.2 → direct_cli-0.5.3}/tests/test_v4tags.py +2 -1
- {direct_cli-0.5.2 → direct_cli-0.5.3}/tests/test_v4wordstat.py +2 -1
- {direct_cli-0.5.2 → direct_cli-0.5.3}/tests/test_v5_live_write.py +7 -10
- {direct_cli-0.5.2 → direct_cli-0.5.3}/tests/test_vendor_imports.py +2 -2
- {direct_cli-0.5.2 → direct_cli-0.5.3}/tests/test_wsdl_parity_gate.py +14 -2
- {direct_cli-0.5.2 → direct_cli-0.5.3}/tests/wsdl_cache/adimages.xml +4 -0
- {direct_cli-0.5.2 → direct_cli-0.5.3}/tests/wsdl_cache/campaigns.xml +6 -0
- {direct_cli-0.5.2 → direct_cli-0.5.3}/tests/wsdl_cache/imports/generalclients.xsd +2 -0
- {direct_cli-0.5.2 → direct_cli-0.5.3}/tests/wsdl_cache/strategies.xml +14 -0
- direct_cli-0.5.2/direct_cli/browser/session.py +0 -709
- direct_cli-0.5.2/direct_cli/translations/dictionaries.json +0 -10
- direct_cli-0.5.2/tests/test_playwright_session.py +0 -306
- {direct_cli-0.5.2 → direct_cli-0.5.3}/.env.example +0 -0
- {direct_cli-0.5.2 → direct_cli-0.5.3}/.github/copilot-instructions.md +0 -0
- {direct_cli-0.5.2 → direct_cli-0.5.3}/.github/workflows/api-coverage.yml +0 -0
- {direct_cli-0.5.2 → direct_cli-0.5.3}/.github/workflows/claude.yml +0 -0
- {direct_cli-0.5.2 → direct_cli-0.5.3}/.gitignore +0 -0
- {direct_cli-0.5.2 → direct_cli-0.5.3}/.mcp.json +0 -0
- {direct_cli-0.5.2 → direct_cli-0.5.3}/AGENTS.md +0 -0
- {direct_cli-0.5.2 → direct_cli-0.5.3}/CLAUDE.md +0 -0
- {direct_cli-0.5.2 → direct_cli-0.5.3}/MANIFEST.in +0 -0
- {direct_cli-0.5.2 → direct_cli-0.5.3}/README_ru.md +0 -0
- {direct_cli-0.5.2 → direct_cli-0.5.3}/direct_cli/__init__.py +0 -0
- {direct_cli-0.5.2 → direct_cli-0.5.3}/direct_cli/_autotargeting.py +0 -0
- {direct_cli-0.5.2 → direct_cli-0.5.3}/direct_cli/_bidding_strategy.py +0 -0
- {direct_cli-0.5.2 → direct_cli-0.5.3}/direct_cli/_captcha.py +0 -0
- {direct_cli-0.5.2 → direct_cli-0.5.3}/direct_cli/_deprecated.py +0 -0
- {direct_cli-0.5.2 → direct_cli-0.5.3}/direct_cli/_flag_validation.py +0 -0
- {direct_cli-0.5.2 → direct_cli-0.5.3}/direct_cli/_smoke_probes.py +0 -0
- {direct_cli-0.5.2 → direct_cli-0.5.3}/direct_cli/_vendor/__init__.py +0 -0
- {direct_cli-0.5.2 → direct_cli-0.5.3}/direct_cli/_vendor/tapi_yandex_direct/__init__.py +0 -0
- {direct_cli-0.5.2 → direct_cli-0.5.3}/direct_cli/_vendor/tapi_yandex_direct/endpoints.py +0 -0
- {direct_cli-0.5.2 → direct_cli-0.5.3}/direct_cli/_vendor/tapi_yandex_direct/exceptions.py +0 -0
- {direct_cli-0.5.2 → direct_cli-0.5.3}/direct_cli/_vendor/tapi_yandex_direct/resource_mapping.py +0 -0
- {direct_cli-0.5.2 → direct_cli-0.5.3}/direct_cli/_vendor/tapi_yandex_direct/tapi_yandex_direct.py +0 -0
- {direct_cli-0.5.2 → direct_cli-0.5.3}/direct_cli/_vendor/tapi_yandex_direct/tapi_yandex_direct.pyi +0 -0
- {direct_cli-0.5.2 → direct_cli-0.5.3}/direct_cli/_vendor/tapi_yandex_direct/v4/__init__.py +0 -0
- {direct_cli-0.5.2 → direct_cli-0.5.3}/direct_cli/_vendor/tapi_yandex_direct/v4/adapter.py +0 -0
- {direct_cli-0.5.2 → direct_cli-0.5.3}/direct_cli/_vendor/tapi_yandex_direct/v4/adapter.pyi +0 -0
- {direct_cli-0.5.2 → direct_cli-0.5.3}/direct_cli/_vendor/tapi_yandex_direct/v4/resource_mapping.py +0 -0
- {direct_cli-0.5.2 → direct_cli-0.5.3}/direct_cli/api.py +0 -0
- {direct_cli-0.5.2 → direct_cli-0.5.3}/direct_cli/browser/_clock.py +0 -0
- {direct_cli-0.5.2 → direct_cli-0.5.3}/direct_cli/browser/diagnostics.py +0 -0
- {direct_cli-0.5.2 → direct_cli-0.5.3}/direct_cli/browser/store.py +0 -0
- {direct_cli-0.5.2 → direct_cli-0.5.3}/direct_cli/commands/__init__.py +0 -0
- {direct_cli-0.5.2 → direct_cli-0.5.3}/direct_cli/commands/_batch.py +0 -0
- {direct_cli-0.5.2 → direct_cli-0.5.3}/direct_cli/commands/_campaigns_base.py +0 -0
- {direct_cli-0.5.2 → direct_cli-0.5.3}/direct_cli/commands/_campaigns_cpm_banner.py +0 -0
- {direct_cli-0.5.2 → direct_cli-0.5.3}/direct_cli/commands/_campaigns_dynamic.py +0 -0
- {direct_cli-0.5.2 → direct_cli-0.5.3}/direct_cli/commands/_campaigns_mobile_app.py +0 -0
- {direct_cli-0.5.2 → direct_cli-0.5.3}/direct_cli/commands/_campaigns_smart.py +0 -0
- {direct_cli-0.5.2 → direct_cli-0.5.3}/direct_cli/commands/_campaigns_text.py +0 -0
- {direct_cli-0.5.2 → direct_cli-0.5.3}/direct_cli/commands/_campaigns_unified.py +0 -0
- {direct_cli-0.5.2 → direct_cli-0.5.3}/direct_cli/commands/_execute.py +0 -0
- {direct_cli-0.5.2 → direct_cli-0.5.3}/direct_cli/commands/_get.py +0 -0
- {direct_cli-0.5.2 → direct_cli-0.5.3}/direct_cli/commands/_set_bids.py +0 -0
- {direct_cli-0.5.2 → direct_cli-0.5.3}/direct_cli/commands/adextensions.py +0 -0
- {direct_cli-0.5.2 → direct_cli-0.5.3}/direct_cli/commands/adgroups.py +0 -0
- {direct_cli-0.5.2 → direct_cli-0.5.3}/direct_cli/commands/adimages.py +0 -0
- {direct_cli-0.5.2 → direct_cli-0.5.3}/direct_cli/commands/ads/__init__.py +0 -0
- {direct_cli-0.5.2 → direct_cli-0.5.3}/direct_cli/commands/ads/_cli.py +0 -0
- {direct_cli-0.5.2 → direct_cli-0.5.3}/direct_cli/commands/ads/base.py +0 -0
- {direct_cli-0.5.2 → direct_cli-0.5.3}/direct_cli/commands/ads/batch.py +0 -0
- {direct_cli-0.5.2 → direct_cli-0.5.3}/direct_cli/commands/ads/builder.py +0 -0
- {direct_cli-0.5.2 → direct_cli-0.5.3}/direct_cli/commands/ads/mobile_app.py +0 -0
- {direct_cli-0.5.2 → direct_cli-0.5.3}/direct_cli/commands/ads/objects.py +0 -0
- {direct_cli-0.5.2 → direct_cli-0.5.3}/direct_cli/commands/ads/responsive.py +0 -0
- {direct_cli-0.5.2 → direct_cli-0.5.3}/direct_cli/commands/ads/shopping.py +0 -0
- {direct_cli-0.5.2 → direct_cli-0.5.3}/direct_cli/commands/ads/text.py +0 -0
- {direct_cli-0.5.2 → direct_cli-0.5.3}/direct_cli/commands/advideos.py +0 -0
- {direct_cli-0.5.2 → direct_cli-0.5.3}/direct_cli/commands/agencyclients.py +0 -0
- {direct_cli-0.5.2 → direct_cli-0.5.3}/direct_cli/commands/audiencetargets.py +0 -0
- {direct_cli-0.5.2 → direct_cli-0.5.3}/direct_cli/commands/bids.py +0 -0
- {direct_cli-0.5.2 → direct_cli-0.5.3}/direct_cli/commands/businesses.py +0 -0
- {direct_cli-0.5.2 → direct_cli-0.5.3}/direct_cli/commands/campaigns.py +0 -0
- {direct_cli-0.5.2 → direct_cli-0.5.3}/direct_cli/commands/changes.py +0 -0
- {direct_cli-0.5.2 → direct_cli-0.5.3}/direct_cli/commands/creatives.py +0 -0
- {direct_cli-0.5.2 → direct_cli-0.5.3}/direct_cli/commands/dynamicads.py +0 -0
- {direct_cli-0.5.2 → direct_cli-0.5.3}/direct_cli/commands/dynamicfeedadtargets.py +0 -0
- {direct_cli-0.5.2 → direct_cli-0.5.3}/direct_cli/commands/feeds.py +0 -0
- {direct_cli-0.5.2 → direct_cli-0.5.3}/direct_cli/commands/keywordbids.py +0 -0
- {direct_cli-0.5.2 → direct_cli-0.5.3}/direct_cli/commands/keywords.py +0 -0
- {direct_cli-0.5.2 → direct_cli-0.5.3}/direct_cli/commands/keywordsresearch.py +0 -0
- {direct_cli-0.5.2 → direct_cli-0.5.3}/direct_cli/commands/leads.py +0 -0
- {direct_cli-0.5.2 → direct_cli-0.5.3}/direct_cli/commands/negativekeywordsharedsets.py +0 -0
- {direct_cli-0.5.2 → direct_cli-0.5.3}/direct_cli/commands/reports.py +0 -0
- {direct_cli-0.5.2 → direct_cli-0.5.3}/direct_cli/commands/retargeting.py +0 -0
- {direct_cli-0.5.2 → direct_cli-0.5.3}/direct_cli/commands/smartadtargets.py +0 -0
- {direct_cli-0.5.2 → direct_cli-0.5.3}/direct_cli/commands/trackingparams.py +0 -0
- {direct_cli-0.5.2 → direct_cli-0.5.3}/direct_cli/commands/turbopages.py +0 -0
- {direct_cli-0.5.2 → direct_cli-0.5.3}/direct_cli/commands/v4account.py +0 -0
- {direct_cli-0.5.2 → direct_cli-0.5.3}/direct_cli/commands/v4adimage.py +0 -0
- {direct_cli-0.5.2 → direct_cli-0.5.3}/direct_cli/commands/v4forecast.py +0 -0
- {direct_cli-0.5.2 → direct_cli-0.5.3}/direct_cli/commands/v4goals.py +0 -0
- {direct_cli-0.5.2 → direct_cli-0.5.3}/direct_cli/commands/v4keywords.py +0 -0
- {direct_cli-0.5.2 → direct_cli-0.5.3}/direct_cli/commands/v4shells.py +0 -0
- {direct_cli-0.5.2 → direct_cli-0.5.3}/direct_cli/commands/v4tags.py +0 -0
- {direct_cli-0.5.2 → direct_cli-0.5.3}/direct_cli/commands/vcards.py +0 -0
- {direct_cli-0.5.2 → direct_cli-0.5.3}/direct_cli/reports_coverage.py +0 -0
- {direct_cli-0.5.2 → direct_cli-0.5.3}/direct_cli/tracking_params.py +0 -0
- {direct_cli-0.5.2 → direct_cli-0.5.3}/direct_cli/translations/adextensions.json +0 -0
- {direct_cli-0.5.2 → direct_cli-0.5.3}/direct_cli/translations/adgroups.json +0 -0
- {direct_cli-0.5.2 → direct_cli-0.5.3}/direct_cli/translations/adimages.json +0 -0
- {direct_cli-0.5.2 → direct_cli-0.5.3}/direct_cli/translations/ads.json +0 -0
- {direct_cli-0.5.2 → direct_cli-0.5.3}/direct_cli/translations/advideos.json +0 -0
- {direct_cli-0.5.2 → direct_cli-0.5.3}/direct_cli/translations/agencyclients.json +0 -0
- {direct_cli-0.5.2 → direct_cli-0.5.3}/direct_cli/translations/audiencetargets.json +0 -0
- {direct_cli-0.5.2 → direct_cli-0.5.3}/direct_cli/translations/auth.json +0 -0
- {direct_cli-0.5.2 → direct_cli-0.5.3}/direct_cli/translations/balance.json +0 -0
- {direct_cli-0.5.2 → direct_cli-0.5.3}/direct_cli/translations/bids.json +0 -0
- {direct_cli-0.5.2 → direct_cli-0.5.3}/direct_cli/translations/businesses.json +0 -0
- {direct_cli-0.5.2 → direct_cli-0.5.3}/direct_cli/translations/campaigns.json +0 -0
- {direct_cli-0.5.2 → direct_cli-0.5.3}/direct_cli/translations/changes.json +0 -0
- {direct_cli-0.5.2 → direct_cli-0.5.3}/direct_cli/translations/common.json +0 -0
- {direct_cli-0.5.2 → direct_cli-0.5.3}/direct_cli/translations/creatives.json +0 -0
- {direct_cli-0.5.2 → direct_cli-0.5.3}/direct_cli/translations/dynamicads.json +0 -0
- {direct_cli-0.5.2 → direct_cli-0.5.3}/direct_cli/translations/dynamicfeedadtargets.json +0 -0
- {direct_cli-0.5.2 → direct_cli-0.5.3}/direct_cli/translations/feeds.json +0 -0
- {direct_cli-0.5.2 → direct_cli-0.5.3}/direct_cli/translations/keywordbids.json +0 -0
- {direct_cli-0.5.2 → direct_cli-0.5.3}/direct_cli/translations/keywords.json +0 -0
- {direct_cli-0.5.2 → direct_cli-0.5.3}/direct_cli/translations/keywordsresearch.json +0 -0
- {direct_cli-0.5.2 → direct_cli-0.5.3}/direct_cli/translations/leads.json +0 -0
- {direct_cli-0.5.2 → direct_cli-0.5.3}/direct_cli/translations/negativekeywordsharedsets.json +0 -0
- {direct_cli-0.5.2 → direct_cli-0.5.3}/direct_cli/translations/reports.json +0 -0
- {direct_cli-0.5.2 → direct_cli-0.5.3}/direct_cli/translations/retargeting.json +0 -0
- {direct_cli-0.5.2 → direct_cli-0.5.3}/direct_cli/translations/sitelinks.json +0 -0
- {direct_cli-0.5.2 → direct_cli-0.5.3}/direct_cli/translations/smartadtargets.json +0 -0
- {direct_cli-0.5.2 → direct_cli-0.5.3}/direct_cli/translations/strategies.json +0 -0
- {direct_cli-0.5.2 → direct_cli-0.5.3}/direct_cli/translations/turbopages.json +0 -0
- {direct_cli-0.5.2 → direct_cli-0.5.3}/direct_cli/translations/v4account.json +0 -0
- {direct_cli-0.5.2 → direct_cli-0.5.3}/direct_cli/translations/v4adimage.json +0 -0
- {direct_cli-0.5.2 → direct_cli-0.5.3}/direct_cli/translations/v4events.json +0 -0
- {direct_cli-0.5.2 → direct_cli-0.5.3}/direct_cli/translations/v4finance.json +0 -0
- {direct_cli-0.5.2 → direct_cli-0.5.3}/direct_cli/translations/v4forecast.json +0 -0
- {direct_cli-0.5.2 → direct_cli-0.5.3}/direct_cli/translations/v4goals.json +0 -0
- {direct_cli-0.5.2 → direct_cli-0.5.3}/direct_cli/translations/v4keywords.json +0 -0
- {direct_cli-0.5.2 → direct_cli-0.5.3}/direct_cli/translations/v4shells.json +0 -0
- {direct_cli-0.5.2 → direct_cli-0.5.3}/direct_cli/translations/v4tags.json +0 -0
- {direct_cli-0.5.2 → direct_cli-0.5.3}/direct_cli/translations/v4wordstat.json +0 -0
- {direct_cli-0.5.2 → direct_cli-0.5.3}/direct_cli/translations/vcards.json +0 -0
- {direct_cli-0.5.2 → direct_cli-0.5.3}/direct_cli/utils.py +0 -0
- {direct_cli-0.5.2 → direct_cli-0.5.3}/direct_cli/v4/__init__.py +0 -0
- {direct_cli-0.5.2 → direct_cli-0.5.3}/direct_cli/v4/emit.py +0 -0
- {direct_cli-0.5.2 → direct_cli-0.5.3}/direct_cli/v4/money.py +0 -0
- {direct_cli-0.5.2 → direct_cli-0.5.3}/direct_cli/v4_contracts.py +0 -0
- {direct_cli-0.5.2 → direct_cli-0.5.3}/direct_cli.egg-info/dependency_links.txt +0 -0
- {direct_cli-0.5.2 → direct_cli-0.5.3}/direct_cli.egg-info/entry_points.txt +0 -0
- {direct_cli-0.5.2 → direct_cli-0.5.3}/direct_cli.egg-info/top_level.txt +0 -0
- {direct_cli-0.5.2 → direct_cli-0.5.3}/docs/audits/API_COVERAGE.md +0 -0
- {direct_cli-0.5.2 → direct_cli-0.5.3}/docs/audits/PROJECT_WIRE_SHAPE_AUDIT_2026-05-30.md +0 -0
- {direct_cli-0.5.2 → direct_cli-0.5.3}/docs/audits/WIRE_SHAPE_TRIAGE_2026-05-30.md +0 -0
- {direct_cli-0.5.2 → direct_cli-0.5.3}/docs/audits/issue-198-mutating-wsdl-audit.md +0 -0
- {direct_cli-0.5.2 → direct_cli-0.5.3}/docs/audits/wire_shape.json +0 -0
- {direct_cli-0.5.2 → direct_cli-0.5.3}/docs/superpowers/plans/2026-04-12-issue-32-completion.md +0 -0
- {direct_cli-0.5.2 → direct_cli-0.5.3}/docs/superpowers/specs/2026-04-23-vendor-tapi-yandex-direct-design.md +0 -0
- {direct_cli-0.5.2 → direct_cli-0.5.3}/scripts/anonymize_cassettes.py +0 -0
- {direct_cli-0.5.2 → direct_cli-0.5.3}/scripts/build_wsdl_optional_field_audit.py +0 -0
- {direct_cli-0.5.2 → direct_cli-0.5.3}/scripts/check_all_docs_urls.py +0 -0
- {direct_cli-0.5.2 → direct_cli-0.5.3}/scripts/check_reports_drift.py +0 -0
- {direct_cli-0.5.2 → direct_cli-0.5.3}/scripts/check_wsdl_drift.py +0 -0
- {direct_cli-0.5.2 → direct_cli-0.5.3}/scripts/measure_criteria_limits.py +0 -0
- {direct_cli-0.5.2 → direct_cli-0.5.3}/scripts/patch_vendor_imports.py +0 -0
- {direct_cli-0.5.2 → direct_cli-0.5.3}/scripts/preflight_check.sh +0 -0
- {direct_cli-0.5.2 → direct_cli-0.5.3}/scripts/probe_drift_urls.sh +0 -0
- {direct_cli-0.5.2 → direct_cli-0.5.3}/scripts/recon_761_utm_split.py +0 -0
- {direct_cli-0.5.2 → direct_cli-0.5.3}/scripts/recon_774_audit_saved_utm.py +0 -0
- {direct_cli-0.5.2 → direct_cli-0.5.3}/scripts/recon_774_utm_reorder.py +0 -0
- {direct_cli-0.5.2 → direct_cli-0.5.3}/scripts/refresh_reports_cache.py +0 -0
- {direct_cli-0.5.2 → direct_cli-0.5.3}/scripts/refresh_wsdl_cache.py +0 -0
- {direct_cli-0.5.2 → direct_cli-0.5.3}/scripts/release_pypi.sh +0 -0
- {direct_cli-0.5.2 → direct_cli-0.5.3}/scripts/sandbox_write_live.py +0 -0
- {direct_cli-0.5.2 → direct_cli-0.5.3}/scripts/test_dangerous_commands.sh +0 -0
- {direct_cli-0.5.2 → direct_cli-0.5.3}/scripts/test_safe_commands.sh +0 -0
- {direct_cli-0.5.2 → direct_cli-0.5.3}/scripts/test_sandbox_write.sh +0 -0
- {direct_cli-0.5.2 → direct_cli-0.5.3}/scripts/update_vendor.sh +0 -0
- {direct_cli-0.5.2 → direct_cli-0.5.3}/setup.py +0 -0
- {direct_cli-0.5.2 → direct_cli-0.5.3}/tests/API_COVERAGE.md +0 -0
- {direct_cli-0.5.2 → direct_cli-0.5.3}/tests/API_ISSUE_AUDIT.md +0 -0
- {direct_cli-0.5.2 → direct_cli-0.5.3}/tests/MANUAL_COVERAGE.md +0 -0
- {direct_cli-0.5.2 → direct_cli-0.5.3}/tests/__init__.py +0 -0
- {direct_cli-0.5.2 → direct_cli-0.5.3}/tests/_orphan_store.py +0 -0
- {direct_cli-0.5.2 → direct_cli-0.5.3}/tests/cassettes/test_integration_write/TestWriteAdExtensions.test_add_delete.yaml +0 -0
- {direct_cli-0.5.2 → direct_cli-0.5.3}/tests/cassettes/test_integration_write/TestWriteAdGroups.test_add_update_delete.yaml +0 -0
- {direct_cli-0.5.2 → direct_cli-0.5.3}/tests/cassettes/test_integration_write/TestWriteAdImages.test_add_delete.yaml +0 -0
- {direct_cli-0.5.2 → direct_cli-0.5.3}/tests/cassettes/test_integration_write/TestWriteAds.test_add_text_ad_update_delete.yaml +0 -0
- {direct_cli-0.5.2 → direct_cli-0.5.3}/tests/cassettes/test_integration_write/TestWriteAudienceTargets.test_add_delete.yaml +0 -0
- {direct_cli-0.5.2 → direct_cli-0.5.3}/tests/cassettes/test_integration_write/TestWriteBidModifiersAdd.test_add_delete_mobile.yaml +0 -0
- {direct_cli-0.5.2 → direct_cli-0.5.3}/tests/cassettes/test_integration_write/TestWriteBidModifiersSet.test_set_without_id_is_rejected.yaml +0 -0
- {direct_cli-0.5.2 → direct_cli-0.5.3}/tests/cassettes/test_integration_write/TestWriteBids.test_set_bid.yaml +0 -0
- {direct_cli-0.5.2 → direct_cli-0.5.3}/tests/cassettes/test_integration_write/TestWriteBidsRead.test_bids_get.yaml +0 -0
- {direct_cli-0.5.2 → direct_cli-0.5.3}/tests/cassettes/test_integration_write/TestWriteBidsRead.test_bids_set_auto.yaml +0 -0
- {direct_cli-0.5.2 → direct_cli-0.5.3}/tests/cassettes/test_integration_write/TestWriteCampaignDraftLifecycle.test_draft_create_get_delete.yaml +0 -0
- {direct_cli-0.5.2 → direct_cli-0.5.3}/tests/cassettes/test_integration_write/TestWriteCampaigns.test_campaign_lifecycle.yaml +0 -0
- {direct_cli-0.5.2 → direct_cli-0.5.3}/tests/cassettes/test_integration_write/TestWriteDynamicAds.test_add_update_delete.yaml +0 -0
- {direct_cli-0.5.2 → direct_cli-0.5.3}/tests/cassettes/test_integration_write/TestWriteFeeds.test_add_update_delete.yaml +0 -0
- {direct_cli-0.5.2 → direct_cli-0.5.3}/tests/cassettes/test_integration_write/TestWriteKeywordBids.test_set_keyword_bid.yaml +0 -0
- {direct_cli-0.5.2 → direct_cli-0.5.3}/tests/cassettes/test_integration_write/TestWriteKeywords.test_add_update_delete.yaml +0 -0
- {direct_cli-0.5.2 → direct_cli-0.5.3}/tests/cassettes/test_integration_write/TestWriteNegativeKeywordSharedSets.test_add_update_delete.yaml +0 -0
- {direct_cli-0.5.2 → direct_cli-0.5.3}/tests/cassettes/test_integration_write/TestWriteRetargeting.test_add_delete.yaml +0 -0
- {direct_cli-0.5.2 → direct_cli-0.5.3}/tests/cassettes/test_integration_write/TestWriteRetargetingUpdate.test_retargeting_update.yaml +0 -0
- {direct_cli-0.5.2 → direct_cli-0.5.3}/tests/cassettes/test_integration_write/TestWriteSitelinks.test_add_delete.yaml +0 -0
- {direct_cli-0.5.2 → direct_cli-0.5.3}/tests/cassettes/test_integration_write/TestWriteSmartAdTargets.test_add_update_delete.yaml +0 -0
- {direct_cli-0.5.2 → direct_cli-0.5.3}/tests/cassettes/test_integration_write/TestWriteStrategies.test_strategies_lifecycle.yaml +0 -0
- {direct_cli-0.5.2 → direct_cli-0.5.3}/tests/cassettes/test_integration_write/TestWriteVCards.test_add_delete.yaml +0 -0
- {direct_cli-0.5.2 → direct_cli-0.5.3}/tests/cassettes/test_read_cassettes/test_balance_get_no_shared_account.yaml +0 -0
- {direct_cli-0.5.2 → direct_cli-0.5.3}/tests/cassettes/test_read_cassettes/test_read_command[adextensions_get].yaml +0 -0
- {direct_cli-0.5.2 → direct_cli-0.5.3}/tests/cassettes/test_read_cassettes/test_read_command[adgroups_get].yaml +0 -0
- {direct_cli-0.5.2 → direct_cli-0.5.3}/tests/cassettes/test_read_cassettes/test_read_command[adimages_get].yaml +0 -0
- {direct_cli-0.5.2 → direct_cli-0.5.3}/tests/cassettes/test_read_cassettes/test_read_command[ads_get].yaml +0 -0
- {direct_cli-0.5.2 → direct_cli-0.5.3}/tests/cassettes/test_read_cassettes/test_read_command[advideos_get].yaml +0 -0
- {direct_cli-0.5.2 → direct_cli-0.5.3}/tests/cassettes/test_read_cassettes/test_read_command[audiencetargets_get].yaml +0 -0
- {direct_cli-0.5.2 → direct_cli-0.5.3}/tests/cassettes/test_read_cassettes/test_read_command[bidmodifiers_get].yaml +0 -0
- {direct_cli-0.5.2 → direct_cli-0.5.3}/tests/cassettes/test_read_cassettes/test_read_command[bids_get].yaml +0 -0
- {direct_cli-0.5.2 → direct_cli-0.5.3}/tests/cassettes/test_read_cassettes/test_read_command[businesses_get].yaml +0 -0
- {direct_cli-0.5.2 → direct_cli-0.5.3}/tests/cassettes/test_read_cassettes/test_read_command[campaigns_get].yaml +0 -0
- {direct_cli-0.5.2 → direct_cli-0.5.3}/tests/cassettes/test_read_cassettes/test_read_command[changes_check].yaml +0 -0
- {direct_cli-0.5.2 → direct_cli-0.5.3}/tests/cassettes/test_read_cassettes/test_read_command[changes_check_campaigns].yaml +0 -0
- {direct_cli-0.5.2 → direct_cli-0.5.3}/tests/cassettes/test_read_cassettes/test_read_command[changes_check_dictionaries].yaml +0 -0
- {direct_cli-0.5.2 → direct_cli-0.5.3}/tests/cassettes/test_read_cassettes/test_read_command[clients_get].yaml +0 -0
- {direct_cli-0.5.2 → direct_cli-0.5.3}/tests/cassettes/test_read_cassettes/test_read_command[creatives_get].yaml +0 -0
- {direct_cli-0.5.2 → direct_cli-0.5.3}/tests/cassettes/test_read_cassettes/test_read_command[dictionaries_get].yaml +0 -0
- {direct_cli-0.5.2 → direct_cli-0.5.3}/tests/cassettes/test_read_cassettes/test_read_command[dynamicads_get].yaml +0 -0
- {direct_cli-0.5.2 → direct_cli-0.5.3}/tests/cassettes/test_read_cassettes/test_read_command[dynamicfeedadtargets_get].yaml +0 -0
- {direct_cli-0.5.2 → direct_cli-0.5.3}/tests/cassettes/test_read_cassettes/test_read_command[feeds_get].yaml +0 -0
- {direct_cli-0.5.2 → direct_cli-0.5.3}/tests/cassettes/test_read_cassettes/test_read_command[keywordbids_get].yaml +0 -0
- {direct_cli-0.5.2 → direct_cli-0.5.3}/tests/cassettes/test_read_cassettes/test_read_command[keywords_get].yaml +0 -0
- {direct_cli-0.5.2 → direct_cli-0.5.3}/tests/cassettes/test_read_cassettes/test_read_command[keywordsresearch_deduplicate].yaml +0 -0
- {direct_cli-0.5.2 → direct_cli-0.5.3}/tests/cassettes/test_read_cassettes/test_read_command[keywordsresearch_has_search_volume].yaml +0 -0
- {direct_cli-0.5.2 → direct_cli-0.5.3}/tests/cassettes/test_read_cassettes/test_read_command[leads_get].yaml +0 -0
- {direct_cli-0.5.2 → direct_cli-0.5.3}/tests/cassettes/test_read_cassettes/test_read_command[negativekeywordsharedsets_get].yaml +0 -0
- {direct_cli-0.5.2 → direct_cli-0.5.3}/tests/cassettes/test_read_cassettes/test_read_command[reports_get].yaml +0 -0
- {direct_cli-0.5.2 → direct_cli-0.5.3}/tests/cassettes/test_read_cassettes/test_read_command[retargeting_get].yaml +0 -0
- {direct_cli-0.5.2 → direct_cli-0.5.3}/tests/cassettes/test_read_cassettes/test_read_command[sitelinks_get].yaml +0 -0
- {direct_cli-0.5.2 → direct_cli-0.5.3}/tests/cassettes/test_read_cassettes/test_read_command[smartadtargets_get].yaml +0 -0
- {direct_cli-0.5.2 → direct_cli-0.5.3}/tests/cassettes/test_read_cassettes/test_read_command[strategies_get].yaml +0 -0
- {direct_cli-0.5.2 → direct_cli-0.5.3}/tests/cassettes/test_read_cassettes/test_read_command[turbopages_get].yaml +0 -0
- {direct_cli-0.5.2 → direct_cli-0.5.3}/tests/cassettes/test_read_cassettes/test_read_command[v4adimage_get].yaml +0 -0
- {direct_cli-0.5.2 → direct_cli-0.5.3}/tests/cassettes/test_read_cassettes/test_read_command[v4events_get_events_log].yaml +0 -0
- {direct_cli-0.5.2 → direct_cli-0.5.3}/tests/cassettes/test_read_cassettes/test_read_command[v4finance_get_clients_units].yaml +0 -0
- {direct_cli-0.5.2 → direct_cli-0.5.3}/tests/cassettes/test_read_cassettes/test_read_command[v4forecast_list].yaml +0 -0
- {direct_cli-0.5.2 → direct_cli-0.5.3}/tests/cassettes/test_read_cassettes/test_read_command[v4goals_get_stat_goals].yaml +0 -0
- {direct_cli-0.5.2 → direct_cli-0.5.3}/tests/cassettes/test_read_cassettes/test_read_command[v4tags_get_campaigns].yaml +0 -0
- {direct_cli-0.5.2 → direct_cli-0.5.3}/tests/cassettes/test_read_cassettes/test_read_command[v4wordstat_list_reports].yaml +0 -0
- {direct_cli-0.5.2 → direct_cli-0.5.3}/tests/cassettes/test_read_cassettes/test_read_command[vcards_get].yaml +0 -0
- {direct_cli-0.5.2 → direct_cli-0.5.3}/tests/cassettes/test_read_cassettes/test_v4account_get_accounts_no_shared_account.yaml +0 -0
- {direct_cli-0.5.2 → direct_cli-0.5.3}/tests/cassettes/test_read_cassettes/test_v4finance_check_payment_unknown_transaction.yaml +0 -0
- {direct_cli-0.5.2 → direct_cli-0.5.3}/tests/cassettes/test_read_cassettes/test_v4forecast_get_unknown_forecast.yaml +0 -0
- {direct_cli-0.5.2 → direct_cli-0.5.3}/tests/cassettes/test_read_cassettes/test_v4goals_get_retargeting_goals_server_error.yaml +0 -0
- {direct_cli-0.5.2 → direct_cli-0.5.3}/tests/cassettes/test_read_cassettes/test_v4tags_get_banners_server_error.yaml +0 -0
- {direct_cli-0.5.2 → direct_cli-0.5.3}/tests/cassettes/test_read_cassettes/test_v4wordstat_get_report_unknown_report.yaml +0 -0
- {direct_cli-0.5.2 → direct_cli-0.5.3}/tests/cassettes/test_v5_live_write/test_v5_live_draft_adgroups_add_update_delete.yaml +0 -0
- {direct_cli-0.5.2 → direct_cli-0.5.3}/tests/cassettes/test_v5_live_write/test_v5_live_draft_adimages_add_get_delete.yaml +0 -0
- {direct_cli-0.5.2 → direct_cli-0.5.3}/tests/cassettes/test_v5_live_write/test_v5_live_draft_ads_add_update_delete.yaml +0 -0
- {direct_cli-0.5.2 → direct_cli-0.5.3}/tests/cassettes/test_v5_live_write/test_v5_live_draft_ads_suspend_resume_archive_unarchive.yaml +0 -0
- {direct_cli-0.5.2 → direct_cli-0.5.3}/tests/cassettes/test_v5_live_write/test_v5_live_draft_advideos_add_get.yaml +0 -0
- {direct_cli-0.5.2 → direct_cli-0.5.3}/tests/cassettes/test_v5_live_write/test_v5_live_draft_audiencetargets_add_delete.yaml +0 -0
- {direct_cli-0.5.2 → direct_cli-0.5.3}/tests/cassettes/test_v5_live_write/test_v5_live_draft_audiencetargets_suspend_resume.yaml +0 -0
- {direct_cli-0.5.2 → direct_cli-0.5.3}/tests/cassettes/test_v5_live_write/test_v5_live_draft_bids_set.yaml +0 -0
- {direct_cli-0.5.2 → direct_cli-0.5.3}/tests/cassettes/test_v5_live_write/test_v5_live_draft_campaign_create_get_delete.yaml +0 -0
- {direct_cli-0.5.2 → direct_cli-0.5.3}/tests/cassettes/test_v5_live_write/test_v5_live_draft_creatives_chain_advideo_to_creative.yaml +0 -0
- {direct_cli-0.5.2 → direct_cli-0.5.3}/tests/cassettes/test_v5_live_write/test_v5_live_draft_dynamicads_add_delete.yaml +0 -0
- {direct_cli-0.5.2 → direct_cli-0.5.3}/tests/cassettes/test_v5_live_write/test_v5_live_draft_dynamicads_suspend_resume.yaml +0 -0
- {direct_cli-0.5.2 → direct_cli-0.5.3}/tests/cassettes/test_v5_live_write/test_v5_live_draft_feeds_add_update_delete.yaml +0 -0
- {direct_cli-0.5.2 → direct_cli-0.5.3}/tests/cassettes/test_v5_live_write/test_v5_live_draft_keywordbids_set.yaml +0 -0
- {direct_cli-0.5.2 → direct_cli-0.5.3}/tests/cassettes/test_v5_live_write/test_v5_live_draft_keywords_add_update_delete.yaml +0 -0
- {direct_cli-0.5.2 → direct_cli-0.5.3}/tests/cassettes/test_v5_live_write/test_v5_live_draft_keywords_suspend_resume.yaml +0 -0
- {direct_cli-0.5.2 → direct_cli-0.5.3}/tests/cassettes/test_v5_live_write/test_v5_live_draft_retargeting_add_update_delete.yaml +0 -0
- {direct_cli-0.5.2 → direct_cli-0.5.3}/tests/cassettes/test_v5_live_write/test_v5_live_draft_sitelinks_add_get_delete.yaml +0 -0
- {direct_cli-0.5.2 → direct_cli-0.5.3}/tests/cassettes/test_v5_live_write/test_v5_live_draft_smartadtargets_add_update_delete.yaml +0 -0
- {direct_cli-0.5.2 → direct_cli-0.5.3}/tests/cassettes/test_v5_live_write/test_v5_live_draft_smartadtargets_suspend_resume.yaml +0 -0
- {direct_cli-0.5.2 → direct_cli-0.5.3}/tests/cassettes/test_v5_live_write/test_v5_live_draft_strategies_add_update_archive_unarchive.yaml +0 -0
- {direct_cli-0.5.2 → direct_cli-0.5.3}/tests/fixtures/masters_grid_campaigns.json +0 -0
- {direct_cli-0.5.2 → direct_cli-0.5.3}/tests/fixtures/masters_wizard_create.html +0 -0
- {direct_cli-0.5.2 → direct_cli-0.5.3}/tests/fixtures/masters_wizard_draft_overview.html +0 -0
- {direct_cli-0.5.2 → direct_cli-0.5.3}/tests/fixtures/masters_wizard_edit_moderation.html +0 -0
- {direct_cli-0.5.2 → direct_cli-0.5.3}/tests/fixtures/masters_wizard_edit_name.html +0 -0
- {direct_cli-0.5.2 → direct_cli-0.5.3}/tests/fixtures/masters_wizard_edit_stage_a.html +0 -0
- {direct_cli-0.5.2 → direct_cli-0.5.3}/tests/fixtures/masters_wizard_edit_stage_b.html +0 -0
- {direct_cli-0.5.2 → direct_cli-0.5.3}/tests/fixtures/masters_wizard_edit_stage_d.html +0 -0
- {direct_cli-0.5.2 → direct_cli-0.5.3}/tests/fixtures/masters_wizard_overview.html +0 -0
- {direct_cli-0.5.2 → direct_cli-0.5.3}/tests/fixtures/test-video.mp4 +0 -0
- {direct_cli-0.5.2 → direct_cli-0.5.3}/tests/reports_cache/raw/fields-list.html +0 -0
- {direct_cli-0.5.2 → direct_cli-0.5.3}/tests/reports_cache/raw/headers.html +0 -0
- {direct_cli-0.5.2 → direct_cli-0.5.3}/tests/reports_cache/raw/period.html +0 -0
- {direct_cli-0.5.2 → direct_cli-0.5.3}/tests/reports_cache/raw/spec.html +0 -0
- {direct_cli-0.5.2 → direct_cli-0.5.3}/tests/reports_cache/raw/type.html +0 -0
- {direct_cli-0.5.2 → direct_cli-0.5.3}/tests/reports_cache/spec.json +0 -0
- {direct_cli-0.5.2 → direct_cli-0.5.3}/tests/test_auth_bw.py +0 -0
- {direct_cli-0.5.2 → direct_cli-0.5.3}/tests/test_auth_op.py +0 -0
- {direct_cli-0.5.2 → direct_cli-0.5.3}/tests/test_auth_write_json.py +0 -0
- {direct_cli-0.5.2 → direct_cli-0.5.3}/tests/test_autotargeting.py +0 -0
- {direct_cli-0.5.2 → direct_cli-0.5.3}/tests/test_bidding_strategy_constants.py +0 -0
- {direct_cli-0.5.2 → direct_cli-0.5.3}/tests/test_cassette_integrity.py +0 -0
- {direct_cli-0.5.2 → direct_cli-0.5.3}/tests/test_changes.py +0 -0
- {direct_cli-0.5.2 → direct_cli-0.5.3}/tests/test_check_all_docs_urls.py +0 -0
- {direct_cli-0.5.2 → direct_cli-0.5.3}/tests/test_cli_contract.py +0 -0
- {direct_cli-0.5.2 → direct_cli-0.5.3}/tests/test_dry_run.py +0 -0
- {direct_cli-0.5.2 → direct_cli-0.5.3}/tests/test_dry_run_adgroups.py +0 -0
- {direct_cli-0.5.2 → direct_cli-0.5.3}/tests/test_dry_run_ads.py +0 -0
- {direct_cli-0.5.2 → direct_cli-0.5.3}/tests/test_dry_run_campaigns.py +0 -0
- {direct_cli-0.5.2 → direct_cli-0.5.3}/tests/test_dry_run_common.py +0 -0
- {direct_cli-0.5.2 → direct_cli-0.5.3}/tests/test_dry_run_extensions.py +0 -0
- {direct_cli-0.5.2 → direct_cli-0.5.3}/tests/test_dry_run_keywords.py +0 -0
- {direct_cli-0.5.2 → direct_cli-0.5.3}/tests/test_dry_run_shared.py +0 -0
- {direct_cli-0.5.2 → direct_cli-0.5.3}/tests/test_dry_run_strategies.py +0 -0
- {direct_cli-0.5.2 → direct_cli-0.5.3}/tests/test_dry_run_strategy_smart.py +0 -0
- {direct_cli-0.5.2 → direct_cli-0.5.3}/tests/test_dry_run_strategy_text.py +0 -0
- {direct_cli-0.5.2 → direct_cli-0.5.3}/tests/test_dry_run_strategy_unified.py +0 -0
- {direct_cli-0.5.2 → direct_cli-0.5.3}/tests/test_dry_run_targets.py +0 -0
- {direct_cli-0.5.2 → direct_cli-0.5.3}/tests/test_env_loading.py +0 -0
- {direct_cli-0.5.2 → direct_cli-0.5.3}/tests/test_field_names_option.py +0 -0
- {direct_cli-0.5.2 → direct_cli-0.5.3}/tests/test_flag_validation.py +0 -0
- {direct_cli-0.5.2 → direct_cli-0.5.3}/tests/test_handle_api_errors.py +0 -0
- {direct_cli-0.5.2 → direct_cli-0.5.3}/tests/test_integration.py +0 -0
- {direct_cli-0.5.2 → direct_cli-0.5.3}/tests/test_read_cassettes.py +0 -0
- {direct_cli-0.5.2 → direct_cli-0.5.3}/tests/test_reference_output.py +0 -0
- {direct_cli-0.5.2 → direct_cli-0.5.3}/tests/test_reports_drift.py +0 -0
- {direct_cli-0.5.2 → direct_cli-0.5.3}/tests/test_sandbox_write_audit.py +0 -0
- {direct_cli-0.5.2 → direct_cli-0.5.3}/tests/test_unknown_option_hints.py +0 -0
- {direct_cli-0.5.2 → direct_cli-0.5.3}/tests/test_v4_exit_codes.py +0 -0
- {direct_cli-0.5.2 → direct_cli-0.5.3}/tests/test_v4_foundation.py +0 -0
- {direct_cli-0.5.2 → direct_cli-0.5.3}/tests/test_v4_live_contracts.py +0 -0
- {direct_cli-0.5.2 → direct_cli-0.5.3}/tests/test_v4_runtime_shape.py +0 -0
- {direct_cli-0.5.2 → direct_cli-0.5.3}/tests/test_v4_safety.py +0 -0
- {direct_cli-0.5.2 → direct_cli-0.5.3}/tests/test_v4meta.py +0 -0
- {direct_cli-0.5.2 → direct_cli-0.5.3}/tests/wsdl_cache/adextensions.xml +0 -0
- {direct_cli-0.5.2 → direct_cli-0.5.3}/tests/wsdl_cache/adgroups.xml +0 -0
- {direct_cli-0.5.2 → direct_cli-0.5.3}/tests/wsdl_cache/ads.xml +0 -0
- {direct_cli-0.5.2 → direct_cli-0.5.3}/tests/wsdl_cache/advideos.xml +0 -0
- {direct_cli-0.5.2 → direct_cli-0.5.3}/tests/wsdl_cache/agencyclients.xml +0 -0
- {direct_cli-0.5.2 → direct_cli-0.5.3}/tests/wsdl_cache/audiencetargets.xml +0 -0
- {direct_cli-0.5.2 → direct_cli-0.5.3}/tests/wsdl_cache/bidmodifiers.xml +0 -0
- {direct_cli-0.5.2 → direct_cli-0.5.3}/tests/wsdl_cache/bids.xml +0 -0
- {direct_cli-0.5.2 → direct_cli-0.5.3}/tests/wsdl_cache/businesses.xml +0 -0
- {direct_cli-0.5.2 → direct_cli-0.5.3}/tests/wsdl_cache/changes.xml +0 -0
- {direct_cli-0.5.2 → direct_cli-0.5.3}/tests/wsdl_cache/clients.xml +0 -0
- {direct_cli-0.5.2 → direct_cli-0.5.3}/tests/wsdl_cache/creatives.xml +0 -0
- {direct_cli-0.5.2 → direct_cli-0.5.3}/tests/wsdl_cache/dictionaries.xml +0 -0
- {direct_cli-0.5.2 → direct_cli-0.5.3}/tests/wsdl_cache/dynamicfeedadtargets.xml +0 -0
- {direct_cli-0.5.2 → direct_cli-0.5.3}/tests/wsdl_cache/dynamictextadtargets.xml +0 -0
- {direct_cli-0.5.2 → direct_cli-0.5.3}/tests/wsdl_cache/feeds.xml +0 -0
- {direct_cli-0.5.2 → direct_cli-0.5.3}/tests/wsdl_cache/imports/adextensiontypes.xsd +0 -0
- {direct_cli-0.5.2 → direct_cli-0.5.3}/tests/wsdl_cache/imports/general.xsd +0 -0
- {direct_cli-0.5.2 → direct_cli-0.5.3}/tests/wsdl_cache/keywordbids.xml +0 -0
- {direct_cli-0.5.2 → direct_cli-0.5.3}/tests/wsdl_cache/keywords.xml +0 -0
- {direct_cli-0.5.2 → direct_cli-0.5.3}/tests/wsdl_cache/keywordsresearch.xml +0 -0
- {direct_cli-0.5.2 → direct_cli-0.5.3}/tests/wsdl_cache/leads.xml +0 -0
- {direct_cli-0.5.2 → direct_cli-0.5.3}/tests/wsdl_cache/negativekeywordsharedsets.xml +0 -0
- {direct_cli-0.5.2 → direct_cli-0.5.3}/tests/wsdl_cache/retargetinglists.xml +0 -0
- {direct_cli-0.5.2 → direct_cli-0.5.3}/tests/wsdl_cache/sitelinks.xml +0 -0
- {direct_cli-0.5.2 → direct_cli-0.5.3}/tests/wsdl_cache/smartadtargets.xml +0 -0
- {direct_cli-0.5.2 → direct_cli-0.5.3}/tests/wsdl_cache/turbopages.xml +0 -0
- {direct_cli-0.5.2 → direct_cli-0.5.3}/tests/wsdl_cache/vcards.xml +0 -0
|
@@ -38,6 +38,9 @@ jobs:
|
|
|
38
38
|
- name: Type check
|
|
39
39
|
run: mypy .
|
|
40
40
|
|
|
41
|
+
- name: Pyright
|
|
42
|
+
run: pyright
|
|
43
|
+
|
|
41
44
|
- name: Run offline tests
|
|
42
45
|
# Offline default comes from pyproject addopts (skips the live-network
|
|
43
46
|
# markers). integration_write stays in — it replays VCR cassettes offline.
|
|
@@ -1,9 +1,279 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
-
##
|
|
3
|
+
## 0.5.3 — 2026-10-07
|
|
4
|
+
|
|
5
|
+
### Fixed
|
|
6
|
+
|
|
7
|
+
**`masters update` — the whole-form save can no longer silently wipe the
|
|
8
|
+
campaign's target actions or Metrika counters, whichever field the call
|
|
9
|
+
mutates (#872).**
|
|
10
|
+
|
|
11
|
+
The edit page has exactly one save button and the save submits the WHOLE
|
|
12
|
+
form, so every update — not just a URL/UTM one — resubmits the "Целевые
|
|
13
|
+
действия" table and the counters section. The #836 protection (settled
|
|
14
|
+
pre-save baselines + pre-click guard + post-save verification) was gated
|
|
15
|
+
on `landing_url`/`tracking_params` being present, so a
|
|
16
|
+
`masters update --weekly-budget` run saved a mid-hydration, emptied
|
|
17
|
+
target-action table as a success: 4 live Мастер кампаний lost their
|
|
18
|
+
goals and stopped spending (0₽/day with a healthy weekly budget). The
|
|
19
|
+
protection now keys on the save itself: every mutating `update_master`
|
|
20
|
+
call reads the same baselines, refuses to click Save when a protected
|
|
21
|
+
section no longer matches its baseline ("Refusing to save"), and
|
|
22
|
+
verifies the untouched goal set after the reload — an unchanged-table
|
|
23
|
+
call with an unreadable baseline on a rendered section aborts instead of
|
|
24
|
+
saving blind. A max-clicks campaign (no target-actions section rendered)
|
|
25
|
+
and add/remove-target-action calls keep their existing semantics. On
|
|
26
|
+
success the result row now carries `TargetActions` — the verified goal
|
|
27
|
+
set that survived the save — so a `--from-file` batch shows the
|
|
28
|
+
protected goals per campaign instead of an implicit absence of failure.
|
|
29
|
+
Cost: an unchanged-table update spends a few extra seconds stabilizing
|
|
30
|
+
the section reads.
|
|
31
|
+
|
|
32
|
+
The protection also checks each goal's **price (CPA)**, not just which
|
|
33
|
+
goals are present (#876). The save resubmits every goal's bid, so a price
|
|
34
|
+
that shifts or resets while the goal stays would let the strategy keep
|
|
35
|
+
spending at a different bid with no visible symptom. Both the pre-click
|
|
36
|
+
guard and the post-save check now compare the full `{GoalId: Price}` map
|
|
37
|
+
(the baseline with any `--target-action-price` changes applied), and
|
|
38
|
+
`TargetActions` in the result row is `[{"GoalId": …, "Price": …}]`.
|
|
39
|
+
|
|
40
|
+
**`direct playwright login` — honest `verified` reporting (#870).**
|
|
41
|
+
|
|
42
|
+
`verified` in the command's output now reflects what actually happened:
|
|
43
|
+
a render timeout during verification saves the session with
|
|
44
|
+
`verified: false` and exit code 0 (previously the whole command failed;
|
|
45
|
+
the #692 invariant — never CLAIM a verified session without a rendered
|
|
46
|
+
page — is now carried by the explicit `false`, which JSON consumers
|
|
47
|
+
should check). A live check that lands on Yandex's login page judges the
|
|
48
|
+
auth cookies' (`Session_id`/`sessionid2`) `expires_utc` before claiming
|
|
49
|
+
they expired — long-lived tracking cookies no longer mask a dead session
|
|
50
|
+
— and a headless run gets one headful retry for the bot challenge. A
|
|
51
|
+
stale Chromium ProcessSingleton lock (a previous CLI browser process
|
|
52
|
+
that died with its `hostname-PID` symlink still on disk) is cleared and
|
|
53
|
+
the launch retried once instead of failing with a raw
|
|
54
|
+
`SingletonLock: File exists` traceback; `masters login` gained
|
|
55
|
+
`--allow-no-tty` for scripted runs whose window is visible anyway.
|
|
4
56
|
|
|
5
57
|
### Added
|
|
6
58
|
|
|
59
|
+
**`bidmodifiers` — live-only type `RETARGETING_SEARCH_ADJUSTMENT` (#871).**
|
|
60
|
+
|
|
61
|
+
Adds the search-retargeting bid modifier type the live API accepts but the
|
|
62
|
+
published WSDL and docs do not declare (confirmed by direct probes,
|
|
63
|
+
2026-10-07): the add payload nests under
|
|
64
|
+
`RetargetingSearchAdjustments` (a list, like `RetargetingAdjustments`),
|
|
65
|
+
requires `--retargeting-condition-id` together with `--value`, and `get`
|
|
66
|
+
selects it via `--types` alone — the type has no per-type
|
|
67
|
+
`RetargetingSearchAdjustmentFieldNames` parameter (the live API rejects it).
|
|
68
|
+
|
|
69
|
+
**Known limitation (#890):** Yandex currently rejects creating this
|
|
70
|
+
modifier server-side (5005 on every documented retargeting-list class,
|
|
71
|
+
while older types create fine on the same campaign) — the type appears to
|
|
72
|
+
be a half-enabled pilot. The CLI mirrors the confirmed signature so
|
|
73
|
+
`bidmodifiers add --type RETARGETING_SEARCH_ADJUSTMENT` works the moment
|
|
74
|
+
the API enables acceptance; `--dry-run` shows the exact payload.
|
|
75
|
+
|
|
76
|
+
**`masters list` — strategy and weekly budget per campaign.**
|
|
77
|
+
|
|
78
|
+
Every GridCampaigns row already carries the grid's «Бюджет и стратегия»
|
|
79
|
+
column (live capture 2026-10-03), so `masters list` now surfaces it with no
|
|
80
|
+
extra page request: `Strategy` (the grid's raw enum — `OPTIMIZE_CLICKS` /
|
|
81
|
+
`OPTIMIZE_CONVERSIONS` — passed through verbatim rather than remapped),
|
|
82
|
+
`WeeklyBudget` (`strategy.budget.sum` when its `period` is `WEEK`), and the
|
|
83
|
+
strategy scalars `AvgCpa` / `GoalId` / `PayForConversion` / `IsAutoBudget`,
|
|
84
|
+
plus `EndDate` alongside the existing `StartDate`. Rows without a strategy
|
|
85
|
+
object (e.g. drafts) report all of these as `null`.
|
|
86
|
+
|
|
87
|
+
**`masters status` — inspect the active browser session with no network call (#862).**
|
|
88
|
+
|
|
89
|
+
Reports which of `_open_session`'s tiers `masters` commands would currently
|
|
90
|
+
use — the CLI's own persistent profile from `masters login` (tier 1.5), a
|
|
91
|
+
saved `direct playwright login` session (tier 2), or neither (tier 3,
|
|
92
|
+
fresh Chrome-cookie decrypt on every call) — plus the persistent profile's
|
|
93
|
+
path/usability and the saved session's existence/expiry (reusing
|
|
94
|
+
`direct_cli.browser.store.session_status`, the same read `direct playwright
|
|
95
|
+
doctor` already does). Never launches a browser or touches Yandex; unlike
|
|
96
|
+
every other `masters` subcommand, `status` is pure on-disk inspection.
|
|
97
|
+
Defaults to `text` output like other local reference/diagnostic commands
|
|
98
|
+
(`direct playwright doctor`, `trackingparams`). An explicit `--profile-dir`
|
|
99
|
+
on the `status` call itself reports tier 1 ("explicit --profile-dir"),
|
|
100
|
+
matching `_open_session`'s real precedence: a real `masters` read command
|
|
101
|
+
given the same flag always decrypts fresh from that profile, bypassing
|
|
102
|
+
tier 1.5/2 entirely — `status` models that override instead of reporting a
|
|
103
|
+
tier the real command would not actually use (cycle-review of #864).
|
|
104
|
+
|
|
105
|
+
**`masters update --add-video-url` — select an already-uploaded video (#812).**
|
|
106
|
+
|
|
107
|
+
Adds a video to a campaign's "Варианты видео" section by selecting one the
|
|
108
|
+
account has already uploaded to some other campaign, instead of uploading a
|
|
109
|
+
new local file. Yandex keeps a single account-wide video library shared
|
|
110
|
+
across all campaigns, exposed on the video manager modal's "Ваши кампании"
|
|
111
|
+
tab.
|
|
112
|
+
|
|
113
|
+
Live-verified 2026-08-26 (campaign 713234191, throwaway, read-only up to
|
|
114
|
+
Cancel — no Save was clicked): selecting a card there
|
|
115
|
+
(`VideoSuggestionsEditor.CreativeSourcePanel.USER` →
|
|
116
|
+
`SuggestedCreativesContainer.SuggestedCreative.Select.<url>`) immediately
|
|
117
|
+
moves it into the modal's `SelectedCreativesGrid` — no async upload-processing
|
|
118
|
+
wait needed, since the video already exists server-side. The 2-video cap is
|
|
119
|
+
enforced client-side: selecting a 3rd disables `VideoSuggestionsEditor.Save`
|
|
120
|
+
and changes its label to "Выбрано больше 2 видео", the same Save button the
|
|
121
|
+
upload flow uses. Mutually exclusive with `--add-video` (and `AddVideoUrl`
|
|
122
|
+
with `AddVideo` in `--from-file`/`--masters-json` batch rows).
|
|
123
|
+
|
|
124
|
+
`--add-video`'s own file-upload sequence remains **NOT LIVE-VERIFIED**
|
|
125
|
+
(issue #812's original ask) — only the surrounding modal's DOM was confirmed
|
|
126
|
+
live in #811; a real `set_input_files` upload was never attempted on this
|
|
127
|
+
account.
|
|
128
|
+
|
|
129
|
+
Requesting a URL already present in the campaign's video set is a safe
|
|
130
|
+
no-op — the library tab's Select control is a TOGGLE, not an idempotent
|
|
131
|
+
"ensure selected" button, so a blind click on an already-selected card
|
|
132
|
+
would have deselected it and Save would have persisted that as a removal.
|
|
133
|
+
`--add-video-url` now checks the current set first and returns without
|
|
134
|
+
opening the modal when the video is already there, and correctly reports
|
|
135
|
+
no change was made (no `AddedVideoUrl` in the JSON output, no false "did
|
|
136
|
+
not save as requested" from the post-save verification pass).
|
|
137
|
+
|
|
138
|
+
**`direct dictionaries get-retargeting-goals` — domain/app → `ExternalId` (#852).**
|
|
139
|
+
|
|
140
|
+
Direct Commander can resolve a site address or app name into the
|
|
141
|
+
`ExternalId` used by `RetargetingList(Type=AUDIENCE) -> Rules[].Arguments[].ExternalId`,
|
|
142
|
+
but `direct-cli` had no equivalent. It calls `getRetargetingGoals`, an
|
|
143
|
+
**undocumented/internal v5 method** not declared in the `dictionaries` WSDL
|
|
144
|
+
(confirmed live: it is absent from the cached WSDL, no `X-Direct-Commander`
|
|
145
|
+
header is required or wanted, and Yandex gives no stability guarantees for
|
|
146
|
+
it). `--name` and `--ids` are mutually exclusive and one is required:
|
|
147
|
+
|
|
148
|
+
```
|
|
149
|
+
direct dictionaries get-retargeting-goals --name exist.ru
|
|
150
|
+
direct dictionaries get-retargeting-goals --ids 19000000660,19000001592
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
A `--name` search can return more than one goal `Type` for similar names
|
|
154
|
+
(e.g. `HOST` and `APPLICATION`) — pick the entry with the `Type` your use
|
|
155
|
+
case needs. Unblocks `axisrow/yandex-direct-mcp-plugin#168`.
|
|
156
|
+
|
|
157
|
+
**`direct history get` — read «История изменений» (#837).**
|
|
158
|
+
|
|
159
|
+
The API has no per-field change journal at all: `changes.checkCampaigns`
|
|
160
|
+
reports only `ChangesIn: SELF|CHILDREN|STAT`, `changes.check` returns bare
|
|
161
|
+
lists of changed IDs, and Live v4's `GetEventsLog` logs system events
|
|
162
|
+
(`BannerModerated`, `MoneyOut`, …). None of them answer "who changed this
|
|
163
|
+
field, when, and from what to what" — the question that arises after a bulk
|
|
164
|
+
edit silently drops a strategy goal or a Metrika counter.
|
|
165
|
+
|
|
166
|
+
That history exists only in the web interface, so this is a new browser-backed
|
|
167
|
+
group alongside `masters`, sharing its session stack (`--headful`/
|
|
168
|
+
`--profile-dir`/`--chrome-profile`, no `--login`):
|
|
169
|
+
|
|
170
|
+
```
|
|
171
|
+
direct history get --campaign-ids 77593206 --categories CAMPAIGN_STRATEGY
|
|
172
|
+
direct history get --date-from 2026-07-01 --date-to 2026-08-13 --limit 50
|
|
173
|
+
```
|
|
174
|
+
|
|
175
|
+
A separate group rather than a `masters` subcommand because the section is
|
|
176
|
+
account-wide: its records cover ordinary ТГО/ЕПК campaigns, ad groups and ads
|
|
177
|
+
as much as Мастера, and `--campaign-ids` is only a filter over that.
|
|
178
|
+
|
|
179
|
+
Like `masters list` (#639), it does not scrape the DOM — it replays the
|
|
180
|
+
section's own data call, `POST /web-api/user-action-log/api`. Live recon
|
|
181
|
+
established two things that differ from the campaigns grid: `variables.login`
|
|
182
|
+
is required (dropping it returns HTTP 200 with `errors: ["Нет прав"]`, not an
|
|
183
|
+
auth failure), while the URL-level `ulogin` parameter is *not* needed — the
|
|
184
|
+
opposite of `GridCampaigns`, where one's own login as `ulogin` causes HTTP
|
|
185
|
+
401. Filters are applied server-side; results paginate automatically.
|
|
186
|
+
|
|
187
|
+
Each record carries `Datetime`, `Login`, `ChangeSource`, `Category`,
|
|
188
|
+
`EventType` and `CampaignId`/`CampaignName`, with the event's own fields under
|
|
189
|
+
`Event`, passed through as-is because it is a union with a different shape per
|
|
190
|
+
`EventType`. A dropped strategy goal reads directly off
|
|
191
|
+
`Event.oldStrategy.goalId` → `Event.newStrategy.goalId`.
|
|
192
|
+
|
|
193
|
+
Pagination de-duplicates on `gtid`: the cursor is a timestamp with one-second
|
|
194
|
+
granularity, so consecutive pages overlap on records sharing a second. Live
|
|
195
|
+
measurement over 2026-07-01..08-13 returned 6877 rows for 3257 distinct
|
|
196
|
+
records — an inflation invisible in a two-page check, since the first overlap
|
|
197
|
+
landed on page 3.
|
|
198
|
+
|
|
199
|
+
**`masters update --from-file` / `--masters-json` — update many campaigns in
|
|
200
|
+
one browser session (#834).**
|
|
201
|
+
|
|
202
|
+
`masters update` took exactly one campaign id, so editing 10–20 Мастеров
|
|
203
|
+
кампаний meant 10–20 separate CLI processes, each launching its own Chromium
|
|
204
|
+
and re-entering Yandex. A live run of 17 campaigns (#829) showed that it is
|
|
205
|
+
the repeated re-entry — not the editing — that fails.
|
|
206
|
+
|
|
207
|
+
Batch mode follows the existing API-command convention (`keywords add`,
|
|
208
|
+
#562): a JSONL plan via `--from-file`, or the same array inline via
|
|
209
|
+
`--masters-json`, mutually exclusive with each other and with a positional
|
|
210
|
+
`CAMPAIGN_ID`, and supported only with `--format json`.
|
|
211
|
+
|
|
212
|
+
```
|
|
213
|
+
direct masters update --from-file plan.jsonl --moderation-statuses
|
|
214
|
+
```
|
|
215
|
+
|
|
216
|
+
```jsonl
|
|
217
|
+
{"CampaignId": 713234064, "Headlines": {"1": "Новый заголовок"}}
|
|
218
|
+
{"CampaignId": 713231614, "LandingUrl": "https://lp.example.ru/x"}
|
|
219
|
+
```
|
|
220
|
+
|
|
221
|
+
Each row is one campaign: PascalCase keys that mirror this command's own
|
|
222
|
+
flags 1:1, with slot numbers and positions **1-based exactly as in
|
|
223
|
+
`--headline "2=..."`**. Rows are fully typed-checked before the first save
|
|
224
|
+
(the same bounds, enum values and duplicate rules Click enforces for the
|
|
225
|
+
flags), so a typo in the last row cannot leave earlier campaigns already
|
|
226
|
+
mutated. An unknown key is an error naming the row and the allowed set,
|
|
227
|
+
never a silent skip.
|
|
228
|
+
|
|
229
|
+
Execution notes:
|
|
230
|
+
|
|
231
|
+
- the whole plan runs over **one** browser session, paced between campaigns
|
|
232
|
+
(`--pacing-ms`, default 1000);
|
|
233
|
+
- a campaign that fails is reported with an `Error` and does not stop the
|
|
234
|
+
rest; the command exits non-zero afterwards (#816/#831 semantics);
|
|
235
|
+
- if the session goes stale mid-plan it is re-opened once and the run
|
|
236
|
+
resumes from the first campaign that was never saved — **an
|
|
237
|
+
already-saved campaign is never saved twice**;
|
|
238
|
+
- `--moderation-statuses` reads each campaign's current statuses after its
|
|
239
|
+
save. Moderation usually has not run yet at that point, so this is a
|
|
240
|
+
snapshot, not a final verdict;
|
|
241
|
+
- `--dry-run` validates the plan (including that local image/video files
|
|
242
|
+
exist) and prints what would be applied without opening a browser. It now
|
|
243
|
+
works for a single `CAMPAIGN_ID` too, printing the same report shape.
|
|
244
|
+
|
|
245
|
+
**`masters get --tracking-params` — read a campaign's UTM string (#824).**
|
|
246
|
+
|
|
247
|
+
`masters get` never returned `TrackingParams`, either set or empty: the
|
|
248
|
+
overview page it reads has no such field at all — "UTM-метки и параметры
|
|
249
|
+
URL" only exists on the edit page, behind the "Дополнительные параметры"
|
|
250
|
+
spoiler. This left `masters update --tracking-params`'s own result as the
|
|
251
|
+
only evidence a save had taken effect, with no independent way to audit a
|
|
252
|
+
batch of campaigns afterwards.
|
|
253
|
+
|
|
254
|
+
A flag on the existing `get`, not a new subcommand — same shape as
|
|
255
|
+
`--moderation-statuses` (#814): one more edit-page navigation, merged into
|
|
256
|
+
the same result rather than paying for it on every `get`.
|
|
257
|
+
|
|
258
|
+
```
|
|
259
|
+
direct masters get 74736436 --tracking-params
|
|
260
|
+
```
|
|
261
|
+
|
|
262
|
+
adds `TrackingParams` to the result:
|
|
263
|
+
|
|
264
|
+
- a UTM string is set → the string itself;
|
|
265
|
+
- the field is mounted and genuinely empty → `""`;
|
|
266
|
+
- the section could not be confirmed readable within the wait budget →
|
|
267
|
+
`TrackingParams` is omitted, rather than reporting a misleading `""`
|
|
268
|
+
(mirrors how the DRAFT overview path already omits an unreadable
|
|
269
|
+
`LandingUrl`).
|
|
270
|
+
|
|
271
|
+
Reuses `_read_tracking_params`/`_wait_for_utm_section`, the same reader
|
|
272
|
+
`masters update`'s own verify path already relies on (#769/#774), so `get`
|
|
273
|
+
and `update --tracking-params` agree on what "empty" vs "unreadable" means
|
|
274
|
+
for this field. Without the flag, `get`'s output and page loads are
|
|
275
|
+
unchanged.
|
|
276
|
+
|
|
7
277
|
**`masters get --moderation-statuses` — detect per-element moderation
|
|
8
278
|
rejections in Мастер кампаний (#814).**
|
|
9
279
|
|
|
@@ -107,10 +377,277 @@ All real-page interactions in this recon pass were abandoned via the
|
|
|
107
377
|
modal's Cancel button (never Save) — the campaign's saved image set is
|
|
108
378
|
confirmed unchanged before and after.
|
|
109
379
|
|
|
380
|
+
**`masters counters get` — read a Мастер's linked Metrika counters (#842).**
|
|
381
|
+
|
|
382
|
+
Sibling of `masters targetactions get`/`masters audience get`, and the missing
|
|
383
|
+
half of the target-action story: a goal can only be added from a *linked*
|
|
384
|
+
counter's goals, so "`--add-target-action` can't find the goal" and "no counter
|
|
385
|
+
is linked at all" used to look identical from the CLI.
|
|
386
|
+
|
|
387
|
+
```
|
|
388
|
+
direct masters counters get 713234204
|
|
389
|
+
```
|
|
390
|
+
|
|
391
|
+
Each entry carries the raw tag `Text` plus the `CounterId`/`Domain` parsed from
|
|
392
|
+
it. `SectionPresent` disambiguates the empty case: `false` means the "Счетчики
|
|
393
|
+
Яндекс Метрики" block is not on the page at all (see #843 below), `true` with
|
|
394
|
+
`Count: 0` is the genuinely counter-less campaign.
|
|
395
|
+
|
|
396
|
+
Two read hazards found live and guarded against, both of which make a
|
|
397
|
+
single-sample read confidently wrong rather than merely early:
|
|
398
|
+
|
|
399
|
+
* The block is briefly present in the initial render and is **unmounted** once
|
|
400
|
+
the form settles on a `max-clicks` campaign (measured on 74736436: `block=1`
|
|
401
|
+
at t+0, `block=0` from t+3s) — the mirror image of this module's usual
|
|
402
|
+
hydration races, where absence is the transient state.
|
|
403
|
+
* A linked tag's own text hydrates in two stages — `"72112213"` at t+0, then
|
|
404
|
+
`"gc.ksamata.ru • 72112213\n30 целей"` from t+4s. The early value has no
|
|
405
|
+
separator, so it parses to `CounterId: null` even though the id is right
|
|
406
|
+
there.
|
|
407
|
+
|
|
408
|
+
Both are handled by polling for a *stable* reading, mirroring
|
|
409
|
+
`_wait_for_target_actions_settled`.
|
|
410
|
+
|
|
110
411
|
### Fixed
|
|
111
412
|
|
|
112
|
-
**`masters` —
|
|
113
|
-
|
|
413
|
+
**`masters login` — no more visible blinking probe tab next to the login
|
|
414
|
+
form (#858).**
|
|
415
|
+
|
|
416
|
+
`login_persistent_session` (`direct_cli/browser/session.py`) detected a
|
|
417
|
+
completed manual login by keeping a second, visible tab open in the same
|
|
418
|
+
browser window and re-navigating it to the campaigns grid once a second
|
|
419
|
+
(`probe.goto(GRID_URL)` + HTML marker scans in a loop) until the session
|
|
420
|
+
appeared or the timeout hit. Next to the form the user was typing into, that
|
|
421
|
+
tab blinked/reloaded every second with no explanation — the motivation (never
|
|
422
|
+
navigate the page the human is typing into, so a half-filled form or a pending
|
|
423
|
+
2FA prompt is never wiped) was right, but the mechanism was both noisy UX and
|
|
424
|
+
a heavyweight way to ask "is there a session cookie yet".
|
|
425
|
+
|
|
426
|
+
The wait now polls the shared cookie jar instead: `context.cookies()` is read
|
|
427
|
+
every second for Yandex's SSO session cookies (`Session_id`/`sessionid2` —
|
|
428
|
+
the same jar `_chrome_crypto` copies out of Chrome to authenticate a fresh
|
|
429
|
+
context, so their presence *is* the Direct session). No navigation and no
|
|
430
|
+
second tab happen while the user is still typing. Only when a session-cookie
|
|
431
|
+
signature appears (or changes after a rejected attempt — a stale
|
|
432
|
+
`Session_id` left in a reused profile does not re-trigger the probe every
|
|
433
|
+
second) is a short-lived probe tab opened for a single verification
|
|
434
|
+
navigation, running the same fail-closed checks as before (`commit`
|
|
435
|
+
navigation per #686, marker poll per #692, captcha and login-page content
|
|
436
|
+
scans) on the full marker budget rather than a 1s slice, then closed. The
|
|
437
|
+
page the human is typing into is never navigated, exactly as before.
|
|
438
|
+
|
|
439
|
+
Verification outcomes are now a tri-state: authenticated (login recorded,
|
|
440
|
+
command exits), login-page-served (conclusive "jar does not add up to a valid
|
|
441
|
+
session" — wait for different cookies), or inconclusive (unrendered page,
|
|
442
|
+
retried next tick). The one-shot verification's navigation reuses #865's
|
|
443
|
+
`_goto_with_network_retry` unchanged: a transient network-layer abort
|
|
444
|
+
(issue #857's `net::ERR_ABORTED` on a flaky VPN) is retried in place, and a
|
|
445
|
+
connection that stays dead fails as `BrowserNetworkError` — never mistaken
|
|
446
|
+
for an auth problem. A browser/context that dies mid-wait (crash, or the
|
|
447
|
+
user closing the window to abort the login) fails fast with a clean "run
|
|
448
|
+
`direct masters login` again" error instead of a raw PlaywrightError
|
|
449
|
+
traceback — every call such a death can break is CDP-only, so retrying it
|
|
450
|
+
until the timeout could never succeed (review of #858). The session-cookie
|
|
451
|
+
signature also rejects look-alike domains (`notyandex.ru`) that a bare
|
|
452
|
+
`endswith("yandex.ru")` would have accepted.
|
|
453
|
+
|
|
454
|
+
**`masters` status parsing — read the header status element, not the whole
|
|
455
|
+
page body (#848).**
|
|
456
|
+
|
|
457
|
+
`masters archive`/`suspend` refused to act on campaign 107705868 with
|
|
458
|
+
`Could not determine current status … (unrecognised status text) — refusing to
|
|
459
|
+
click blind`, while the same error's own diagnostic reported the page's
|
|
460
|
+
`CampaignHeader.ActionButton.stop` ("Остановить кампанию") as present and
|
|
461
|
+
enabled — i.e. the campaign was plainly ACTIVE. The refusal itself is correct
|
|
462
|
+
behaviour (never click blind), but it left the campaign unreachable by every
|
|
463
|
+
lifecycle command.
|
|
464
|
+
|
|
465
|
+
`_read_status_text` matched four fixed Russian phrases as substrings of
|
|
466
|
+
`inner_text("body")` — the entire page. Live recon 2026-08-14 established that
|
|
467
|
+
`CampaignHeader.Status` carries the status verbatim in *every* state, not just
|
|
468
|
+
DRAFT as this module's older comments assumed: confirmed on real campaigns as
|
|
469
|
+
"Кампания активна" (713234142), "Кампания остановлена" (107705868) and
|
|
470
|
+
"Кампания в\xa0архиве" (100571135). The parser now reads that element first and
|
|
471
|
+
falls back to body text only when it is absent or unrecognised.
|
|
472
|
+
|
|
473
|
+
Matching the header node also removes the substring-over-everything failure
|
|
474
|
+
mode: the dashboard renders free-form moderation prose that mentions the
|
|
475
|
+
campaign (confirmed live on 713234142: "…Кампания запущена из\xa0прошедших
|
|
476
|
+
модерацию элементов объявления"), so which status won depended on which
|
|
477
|
+
unrelated banners happened to be rendered. Marker comparison is additionally
|
|
478
|
+
normalised over case, U+00A0 and collapsed whitespace, so a marker can no
|
|
479
|
+
longer silently never match over an invisible character — a pitfall that had
|
|
480
|
+
already cost a full debugging pass twice (#704, #730).
|
|
481
|
+
|
|
482
|
+
Unrecognised text still returns `None`, so the refuse-to-click-blind guard is
|
|
483
|
+
unchanged.
|
|
484
|
+
|
|
485
|
+
**`masters update --add-metrika-counter` — read the suggestion popup at all,
|
|
486
|
+
and accept a bare counter id (#846).**
|
|
487
|
+
|
|
488
|
+
The counter autocomplete could never match anything, for any input. The option
|
|
489
|
+
search was scoped to `[data-testid="MetrikaCountersTagGroup.editor.listBox"]`,
|
|
490
|
+
but no element carries that testid: live recon (campaign 74099856) found the
|
|
491
|
+
suggestions are portalled into a `dc-Popup` under `<body>` — outside the editor
|
|
492
|
+
subtree entirely — and the `…editor.listBox` string appears only as the *prefix*
|
|
493
|
+
of each option's own testid, `…editor.listBox.{counter id}`. The locator
|
|
494
|
+
resolved to zero elements, `get_by_role("option")` under it always counted 0,
|
|
495
|
+
and every add timed out.
|
|
496
|
+
|
|
497
|
+
The timeout was reported as "No suggestion exactly matching {text} appeared",
|
|
498
|
+
which blamed the caller's text for a selector bug — the operator in #846 spent
|
|
499
|
+
three attempts (bare id, domain, full `label • domain • id` line) trying to
|
|
500
|
+
respell a value that was never being compared against anything.
|
|
501
|
+
|
|
502
|
+
Options are now located directly by that testid prefix, and because the testid
|
|
503
|
+
carries the counter id, `--add-metrika-counter` accepts the **bare numeric id**:
|
|
504
|
+
|
|
505
|
+
```
|
|
506
|
+
direct masters update <id> --promotion-goal max-conversions \
|
|
507
|
+
--add-metrika-counter 72112213
|
|
508
|
+
```
|
|
509
|
+
|
|
510
|
+
The id is matched against the option's own id, never as a substring of the
|
|
511
|
+
display text, so a short id cannot silently select a counter whose label or
|
|
512
|
+
domain merely contains those digits. The full suggestion line still works. This
|
|
513
|
+
closes the round trip with `masters counters get`, whose `CounterId` can now be
|
|
514
|
+
passed straight back in — its tag text (`gc.ksamata.ru • 72112213`) has a
|
|
515
|
+
different shape from the suggestion (`Ксамата Директ • gc.ksamata.ru •
|
|
516
|
+
72112213`) and never could be.
|
|
517
|
+
|
|
518
|
+
Both failure messages now diagnose rather than accuse: a non-match lists what
|
|
519
|
+
Yandex actually offered, and an empty list points at account access to the
|
|
520
|
+
counter, since the search runs server-side on partial input (typing `7211`
|
|
521
|
+
surfaces the full suggestion).
|
|
522
|
+
|
|
523
|
+
Live-verified end to end on campaign 74099856: the counter links, its tag
|
|
524
|
+
renders, and the "Счетчик отсутствует" warning under «Целевые действия» clears.
|
|
525
|
+
That recon was deliberately not saved.
|
|
526
|
+
|
|
527
|
+
**`masters counters get` — stop reporting an unstable `SectionPresent` (#846).**
|
|
528
|
+
|
|
529
|
+
The same campaign could report `SectionPresent: true` in one run and `false` in
|
|
530
|
+
the next with no update in between. `_metrika_counters_section_present` already
|
|
531
|
+
polled for a *stable* answer rather than trusting one sample — the block is
|
|
532
|
+
briefly rendered at t+0 on a max-clicks campaign and unmounted once the form
|
|
533
|
+
settles (~t+3000ms live) — but its stability window was calibrated shorter than
|
|
534
|
+
the transient it guards against.
|
|
535
|
+
|
|
536
|
+
At 500ms × 3 ticks the loop settles after 3 samples spanning 2 gaps, ~1.0s: it
|
|
537
|
+
certified "stably present" a full 2 seconds *before* the unmount. Whether a run
|
|
538
|
+
saw `true` or `false` came down to how the poll ticks happened to line up
|
|
539
|
+
against the flip.
|
|
540
|
+
|
|
541
|
+
The window is now 500ms × 8 (~3.5s of agreement) against its own 8s budget,
|
|
542
|
+
rather than borrowing the autocomplete's 5s. The budget must exceed *flip +
|
|
543
|
+
window*, not just the window, because the run of agreement only starts after
|
|
544
|
+
the flip — at 5s the max-clicks case never settled at all and reached the right
|
|
545
|
+
answer only through the "report the last read" fallback, which is not a
|
|
546
|
+
property worth depending on. Both outcomes now settle deliberately:
|
|
547
|
+
max-conversions at ~3.5s, max-clicks at ~6.5s.
|
|
548
|
+
|
|
549
|
+
The presence locator is also re-resolved per sample instead of once before the
|
|
550
|
+
loop. Playwright locators are lazy and re-query on each `count()`, so this was
|
|
551
|
+
not itself the live bug, but a loop whose entire purpose is watching the DOM
|
|
552
|
+
change should not depend on that implicitly.
|
|
553
|
+
|
|
554
|
+
Regression tests model the live timeline against the fake clock and fail on the
|
|
555
|
+
old constants.
|
|
556
|
+
|
|
557
|
+
**`masters update` — apply Metrika counters BEFORE target actions (#844).**
|
|
558
|
+
|
|
559
|
+
`update_master` linked counters ~200 lines *after* it added target actions, so
|
|
560
|
+
`--add-metrika-counter` together with `--add-target-action` in one call was
|
|
561
|
+
impossible by construction: the "Добавить" popup only lists goals belonging to
|
|
562
|
+
an already-linked counter, so the goal was looked up against the counter set as
|
|
563
|
+
it stood before the counter was attached, and the call failed with "Could not
|
|
564
|
+
find goal N in the 'Добавить' target-action popup".
|
|
565
|
+
|
|
566
|
+
This is an ordering bug, not a hydration race — waiting longer at the old
|
|
567
|
+
position never helped, because the counter simply was not linked yet.
|
|
568
|
+
Live-confirmed on campaign 74736436: the same call that used to fail now links
|
|
569
|
+
the counter and adds the goal, and the restore path
|
|
570
|
+
|
|
571
|
+
```
|
|
572
|
+
direct masters update <id> --promotion-goal max-conversions \
|
|
573
|
+
--add-metrika-counter "…" --add-target-action "<goal_id>=150"
|
|
574
|
+
```
|
|
575
|
+
|
|
576
|
+
completes end to end — which matters because none of its three parts can be
|
|
577
|
+
applied on their own (`max-conversions` will not save without a goal, and a goal
|
|
578
|
+
cannot be added without a counter).
|
|
579
|
+
|
|
580
|
+
**`masters update --add-metrika-counter` — name the promotion-goal
|
|
581
|
+
precondition instead of blaming markup drift (#843).**
|
|
582
|
+
|
|
583
|
+
The command failed with "Could not click the 'Счетчики Яндекс Метрики' launcher
|
|
584
|
+
button — Yandex may have changed the page's markup", sending the operator
|
|
585
|
+
hunting for a drifted testid that had not drifted. Live recon confirmed the
|
|
586
|
+
selector is correct and the section simply does not exist yet: on campaign
|
|
587
|
+
74736436 (goal `max-clicks`) both the launcher and the whole
|
|
588
|
+
`MetrikaCountersBlock` settle to absent, and both mount once the goal is
|
|
589
|
+
switched to `max-conversions`. The error now says so and names the fix — pass
|
|
590
|
+
`--promotion-goal max-conversions` in the same call, since it is applied before
|
|
591
|
+
counters.
|
|
592
|
+
|
|
593
|
+
**`masters update --promotion-goal` — retry a swallowed dropdown click, and
|
|
594
|
+
report Yandex's own rejection text (#840).**
|
|
595
|
+
|
|
596
|
+
`--promotion-goal max-conversions` failed with "Could not find the 'Максимум
|
|
597
|
+
целевых действий' option in the 'Цель продвижения' dropdown — Yandex may have
|
|
598
|
+
changed the page's markup" on campaigns whose markup had not changed at all.
|
|
599
|
+
Live recon (campaign 74736436) confirmed the heading, the trigger XPath and
|
|
600
|
+
the `CampaignTargetSelect.TargetSelect.ListBox.INVOLVED_CONVERSION` testid all
|
|
601
|
+
still match; instrumenting a failing run found the real cause — the same
|
|
602
|
+
menu-trigger hydration race as #723/#725. The trigger already matched
|
|
603
|
+
(`count=1`) while un-hydrated (`aria-expanded=None`, empty `inner_text()`), so
|
|
604
|
+
Playwright's actionability check passed, the click landed on a node with no
|
|
605
|
+
React handler attached, and nothing opened. Because the option rows exist in
|
|
606
|
+
the DOM only while the dropdown is open, the follow-up `option.click()` then
|
|
607
|
+
misreported a hydration race as a markup change. `_set_promotion_goal` now
|
|
608
|
+
opens the dropdown through `_click_and_wait_for_popup` (retrying the open),
|
|
609
|
+
like the other menu/modal triggers in this module.
|
|
610
|
+
|
|
611
|
+
Saves refused by Yandex's client-side validation are also no longer reported
|
|
612
|
+
with a generic "Yandex may have rejected the value". `update_master` now reads
|
|
613
|
+
the inline error text before `_verify_saved`'s reload discards it and quotes it
|
|
614
|
+
back — closing the follow-up the module docstring's "Save verification"
|
|
615
|
+
paragraph had left open. On a campaign with no Metrika goals, switching to
|
|
616
|
+
`max-conversions` is refused outright (the save request is never sent), and the
|
|
617
|
+
error now names why: "Добавьте хотя бы одну цель для сайта, чтобы создать и
|
|
618
|
+
запустить кампанию." Note that the "Счетчики Яндекс Метрики" section itself
|
|
619
|
+
only renders once the promotion goal is `max-conversions`, so a campaign that
|
|
620
|
+
lost both needs the counter, the goal and the promotion goal set in one call.
|
|
621
|
+
|
|
622
|
+
**`masters launch`/`masters update --launch` — diagnose an empty "Целевые
|
|
623
|
+
действия" table instead of a generic redirect timeout (#823).**
|
|
624
|
+
|
|
625
|
+
Yandex silently refuses to save/launch a DRAFT campaign with no Яндекс
|
|
626
|
+
Метрика conversion goal ("Добавьте хотя бы одну цель для сайта, чтобы
|
|
627
|
+
создать и запустить кампанию") — same failure mode `create_master` already
|
|
628
|
+
diagnoses (#777), but the edit page's launch/save-as-draft click
|
|
629
|
+
(`_click_draft_terminal_button`, shared by `launch_master` and
|
|
630
|
+
`update_master --launch`) previously only reported the generic "Yandex did
|
|
631
|
+
not redirect away from the edit page" message on timeout, with no clue as
|
|
632
|
+
to why. Neither terminal button ever gains a disabled/`aria-disabled`
|
|
633
|
+
marker in this state, so a caller had to rediscover the root cause by hand
|
|
634
|
+
each time (live-reported in #823: a cloned Мастер кампаний whose landing
|
|
635
|
+
URL was then changed via `masters update --landing-url` lost its inherited
|
|
636
|
+
goals, and `masters launch` failed identically on three consecutive
|
|
637
|
+
attempts, including `--headful`).
|
|
638
|
+
|
|
639
|
+
On timeout, `_click_draft_terminal_button` now re-reads the "Целевые
|
|
640
|
+
действия" table (reusing `_wait_for_target_actions_settled`/
|
|
641
|
+
`_read_target_actions_or_none`, the same reader `create_master`'s pre-click
|
|
642
|
+
gate uses — confirmed to be the identical widget on both pages) and raises
|
|
643
|
+
a specific error naming the empty goals table when that's the cause,
|
|
644
|
+
falling back to the original generic message otherwise. This diagnosis
|
|
645
|
+
itself polls for up to another ~25s after the original redirect deadline
|
|
646
|
+
already elapsed, so a merely-slow (not stuck) Yandex redirect can land
|
|
647
|
+
*during* the diagnosis; `page.url` is re-checked immediately before each
|
|
648
|
+
`raise` so a redirect landing mid-diagnosis is treated as success rather
|
|
649
|
+
than reported as a false failure on an already-successful, no-rollback
|
|
650
|
+
launch/save (cycle-review PR #826, Codex).
|
|
114
651
|
|
|
115
652
|
The suggestion-text format (`"{label} • {domain/path} • {numeric counter
|
|
116
653
|
id}"`) and the read-back tag-display format (`"{domain} • {numeric
|
|
@@ -331,6 +868,40 @@ Code review on #782/PR #784 found two edge cases in `delete_master`
|
|
|
331
868
|
|
|
332
869
|
### BREAKING CHANGES
|
|
333
870
|
|
|
871
|
+
**`masters` batch commands stream JSONL instead of a pretty JSON array (#866).**
|
|
872
|
+
|
|
873
|
+
`masters get|suspend|resume|launch|archive` with several campaign IDs used to
|
|
874
|
+
buffer every per-ID outcome in memory and print a single pretty JSON array
|
|
875
|
+
after the whole loop. A process killed mid-batch (a wrapper's subprocess
|
|
876
|
+
timeout — the Yandex Direct MCP plugin runs these with a 180 s default — or
|
|
877
|
+
OOM/crash) took every already-applied mutation's report with it: a caller of
|
|
878
|
+
irreversible `launch`/`archive` had no way to learn which campaigns had
|
|
879
|
+
already changed, and a blind retry re-applied live mutations. Transport-level
|
|
880
|
+
sibling of #766/#816, which fixed the in-process cases.
|
|
881
|
+
|
|
882
|
+
- With `--format json` (the default), each ID's outcome — the resulting row,
|
|
883
|
+
or its `{"CampaignId": ..., "Error": ...}` entry — is printed the moment it
|
|
884
|
+
is known, as one compact JSON object per line (JSONL), with an explicit
|
|
885
|
+
`flush()` after every record. stdout in a non-tty subprocess is
|
|
886
|
+
block-buffered, so without the per-record flush nothing would survive a
|
|
887
|
+
kill; on POSIX `subprocess.run` attaches the flushed partial stdout to
|
|
888
|
+
`TimeoutExpired`, so wrappers (and the plugin's
|
|
889
|
+
`CliTimeoutError.partial_stdout`) can now reconcile a partial batch without
|
|
890
|
+
any plugin-side change.
|
|
891
|
+
- **Migration:** parse stdout line-by-line instead of as one JSON document
|
|
892
|
+
(`for line in stdout.splitlines(): json.loads(line)`). Single-ID output
|
|
893
|
+
remains a single JSON object (one line, still `json.loads`-able whole); a
|
|
894
|
+
zero-ID batch prints `[]`. An empty batch keeps `json.loads`-compatibility
|
|
895
|
+
for that case only.
|
|
896
|
+
- `_with_session`'s stale-session self-heal re-runs the whole operation, so
|
|
897
|
+
IDs completed before the auth failure appear once per real attempt — every
|
|
898
|
+
line is a genuine outcome, and the trailing lines are the final attempt's.
|
|
899
|
+
- `--output` gets the same per-record durability: the file is opened once and
|
|
900
|
+
each record is written+flushed into it as its ID completes (previously a
|
|
901
|
+
single pretty array written at the end).
|
|
902
|
+
- Non-JSON formats (`text`/`table`/`csv`/`tsv`) are unchanged — consolidated
|
|
903
|
+
end-of-run output; they are human-readable reports, not machine streams.
|
|
904
|
+
|
|
334
905
|
**`masters add` now requires `--add-target-action` (#777).**
|
|
335
906
|
|
|
336
907
|
`masters add` previously could not create a campaign at all. Yandex's create
|