extendvcc-cli 0.1.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (35) hide show
  1. extendvcc_cli-0.1.0/.github/ISSUE_TEMPLATE/bug_report.yml +76 -0
  2. extendvcc_cli-0.1.0/.github/ISSUE_TEMPLATE/config.yml +5 -0
  3. extendvcc_cli-0.1.0/.github/ISSUE_TEMPLATE/feature_request.yml +36 -0
  4. extendvcc_cli-0.1.0/.github/workflows/ci.yml +42 -0
  5. extendvcc_cli-0.1.0/.github/workflows/release.yml +94 -0
  6. extendvcc_cli-0.1.0/.gitignore +17 -0
  7. extendvcc_cli-0.1.0/AGENTS.md +95 -0
  8. extendvcc_cli-0.1.0/CHANGELOG.md +32 -0
  9. extendvcc_cli-0.1.0/CLAUDE.md +67 -0
  10. extendvcc_cli-0.1.0/CONTRIBUTING.md +24 -0
  11. extendvcc_cli-0.1.0/LICENSE +21 -0
  12. extendvcc_cli-0.1.0/PKG-INFO +179 -0
  13. extendvcc_cli-0.1.0/README.md +151 -0
  14. extendvcc_cli-0.1.0/SECURITY.md +41 -0
  15. extendvcc_cli-0.1.0/docs/testing-policy.md +97 -0
  16. extendvcc_cli-0.1.0/pyproject.toml +57 -0
  17. extendvcc_cli-0.1.0/src/extendvcc/__init__.py +46 -0
  18. extendvcc_cli-0.1.0/src/extendvcc/_exit_codes.py +24 -0
  19. extendvcc_cli-0.1.0/src/extendvcc/_jsonl.py +35 -0
  20. extendvcc_cli-0.1.0/src/extendvcc/_paths.py +28 -0
  21. extendvcc_cli-0.1.0/src/extendvcc/auth.py +900 -0
  22. extendvcc_cli-0.1.0/src/extendvcc/cards.py +761 -0
  23. extendvcc_cli-0.1.0/src/extendvcc/cli.py +883 -0
  24. extendvcc_cli-0.1.0/src/extendvcc/client.py +491 -0
  25. extendvcc_cli-0.1.0/src/extendvcc/imap_otp.py +170 -0
  26. extendvcc_cli-0.1.0/src/extendvcc/ledger.py +535 -0
  27. extendvcc_cli-0.1.0/src/extendvcc/models.py +74 -0
  28. extendvcc_cli-0.1.0/src/extendvcc/py.typed +0 -0
  29. extendvcc_cli-0.1.0/tests/fixtures/op_credit_card_template.json +89 -0
  30. extendvcc_cli-0.1.0/tests/test_auth.py +329 -0
  31. extendvcc_cli-0.1.0/tests/test_cards.py +1233 -0
  32. extendvcc_cli-0.1.0/tests/test_cli.py +424 -0
  33. extendvcc_cli-0.1.0/tests/test_client.py +188 -0
  34. extendvcc_cli-0.1.0/tests/test_imap_otp.py +39 -0
  35. extendvcc_cli-0.1.0/tests/test_ledger.py +373 -0
@@ -0,0 +1,76 @@
1
+ name: Bug Report
2
+ description: Something isn't working as expected.
3
+ title: "bug: "
4
+ labels: ["bug"]
5
+ body:
6
+ - type: markdown
7
+ attributes:
8
+ value: |
9
+ Before submitting: confirm the bug is reproducible with the latest version. **Do not include card numbers, CVCs, API tokens, or any account credentials in this report.**
10
+
11
+ - type: textarea
12
+ id: description
13
+ attributes:
14
+ label: Description
15
+ description: What happened? What did you expect to happen instead?
16
+ validations:
17
+ required: true
18
+
19
+ - type: textarea
20
+ id: reproduction
21
+ attributes:
22
+ label: Steps to Reproduce
23
+ description: Minimal steps to trigger the bug.
24
+ placeholder: |
25
+ 1. Run `extendvcc ...`
26
+ 2. Observe ...
27
+ validations:
28
+ required: true
29
+
30
+ - type: textarea
31
+ id: expected
32
+ attributes:
33
+ label: Expected Behavior
34
+ description: What should have happened?
35
+ validations:
36
+ required: true
37
+
38
+ - type: textarea
39
+ id: actual
40
+ attributes:
41
+ label: Actual Behavior
42
+ description: What actually happened? Include the full error output (with secrets redacted).
43
+ validations:
44
+ required: true
45
+
46
+ - type: input
47
+ id: os
48
+ attributes:
49
+ label: Operating System
50
+ placeholder: "e.g. macOS 14.5, Ubuntu 22.04"
51
+ validations:
52
+ required: true
53
+
54
+ - type: input
55
+ id: python
56
+ attributes:
57
+ label: Python Version
58
+ placeholder: "e.g. 3.12.3"
59
+ validations:
60
+ required: true
61
+
62
+ - type: input
63
+ id: extendvcc
64
+ attributes:
65
+ label: extendvcc Version
66
+ placeholder: "e.g. 0.1.0 (run: extendvcc --version)"
67
+ validations:
68
+ required: true
69
+
70
+ - type: checkboxes
71
+ id: no_secrets
72
+ attributes:
73
+ label: Security Confirmation
74
+ options:
75
+ - label: I have confirmed this report contains no card numbers, CVCs, API tokens, IMAP passwords, session data, or any other sensitive credentials.
76
+ required: true
@@ -0,0 +1,5 @@
1
+ blank_issues_enabled: false
2
+ contact_links:
3
+ - name: Security Vulnerability
4
+ url: https://github.com/4LAU/extendvcc/security/advisories/new
5
+ about: Report a security vulnerability privately via GitHub Security Advisories. Do not open a public issue.
@@ -0,0 +1,36 @@
1
+ name: Feature Request
2
+ description: Propose a new feature or improvement.
3
+ title: "feat: "
4
+ labels: ["enhancement"]
5
+ body:
6
+ - type: textarea
7
+ id: problem
8
+ attributes:
9
+ label: Problem
10
+ description: What problem does this solve? What can't you do today, or what's unnecessarily hard?
11
+ validations:
12
+ required: true
13
+
14
+ - type: textarea
15
+ id: solution
16
+ attributes:
17
+ label: Proposed Solution
18
+ description: What would you like to see? Describe the behavior, CLI flags, or API changes you have in mind.
19
+ validations:
20
+ required: true
21
+
22
+ - type: textarea
23
+ id: alternatives
24
+ attributes:
25
+ label: Alternatives Considered
26
+ description: What workarounds have you tried? Are there other ways to solve this?
27
+ validations:
28
+ required: false
29
+
30
+ - type: textarea
31
+ id: context
32
+ attributes:
33
+ label: Additional Context
34
+ description: Anything else that would help — links, related issues, examples from other tools.
35
+ validations:
36
+ required: false
@@ -0,0 +1,42 @@
1
+ name: CI
2
+
3
+ on:
4
+ push:
5
+ branches: [main]
6
+ pull_request:
7
+
8
+ jobs:
9
+ lint:
10
+ runs-on: ubuntu-latest
11
+ steps:
12
+ - uses: actions/checkout@v4
13
+ - uses: astral-sh/setup-uv@v6
14
+ - run: uv venv && uv pip install -e '.[dev]'
15
+ - run: uv run ruff check src/ tests/
16
+ - run: uv run ruff format --check src/ tests/
17
+
18
+ test:
19
+ runs-on: ubuntu-latest
20
+ strategy:
21
+ matrix:
22
+ python-version: ["3.11", "3.12", "3.13"]
23
+ steps:
24
+ - uses: actions/checkout@v4
25
+ - uses: astral-sh/setup-uv@v6
26
+ with:
27
+ python-version: ${{ matrix.python-version }}
28
+ - run: uv venv && uv pip install -e '.[dev]'
29
+ - run: uv run pytest tests/ -v
30
+
31
+ gitleaks:
32
+ runs-on: ubuntu-latest
33
+ steps:
34
+ - uses: actions/checkout@v4
35
+ with:
36
+ fetch-depth: 0
37
+ - name: Install gitleaks
38
+ run: |
39
+ curl -sSfL "https://github.com/gitleaks/gitleaks/releases/download/v8.24.3/gitleaks_8.24.3_linux_x64.tar.gz" \
40
+ | tar xz -C /usr/local/bin gitleaks
41
+ - name: Scan
42
+ run: gitleaks detect --source . -v --redact
@@ -0,0 +1,94 @@
1
+ name: Release
2
+
3
+ on:
4
+ push:
5
+ tags: ["v*"]
6
+
7
+ permissions:
8
+ contents: write
9
+
10
+ jobs:
11
+ build:
12
+ strategy:
13
+ matrix:
14
+ include:
15
+ - os: macos-latest
16
+ arch: arm64
17
+ artifact: extendvcc-macos-arm64
18
+ - os: macos-13
19
+ arch: x86_64
20
+ artifact: extendvcc-macos-x86_64
21
+ - os: ubuntu-latest
22
+ arch: x86_64
23
+ artifact: extendvcc-linux-x86_64
24
+ - os: windows-latest
25
+ arch: x86_64
26
+ artifact: extendvcc-windows-x86_64.exe
27
+
28
+ runs-on: ${{ matrix.os }}
29
+ steps:
30
+ - uses: actions/checkout@v4
31
+ - uses: actions/setup-python@v5
32
+ with:
33
+ python-version: "3.13"
34
+ - run: pip install pyinstaller
35
+ - run: pip install -e .
36
+ - run: pyinstaller --onefile --name ${{ matrix.artifact }} --strip src/extendvcc/cli.py
37
+ shell: bash
38
+ - uses: actions/upload-artifact@v4
39
+ with:
40
+ name: ${{ matrix.artifact }}
41
+ path: dist/${{ matrix.artifact }}*
42
+
43
+ test:
44
+ runs-on: ubuntu-latest
45
+ steps:
46
+ - uses: actions/checkout@v4
47
+ - uses: actions/setup-python@v5
48
+ with:
49
+ python-version: "3.13"
50
+ - run: pip install -e ".[dev]"
51
+ - run: ruff check src/ tests/
52
+ - run: ruff format --check src/ tests/
53
+ - run: pytest tests/ -q
54
+
55
+ release:
56
+ needs: [build, test]
57
+ runs-on: ubuntu-latest
58
+ steps:
59
+ - uses: actions/download-artifact@v4
60
+ with:
61
+ merge-multiple: true
62
+ - uses: softprops/action-gh-release@v2
63
+ with:
64
+ files: extendvcc-*
65
+ generate_release_notes: true
66
+
67
+ build-dist:
68
+ needs: [test]
69
+ runs-on: ubuntu-latest
70
+ steps:
71
+ - uses: actions/checkout@v4
72
+ - uses: actions/setup-python@v5
73
+ with:
74
+ python-version: "3.13"
75
+ - run: pip install build
76
+ - run: python -m build
77
+ - uses: actions/upload-artifact@v4
78
+ with:
79
+ name: python-dist
80
+ path: dist/
81
+
82
+ publish:
83
+ needs: [build-dist, test]
84
+ runs-on: ubuntu-latest
85
+ environment: release
86
+ permissions:
87
+ id-token: write
88
+ contents: read
89
+ steps:
90
+ - uses: actions/download-artifact@v4
91
+ with:
92
+ name: python-dist
93
+ path: dist/
94
+ - uses: pypa/gh-action-pypi-publish@cef221092ed1bacb1cc03d23a2d87d1d172e277b # v1.14.0
@@ -0,0 +1,17 @@
1
+ __pycache__/
2
+ *.pyc
3
+ *.pyo
4
+ dist/
5
+ build/
6
+ *.egg-info/
7
+ .venv/
8
+ .env
9
+ .env.*
10
+ .claude/
11
+ .codex/
12
+ .ruff_cache/
13
+ .pytest_cache/
14
+ *.jsonl
15
+ *.json
16
+ !tests/fixtures/*.json
17
+ .worktrees/
@@ -0,0 +1,95 @@
1
+ # AGENTS.md: Contributor Guide
2
+
3
+ Tool-agnostic guide for AI agents and human contributors. For Claude Code specifics, see `CLAUDE.md`.
4
+
5
+ ---
6
+
7
+ ## Project Overview
8
+
9
+ `extendvcc` is an unofficial Python client and CLI for Extend's private virtual card API (`api.paywithextend.com`). It handles Cognito SRP authentication with device remembering and email OTP, the full virtual-card lifecycle (create, list, update, cancel, close, reveal), parent credit-card enrollment, and a JSONL audit ledger for all card mutations. HTTP uses `impit` for Chrome TLS fingerprinting, which is necessary because Extend blocks non-browser TLS profiles.
10
+
11
+ ---
12
+
13
+ ## Build, Lint, Test
14
+
15
+ All three must be clean before any change is considered done:
16
+
17
+ ```bash
18
+ uv run ruff check src/ tests/
19
+ uv run ruff format --check src/ tests/
20
+ uv run pytest tests/ -v
21
+ ```
22
+
23
+ Install dev dependencies first if needed: `uv pip install -e '.[dev]'`
24
+
25
+ ---
26
+
27
+ ## Module Map
28
+
29
+ ```
30
+ src/extendvcc/
31
+ ├── __init__.py # Public API re-exports
32
+ ├── _paths.py # Lazy state_dir() / ledger_path() with CLI override, env, default
33
+ ├── _jsonl.py # Vendored append_jsonl helper
34
+ ├── auth.py # Cognito SRP login, device remembering, token refresh, session persistence
35
+ ├── client.py # HTTP client (impit), kill switch, rate limiting, account-risk detection
36
+ ├── cards.py # Card CRUD — create, list, get, update, cancel, close, reveal, enroll, bulk
37
+ ├── imap_otp.py # IMAP-based OTP retrieval for Cognito EMAIL_OTP challenges
38
+ ├── ledger.py # JSONL audit ledger for card mutations (pending/confirm/fail)
39
+ ├── models.py # CardStatus, VirtualCard, CreditCard, Issuer, Recurrence
40
+ └── cli.py # Full lifecycle CLI (login, cards, create, reveal, etc.)
41
+ ```
42
+
43
+ ---
44
+
45
+ ## Coding Style
46
+
47
+ - **HTTP:** All network requests go through `impit` with Chrome TLS fingerprinting. Never import `httpx` or `requests` directly; Extend detects and blocks non-browser TLS profiles.
48
+ - **Paths:** Use `_paths.py` helpers (`state_dir()`, `ledger_path()`). Never compute paths at module level. Always resolve lazily so CLI flags and env vars take effect.
49
+ - **Functions:** One function at a time, max ~30 lines. Search existing code before adding anything new.
50
+ - **Logging:** Never log or print card numbers, CVCs, or full tokens. Mask to last 4 digits when logging is necessary.
51
+ - **Tests:** All tests run offline with fakes. Mock only at the I/O boundary (HTTP client, filesystem, IMAP). See `docs/testing-policy.md`.
52
+
53
+ ---
54
+
55
+ ## Commit Conventions
56
+
57
+ Follow Conventional Commits:
58
+
59
+ ```
60
+ feat: add bulk-create pacing option
61
+ fix: handle expired session token on first request
62
+ chore: bump impit to 0.13.0
63
+ style: fix trailing whitespace
64
+ docs: add reveal example to README
65
+ test: add ledger concurrent-write invariant
66
+ ```
67
+
68
+ Scope is optional. Keep the subject line under 72 characters.
69
+
70
+ ---
71
+
72
+ ## What Needs Approval vs. Proceed
73
+
74
+ **Get maintainer approval before changing:**
75
+ - Auth flow (Cognito SRP, device remembering, OTP, token refresh)
76
+ - Anything that touches card number or CVC handling
77
+ - Public API surface (`__init__.py` exports, CLI command names/flags)
78
+ - Destructive operations (data deletion, credential rotation)
79
+
80
+ **Proceed without asking:**
81
+ - Bug fixes inside existing functions
82
+ - New tests
83
+ - Documentation updates
84
+ - Dependency version bumps
85
+ - Refactors that don't change the public API
86
+
87
+ ---
88
+
89
+ ## Security Rules
90
+
91
+ - **Never store or log PAN/CVC:** the ledger never persists card numbers or CVCs. Mask to last 4 when logging.
92
+ - **Never make real Extend API calls in tests:** all tests run offline with fakes. Network access in tests is not skipped; it is deleted.
93
+ - **Never commit session files, credential caches, or `.env*` files:** these contain live auth tokens.
94
+ - **Credentials via env vars:** `EXTENDVCC_EMAIL`, `EXTENDVCC_PASSWORD`, `EXTENDVCC_IMAP_*`. Interactive prompts in CLI. No hardcoded credentials anywhere.
95
+ - Session and state files are written with `0600` permissions. The HTTP client has a kill switch that disables itself on risk signals (403, WAF blocks, verification prompts).
@@ -0,0 +1,32 @@
1
+ # Changelog
2
+
3
+ All notable changes to this project will be documented in this file.
4
+
5
+ The format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
6
+
7
+ ---
8
+
9
+ ## [Unreleased]
10
+
11
+ ---
12
+
13
+ ## [0.1.0] - 2026-06-14
14
+
15
+ ### Added
16
+
17
+ - **Authentication:** Cognito SRP login with device remembering; automatic token refresh; session persistence to disk with `0600` permissions.
18
+ - **Email OTP:** IMAP-based OTP retrieval for Cognito `EMAIL_OTP` challenges, configurable via `EXTENDVCC_IMAP_*` env vars.
19
+ - **Virtual card lifecycle:** create, list, get, update, cancel, close, and reveal (PAN + CVC + expiry) virtual cards.
20
+ - **Reveal:** masked stdout output by default; `--json-path` writes full credentials to a file with `0600` permissions.
21
+ - **Parent credit cards:** enroll and activate parent credit cards.
22
+ - **Bulk create:** create multiple virtual cards with configurable pacing to avoid rate limits.
23
+ - **Recurring cards:** support for `DAILY`, `WEEKLY`, and `MONTHLY` recurrence periods with configurable terminators.
24
+ - **JSONL audit ledger:** append-only ledger records every card mutation as pending → confirmed or failed; `reconcile` command flags unconfirmed entries.
25
+ - **Kill switch:** HTTP client disables itself on account-risk signals (403, WAF blocks, verification prompts) to avoid account suspension.
26
+ - **Chrome TLS fingerprinting:** all HTTP via `impit`; Extend blocks non-browser TLS profiles.
27
+ - **CLI:** full lifecycle commands: `login`, `accounts`, `issuers`, `cards`, `card`, `usage`, `enroll`, `activate`, `create`, `bulk`, `update`, `cancel`, `close`, `reveal`, `reconcile`, `status`, `clear-disabled`.
28
+ - **Python API:** public re-exports in `extendvcc.__init__` for programmatic use.
29
+ - **Typed:** `py.typed` marker (PEP 561); type hints throughout.
30
+
31
+ [Unreleased]: https://github.com/4LAU/extendvcc/compare/v0.1.0...HEAD
32
+ [0.1.0]: https://github.com/4LAU/extendvcc/releases/tag/v0.1.0
@@ -0,0 +1,67 @@
1
+ # extendvcc
2
+
3
+ Unofficial Python client and CLI for Extend's private virtual card API.
4
+
5
+ **Stack:** Python 3.11+, src/ layout, hatchling build, impit (Chrome TLS fingerprinting), filelock, argparse CLI, pytest, ruff.
6
+
7
+ ---
8
+
9
+ ## Critical Rules
10
+
11
+ 1. **NEVER expose secrets:** card numbers, CVCs, API tokens, session files, PII. If exposed: STOP and rotate immediately.
12
+ 2. **NEVER use `git add .`:** add files individually. gitleaks pre-commit enforces this.
13
+ 3. **NEVER log or print card numbers, CVCs, or full tokens.** Mask to last 4 digits when logging is necessary.
14
+ 4. **NEVER make real Extend API calls in tests.** All tests run offline with fakes.
15
+ 5. **NEVER commit session files, credential caches, or `.env*` files.**
16
+ 6. **NEVER claim tests pass without showing actual output.**
17
+
18
+ ---
19
+
20
+ ## Definition of Done
21
+
22
+ ```bash
23
+ uv run ruff check src/ tests/
24
+ uv run ruff format --check src/ tests/
25
+ uv run pytest tests/ -v
26
+ ```
27
+
28
+ All three clean before claiming done.
29
+
30
+ ---
31
+
32
+ ## Code Practices
33
+
34
+ **Ask maintainer approval for:** auth flow changes, anything touching card number/CVC handling, public API surface changes, destructive ops. Everything else: proceed.
35
+
36
+ **Generation:** Search existing code first. One function at a time (max 30 lines).
37
+
38
+ **impit:** All HTTP goes through `impit` with Chrome TLS fingerprinting. Never use bare `httpx` or `requests`; Extend fingerprints non-browser clients.
39
+
40
+ **Credentials:** Env vars (`EXTENDVCC_EMAIL`, `EXTENDVCC_PASSWORD`, `EXTENDVCC_IMAP_*`). Interactive prompts in CLI. No 1Password integration in this package.
41
+
42
+ **Paths:** Lazy resolution via `_paths.py`. CLI flags override env vars override defaults. Never use module-level `Path` constants that resolve at import time.
43
+
44
+ ---
45
+
46
+ ## Module Map
47
+
48
+ ```
49
+ src/extendvcc/
50
+ ├── __init__.py # Public API re-exports
51
+ ├── _paths.py # Lazy state_dir() / ledger_path() with CLI override, env, default
52
+ ├── _jsonl.py # Vendored append_jsonl helper
53
+ ├── auth.py # Cognito SRP login, device remembering, token refresh, session persistence
54
+ ├── client.py # HTTP client (impit), kill switch, rate limiting, account-risk detection
55
+ ├── cards.py # Card CRUD — create, list, get, update, cancel, close, reveal, enroll, bulk
56
+ ├── imap_otp.py # IMAP-based OTP retrieval for Cognito EMAIL_OTP challenges
57
+ ├── ledger.py # JSONL audit ledger for card mutations (pending/confirm/fail)
58
+ ├── models.py # CardStatus, VirtualCard, CreditCard, Issuer, Recurrence
59
+ └── cli.py # Full lifecycle CLI (login, cards, create, reveal, etc.)
60
+ ```
61
+
62
+ ---
63
+
64
+ ## Testing
65
+
66
+ See `docs/testing-policy.md`. All tests offline, mock only at I/O boundary, every test protects a named invariant.
67
+
@@ -0,0 +1,24 @@
1
+ # Contributing
2
+
3
+ Fork the repo, create a feature branch, open a PR.
4
+
5
+ ## Setup
6
+
7
+ ```bash
8
+ pip install -e '.[dev]'
9
+ ```
10
+
11
+ ## Checks
12
+
13
+ Both must pass before merging:
14
+
15
+ ```bash
16
+ ruff check src/ tests/
17
+ pytest tests/ -v
18
+ ```
19
+
20
+ All tests run offline; no real API calls are made.
21
+
22
+ ## Pre-commit
23
+
24
+ A gitleaks pre-commit hook is required to prevent accidental secret leaks.
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 extendvcc contributors
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.