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.
- {letz_cli-0.2.0 → letz_cli-0.3.0}/PKG-INFO +5 -3
- {letz_cli-0.2.0 → letz_cli-0.3.0}/README.md +4 -2
- {letz_cli-0.2.0 → letz_cli-0.3.0}/letz/__init__.py +1 -1
- {letz_cli-0.2.0 → letz_cli-0.3.0}/letz/__main__.py +5 -4
- {letz_cli-0.2.0 → letz_cli-0.3.0}/letz/api.py +13 -5
- {letz_cli-0.2.0 → letz_cli-0.3.0}/.gitignore +0 -0
- {letz_cli-0.2.0 → letz_cli-0.3.0}/LICENSE +0 -0
- {letz_cli-0.2.0 → letz_cli-0.3.0}/letz/gauntlet.py +0 -0
- {letz_cli-0.2.0 → letz_cli-0.3.0}/pyproject.toml +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: letz-cli
|
|
3
|
-
Version: 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`), `--
|
|
88
|
-
`./letz_work`), `--version`. Global flags go
|
|
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`), `--
|
|
67
|
-
`./letz_work`), `--version`. Global flags go
|
|
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
|
|
|
@@ -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
|
-
|
|
28
|
-
|
|
29
|
-
headers
|
|
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
|