feat: add Bitwarden CLI integration#3
Conversation
Add bw_read() function and --bw-token-ref / --bw-login-ref CLI options as a fallback after 1Password for retrieving credentials from Bitwarden. Co-Authored-By: Claude Opus 4.6 <[email protected]>
|
@claude review |
|
Claude finished @axisrow's task in 1m 48s —— View job Code Review: feat: add Bitwarden CLI integration
Overall this is a clean, well-structured implementation that follows the same pattern as the existing 1Password integration. The credential chain logic is correct, tests cover the main scenarios, and the subprocess calls are safe (list form, not Issues1. Bitwarden session not unlocked — unhelpful error message ( The most common failure mode when using the Bitwarden CLI is that the vault is locked (no with no guidance on how to fix it. A hint about needing if result.returncode != 0:
stderr = result.stderr.strip()
if "locked" in stderr.lower():
raise RuntimeError(
f"Bitwarden CLI error: {stderr}. "
"Ensure your vault is unlocked: eval $(bw unlock)"
)
raise RuntimeError(f"Bitwarden CLI error: {stderr}")2. [bw_path, "get", field, item]This produces 3. Empty output not guarded ( If 4. Test: def test_bw_read_timeout(self, mock_which, mock_run):
import subprocess
mock_run.side_effect = subprocess.TimeoutExpired(cmd="bw", timeout=10)The 5. Incomplete env var cleanup in tests (
monkeypatch.delenv("YANDEX_DIRECT_OP_LOGIN_REF", raising=False) # add this6. Code duplication between The two functions are structurally nearly identical. This is acceptable given the different CLI interfaces ( Minor Nits
Summary
The implementation is ready to merge as-is; the issues above are improvements rather than blockers. Items 1 and 5 are the most worth fixing before merge. |
- Add helpful error message when Bitwarden vault is locked - Move subprocess import to top-level in test file - Clean up env vars in tests for robustness - Add test for locked vault scenario Co-Authored-By: Claude Opus 4.6 <[email protected]>
|
@claude review |
|
Claude finished @axisrow's task in 1m 59s —— View job Code Review: feat: add Bitwarden CLI integration
Clean, well-structured implementation that mirrors the 1Password integration correctly. Subprocess calls are safe (list form, no Issues1. The test clears 1Password env vars but not monkeypatch.delenv("YANDEX_DIRECT_BW_TOKEN_REF", raising=False) # add this
monkeypatch.delenv("YANDEX_DIRECT_BW_LOGIN_REF", raising=False) # add this
monkeypatch.setenv("YANDEX_DIRECT_BW_TOKEN_REF", "yandex-direct-item")Same issue in 2. Docstring lists field: Field to read (password, username, notes)Bitwarden CLI's 3. Priority comment is slightly misleading ( # Priority: arguments > env vars > 1Password > BitwardenBut the 4. Empty string returned by If What Looks Good
Minor Nits
The implementation is ready to merge. Issue 1 is the only one that could cause a CI flake; the rest are polish. |
- Add BW_LOGIN_REF cleanup in test_get_credentials_bw_fallback and test_get_credentials_explicit_bw_ref_param for env isolation - Remove notes from bw_read docstring (only password/username used) - Clarify priority comment to include .env step Co-Authored-By: Claude Opus 4.6 <[email protected]>
|
@claude review |
|
Claude finished @axisrow's task in 1m 44s —— View job Code Review: feat: add Bitwarden CLI integration
Clean implementation that correctly follows the existing 1Password pattern. Subprocess calls are safe (list form, no Issues1. This test clears monkeypatch.delenv("YANDEX_DIRECT_OP_LOGIN_REF", raising=False) # add
monkeypatch.delenv("YANDEX_DIRECT_BW_LOGIN_REF", raising=False) # add2. Both What Looks Good
The implementation is ready to merge. Issue 1 is the only one that could cause a CI flake; issue 2 is documentation polish. |
- Add OP_LOGIN_REF and BW_LOGIN_REF cleanup to CLI integration tests - Add field comments to .env.example Bitwarden references Co-Authored-By: Claude Opus 4.6 <[email protected]>
|
@claude review |
|
Claude finished @axisrow's task in 1m 31s —— View job Code Review: feat: add Bitwarden CLI integration
All issues from previous review rounds have been addressed. The implementation is clean, correct, and ready to merge. One small remaining gap worth noting. Remaining Issue
The test clears monkeypatch.delenv("YANDEX_DIRECT_BW_LOGIN_REF", raising=False) # add thisWhat Looks Good
The implementation is ready to merge. The one remaining item is a low-risk robustness fix for a test that could flake only if |
Co-Authored-By: Claude Opus 4.6 <[email protected]>
|
@claude review |
|
Claude finished @axisrow's task in 1m 16s —— View job Code Review: feat: add Bitwarden CLI integration
All issues from previous review rounds have been addressed. This implementation is clean, correct, and ready to merge. What's In Good Shape
One Minor Observation (Not a Blocker)
The PR is ready to merge. ✅ |
…y, advideos discovery (#119) * fix: close #112 with imported XSD validation, runtime-deprecated registry, advideos discovery Closes #112 by addressing the two open bug classes from the audit (the other three were resolved on main). PR-A — Runtime-deprecated method registry (closes AC #3): - Add RUNTIME_DEPRECATED_METHODS to direct_cli/wsdl_coverage.py (registry of WSDL-present methods that Yandex rejects at runtime, e.g. agencyclients.add with error_code=3500). - Add assert_not_runtime_deprecated() helper in direct_cli/utils.py raising click.UsageError with a stable replacement hint. - Wire the assertion into agencyclients.add so both real-call and --dry-run paths fail with a clear message before request construction. - Add 8th schema gate bucket runtime_deprecated_unguarded in scripts/build_api_coverage_report.py: walks RUNTIME_DEPRECATED_METHODS, uses the captured-client harness to verify each entry's CLI command actually rejects with UsageError before reaching the API. Bucket flips schema_parity_ok to False; mirrored into report["schema"] and a top-level report["runtime_deprecated_methods"] section for machine-readable parity. PR-B — Imported XSD validation + AdVideos smoke discovery (closes AC #1, #4): - Cache imported XSD schemas in tests/wsdl_cache/imports/ (general.xsd, generalclients.xsd, adextensiontypes.xsd). Add IMPORTED_XSD_REGISTRY (namespace -> filename) and fetch_imported_xsd() in wsdl_coverage.py. - Resolve <xsd:import> nested types in get_operation_request_schema() via _load_schema_contexts(), and walk extension/@base inheritance so item_fields for types like gc:NotificationAdd and gc:ClientUpdateItem now contain resolved nested + inherited fields (e.g. Notification.EmailSubscriptions, Clients[].Settings). - Add find_nested_schema_violations() in wsdl_coverage.py (recursive walk of body keys against the resolved schema). - Add 9th schema gate bucket nested_schema_violations: dry-runs every entry in PAYLOAD_CASES (excluding DRY_RUN_PAYLOAD_EXCLUSIONS) and flags any unknown nested key. - Extract get_first_advideo_probe_id from tests/test_integration.py into reusable direct_cli/_smoke_probes.py module with CLI entry point (python3 -m direct_cli._smoke_probes advideo). Honors YANDEX_DIRECT_TEST_ADVIDEO_ID env override; returns None on any failure so smoke scripts can skip without falsely passing. Includes graceful handling of create_client failures (auth/network). - Update scripts/test_safe_commands.sh to use run_advideos_probe_get with [INFO] skip-on-no-id semantics instead of dummy --ids 1. - Extend refresh_all_caches() and scripts/check_wsdl_drift.py to also refresh and monitor IMPORTED_XSD_REGISTRY entries. Tests added (9 new): PR-A: registry shape, block invocation (real + --dry-run paths), schema gate flags unguarded entry. PR-B: imported XSD cache↔registry parity, resolves imports (Notification.{Email,Lang,EmailSubscriptions}), resolves extension/@base fields (clients.update Clients[].Notification/Settings), payload nested violation flagging, smoke probe returns None gracefully without credentials. Schema gate goes from 7 to 9 buckets, all green: schema_parity_ok=True. Full test suite: 338 passed, 29 skipped (integration, no creds), 0 failed. Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]> * fix(schema-gate): unify dry_run_failed classification across click versions Older click (Python 3.9) returns exit_code=0 with usage/error text in stdout when a missing CLI group is invoked, while newer click returns exit_code=2. The previous logic took two paths (exit-code-based dry_run_failed vs. JSON-parse-based invalid_json), so the same input produced different bucket kinds across versions. Collapse to a single dry_run_failed kind: if either the exit code is non-zero or the output is not parseable JSON, the case is reported as dry_run_failed with the relevant error text. invalid_json was redundant and not asserted anywhere. Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]> * fix: address PR #119 review findings - Dedupe nested-schema violation reports: the recursive call already walks the same expected-key logic for each child, so the inline loop that appended unknown direct keys produced duplicate entries (clients.update Email/ClientId would each show up twice in the gate output). Delegate unknown-key detection entirely to the recursion. - Add XSD_BASE_URL constant for tests/wsdl_cache/imports/ refresh; the hardcoded soap.direct.yandex.ru URL is now grep-able and documented alongside WSDL_BASE_URL. - Add test_runtime_deprecated_methods_have_capture_fixture to enforce that every RUNTIME_DEPRECATED_METHODS entry has a complete RUNTIME_DEPRECATED_CAPTURE_FIXTURES record covering all of the target command's required options. Without this, Click would fail with "Missing option ..." before the deprecation guard runs and the gate would misclassify a guarded method as unguarded. Added a docstring on RUNTIME_DEPRECATED_CAPTURE_FIXTURES referencing the same invariant. Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]> * perf(schema-gate): avoid duplicate get_operation_field_name_enums calls The _field_name_operations comprehension called the function twice per operation: once in the if-filter and once as the value. Each call walks get_operation_request_schema → _load_schema_contexts → parse_wsdl_field_enums, so for ~27 services × ~5 operations this was ~270 redundant schema-context loads per coverage report build. Compute the dict once, then filter empty values. No behavior change. Pre-existing inefficiency that became more visible once _load_schema_contexts started resolving imported XSD types. Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]> --------- Co-authored-by: Claude Opus 4.7 (1M context) <[email protected]>
* fix(auth): stop --help and unit tests from hanging on client-login network call A regression from #480 made get_credentials() resolve the bare Client-Login via a network clients.get on every CLI invocation when an OAuth profile with an email login lacked the login_migration_checked flag — including plain `<group> --help`. That call had no timeout (neither the vendored client nor tapi2 set one), so a slow link or a Yandex SmartCaptcha gateway could hang the CLI indefinitely. Under CliRunner this also hung the unit suite (it stalled on test_registered_mapped_groups_show_docs_url, which fires ~30 `--help` calls). Three independent guards: - auth.py: cap the best-effort resolver with LOGIN_RESOLVE_TIMEOUT_SECONDS=8 and disable retries, so it can never wait forever. - cli.py: skip the resolver entirely on --help/--version/-h passes (allow_login_resolve=False) — no command runs, so no login is needed. - tests/conftest.py: autouse fixture neutralizes the network resolver for the whole unit suite; the module-level test-credential probe no longer triggers it. Adds regression tests asserting a --help pass makes zero resolver calls and that allow_login_resolve=False suppresses the migration. Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]> * fix: resolve 13 confirmed bugs from the #483 bug hunt Verified each finding against the live API, the WSDL cache, and the official Yandex Direct docs before fixing. Bugs 10 and 14 were false positives (no UsageError source inside the try — style only) and are intentionally skipped. Functional fixes: - bids/keywordbids get: reject empty SelectionCriteria with a UsageError before the request (live API error 4001) (#1, #2). - bids get: re-raise UsageError so the new guard surfaces with exit code 2 (#8). - bids set-auto: require exactly one of CampaignId/AdGroupId/KeywordId via add_single_id_selector (docs: the three are mutually exclusive) (#4). - vendored to_columns: pad short report rows instead of IndexError (#3). - vendored error handler: error_data.get("error_detail") to avoid KeyError (#11). - reports build_report_request: reject empty FieldNames (live API error 8000) (#12). Swallowed-validation fixes (add `except click.UsageError/ClickException: raise` before the bare `except Exception`): balance (#6), strategies get (#7), campaigns get (#9), ads get (#13), negativekeywordsharedsets update (#5). Type annotation: parse_priority_goals_spec items -> List[Dict[str, Any]] (#15). build_api_coverage_report: supply --campaign-ids for bids/keywordbids get wire-capture so the empty-criteria guard does not break schema parity. Closes #483 Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]> * fix: broaden #483 coverage and add CHANGELOG (cherry-picked from #484) Incorporates the genuinely-better parts of the parallel PR #484 into this branch, which already carries the regression tests, the #15 annotation fix, and the localized messages: - ads: re-raise click.UsageError in all lifecycle handlers (update, delete, archive, unarchive, suspend, resume, moderate) in addition to get — so the whole resource matches bug #13's "get + 8 lifecycle" scope. Defensive: these handlers have no pre-API UsageError source today, but the guard keeps them consistent and future-proof. - retargeting get/delete, advideos get: same defensive re-raise for uniformity (issue #483 items 10/14; not live defects, but harmless consistency). - bids/keywordbids get: the empty-criteria message now also lists --serving-statuses, which likewise populates SelectionCriteria (kept the t() localization wrapper). - CHANGELOG.md: document the bug-hunt fixes (#483) and the --help network-hang follow-up. Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]> --------- Co-authored-by: axisrow <[email protected]> Co-authored-by: Claude Opus 4.8 (1M context) <[email protected]>
Summary
bw_read()function inauth.pyfor reading secrets via Bitwarden CLI (bw get <field> <item>)--bw-token-refand--bw-login-refCLI options with corresponding env vars (YANDEX_DIRECT_BW_TOKEN_REF,YANDEX_DIRECT_BW_LOGIN_REF)Test plan
tests/test_auth_bw.py(all passing)direct-cli --bw-token-ref <item> campaigns get --helpresolves token from Bitwarden🤖 Generated with Claude Code