ebird-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 (38) hide show
  1. ebird_cli-0.1.0/LICENSE +21 -0
  2. ebird_cli-0.1.0/PKG-INFO +292 -0
  3. ebird_cli-0.1.0/README.md +266 -0
  4. ebird_cli-0.1.0/pyproject.toml +66 -0
  5. ebird_cli-0.1.0/setup.cfg +4 -0
  6. ebird_cli-0.1.0/src/ebird_cli/__init__.py +0 -0
  7. ebird_cli-0.1.0/src/ebird_cli/cli/__init__.py +3 -0
  8. ebird_cli-0.1.0/src/ebird_cli/cli/autocomplete.py +30 -0
  9. ebird_cli-0.1.0/src/ebird_cli/cli/command.py +150 -0
  10. ebird_cli-0.1.0/src/ebird_cli/cli/grammar.py +197 -0
  11. ebird_cli-0.1.0/src/ebird_cli/domain/__init__.py +4 -0
  12. ebird_cli-0.1.0/src/ebird_cli/domain/fields.py +20 -0
  13. ebird_cli-0.1.0/src/ebird_cli/domain/observation.py +21 -0
  14. ebird_cli-0.1.0/src/ebird_cli/domain/region.py +6 -0
  15. ebird_cli-0.1.0/src/ebird_cli/domain/regional_scopes.py +9 -0
  16. ebird_cli-0.1.0/src/ebird_cli/domain/taxonomy.py +36 -0
  17. ebird_cli-0.1.0/src/ebird_cli/domain/taxonomy_cache.py +9 -0
  18. ebird_cli-0.1.0/src/ebird_cli/download_lists.py +52 -0
  19. ebird_cli-0.1.0/src/ebird_cli/main.py +252 -0
  20. ebird_cli-0.1.0/src/ebird_cli/services/__init__.py +4 -0
  21. ebird_cli-0.1.0/src/ebird_cli/services/cache.py +55 -0
  22. ebird_cli-0.1.0/src/ebird_cli/services/dataframe.py +14 -0
  23. ebird_cli-0.1.0/src/ebird_cli/services/ebird_client.py +111 -0
  24. ebird_cli-0.1.0/src/ebird_cli/services/location_catalogue.py +121 -0
  25. ebird_cli-0.1.0/src/ebird_cli/services/observation_query.py +102 -0
  26. ebird_cli-0.1.0/src/ebird_cli/services/printing.py +53 -0
  27. ebird_cli-0.1.0/src/ebird_cli/services/taxonomy.py +18 -0
  28. ebird_cli-0.1.0/src/ebird_cli/utils/__init__.py +0 -0
  29. ebird_cli-0.1.0/src/ebird_cli/utils/logger.py +17 -0
  30. ebird_cli-0.1.0/src/ebird_cli.egg-info/PKG-INFO +292 -0
  31. ebird_cli-0.1.0/src/ebird_cli.egg-info/SOURCES.txt +36 -0
  32. ebird_cli-0.1.0/src/ebird_cli.egg-info/dependency_links.txt +1 -0
  33. ebird_cli-0.1.0/src/ebird_cli.egg-info/entry_points.txt +3 -0
  34. ebird_cli-0.1.0/src/ebird_cli.egg-info/requires.txt +8 -0
  35. ebird_cli-0.1.0/src/ebird_cli.egg-info/top_level.txt +1 -0
  36. ebird_cli-0.1.0/tests/test_download_lists.py +48 -0
  37. ebird_cli-0.1.0/tests/test_program_arguments.py +132 -0
  38. ebird_cli-0.1.0/tests/test_prompt_dispatch.py +118 -0
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2024 Kevin Verreault
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.
@@ -0,0 +1,292 @@
1
+ Metadata-Version: 2.4
2
+ Name: ebird-cli
3
+ Version: 0.1.0
4
+ Summary: A command-line interface for exploring eBird bird observation data.
5
+ Author-email: Kevin Verreault <dev@kevinverreault.com>
6
+ License-Expression: MIT
7
+ Project-URL: Homepage, https://github.com/kevinverreault/ebird-cli
8
+ Project-URL: Repository, https://github.com/kevinverreault/ebird-cli
9
+ Project-URL: Issues, https://github.com/kevinverreault/ebird-cli/issues
10
+ Classifier: Environment :: Console
11
+ Classifier: Operating System :: OS Independent
12
+ Classifier: Programming Language :: Python :: 3
13
+ Classifier: Programming Language :: Python :: 3 :: Only
14
+ Requires-Python: >=3.11
15
+ Description-Content-Type: text/markdown
16
+ License-File: LICENSE
17
+ Requires-Dist: appdirs>=1.4.4
18
+ Requires-Dist: certifi>=2024.8.30
19
+ Requires-Dist: colorama>=0.4.6
20
+ Requires-Dist: ebird-api<4,>=3.0.6
21
+ Requires-Dist: pandas>=2.2.3
22
+ Requires-Dist: prompt_toolkit>=3.0.48
23
+ Requires-Dist: requests>=2.32.3
24
+ Requires-Dist: rich>=13.9.4
25
+ Dynamic: license-file
26
+
27
+ # eBird CLI
28
+
29
+ A command-line interface for exploring eBird bird observation data.
30
+
31
+ ![Notable observations in a region, with life and year list targets highlighted](https://raw.githubusercontent.com/kevinverreault/ebird-cli/main/docs/demo/overview.gif)
32
+
33
+ ## Prerequisites
34
+
35
+ - Python 3.11+
36
+ - eBird API Key (obtainable with free eBird account)
37
+
38
+ ## Installation
39
+
40
+ 1. Clone the repository
41
+ 2. Set up a virtual environment
42
+ 3. Install the `ebird-cli` command from the checkout:
43
+ ```bash
44
+ pip install .
45
+ ```
46
+ 4. Configure your eBird API key
47
+
48
+ ## Configuration
49
+
50
+ ### Command-Line Arguments
51
+
52
+ The CLI supports the following arguments:
53
+
54
+ | Argument | Description | Default | Required |
55
+ | ---------------- | ------------------------------------- | --------------------------------- | -------- |
56
+ | `--api-key` | eBird API key | From `EBIRDAPIKEY` env var | **Yes** |
57
+ | `--region` | eBird subnational level 2 region code | From `EBIRDDEFAULTREGION` env var | **Yes** |
58
+ | `--locale` | Language locale | From `EBIRDLOCALE` (or `fr`) | No |
59
+ | `--lat` | Latitude | From `EBIRDLAT` env var | No |
60
+ | `--long` | Longitude | From `EBIRDLONG` env var | No |
61
+ | `--year-list` | Path to year observations list | From `EBIRDYEARLIST` env var | No |
62
+ | `--life-list` | Path to lifetime observations list | From `EBIRDLIFELIST` env var | No |
63
+ | `--aggregations` | Path to aggregations JSON file | From `EBIRDAGGREGATIONS` env var | No |
64
+
65
+ ### Environment Variables
66
+
67
+ You can configure the CLI using the following environment variables to avoid manual parameter entry:
68
+
69
+ - `EBIRDAPIKEY`: Your eBird API key
70
+ - `EBIRDDEFAULTREGION`: Default region for default search and hotspots filtering (`CA-QC-MR`)
71
+ - `EBIRDLOCALE`: Preferred language locale (`fr`)
72
+ - `EBIRDLAT`: Latitude for location-based searches (`47.87`)
73
+ - `EBIRDLONG`: Longitude for location-based searches (`-72.17`)
74
+ - `EBIRDYEARLIST`: Path to your year observations list (`~/ebird_data/year_list.csv`)
75
+ - `EBIRDLIFELIST`: Path to your lifetime observations list (`~/ebird_data/life_list.csv`)
76
+ - `EBIRDAGGREGATIONS`: Path to your aggregations JSON file (`~/ebird_data/aggregations.json`)
77
+
78
+ ## Usage
79
+
80
+ ### Launching the CLI
81
+
82
+ ```bash
83
+ ebird-cli --api-key <ebird-api-key> --region <region-code> [optional arguments]
84
+ ```
85
+
86
+ `ebird-cli --version` prints the installed version.
87
+
88
+ ### Available Commands
89
+
90
+ When launched, the CLI will display a menu of available commands:
91
+
92
+ - `recent <scope, -region> [-back, -species]`: Fetch recent bird observations
93
+ - `notable <scope, -region> [-back]`: Fetch notable bird observations
94
+
95
+ ### Autocompletion
96
+
97
+ Suggestions appear as you type: command names, scopes, flags, region names for the chosen scope, species names and `-back` values. Press Tab to accept the first suggestion, or press it again to cycle through them.
98
+
99
+ ![Building a full command from autocompletion suggestions](https://raw.githubusercontent.com/kevinverreault/ebird-cli/main/docs/demo/autocomplete.gif)
100
+
101
+ ### Search Scopes
102
+
103
+ Bird observations can be searched in multiple scopes.
104
+
105
+ 1. **Nearby**: Based on provided latitude and longitude
106
+ - Requires `--lat` and `--long` program arguments
107
+ - Shows observations near the specified geographic coordinates
108
+ - `recent nearby`
109
+
110
+ 2. **Hotspot**: eBird-defined hotspots
111
+ - `recent hotspot -region Dunes de Tadoussac`
112
+
113
+ 3. **Regional**: eBird Subnational Level 2
114
+ - `recent regional -region Montréal`
115
+
116
+ 4. **Subnational**: eBird Subnational Level 1
117
+ - `recent subnational -region Québec`
118
+
119
+ The `-region` flag supports context-sensitive autocompletion based on the selected scope. If no `-region` flag is provided, default region will be used.
120
+
121
+ ![Autocompleting a hotspot name and fetching its recent observations](https://raw.githubusercontent.com/kevinverreault/ebird-cli/main/docs/demo/hotspot-autocomplete.gif)
122
+
123
+ ### Search length
124
+
125
+ Use the optional `-back` parameter to change the number of days back to fetch observations.
126
+
127
+ - Values: from 1 to 30
128
+ - Default: 7
129
+
130
+ ```
131
+ notable subnational -region Québec -back 30
132
+ ```
133
+
134
+ ### Aggregations
135
+
136
+ Aggregations allow you to define custom named groups of hotspots that appear alongside eBird hotspots when using the `hotspot` scope. This is useful for combining multiple nearby locations under a single name.
137
+
138
+ Provide the path to your aggregations file via `--aggregations` or the `EBIRDAGGREGATIONS` environment variable:
139
+
140
+ ```bash
141
+ ebird-cli --api-key <key> --region <region> --aggregations ~/ebird_data/aggregations.json
142
+ ```
143
+
144
+ The aggregations file is a JSON array where each element maps a custom name to a list of eBird location IDs:
145
+
146
+ ```json
147
+ [
148
+ {
149
+ "Technoparc": ["L9980102", "L4886900", "L11363355"]
150
+ }
151
+ ]
152
+ ```
153
+
154
+ Once configured, aggregation names are available in the `-region` autocomplete when using the `hotspot` scope:
155
+
156
+ ```
157
+ recent hotspot -region Technoparc
158
+ ```
159
+
160
+ ### Species filter
161
+
162
+ Use the optional `-species` flag with the `recent` command to filter observations to a single species.
163
+
164
+ ```
165
+ recent nearby -species Canard colvert
166
+ recent regional -region Montréal -species Harfang des neiges
167
+ ```
168
+
169
+ The `-species` flag supports context-sensitive autocompletion using the cached taxonomy. Start typing a common name and press Tab to see matching species.
170
+
171
+ ![Autocompleting a species name and filtering recent observations to it](https://raw.githubusercontent.com/kevinverreault/ebird-cli/main/docs/demo/species-filter.gif)
172
+
173
+ > **Note:** Autocompletion and lookup use the locale specified by `--locale` / `EBIRDLOCALE`. Species names must be typed in the configured locale.
174
+
175
+ ### List highlighting
176
+
177
+ When using `--year-list` and `--life-list`:
178
+
179
+ - Year list targets are highlighted in **green**
180
+ - Life list targets are highlighted in **red**
181
+
182
+ ### Obtaining list files
183
+
184
+ The eBird API does not expose life list or year list data, so these CSV files must be downloaded from the eBird website. The `ebird-cli-download-lists` command, installed alongside `ebird-cli`, downloads both into `output_dir` as `life_list.csv` and `year_list.csv`:
185
+
186
+ ```bash
187
+ ebird-cli-download-lists session_id output_dir
188
+ ```
189
+
190
+ This requires a valid eBird session cookie (`EBIRD_SESSIONID`), which can be obtained from your browser after logging in to ebird.org. The command must be re-run whenever your lists change.
191
+
192
+ > **Note:** The life list only changes when you see a species for the first time ever, making it infrequent and easy to manage. The year list changes with every new species seen in the current year and resets annually, requiring more frequent re-runs of the command.
193
+
194
+ ## Data cache
195
+
196
+ To avoid redundant API calls to eBird, the CLI caches reference data locally on first run. The cache is stored in the platform-appropriate user cache directory (e.g. `%LOCALAPPDATA%\ebird_cli\Cache\ebird_cli` on Windows, `~/.cache/ebird_cli` on Linux/macOS).
197
+
198
+ The following data is cached:
199
+
200
+ - **Location data** (per region): subnational level 1 regions, subnational level 2 regions, and every hotspot in the subnational region — stored as JSON files under `location/<national>/` and `location/<national>/<subnational>/`
201
+ - **Taxonomy** (per locale and national region): the full eBird taxonomy filtered to species observed in the configured country — stored as CSV files under `taxonomy/<national>/`
202
+
203
+ Hotspots are fetched again on launch once their cached file is more than 30 days old, so new hotspots appear; if eBird can't be reached, the older file is used. All other cached data is written once and reused on every subsequent launch. To force a refresh (e.g. after a new eBird taxonomy release or a region change), delete the relevant files or the entire cache directory.
204
+
205
+ ## Log file
206
+
207
+ Each launch appends debug messages to `app.log` in the platform-appropriate user log directory, created on first launch:
208
+
209
+ - Windows: `%LOCALAPPDATA%\ebird_cli\ebird_cli\Logs\app.log`
210
+ - Linux: `~/.cache/ebird_cli/log/app.log` (under `$XDG_CACHE_HOME` instead of `~/.cache` when it is set)
211
+ - macOS: `~/Library/Logs/ebird_cli/app.log`
212
+
213
+ The file is never rotated or trimmed; delete it at any time.
214
+
215
+ ## Development setup
216
+
217
+ ### Virtual environment
218
+
219
+ 1. Activate the virtual environment:
220
+ - On Windows: `venv\Scripts\activate`
221
+ - On macOS/Linux: `source venv/bin/activate`
222
+
223
+ 2. Install the project in editable mode with the dev tools (`requirements-dev.txt` adds the test, lint and build tools to the pinned runtime dependencies):
224
+ ```bash
225
+ pip install -e . -r requirements-dev.txt
226
+ ```
227
+ The tests read the version from the installed package, so they need this editable install.
228
+
229
+ ### Tests
230
+
231
+ The test suite runs offline: it never calls the eBird API, needs no API key and never touches your user cache or log directory.
232
+
233
+ ```bash
234
+ python -m pytest
235
+ ```
236
+
237
+ Tests live in `tests/`, mirroring the source layout:
238
+
239
+ - `tests/cli/`: the `recent` and `notable` commands and autocompletion, driven through their public entry points
240
+ - `tests/services/`, `tests/domain/`: cache, location and taxonomy services and domain value parsing
241
+ - `tests/utils/`: where the app log is written
242
+ - `tests/test_prompt_dispatch.py`, `tests/test_program_arguments.py`: prompt input handling and program arguments
243
+ - `tests/conftest.py`: shared fixtures, including `make_cli`, which wires real services and commands the way `main` does
244
+ - `tests/fakes.py`: the in-memory fake eBird client, the only test double
245
+ - `tests/fixtures/`: small hand-written cache, list and aggregation files for the `CA-QC-MR` region
246
+
247
+ Tests named `..._currently_...` pin behaviour that looks accidental, so changing it is a deliberate test change.
248
+
249
+ ### Demo GIFs
250
+
251
+ The GIFs in this README are recorded with [VHS](https://github.com/charmbracelet/vhs) from the tapes in `docs/demo/`. They call the live eBird API, so they need your API key and environment variables set. `config.tape` holds the shared settings and launches the CLI out of frame.
252
+
253
+ On Windows, install VHS and its terminal dependency, then re-record a GIF from the repository root:
254
+
255
+ ```bash
256
+ winget install charmbracelet.vhs tsl0922.ttyd
257
+ ```
258
+
259
+ ```bash
260
+ vhs docs/demo/overview.tape
261
+ ```
262
+
263
+ VHS also needs `ffmpeg` on the `PATH`.
264
+
265
+ ### Cutting a release
266
+
267
+ Releases are published to [PyPI](https://pypi.org/project/ebird-cli/) by the `Release` workflow (`.github/workflows/release.yml`) through trusted publishing; no API token is involved.
268
+
269
+ 1. Bump `version` in `pyproject.toml` and merge it to `main`.
270
+ 2. Optionally, do a dry run: run the `Release` workflow manually from the Actions tab. It uploads that version to [TestPyPI](https://test.pypi.org/project/ebird-cli/), which accepts each version only once. Check the upload installs:
271
+ ```bash
272
+ pip install --index-url https://test.pypi.org/simple/ --extra-index-url https://pypi.org/simple/ ebird-cli
273
+ ```
274
+ 3. Tag the release commit `vX.Y.Z`, matching the version, and push the tag.
275
+ 4. Publish a GitHub release from that tag. The workflow uploads it to PyPI, and fails without uploading if the tag doesn't match the version in `pyproject.toml`.
276
+
277
+ ## Credits
278
+
279
+ Bird observation data and the eBird platform are provided by the [Cornell Lab of Ornithology](https://www.birds.cornell.edu/). This project would not be possible without their freely available [eBird API](https://documenter.getpostman.com/view/664302/S1ENwy59).
280
+
281
+ This project relies on the following open-source packages:
282
+
283
+ - [ebird-api](https://pypi.org/project/ebird-api/) — Python wrapper for the eBird API
284
+ - [prompt_toolkit](https://python-prompt-toolkit.readthedocs.io/) — Interactive command-line interface with autocompletion
285
+ - [rich](https://rich.readthedocs.io/) — Terminal formatting and highlighted output
286
+ - [pandas](https://pandas.pydata.org/) — CSV observation list parsing
287
+ - [colorama](https://pypi.org/project/colorama/) — Cross-platform terminal color support
288
+ - [requests](https://requests.readthedocs.io/) — HTTP requests to the eBird API
289
+
290
+ ## License
291
+
292
+ MIT License
@@ -0,0 +1,266 @@
1
+ # eBird CLI
2
+
3
+ A command-line interface for exploring eBird bird observation data.
4
+
5
+ ![Notable observations in a region, with life and year list targets highlighted](https://raw.githubusercontent.com/kevinverreault/ebird-cli/main/docs/demo/overview.gif)
6
+
7
+ ## Prerequisites
8
+
9
+ - Python 3.11+
10
+ - eBird API Key (obtainable with free eBird account)
11
+
12
+ ## Installation
13
+
14
+ 1. Clone the repository
15
+ 2. Set up a virtual environment
16
+ 3. Install the `ebird-cli` command from the checkout:
17
+ ```bash
18
+ pip install .
19
+ ```
20
+ 4. Configure your eBird API key
21
+
22
+ ## Configuration
23
+
24
+ ### Command-Line Arguments
25
+
26
+ The CLI supports the following arguments:
27
+
28
+ | Argument | Description | Default | Required |
29
+ | ---------------- | ------------------------------------- | --------------------------------- | -------- |
30
+ | `--api-key` | eBird API key | From `EBIRDAPIKEY` env var | **Yes** |
31
+ | `--region` | eBird subnational level 2 region code | From `EBIRDDEFAULTREGION` env var | **Yes** |
32
+ | `--locale` | Language locale | From `EBIRDLOCALE` (or `fr`) | No |
33
+ | `--lat` | Latitude | From `EBIRDLAT` env var | No |
34
+ | `--long` | Longitude | From `EBIRDLONG` env var | No |
35
+ | `--year-list` | Path to year observations list | From `EBIRDYEARLIST` env var | No |
36
+ | `--life-list` | Path to lifetime observations list | From `EBIRDLIFELIST` env var | No |
37
+ | `--aggregations` | Path to aggregations JSON file | From `EBIRDAGGREGATIONS` env var | No |
38
+
39
+ ### Environment Variables
40
+
41
+ You can configure the CLI using the following environment variables to avoid manual parameter entry:
42
+
43
+ - `EBIRDAPIKEY`: Your eBird API key
44
+ - `EBIRDDEFAULTREGION`: Default region for default search and hotspots filtering (`CA-QC-MR`)
45
+ - `EBIRDLOCALE`: Preferred language locale (`fr`)
46
+ - `EBIRDLAT`: Latitude for location-based searches (`47.87`)
47
+ - `EBIRDLONG`: Longitude for location-based searches (`-72.17`)
48
+ - `EBIRDYEARLIST`: Path to your year observations list (`~/ebird_data/year_list.csv`)
49
+ - `EBIRDLIFELIST`: Path to your lifetime observations list (`~/ebird_data/life_list.csv`)
50
+ - `EBIRDAGGREGATIONS`: Path to your aggregations JSON file (`~/ebird_data/aggregations.json`)
51
+
52
+ ## Usage
53
+
54
+ ### Launching the CLI
55
+
56
+ ```bash
57
+ ebird-cli --api-key <ebird-api-key> --region <region-code> [optional arguments]
58
+ ```
59
+
60
+ `ebird-cli --version` prints the installed version.
61
+
62
+ ### Available Commands
63
+
64
+ When launched, the CLI will display a menu of available commands:
65
+
66
+ - `recent <scope, -region> [-back, -species]`: Fetch recent bird observations
67
+ - `notable <scope, -region> [-back]`: Fetch notable bird observations
68
+
69
+ ### Autocompletion
70
+
71
+ Suggestions appear as you type: command names, scopes, flags, region names for the chosen scope, species names and `-back` values. Press Tab to accept the first suggestion, or press it again to cycle through them.
72
+
73
+ ![Building a full command from autocompletion suggestions](https://raw.githubusercontent.com/kevinverreault/ebird-cli/main/docs/demo/autocomplete.gif)
74
+
75
+ ### Search Scopes
76
+
77
+ Bird observations can be searched in multiple scopes.
78
+
79
+ 1. **Nearby**: Based on provided latitude and longitude
80
+ - Requires `--lat` and `--long` program arguments
81
+ - Shows observations near the specified geographic coordinates
82
+ - `recent nearby`
83
+
84
+ 2. **Hotspot**: eBird-defined hotspots
85
+ - `recent hotspot -region Dunes de Tadoussac`
86
+
87
+ 3. **Regional**: eBird Subnational Level 2
88
+ - `recent regional -region Montréal`
89
+
90
+ 4. **Subnational**: eBird Subnational Level 1
91
+ - `recent subnational -region Québec`
92
+
93
+ The `-region` flag supports context-sensitive autocompletion based on the selected scope. If no `-region` flag is provided, default region will be used.
94
+
95
+ ![Autocompleting a hotspot name and fetching its recent observations](https://raw.githubusercontent.com/kevinverreault/ebird-cli/main/docs/demo/hotspot-autocomplete.gif)
96
+
97
+ ### Search length
98
+
99
+ Use the optional `-back` parameter to change the number of days back to fetch observations.
100
+
101
+ - Values: from 1 to 30
102
+ - Default: 7
103
+
104
+ ```
105
+ notable subnational -region Québec -back 30
106
+ ```
107
+
108
+ ### Aggregations
109
+
110
+ Aggregations allow you to define custom named groups of hotspots that appear alongside eBird hotspots when using the `hotspot` scope. This is useful for combining multiple nearby locations under a single name.
111
+
112
+ Provide the path to your aggregations file via `--aggregations` or the `EBIRDAGGREGATIONS` environment variable:
113
+
114
+ ```bash
115
+ ebird-cli --api-key <key> --region <region> --aggregations ~/ebird_data/aggregations.json
116
+ ```
117
+
118
+ The aggregations file is a JSON array where each element maps a custom name to a list of eBird location IDs:
119
+
120
+ ```json
121
+ [
122
+ {
123
+ "Technoparc": ["L9980102", "L4886900", "L11363355"]
124
+ }
125
+ ]
126
+ ```
127
+
128
+ Once configured, aggregation names are available in the `-region` autocomplete when using the `hotspot` scope:
129
+
130
+ ```
131
+ recent hotspot -region Technoparc
132
+ ```
133
+
134
+ ### Species filter
135
+
136
+ Use the optional `-species` flag with the `recent` command to filter observations to a single species.
137
+
138
+ ```
139
+ recent nearby -species Canard colvert
140
+ recent regional -region Montréal -species Harfang des neiges
141
+ ```
142
+
143
+ The `-species` flag supports context-sensitive autocompletion using the cached taxonomy. Start typing a common name and press Tab to see matching species.
144
+
145
+ ![Autocompleting a species name and filtering recent observations to it](https://raw.githubusercontent.com/kevinverreault/ebird-cli/main/docs/demo/species-filter.gif)
146
+
147
+ > **Note:** Autocompletion and lookup use the locale specified by `--locale` / `EBIRDLOCALE`. Species names must be typed in the configured locale.
148
+
149
+ ### List highlighting
150
+
151
+ When using `--year-list` and `--life-list`:
152
+
153
+ - Year list targets are highlighted in **green**
154
+ - Life list targets are highlighted in **red**
155
+
156
+ ### Obtaining list files
157
+
158
+ The eBird API does not expose life list or year list data, so these CSV files must be downloaded from the eBird website. The `ebird-cli-download-lists` command, installed alongside `ebird-cli`, downloads both into `output_dir` as `life_list.csv` and `year_list.csv`:
159
+
160
+ ```bash
161
+ ebird-cli-download-lists session_id output_dir
162
+ ```
163
+
164
+ This requires a valid eBird session cookie (`EBIRD_SESSIONID`), which can be obtained from your browser after logging in to ebird.org. The command must be re-run whenever your lists change.
165
+
166
+ > **Note:** The life list only changes when you see a species for the first time ever, making it infrequent and easy to manage. The year list changes with every new species seen in the current year and resets annually, requiring more frequent re-runs of the command.
167
+
168
+ ## Data cache
169
+
170
+ To avoid redundant API calls to eBird, the CLI caches reference data locally on first run. The cache is stored in the platform-appropriate user cache directory (e.g. `%LOCALAPPDATA%\ebird_cli\Cache\ebird_cli` on Windows, `~/.cache/ebird_cli` on Linux/macOS).
171
+
172
+ The following data is cached:
173
+
174
+ - **Location data** (per region): subnational level 1 regions, subnational level 2 regions, and every hotspot in the subnational region — stored as JSON files under `location/<national>/` and `location/<national>/<subnational>/`
175
+ - **Taxonomy** (per locale and national region): the full eBird taxonomy filtered to species observed in the configured country — stored as CSV files under `taxonomy/<national>/`
176
+
177
+ Hotspots are fetched again on launch once their cached file is more than 30 days old, so new hotspots appear; if eBird can't be reached, the older file is used. All other cached data is written once and reused on every subsequent launch. To force a refresh (e.g. after a new eBird taxonomy release or a region change), delete the relevant files or the entire cache directory.
178
+
179
+ ## Log file
180
+
181
+ Each launch appends debug messages to `app.log` in the platform-appropriate user log directory, created on first launch:
182
+
183
+ - Windows: `%LOCALAPPDATA%\ebird_cli\ebird_cli\Logs\app.log`
184
+ - Linux: `~/.cache/ebird_cli/log/app.log` (under `$XDG_CACHE_HOME` instead of `~/.cache` when it is set)
185
+ - macOS: `~/Library/Logs/ebird_cli/app.log`
186
+
187
+ The file is never rotated or trimmed; delete it at any time.
188
+
189
+ ## Development setup
190
+
191
+ ### Virtual environment
192
+
193
+ 1. Activate the virtual environment:
194
+ - On Windows: `venv\Scripts\activate`
195
+ - On macOS/Linux: `source venv/bin/activate`
196
+
197
+ 2. Install the project in editable mode with the dev tools (`requirements-dev.txt` adds the test, lint and build tools to the pinned runtime dependencies):
198
+ ```bash
199
+ pip install -e . -r requirements-dev.txt
200
+ ```
201
+ The tests read the version from the installed package, so they need this editable install.
202
+
203
+ ### Tests
204
+
205
+ The test suite runs offline: it never calls the eBird API, needs no API key and never touches your user cache or log directory.
206
+
207
+ ```bash
208
+ python -m pytest
209
+ ```
210
+
211
+ Tests live in `tests/`, mirroring the source layout:
212
+
213
+ - `tests/cli/`: the `recent` and `notable` commands and autocompletion, driven through their public entry points
214
+ - `tests/services/`, `tests/domain/`: cache, location and taxonomy services and domain value parsing
215
+ - `tests/utils/`: where the app log is written
216
+ - `tests/test_prompt_dispatch.py`, `tests/test_program_arguments.py`: prompt input handling and program arguments
217
+ - `tests/conftest.py`: shared fixtures, including `make_cli`, which wires real services and commands the way `main` does
218
+ - `tests/fakes.py`: the in-memory fake eBird client, the only test double
219
+ - `tests/fixtures/`: small hand-written cache, list and aggregation files for the `CA-QC-MR` region
220
+
221
+ Tests named `..._currently_...` pin behaviour that looks accidental, so changing it is a deliberate test change.
222
+
223
+ ### Demo GIFs
224
+
225
+ The GIFs in this README are recorded with [VHS](https://github.com/charmbracelet/vhs) from the tapes in `docs/demo/`. They call the live eBird API, so they need your API key and environment variables set. `config.tape` holds the shared settings and launches the CLI out of frame.
226
+
227
+ On Windows, install VHS and its terminal dependency, then re-record a GIF from the repository root:
228
+
229
+ ```bash
230
+ winget install charmbracelet.vhs tsl0922.ttyd
231
+ ```
232
+
233
+ ```bash
234
+ vhs docs/demo/overview.tape
235
+ ```
236
+
237
+ VHS also needs `ffmpeg` on the `PATH`.
238
+
239
+ ### Cutting a release
240
+
241
+ Releases are published to [PyPI](https://pypi.org/project/ebird-cli/) by the `Release` workflow (`.github/workflows/release.yml`) through trusted publishing; no API token is involved.
242
+
243
+ 1. Bump `version` in `pyproject.toml` and merge it to `main`.
244
+ 2. Optionally, do a dry run: run the `Release` workflow manually from the Actions tab. It uploads that version to [TestPyPI](https://test.pypi.org/project/ebird-cli/), which accepts each version only once. Check the upload installs:
245
+ ```bash
246
+ pip install --index-url https://test.pypi.org/simple/ --extra-index-url https://pypi.org/simple/ ebird-cli
247
+ ```
248
+ 3. Tag the release commit `vX.Y.Z`, matching the version, and push the tag.
249
+ 4. Publish a GitHub release from that tag. The workflow uploads it to PyPI, and fails without uploading if the tag doesn't match the version in `pyproject.toml`.
250
+
251
+ ## Credits
252
+
253
+ Bird observation data and the eBird platform are provided by the [Cornell Lab of Ornithology](https://www.birds.cornell.edu/). This project would not be possible without their freely available [eBird API](https://documenter.getpostman.com/view/664302/S1ENwy59).
254
+
255
+ This project relies on the following open-source packages:
256
+
257
+ - [ebird-api](https://pypi.org/project/ebird-api/) — Python wrapper for the eBird API
258
+ - [prompt_toolkit](https://python-prompt-toolkit.readthedocs.io/) — Interactive command-line interface with autocompletion
259
+ - [rich](https://rich.readthedocs.io/) — Terminal formatting and highlighted output
260
+ - [pandas](https://pandas.pydata.org/) — CSV observation list parsing
261
+ - [colorama](https://pypi.org/project/colorama/) — Cross-platform terminal color support
262
+ - [requests](https://requests.readthedocs.io/) — HTTP requests to the eBird API
263
+
264
+ ## License
265
+
266
+ MIT License
@@ -0,0 +1,66 @@
1
+ [build-system]
2
+ requires = ["setuptools>=77"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ name = "ebird-cli"
7
+ version = "0.1.0"
8
+ description = "A command-line interface for exploring eBird bird observation data."
9
+ readme = "README.md"
10
+ license = "MIT"
11
+ license-files = ["LICENSE"]
12
+ authors = [{ name = "Kevin Verreault", email = "dev@kevinverreault.com" }]
13
+ requires-python = ">=3.11"
14
+ classifiers = [
15
+ "Environment :: Console",
16
+ "Operating System :: OS Independent",
17
+ "Programming Language :: Python :: 3",
18
+ "Programming Language :: Python :: 3 :: Only",
19
+ ]
20
+ # Only the packages the code imports; the lower bounds are the versions requirements.txt pins
21
+ dependencies = [
22
+ "appdirs>=1.4.4",
23
+ "certifi>=2024.8.30",
24
+ "colorama>=0.4.6",
25
+ "ebird-api>=3.0.6,<4", # 4.0 moved the modules ebird_client imports
26
+ "pandas>=2.2.3",
27
+ "prompt_toolkit>=3.0.48",
28
+ "requests>=2.32.3",
29
+ "rich>=13.9.4",
30
+ ]
31
+
32
+ [project.scripts]
33
+ ebird-cli = "ebird_cli.main:main"
34
+ ebird-cli-download-lists = "ebird_cli.download_lists:main"
35
+
36
+ [project.urls]
37
+ Homepage = "https://github.com/kevinverreault/ebird-cli"
38
+ Repository = "https://github.com/kevinverreault/ebird-cli"
39
+ Issues = "https://github.com/kevinverreault/ebird-cli/issues"
40
+
41
+ # The wheel holds the ebird_cli package only
42
+ [tool.setuptools.packages.find]
43
+ where = ["src"]
44
+ include = ["ebird_cli*"]
45
+
46
+ [tool.ruff]
47
+ line-length = 120
48
+ target-version = "py311"
49
+ src = ["src"]
50
+
51
+ [tool.ruff.lint]
52
+ select = [
53
+ "E", # pycodestyle errors
54
+ "W", # pycodestyle warnings
55
+ "F", # pyflakes
56
+ "I", # isort
57
+ "B", # flake8-bugbear
58
+ "UP", # pyupgrade
59
+ "SIM", # flake8-simplify
60
+ ]
61
+
62
+ [tool.ruff.lint.isort]
63
+ known-first-party = ["ebird_cli"]
64
+
65
+ [tool.ruff.format]
66
+ quote-style = "double"
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
File without changes
@@ -0,0 +1,3 @@
1
+ from .autocomplete import ContextSensitiveCompleter
2
+
3
+ __all__ = ["ContextSensitiveCompleter"]