datalegion-cli 1.0.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 (28) hide show
  1. datalegion_cli-1.0.0/.github/workflows/ci.yml +33 -0
  2. datalegion_cli-1.0.0/.github/workflows/release.yml +145 -0
  3. datalegion_cli-1.0.0/.gitignore +207 -0
  4. datalegion_cli-1.0.0/PKG-INFO +270 -0
  5. datalegion_cli-1.0.0/README.md +237 -0
  6. datalegion_cli-1.0.0/pyproject.toml +93 -0
  7. datalegion_cli-1.0.0/src/datalegion_cli/__init__.py +3 -0
  8. datalegion_cli-1.0.0/src/datalegion_cli/async_typer.py +56 -0
  9. datalegion_cli-1.0.0/src/datalegion_cli/client.py +150 -0
  10. datalegion_cli-1.0.0/src/datalegion_cli/commands/__init__.py +1 -0
  11. datalegion_cli-1.0.0/src/datalegion_cli/commands/company.py +194 -0
  12. datalegion_cli-1.0.0/src/datalegion_cli/commands/credits.py +57 -0
  13. datalegion_cli-1.0.0/src/datalegion_cli/commands/person.py +221 -0
  14. datalegion_cli-1.0.0/src/datalegion_cli/commands/utility.py +122 -0
  15. datalegion_cli-1.0.0/src/datalegion_cli/config.py +127 -0
  16. datalegion_cli-1.0.0/src/datalegion_cli/logging.py +28 -0
  17. datalegion_cli-1.0.0/src/datalegion_cli/main.py +133 -0
  18. datalegion_cli-1.0.0/src/datalegion_cli/output.py +73 -0
  19. datalegion_cli-1.0.0/src/datalegion_cli/update.py +228 -0
  20. datalegion_cli-1.0.0/tests/__init__.py +1 -0
  21. datalegion_cli-1.0.0/tests/conftest.py +55 -0
  22. datalegion_cli-1.0.0/tests/test_client.py +203 -0
  23. datalegion_cli-1.0.0/tests/test_commands.py +376 -0
  24. datalegion_cli-1.0.0/tests/test_config.py +127 -0
  25. datalegion_cli-1.0.0/tests/test_logging.py +35 -0
  26. datalegion_cli-1.0.0/tests/test_output.py +75 -0
  27. datalegion_cli-1.0.0/tests/test_update.py +168 -0
  28. datalegion_cli-1.0.0/uv.lock +627 -0
@@ -0,0 +1,33 @@
1
+ name: CI
2
+
3
+ on:
4
+ pull_request:
5
+ branches: [main]
6
+
7
+ jobs:
8
+ lint:
9
+ runs-on: blacksmith-2vcpu-ubuntu-2404
10
+ steps:
11
+ - uses: actions/checkout@v6
12
+ - uses: astral-sh/setup-uv@v7
13
+ with:
14
+ enable-cache: true
15
+ - uses: actions/setup-python@v6
16
+ with:
17
+ python-version: "3.11"
18
+ - run: uv sync --group dev
19
+ - run: uv run ruff check src/ tests/
20
+ - run: uv run ruff format --check src/ tests/
21
+
22
+ test:
23
+ runs-on: blacksmith-2vcpu-ubuntu-2404
24
+ steps:
25
+ - uses: actions/checkout@v6
26
+ - uses: astral-sh/setup-uv@v7
27
+ with:
28
+ enable-cache: true
29
+ - uses: actions/setup-python@v6
30
+ with:
31
+ python-version: "3.11"
32
+ - run: uv sync --group dev
33
+ - run: uv run pytest
@@ -0,0 +1,145 @@
1
+ name: Release
2
+
3
+ on:
4
+ push:
5
+ tags:
6
+ - "v*"
7
+
8
+ concurrency:
9
+ group: ${{ github.workflow }}-${{ github.ref }}
10
+ cancel-in-progress: true
11
+
12
+ jobs:
13
+ lint:
14
+ name: Lint & Build
15
+ runs-on: blacksmith-2vcpu-ubuntu-2404
16
+ timeout-minutes: 5
17
+ permissions:
18
+ contents: read
19
+ steps:
20
+ - name: Checkout
21
+ uses: actions/checkout@v6
22
+
23
+ - name: Install uv
24
+ uses: astral-sh/setup-uv@v7
25
+ with:
26
+ enable-cache: true
27
+ cache-dependency-glob: "uv.lock"
28
+
29
+ - name: Set up Python
30
+ run: uv python install 3.11
31
+
32
+ - name: Install dependencies
33
+ run: uv sync --frozen --group dev
34
+
35
+ - name: Run lint
36
+ run: uv run ruff check src/ tests/
37
+
38
+ - name: Check formatting
39
+ run: uv run ruff format --check src/ tests/
40
+
41
+ - name: Build
42
+ run: uv build
43
+
44
+ - name: Prune uv cache
45
+ if: always()
46
+ run: uv cache prune --ci
47
+
48
+ test:
49
+ name: Test (Python ${{ matrix.python-version }})
50
+ runs-on: blacksmith-2vcpu-ubuntu-2404
51
+ timeout-minutes: 10
52
+ permissions:
53
+ contents: read
54
+ strategy:
55
+ fail-fast: false
56
+ matrix:
57
+ python-version: ["3.11", "3.12", "3.13"]
58
+ steps:
59
+ - name: Checkout
60
+ uses: actions/checkout@v6
61
+
62
+ - name: Install uv
63
+ uses: astral-sh/setup-uv@v7
64
+ with:
65
+ enable-cache: true
66
+ cache-dependency-glob: "uv.lock"
67
+
68
+ - name: Set up Python ${{ matrix.python-version }}
69
+ run: uv python install ${{ matrix.python-version }}
70
+
71
+ - name: Install dependencies
72
+ run: uv sync --frozen --group dev
73
+
74
+ - name: Run tests
75
+ run: uv run pytest tests/ -v --tb=short
76
+
77
+ - name: Prune uv cache
78
+ if: always()
79
+ run: uv cache prune --ci
80
+
81
+ publish-pypi:
82
+ name: Publish to PyPI
83
+ needs: [lint, test]
84
+ runs-on: blacksmith-2vcpu-ubuntu-2404
85
+ timeout-minutes: 5
86
+ permissions:
87
+ contents: read
88
+ id-token: write
89
+ steps:
90
+ - name: Checkout
91
+ uses: actions/checkout@v6
92
+
93
+ - name: Install uv
94
+ uses: astral-sh/setup-uv@v7
95
+
96
+ - name: Set up Python
97
+ run: uv python install 3.11
98
+
99
+ - name: Set version from tag
100
+ run: |
101
+ VERSION="${GITHUB_REF_NAME#v}"
102
+ sed -i "s/^version = .*/version = \"${VERSION}\"/" pyproject.toml
103
+
104
+ - name: Build
105
+ run: uv build
106
+
107
+ - name: Publish to PyPI
108
+ run: uv publish --trusted-publishing always
109
+
110
+ github-release:
111
+ name: Create GitHub Release
112
+ needs: [publish-pypi]
113
+ runs-on: blacksmith-2vcpu-ubuntu-2404
114
+ timeout-minutes: 5
115
+ permissions:
116
+ contents: write
117
+ steps:
118
+ - name: Checkout
119
+ uses: actions/checkout@v6
120
+ with:
121
+ ref: main
122
+ token: ${{ secrets.GH_PAT }}
123
+
124
+ - name: Update version in pyproject.toml
125
+ run: |
126
+ VERSION="${GITHUB_REF_NAME#v}"
127
+ sed -i "s/^version = .*/version = \"${VERSION}\"/" pyproject.toml
128
+ git config user.name "github-actions[bot]"
129
+ git config user.email "github-actions[bot]@users.noreply.github.com"
130
+ git add pyproject.toml
131
+ git diff --staged --quiet || git commit -m "chore: bump version to ${VERSION} [skip ci]"
132
+ git push
133
+
134
+ - name: Create Release
135
+ uses: actions/github-script@v8
136
+ with:
137
+ script: |
138
+ const tag = context.ref.replace('refs/tags/', '');
139
+ await github.rest.repos.createRelease({
140
+ owner: context.repo.owner,
141
+ repo: context.repo.repo,
142
+ tag_name: tag,
143
+ name: tag,
144
+ generate_release_notes: true,
145
+ });
@@ -0,0 +1,207 @@
1
+ # Byte-compiled / optimized / DLL files
2
+ __pycache__/
3
+ *.py[codz]
4
+ *$py.class
5
+
6
+ # C extensions
7
+ *.so
8
+
9
+ # Distribution / packaging
10
+ .Python
11
+ build/
12
+ develop-eggs/
13
+ dist/
14
+ downloads/
15
+ eggs/
16
+ .eggs/
17
+ lib/
18
+ lib64/
19
+ parts/
20
+ sdist/
21
+ var/
22
+ wheels/
23
+ share/python-wheels/
24
+ *.egg-info/
25
+ .installed.cfg
26
+ *.egg
27
+ MANIFEST
28
+
29
+ # PyInstaller
30
+ # Usually these files are written by a python script from a template
31
+ # before PyInstaller builds the exe, so as to inject date/other infos into it.
32
+ *.manifest
33
+ *.spec
34
+
35
+ # Installer logs
36
+ pip-log.txt
37
+ pip-delete-this-directory.txt
38
+
39
+ # Unit test / coverage reports
40
+ htmlcov/
41
+ .tox/
42
+ .nox/
43
+ .coverage
44
+ .coverage.*
45
+ .cache
46
+ nosetests.xml
47
+ coverage.xml
48
+ *.cover
49
+ *.py.cover
50
+ .hypothesis/
51
+ .pytest_cache/
52
+ cover/
53
+
54
+ # Translations
55
+ *.mo
56
+ *.pot
57
+
58
+ # Django stuff:
59
+ *.log
60
+ local_settings.py
61
+ db.sqlite3
62
+ db.sqlite3-journal
63
+
64
+ # Flask stuff:
65
+ instance/
66
+ .webassets-cache
67
+
68
+ # Scrapy stuff:
69
+ .scrapy
70
+
71
+ # Sphinx documentation
72
+ docs/_build/
73
+
74
+ # PyBuilder
75
+ .pybuilder/
76
+ target/
77
+
78
+ # Jupyter Notebook
79
+ .ipynb_checkpoints
80
+
81
+ # IPython
82
+ profile_default/
83
+ ipython_config.py
84
+
85
+ # pyenv
86
+ # For a library or package, you might want to ignore these files since the code is
87
+ # intended to run in multiple environments; otherwise, check them in:
88
+ # .python-version
89
+
90
+ # pipenv
91
+ # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
92
+ # However, in case of collaboration, if having platform-specific dependencies or dependencies
93
+ # having no cross-platform support, pipenv may install dependencies that don't work, or not
94
+ # install all needed dependencies.
95
+ #Pipfile.lock
96
+
97
+ # UV
98
+ # Similar to Pipfile.lock, it is generally recommended to include uv.lock in version control.
99
+ # This is especially recommended for binary packages to ensure reproducibility, and is more
100
+ # commonly ignored for libraries.
101
+ #uv.lock
102
+
103
+ # poetry
104
+ # Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control.
105
+ # This is especially recommended for binary packages to ensure reproducibility, and is more
106
+ # commonly ignored for libraries.
107
+ # https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control
108
+ #poetry.lock
109
+ #poetry.toml
110
+
111
+ # pdm
112
+ # Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control.
113
+ # pdm recommends including project-wide configuration in pdm.toml, but excluding .pdm-python.
114
+ # https://pdm-project.org/en/latest/usage/project/#working-with-version-control
115
+ #pdm.lock
116
+ #pdm.toml
117
+ .pdm-python
118
+ .pdm-build/
119
+
120
+ # pixi
121
+ # Similar to Pipfile.lock, it is generally recommended to include pixi.lock in version control.
122
+ #pixi.lock
123
+ # Pixi creates a virtual environment in the .pixi directory, just like venv module creates one
124
+ # in the .venv directory. It is recommended not to include this directory in version control.
125
+ .pixi
126
+
127
+ # PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm
128
+ __pypackages__/
129
+
130
+ # Celery stuff
131
+ celerybeat-schedule
132
+ celerybeat.pid
133
+
134
+ # SageMath parsed files
135
+ *.sage.py
136
+
137
+ # Environments
138
+ .env
139
+ .envrc
140
+ .venv
141
+ env/
142
+ venv/
143
+ ENV/
144
+ env.bak/
145
+ venv.bak/
146
+
147
+ # Spyder project settings
148
+ .spyderproject
149
+ .spyproject
150
+
151
+ # Rope project settings
152
+ .ropeproject
153
+
154
+ # mkdocs documentation
155
+ /site
156
+
157
+ # mypy
158
+ .mypy_cache/
159
+ .dmypy.json
160
+ dmypy.json
161
+
162
+ # Pyre type checker
163
+ .pyre/
164
+
165
+ # pytype static type analyzer
166
+ .pytype/
167
+
168
+ # Cython debug symbols
169
+ cython_debug/
170
+
171
+ # PyCharm
172
+ # JetBrains specific template is maintained in a separate JetBrains.gitignore that can
173
+ # be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
174
+ # and can be added to the global gitignore or merged into this file. For a more nuclear
175
+ # option (not recommended) you can uncomment the following to ignore the entire idea folder.
176
+ .idea/
177
+
178
+ # Abstra
179
+ # Abstra is an AI-powered process automation framework.
180
+ # Ignore directories containing user credentials, local state, and settings.
181
+ # Learn more at https://abstra.io/docs
182
+ .abstra/
183
+
184
+ # Visual Studio Code
185
+ # Visual Studio Code specific template is maintained in a separate VisualStudioCode.gitignore
186
+ # that can be found at https://github.com/github/gitignore/blob/main/Global/VisualStudioCode.gitignore
187
+ # and can be added to the global gitignore or merged into this file. However, if you prefer,
188
+ # you could uncomment the following to ignore the entire vscode folder
189
+ # .vscode/
190
+
191
+ # Ruff stuff:
192
+ .ruff_cache/
193
+
194
+ # PyPI configuration file
195
+ .pypirc
196
+
197
+ # Cursor
198
+ # Cursor is an AI-powered code editor. `.cursorignore` specifies files/directories to
199
+ # exclude from AI features like autocomplete and code analysis. Recommended for sensitive data
200
+ # refer to https://docs.cursor.com/context/ignore-files
201
+ .cursorignore
202
+ .cursorindexingignore
203
+
204
+ # Marimo
205
+ marimo/_static/
206
+ marimo/_lsp/
207
+ __marimo__/
@@ -0,0 +1,270 @@
1
+ Metadata-Version: 2.4
2
+ Name: datalegion-cli
3
+ Version: 1.0.0
4
+ Summary: CLI for the Data Legion API — agent-friendly, fully async
5
+ Project-URL: Homepage, https://www.datalegion.ai
6
+ Project-URL: Documentation, https://www.datalegion.ai/docs
7
+ Project-URL: Repository, https://github.com/datalegion-ai/datalegion-cli
8
+ Project-URL: Issues, https://github.com/datalegion-ai/datalegion-cli/issues
9
+ Author-email: Data Legion <engineering@datalegion.ai>
10
+ License-Expression: MIT
11
+ Keywords: b2b,cli,company-enrichment,contact-data,data-enrichment,datalegion,enrichment,person-enrichment
12
+ Classifier: Development Status :: 4 - Beta
13
+ Classifier: Environment :: Console
14
+ Classifier: Intended Audience :: Developers
15
+ Classifier: License :: OSI Approved :: MIT License
16
+ Classifier: Programming Language :: Python :: 3
17
+ Classifier: Programming Language :: Python :: 3.11
18
+ Classifier: Programming Language :: Python :: 3.12
19
+ Classifier: Programming Language :: Python :: 3.13
20
+ Classifier: Typing :: Typed
21
+ Requires-Python: >=3.11
22
+ Requires-Dist: cryptography>=44.0.0
23
+ Requires-Dist: httpx>=0.28.0
24
+ Requires-Dist: loguru>=0.7.0
25
+ Requires-Dist: packaging>=24.0
26
+ Requires-Dist: pydantic-settings>=2.6.0
27
+ Requires-Dist: pydantic>=2.9.0
28
+ Requires-Dist: rich>=13.9.0
29
+ Requires-Dist: tomli-w>=1.0.0
30
+ Requires-Dist: tomli>=2.0.0; python_version < '3.11'
31
+ Requires-Dist: typer>=0.15.0
32
+ Description-Content-Type: text/markdown
33
+
34
+ # datalegion-cli
35
+
36
+ The official command-line interface for the [Data Legion](https://www.datalegion.ai) API. Built for both humans and AI agents.
37
+
38
+ Look up people and companies by email, phone, name, domain, LinkedIn, or ticker symbol. Search across 1.5B+ contacts and 200M+ companies using SQL syntax. Clean, hash, and validate data fields before or after enrichment. Track credit usage and manage your configuration — all from the terminal.
39
+
40
+ ```bash
41
+ pip install datalegion-cli
42
+ datalegion-cli config set api_key legion_your_key_here
43
+ datalegion-cli person enrich --email john@example.com
44
+ ```
45
+
46
+ ## Features
47
+
48
+ - **Fully async** — async from entry point to HTTP client, powered by `asyncio` and `httpx`
49
+ - **Agent-friendly** — all output is JSON to stdout, logs go to stderr, supports `--stdin` for piping
50
+ - **Encrypted config** — API keys are stored encrypted at rest (Fernet + PBKDF2, machine-tied)
51
+ - **Auto-update** — checks GitHub releases and self-updates via Homebrew, GitHub release, git pull, or pip/uv
52
+ - **Multiple install methods** — Homebrew, GitHub release, pip/uv (with private index support), or from source
53
+
54
+ ## Installation
55
+
56
+ ### Homebrew
57
+
58
+ ```bash
59
+ brew install datalegion/tap/datalegion-cli
60
+ ```
61
+
62
+ ### From PyPI (or private index)
63
+
64
+ ```bash
65
+ uv pip install datalegion-cli
66
+ ```
67
+
68
+ Or with pip:
69
+
70
+ ```bash
71
+ pip install datalegion-cli
72
+ ```
73
+
74
+ To install from a private index:
75
+
76
+ ```bash
77
+ uv pip install datalegion-cli --extra-index-url https://your-private-index.com/simple
78
+ ```
79
+
80
+ ### From GitHub release
81
+
82
+ Download the `.whl` from the [latest release](https://github.com/datalegion/datalegion-cli/releases/latest) and install:
83
+
84
+ ```bash
85
+ uv pip install datalegion_cli-*.whl
86
+ ```
87
+
88
+ ### From source
89
+
90
+ ```bash
91
+ git clone https://github.com/datalegion/datalegion-cli.git
92
+ cd datalegion-cli
93
+ uv sync
94
+ ```
95
+
96
+ ## Configuration
97
+
98
+ Set your API key (stored encrypted on disk):
99
+
100
+ ```bash
101
+ datalegion-cli config set api_key legion_your_key_here
102
+ ```
103
+
104
+ Or via environment variable (takes precedence over file config):
105
+
106
+ ```bash
107
+ export LEGION_API_KEY=legion_your_key_here
108
+ ```
109
+
110
+ ### Other config options
111
+
112
+ ```bash
113
+ # Set the API base URL
114
+ datalegion-cli config set api_base_url https://api.datalegion.ai
115
+
116
+ # Set install method for auto-update (brew, github, git, or pip)
117
+ datalegion-cli config set install_method brew
118
+
119
+ # View all config
120
+ datalegion-cli config list
121
+
122
+ # Show config file path
123
+ datalegion-cli config path
124
+ ```
125
+
126
+ Config is stored at `~/.datalegion/config.toml`. Override the location with `LEGION_CONFIG_DIR`.
127
+
128
+ ### Install method detection
129
+
130
+ The `update` command auto-detects how the CLI was installed:
131
+
132
+ | Method | Detection | Update strategy |
133
+ |--------|-----------|-----------------|
134
+ | `brew` | `brew list` finds the formula | `brew upgrade` |
135
+ | `github` | Set via config | Downloads `.whl` from GitHub release |
136
+ | `git` | `.git` directory in parent tree | `git pull --ff-only` + `uv sync` |
137
+ | `pip` | Default fallback | `uv pip install --upgrade` |
138
+
139
+ You can override detection by setting `install_method` in config. For pip installs from a private index, set the `LEGION_PIP_INDEX_URL` environment variable.
140
+
141
+ ## Usage
142
+
143
+ ### Person enrichment
144
+
145
+ ```bash
146
+ # By email
147
+ datalegion-cli person enrich --email john@example.com
148
+
149
+ # By name + company
150
+ datalegion-cli person enrich --name "Jane Smith" --company "Acme Corp" --city "NYC"
151
+
152
+ # By LinkedIn
153
+ datalegion-cli person enrich --social-url "https://linkedin.com/in/johndoe"
154
+
155
+ # Multiple results with minimum confidence
156
+ datalegion-cli person enrich --email john@example.com --multiple --min-confidence high
157
+
158
+ # Select specific fields
159
+ datalegion-cli person enrich --email john@example.com --fields "full_name,emails,phones"
160
+ ```
161
+
162
+ ### Person search
163
+
164
+ ```bash
165
+ # SQL-based search
166
+ datalegion-cli person search "SELECT * FROM people WHERE company_name ILIKE '%google%' AND state = 'CA'" --limit 5
167
+ ```
168
+
169
+ ### Company enrichment
170
+
171
+ ```bash
172
+ # By domain
173
+ datalegion-cli company enrich --domain google.com
174
+
175
+ # By ticker symbol
176
+ datalegion-cli company enrich --ticker AAPL
177
+
178
+ # By name with industry context
179
+ datalegion-cli company enrich --name "Stripe" --industry "Financial Services"
180
+ ```
181
+
182
+ ### Company search
183
+
184
+ ```bash
185
+ # SQL-based search
186
+ datalegion-cli company search "SELECT * FROM companies WHERE industry ILIKE '%software%' AND legion_employee_count > 100"
187
+ ```
188
+
189
+ ### Utility commands
190
+
191
+ ```bash
192
+ # Clean and normalize fields
193
+ datalegion-cli utility clean '{"email": "John.Doe+tag@gmail.com", "phone": "(555) 123-4567"}'
194
+
195
+ # Hash an email for privacy-preserving lookups
196
+ datalegion-cli utility hash john@example.com
197
+
198
+ # Validate fields before enrichment
199
+ datalegion-cli utility validate --email "test@example.com" --phone "+15551234567"
200
+ ```
201
+
202
+ ### Credits
203
+
204
+ ```bash
205
+ # Check balance
206
+ datalegion-cli credits balance
207
+
208
+ # View usage history
209
+ datalegion-cli credits usage --from 2026-01-01 --to 2026-03-01
210
+ ```
211
+
212
+ ### Other commands
213
+
214
+ ```bash
215
+ # Check for updates
216
+ datalegion-cli update --check
217
+
218
+ # Self-update
219
+ datalegion-cli update
220
+
221
+ # Show version
222
+ datalegion-cli version
223
+ ```
224
+
225
+ ## Agent / automation usage
226
+
227
+ The CLI is designed to be called by AI agents and scripts. Key patterns:
228
+
229
+ ```bash
230
+ # All output is JSON to stdout — parse it directly
231
+ datalegion-cli person enrich --email john@example.com | jq '.matches[0].full_name'
232
+
233
+ # Pipe JSON payloads via stdin
234
+ echo '{"email":"john@example.com","company":"Acme"}' | datalegion-cli person enrich --stdin
235
+
236
+ # Suppress logs with --quiet (only JSON on stdout)
237
+ datalegion-cli --quiet person enrich --email john@example.com
238
+
239
+ # Errors are also JSON
240
+ datalegion-cli person enrich --email bad 2>/dev/null
241
+ # {"ok": false, "error": "bad_request", "message": "..."}
242
+ ```
243
+
244
+ ## Global flags
245
+
246
+ | Flag | Description |
247
+ |------|-------------|
248
+ | `--verbose` / `-v` | Enable debug logging to stderr |
249
+ | `--quiet` / `-q` | Suppress all log output |
250
+ | `--help` | Show help for any command |
251
+
252
+ ## Development
253
+
254
+ ```bash
255
+ # Install dev dependencies
256
+ uv sync
257
+
258
+ # Run tests
259
+ uv run pytest
260
+
261
+ # Lint
262
+ uv run ruff check src/ tests/
263
+
264
+ # Build wheel
265
+ uv build
266
+ ```
267
+
268
+ ## License
269
+
270
+ MIT