letz-cli 0.2.0__tar.gz → 0.3.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.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: letz-cli
3
- Version: 0.2.0
3
+ Version: 0.3.0
4
4
  Summary: Letz Brain CLI - pull campaigns from the Letz platform, draft email sequences locally with Claude, and push them back staged for review.
5
5
  Project-URL: Homepage, https://sam.weareletz.com
6
6
  Author: Letz
@@ -30,6 +30,7 @@ the [Letz](https://sam.weareletz.com) platform staged for review, and let the cl
30
30
  ```bash
31
31
  pip install letz-cli # or: pipx install letz-cli
32
32
  export LETZ_API_URL="https://your-letz-instance.example.com"
33
+ export LETZ_API_KEY="letz_sk_..." # create one in Settings → Claude Code Tools → API Keys
33
34
  letz campaigns # list your campaigns + ids
34
35
  letz prompt <CAMPAIGN_ID> # print the playbook for Claude
35
36
  ```
@@ -84,8 +85,9 @@ letz status <campaign_id>
84
85
  | `push <id> --activate` | push **and** flip the campaign active (begins sending) | no |
85
86
  | `status <id>` | staged enrollments / previews / sends | no |
86
87
 
87
- Flags: `--api <url>` (or `$LETZ_API_URL`), `--work <dir>` (or `$LETZ_WORK`, default
88
- `./letz_work`), `--version`. Global flags go before the command: `letz --api <url> status <id>`.
88
+ Flags: `--api <url>` (or `$LETZ_API_URL`), `--key <api key>` (or `$LETZ_API_KEY`),
89
+ `--work <dir>` (or `$LETZ_WORK`, default `./letz_work`), `--version`. Global flags go
90
+ before the command: `letz --api <url> status <id>`.
89
91
 
90
92
  Requires Python 3.10+. Zero dependencies (pure standard library).
91
93
 
@@ -9,6 +9,7 @@ the [Letz](https://sam.weareletz.com) platform staged for review, and let the cl
9
9
  ```bash
10
10
  pip install letz-cli # or: pipx install letz-cli
11
11
  export LETZ_API_URL="https://your-letz-instance.example.com"
12
+ export LETZ_API_KEY="letz_sk_..." # create one in Settings → Claude Code Tools → API Keys
12
13
  letz campaigns # list your campaigns + ids
13
14
  letz prompt <CAMPAIGN_ID> # print the playbook for Claude
14
15
  ```
@@ -63,8 +64,9 @@ letz status <campaign_id>
63
64
  | `push <id> --activate` | push **and** flip the campaign active (begins sending) | no |
64
65
  | `status <id>` | staged enrollments / previews / sends | no |
65
66
 
66
- Flags: `--api <url>` (or `$LETZ_API_URL`), `--work <dir>` (or `$LETZ_WORK`, default
67
- `./letz_work`), `--version`. Global flags go before the command: `letz --api <url> status <id>`.
67
+ Flags: `--api <url>` (or `$LETZ_API_URL`), `--key <api key>` (or `$LETZ_API_KEY`),
68
+ `--work <dir>` (or `$LETZ_WORK`, default `./letz_work`), `--version`. Global flags go
69
+ before the command: `letz --api <url> status <id>`.
68
70
 
69
71
  Requires Python 3.10+. Zero dependencies (pure standard library).
70
72
 
@@ -2,4 +2,4 @@
2
2
  sequences locally with Claude, and push them back staged for review.
3
3
  Nothing sends until a human clicks Activate.
4
4
  """
5
- __version__ = "0.2.0"
5
+ __version__ = "0.3.0"
@@ -65,7 +65,7 @@ def _save(path, obj):
65
65
 
66
66
  # ---------------------------------------------------------------- commands ----
67
67
  def cmd_campaigns(args):
68
- api = LetzAPI(args.api)
68
+ api = LetzAPI(args.api, api_key=args.key)
69
69
  camps = api.campaigns()
70
70
  if not camps:
71
71
  print(f"No campaigns found at {args.api}")
@@ -84,7 +84,7 @@ def cmd_prompt(args):
84
84
 
85
85
 
86
86
  def cmd_pull(args):
87
- api = LetzAPI(args.api)
87
+ api = LetzAPI(args.api, api_key=args.key)
88
88
  camp = api.campaign(args.campaign_id)
89
89
  if not camp:
90
90
  print(f"✗ Campaign {args.campaign_id} not found at {args.api}")
@@ -197,7 +197,7 @@ def cmd_push(args):
197
197
  # Push in batches so a large campaign never trips the request timeout.
198
198
  # The endpoint is idempotent + atomic per request, so a failed batch can be
199
199
  # safely retried by re-running push.
200
- api = LetzAPI(args.api)
200
+ api = LetzAPI(args.api, api_key=args.key)
201
201
  batch = max(1, args.batch)
202
202
  total_imported = total_skipped = 0
203
203
  last_status = None
@@ -222,7 +222,7 @@ def cmd_push(args):
222
222
 
223
223
 
224
224
  def cmd_status(args):
225
- api = LetzAPI(args.api)
225
+ api = LetzAPI(args.api, api_key=args.key)
226
226
  camp = api.campaign(args.campaign_id) or {}
227
227
  enrs = api.enrollments(args.campaign_id)
228
228
  prev = api.previews(args.campaign_id)
@@ -240,6 +240,7 @@ def main(argv=None):
240
240
  p = argparse.ArgumentParser(prog="letz", description="Letz Brain CLI — off-platform generation, persist, click send.")
241
241
  p.add_argument("--version", action="version", version=f"%(prog)s {__version__}")
242
242
  p.add_argument("--api", default=os.environ.get("LETZ_API_URL", "http://localhost:8000"), help="platform base URL")
243
+ p.add_argument("--key", default=os.environ.get("LETZ_API_KEY", ""), help="API key (or $LETZ_API_KEY) — create one in Settings → API Keys")
243
244
  p.add_argument("--work", default=None, help="work dir (default ./letz_work)")
244
245
  sub = p.add_subparsers(dest="cmd", required=True)
245
246
 
@@ -17,22 +17,30 @@ class LetzAPIError(Exception):
17
17
 
18
18
 
19
19
  class LetzAPI:
20
- def __init__(self, base_url: str = "http://localhost:8000", timeout: int = 120):
20
+ def __init__(self, base_url: str = "http://localhost:8000", timeout: int = 120,
21
+ api_key: str = ""):
21
22
  self.base = base_url.rstrip("/")
22
23
  self.timeout = timeout
24
+ self.api_key = (api_key or "").strip()
23
25
 
24
26
  def _req(self, method: str, path: str, body=None):
25
27
  url = self.base + ("/api/v1" if not path.startswith("/api/") else "") + path
26
28
  data = json.dumps(body).encode() if body is not None else None
27
- req = urllib.request.Request(
28
- url, data=data, method=method,
29
- headers={"Content-Type": "application/json"},
30
- )
29
+ headers = {"Content-Type": "application/json"}
30
+ if self.api_key:
31
+ headers["X-API-Key"] = self.api_key
32
+ req = urllib.request.Request(url, data=data, method=method, headers=headers)
31
33
  try:
32
34
  with urllib.request.urlopen(req, timeout=self.timeout) as resp:
33
35
  raw = resp.read().decode() or "{}"
34
36
  return json.loads(raw)
35
37
  except urllib.error.HTTPError as e:
38
+ if e.code == 401:
39
+ raise LetzAPIError(401, {
40
+ "detail": "Unauthorized. Create an API key in the platform under "
41
+ "Settings → Claude Code Tools → API Keys, then "
42
+ 'export LETZ_API_KEY="letz_sk_..." (or pass --key).'
43
+ })
36
44
  try:
37
45
  payload = json.loads(e.read().decode() or "{}")
38
46
  except Exception:
File without changes
File without changes
File without changes
File without changes