Metadata-Version: 2.4
Name: evaboot
Version: 0.1.0a2
Summary: Command-line client for the Evaboot API: extract Sales Navigator leads, find and verify professional emails.
Author: Evaboot
License-Expression: MIT
Project-URL: Homepage, https://evaboot.com
Project-URL: Get an API token, https://app.evaboot.com/?page=api&int=api
Keywords: evaboot,sales-navigator,linkedin,email-finder,lead-generation,cli
Classifier: Development Status :: 3 - Alpha
Classifier: Environment :: Console
Classifier: Intended Audience :: End Users/Desktop
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Topic :: Office/Business
Classifier: Topic :: Utilities
Requires-Python: >=3.10
Description-Content-Type: text/markdown
License-File: LICENSE
Requires-Dist: click>=8.2
Requires-Dist: requests>=2.28
Provides-Extra: dev
Requires-Dist: pytest>=7.0; extra == "dev"
Dynamic: license-file

<a id="readme-top"></a>

<!-- PROJECT SHIELDS -->
<!--
*** Reference-style links are used throughout for readability.
*** Definitions live at the bottom of this file.
-->
[![PyPI][pypi-shield]][pypi-url]
[![Python][python-shield]][python-url]
[![Click][click-shield]][click-url]

<!-- PROJECT TITLE -->
<br />
<div align="center">
  <h3 align="center">Evaboot CLI</h3>

  <p align="center">
    A terminal client for the Evaboot public API: extract, email find/verify,
    agent, and Sales Navigator lists, searches and alerts run on your own
    connected seat.
    <br />
    <br />
    <a href="#commands">Commands</a>
    ·
    <a href="#getting-started">Getting Started</a>
    ·
    <a href="#roadmap">Roadmap</a>
  </p>
</div>

<!-- TABLE OF CONTENTS -->
<details>
  <summary>Table of Contents</summary>
  <ol>
    <li>
      <a href="#about-the-project">About The Project</a>
      <ul>
        <li><a href="#how-it-works">How It Works</a></li>
        <li><a href="#built-with">Built With</a></li>
      </ul>
    </li>
    <li>
      <a href="#getting-started">Getting Started</a>
      <ul>
        <li><a href="#prerequisites">Prerequisites</a></li>
        <li><a href="#installation">Installation</a></li>
      </ul>
    </li>
    <li>
      <a href="#usage">Usage</a>
      <ul>
        <li><a href="#quickstart">Quickstart</a></li>
        <li><a href="#commands">Commands</a></li>
        <li><a href="#sales-navigator-connection">Sales Navigator connection</a></li>
        <li><a href="#configuration">Configuration</a></li>
        <li><a href="#exit-codes">Exit codes</a></li>
      </ul>
    </li>
    <li><a href="#roadmap">Roadmap</a></li>
    <li><a href="#contributing">Contributing</a></li>
    <li><a href="#license">License</a></li>
    <li><a href="#contact">Contact</a></li>
    <li><a href="#acknowledgments">Acknowledgments</a></li>
  </ol>
</details>

<!-- ABOUT THE PROJECT -->
## About The Project

**Evaboot CLI** is a terminal client for the **Evaboot public API**
(`https://api.evaboot.com`, Bearer token), shipped to customers. One
`evaboot` command, two lanes, both hitting the same API with the same token:

- **Product commands**: `extract`, `email find` / `email verify`, `quota`,
  `generate`, `agent`.
- **`sn` command group**: searches, saved searches, lead and account lists,
  alerts. These are passthrough calls to `/v1/sn/`, which run the operation
  server-side on your own Sales Navigator seat. The CLI itself never calls
  linkedin.com and never holds a LinkedIn cookie.

Nothing is deployed and there is no server side here: the CLI is a client.
It reads the API token from a `--token` flag, then `EVABOOT_API_TOKEN`, then
`~/.evaboot/config.json`, and the only state it keeps on disk is that config
file.

<p align="right">(<a href="#readme-top">back to top</a>)</p>

### How It Works

Every command prints `json.dumps(...)` on stdout, even for a one-line result,
so the CLI stays pipeable into `jq` and callable from scripts. Errors are
mapped to a human sentence, not a traceback, and exit with a non-zero status
on failure that names the failure mode (see [Exit codes](#exit-codes)).

<p align="right">(<a href="#readme-top">back to top</a>)</p>

### Built With

* [![Python][python-shield]][python-url] — 3.10+, stdlib-first
* [![Click][click-shield]][click-url] — the command tree
* [![Requests][requests-shield]][requests-url]: HTTP against `api.evaboot.com`
* [![Pytest][pytest-shield]][pytest-url]: mocked HTTP, no network or credentials

<p align="right">(<a href="#readme-top">back to top</a>)</p>

<!-- GETTING STARTED -->
## Getting Started

### Prerequisites

* **Python ≥ 3.10**
* An **Evaboot API token**: get one at
  [app.evaboot.com](https://app.evaboot.com/?page=api&int=api)
* For the `sn` group only: a LinkedIn account connected once in the **Evaboot
  Chrome extension**. There is nothing to configure in the CLI for this.

### Installation

```sh
pipx install evaboot
```

[pipx](https://pipx.pypa.io) puts the `evaboot` command on your PATH in its own
virtualenv, so it cannot collide with your projects' dependencies. Plain
`pip install evaboot` works too if you would rather manage the environment
yourself.

Check it:

```sh
evaboot --version
```

<p align="right">(<a href="#readme-top">back to top</a>)</p>

<!-- USAGE -->
## Usage

### Quickstart

```bash
# Install
pipx install evaboot

# Authenticate (prints where to get your token, then prompts)
evaboot auth login

# Check your credits
evaboot quota

# Extract profiles from a Sales Navigator search URL
evaboot extract url --url "https://www.linkedin.com/sales/search/people?query=..."

# Find a professional email
evaboot email find single --first-name John --last-name Doe --company-domain example.com

# Build a search from a sentence
evaboot agent search "CMOs at French SaaS companies"
```

### Commands

#### `auth` — API Authentication

| Command | Description |
|---------|-------------|
| `auth login [--token TOKEN]` | Save your API token |
| `auth status` | Check if your token is valid |

#### `extract` — Profile Extraction

| Command | Description |
|---------|-------------|
| `extract url --url URL [--name NAME] [--webhook URL] [--enrich-email none\|matching\|all]` | Extract from a search URL |
| `extract single --profile-id ID [--enrich-email ...]` | Extract a single profile |
| `extract profiles --profile-ids IDS [--name NAME] [--webhook URL] [--enrich-email ...]` | Extract a list of profiles (comma-separated) |
| `extract get --id ID` | Get extraction results |
| `extract list` | List all extractions |

#### `email find` — Email Finder

| Command | Description |
|---------|-------------|
| `email find single --first-name NAME --last-name NAME [--company-name NAME] [--company-domain DOMAIN]` | Find a single email |
| `email find bulk --job-name NAME --prospects PATH [--webhook URL]` | Bulk find from JSON file |
| `email find get --id ID` | Get job results |
| `email find list` | List all jobs |

#### `email verify` — Email Verification

| Command | Description |
|---------|-------------|
| `email verify single --email EMAIL` | Verify a single email |
| `email verify bulk --job-name NAME --prospects PATH [--webhook URL]` | Bulk verify from JSON file |
| `email verify get --id ID` | Get job results |
| `email verify list` | List all jobs |

#### `quota` and `generate`

```bash
evaboot quota
evaboot generate search --description "CTOs in SaaS companies in France" --type LEAD
```

#### `agent` — Natural-language search

| Command | Description |
|---------|-------------|
| `agent search "<message>" [--continue ID] [--quiet] [--no-wait]` | Turn a sentence into a validated SN search URL |
| `agent job <JOB_ID> [--quiet] [--wait]` | Check a company-first (`two_step`) search job |

A plain lead search answers in one response. A company-first search creates real
account lists on the caller's own Sales Navigator seat and takes minutes, so the
API returns a job and `agent search` polls it to completion (`--no-wait` returns
the job id instead).

#### `sn search` — Search Operations

Requires a LinkedIn account connected in the Evaboot Chrome extension (see
below).

`QUERY` is LinkedIn's own search query DSL, e.g. `(filters:List(...))`; this CLI does not construct it. Get one from the `url` field of `evaboot agent search` or `evaboot generate search`: it is a Sales Navigator search URL of the form `https://www.linkedin.com/sales/search/people?query=<value>`, and `QUERY` is that URL's `query` parameter, URL-decoded.

| Command | Description |
|---------|-------------|
| `sn search paginate-leads --query QUERY [--start 0] [--count 25]` | Paginate lead search results |
| `sn search paginate-accounts --query QUERY [--start 0] [--count 25]` | Paginate account search results |
| `sn search save-lead-search --name NAME --filters JSON [--keywords KW] [--frequency DAILY\|WEEKLY]` | Save a lead search |
| `sn search save-account-search --name NAME --filters JSON [--keywords KW] [--frequency DAILY\|WEEKLY]` | Save an account search |
| `sn search list-saved [--type lead\|account] [--start 0] [--count 25]` | List saved searches |
| `sn search delete-saved --search-id ID` | Delete a saved search |

#### `sn lists` — List Management

| Command | Description |
|---------|-------------|
| `sn lists list-leads [--start 0] [--count 25]` | List all lead lists |
| `sn lists list-accounts [--start 0] [--count 25]` | List all account lists |
| `sn lists create-lead-list --name NAME [--description DESC]` | Create a lead list |
| `sn lists create-account-list --name NAME [--description DESC]` | Create an account list |
| `sn lists create-lead-list-from-ids --urns URN [--urns URN ...] --name NAME` | Create lead list from URNs |
| `sn lists create-account-list-from-ids --ids IDS --name NAME` | Create account list from IDs |
| `sn lists add-leads --list-id ID --urns URN [--urns URN ...]` | Add leads to a list |
| `sn lists add-accounts --list-id ID --ids IDS` | Add accounts to a list |
| `sn lists turn-lead-search-into-list --url URL --name NAME [--max-results 1000]` | Convert lead search to list |
| `sn lists turn-account-search-into-list --url URL --name NAME [--max-results 1000]` | Convert account search to list |
| `sn lists delete --list-id ID` | Delete a list |

A lead profile URN is `urn:li:fs_salesProfile:(<profileId>,<context>,<trackingToken>)`
and contains commas, so `--urns` does **not** comma-split. Repeat the flag for several
URNs, or separate them with `;`. Get them from the `entityUrn` field of
`sn search paginate-leads`: the tracking token is scoped to the search that produced it,
so URNs cannot be hand-built or reused from an older search. `--ids` on the account
commands still takes a comma-separated list, since company ids contain no commas.

#### `sn alerts` — Alert Operations

| Command | Description |
|---------|-------------|
| `sn alerts list-all [--count 50] [--start 0]` | List all alerts |
| `sn alerts list-leads [--count 50] [--start 0] [--formatted]` | List lead alerts |
| `sn alerts list-accounts [--count 50] [--start 0] [--formatted]` | List account alerts |
| `sn alerts for-lead --profile-urn URN [--count 50] [--start 0] [--formatted]` | Alerts for a specific lead |
| `sn alerts for-company --company-id ID [--count 50] [--start 0] [--formatted]` | Alerts for a specific company |
| `sn alerts from-lead-list --list-id ID [--count 50] [--start 0] [--formatted]` | Alerts from a lead list |
| `sn alerts from-account-list --list-id ID [--count 50] [--start 0] [--formatted]` | Alerts from an account list |

`--formatted` reshapes the raw LinkedIn payload into simplified records, done
server-side. `sn alerts list-all` has no `--formatted`: it has no single record
shape to reshape into.

### Sales Navigator connection

`sn` commands run on your own Sales Navigator seat, server-side. Connect your
LinkedIn account once in the Evaboot Chrome extension; there is nothing to paste
here. If a command answers "Connect your LinkedIn account in the Evaboot Chrome
extension", that connection is missing or has expired.

`turn-lead-search-into-list` and `turn-account-search-into-list` take minutes:
they start a job on the server and block while polling it, printing progress on
stderr and the final result as JSON on stdout. A run that created some lists but
failed a batch prints a warning on stderr and still exits 0; the per-batch detail
is in the JSON. Both are limited to 20 launches per day.

### Shell completion

`evaboot completion <shell>` prints a completion script for `bash`, `zsh` or
`fish`. Add the matching line to your shell profile once:

```bash
# bash (~/.bashrc)
eval "$(evaboot completion bash)"

# zsh (~/.zshrc)
eval "$(evaboot completion zsh)"

# fish (~/.config/fish/config.fish)
evaboot completion fish | source
```

On zsh the line has to come *after* `compinit` has been loaded, which is the
case in a normal interactive shell and in oh-my-zsh, but not if you source
`.zshrc` by hand from a bare `zsh -c`. Without it the script fails with
`command not found: compdef`.

Open a new shell and tab-completion covers subcommands and options.

<p align="right">(<a href="#readme-top">back to top</a>)</p>

### Configuration

The CLI resolves the API token from three tiers, in this order (first one
found wins):

1. **`--token` flag**, on the top-level `evaboot` command:
   `evaboot --token YOUR_TOKEN quota`.
2. **`EVABOOT_API_TOKEN` environment variable**, useful in CI, a container,
   or any ephemeral shell where running `auth login` first is inconvenient:
   `EVABOOT_API_TOKEN=YOUR_TOKEN evaboot quota`.
3. **`~/.evaboot/config.json`**, written by `evaboot auth login`:

   ```json
   {
     "api_token": "your-evaboot-api-token"
   }
   ```

`auth login` overwrites this file with your token; there is nothing else to
configure. The file is written `0600` inside a `0700` directory, since it holds
a bearer token to your account. This file is the only state the CLI keeps on
disk; the flag and environment variable are never written to it.

<p align="right">(<a href="#readme-top">back to top</a>)</p>

### Exit codes

A script gating on `$?` can tell "wait and retry" apart from "stop, this will
never work":

| Code | Meaning |
|------|---------|
| 0 | Success |
| 1 | Generic failure |
| 2 | Usage error (bad flags or arguments; click's own default) |
| 3 | Authentication or authorization failure (expired token, permission denied) |
| 4 | Payment or quota (insufficient credits) |
| 5 | Your Sales Navigator seat is not connected |
| 6 | Rate limited or the upstream is temporarily refusing requests |
| 7 | Network unreachable or the request timed out |

<p align="right">(<a href="#readme-top">back to top</a>)</p>

<!-- ROADMAP -->
## Roadmap

- [x] Evaboot API surface (`auth`, `quota`, `extract`, `email`, `generate`)
- [x] Sales Navigator group (`sn search`, `sn lists`, `sn alerts`) as `/v1/sn/`
      passthrough calls
- [x] Natural-language `agent search` (with company-first job polling)
- [x] CI on 3.10 + 3.12, credential-free
- [x] Packaged install (`pipx install evaboot`)

<p align="right">(<a href="#readme-top">back to top</a>)</p>

<!-- CONTRIBUTING -->
## Contributing

The Evaboot CLI is developed and maintained by Evaboot. For questions, bug
reports, or feature requests, contact support (see [Contact](#contact)).

<p align="right">(<a href="#readme-top">back to top</a>)</p>

<!-- LICENSE -->
## License

MIT.

<p align="right">(<a href="#readme-top">back to top</a>)</p>

<!-- CONTACT -->
## Contact

Questions or problems: support@evaboot.com

Project: [pypi.org/project/evaboot][pypi-url]

<p align="right">(<a href="#readme-top">back to top</a>)</p>

<!-- ACKNOWLEDGMENTS -->
## Acknowledgments

* Sales Navigator filter construction is decided server-side by the Evaboot
  backend, not by this CLI: it is a wire client and builds no filter logic
  itself
* [Best-README-Template](https://github.com/othneildrew/Best-README-Template) — README structure

<p align="right">(<a href="#readme-top">back to top</a>)</p>

<!-- MARKDOWN LINKS & IMAGES -->
[pypi-shield]: https://img.shields.io/pypi/v/evaboot?style=for-the-badge
[pypi-url]: https://pypi.org/project/evaboot/
[python-shield]: https://img.shields.io/badge/python-3.10+-3776AB?style=for-the-badge&logo=python&logoColor=white
[python-url]: https://www.python.org/
[click-shield]: https://img.shields.io/badge/Click-8.0+-000000?style=for-the-badge&logo=python&logoColor=white
[click-url]: https://click.palletsprojects.com/
[requests-shield]: https://img.shields.io/badge/Requests-2.28+-2C5BB4?style=for-the-badge&logo=python&logoColor=white
[requests-url]: https://requests.readthedocs.io/
[pytest-shield]: https://img.shields.io/badge/pytest-0A9EDC?style=for-the-badge&logo=pytest&logoColor=white
[pytest-url]: https://docs.pytest.org/
