Metadata-Version: 2.5
Name: letz-cli
Version: 0.3.1
Summary: Letz Brain CLI - pull campaigns from the Letz platform, draft email sequences locally with Claude, and push them back staged for review.
Project-URL: Homepage, https://sam.weareletz.com
Author: Letz
License-Expression: MIT
License-File: LICENSE
Keywords: claude,email,letz,outreach,sdr,sequences
Classifier: Environment :: Console
Classifier: Intended Audience :: Other Audience
Classifier: Operating System :: OS Independent
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Topic :: Communications :: Email
Requires-Python: >=3.10
Description-Content-Type: text/markdown

# Letz Brain CLI

Generate email sequences **off-platform** (on flat-rate Claude compute), push them to
the [Letz](https://sam.weareletz.com) platform staged for review, and let the cloud
**just click send**. Zero cloud AI cost.

## 60-second start

```bash
pip install letz-cli                # or: pipx install letz-cli
export LETZ_API_KEY="letz_sk_..."   # create one in Settings → Claude Code Tools → API Keys
letz campaigns                      # list your campaigns + ids
letz prompt <CAMPAIGN_ID>           # print the playbook for Claude
```

The CLI talks to the Letz cloud by default. Self-hosted or local instance? Point it
elsewhere with `export LETZ_API_URL="https://your-instance.example.com"` or `--api`.

Paste the output of `letz prompt` into a [Claude Code](https://docs.claude.com/en/docs/claude-code/overview)
session signed in with a **Claude Pro or Max subscription** (not a metered API key — the
subscription is what makes generation free). Claude runs the whole loop below for you,
writes every email, and stages the result **paused**. You review in the platform UI and
click **Activate** when happy. Nothing ever sends on its own.

## Why

Every time the cloud app composes a sequence it spends metered AI money. This CLI moves
all of that generation onto your own Claude subscription. The platform only renders the
finished drafts for review and sends them when a human clicks **Activate**.

## The loop, step by step

```bash
# 1. Pull a campaign's config + leads. Writes letz_work/<id>.pull.json and a
#    letz_work/<id>.ready.json scaffold (one entry per lead, empty emails).
letz pull <campaign_id>

# 2. GENERATION happens HERE: fill the "emails" arrays in letz_work/<id>.ready.json.
#    In an interactive Claude session, Claude writes the sequences directly into that
#    file, following the campaign's style guide + brand persona.

# 3. Quality gate (pure Python, no AI). Fails on em dashes, spam phrases, forbidden
#    names, over-length, etc.
letz check <campaign_id>

# 4. Push staged (PAUSED — nothing sends). Idempotent; safe to re-run.
letz push <campaign_id>

# 5. Review the drafts in the platform UI (campaign detail → previews), then click
#    Activate. Or release from the CLI:
letz push <campaign_id> --activate   # flips campaign active → sending begins in-window

# Inspect anytime:
letz status <campaign_id>
```

## Commands

| Command | What it does | AI? |
|---|---|---|
| `campaigns` | list your campaigns with their ids | no |
| `prompt [id]` | print the recommended prompt to paste into Claude Code | no |
| `pull <id>` | fetch campaign + persona + leads → pull.json + ready.json scaffold | no |
| `check <id>` | run the deterministic quality gauntlet on ready.json | no |
| `push <id>` | import drafts, staged paused; re-runs the gauntlet first | no |
| `push <id> --activate` | push **and** flip the campaign active (begins sending) | no |
| `status <id>` | staged enrollments / previews / sends | no |

Flags: `--api <url>` (or `$LETZ_API_URL`), `--key <api key>` (or `$LETZ_API_KEY`),
`--work <dir>` (or `$LETZ_WORK`, default `./letz_work`), `--version`. Global flags go
before the command: `letz --api <url> status <id>`.

Requires Python 3.10+. Zero dependencies (pure standard library).

## Safety

- The CLI talks only to the platform's REST API. Pushing drafts can never trigger a
  send by itself.
- `push` stages **paused** by default and re-runs the gauntlet; it refuses hard-failing
  content unless `--force`.
- Only `--activate` (or a human clicking Activate) ever causes a send.
