ermes-cli 1.0.0__py3-none-any.whl

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.
@@ -0,0 +1,281 @@
1
+ Metadata-Version: 2.5
2
+ Name: ermes-cli
3
+ Version: 1.0.0
4
+ Summary: CLI-first access to Ermes marketing automation, with Auth0 multi-project auth and an optional compatibility MCP.
5
+ Project-URL: Homepage, https://ermes.publinker.com
6
+ Project-URL: Repository, https://github.com/Martech-DM/ermes
7
+ Author: Martech-DM
8
+ License: Proprietary
9
+ Keywords: cli,email,ermes,marketing-automation
10
+ Classifier: Environment :: Console
11
+ Classifier: Intended Audience :: Developers
12
+ Classifier: Operating System :: OS Independent
13
+ Classifier: Programming Language :: Python :: 3
14
+ Classifier: Programming Language :: Python :: 3 :: Only
15
+ Classifier: Topic :: Communications :: Email
16
+ Requires-Python: >=3.10
17
+ Requires-Dist: fastmcp>=2.0
18
+ Requires-Dist: httpx>=0.28
19
+ Requires-Dist: keyring>=24.0
20
+ Requires-Dist: pyyaml>=6.0
21
+ Requires-Dist: requests>=2.31
22
+ Requires-Dist: rich>=14.0
23
+ Requires-Dist: typer>=0.24
24
+ Requires-Dist: tzdata>=2024.1
25
+ Provides-Extra: dev
26
+ Requires-Dist: pytest-httpx>=0.35; extra == 'dev'
27
+ Requires-Dist: pytest>=8.0; extra == 'dev'
28
+ Requires-Dist: responses>=0.25; extra == 'dev'
29
+ Description-Content-Type: text/markdown
30
+
31
+ # Ermes CLI and optional compatibility MCP
32
+
33
+ This package gives Ermes operators a CLI-first surface for explicit-project API reads and guarded
34
+ writes. It also retains a 10-tool read-only MCP for temporary compatibility, but normal agent
35
+ sessions leave that server unregistered.
36
+
37
+ Version 1.0.0 (DEV-2261). Verified 2026-10-20.
38
+
39
+ ## Install the CLI
40
+
41
+ ```bash
42
+ pipx install ermes-cli # from PyPI; `pipx upgrade ermes-cli` later
43
+ ermes version
44
+ ```
45
+
46
+ Or run the installer, which also checks Python/pipx and verifies the entrypoints:
47
+
48
+ ```bash
49
+ ./cli/scripts/install-cli.sh # latest release
50
+ ./cli/scripts/install-cli.sh --version=1.0.0 # pinned
51
+ ./cli/scripts/install-cli.sh --branch=main # developers: straight from git
52
+ ```
53
+
54
+ Releases are cut by pushing a tag `ermes-cli-vX.Y.Z` equal to `cli/pyproject.toml`'s version
55
+ (`.github/workflows/publish-cli.yml` runs the tests, builds the wheel, publishes to PyPI and attaches
56
+ it to a GitHub Release). `install-cli.sh` is idempotent and safe to re-run for upgrades.
57
+
58
+ If a temporary rollback needs fixed typed schemas, register the compatibility MCP only for one
59
+ explicit project:
60
+
61
+ ```bash
62
+ ./cli/scripts/install-mcp.sh --project=<project-name-or-id>
63
+ ```
64
+
65
+ Remove it after the rollback window. Removal preserves Auth0 tokens, project keys, and every file
66
+ under `~/.ermes`:
67
+
68
+ ```bash
69
+ ./cli/scripts/install-mcp.sh --uninstall
70
+ ```
71
+
72
+ Never run `ermes logout` as part of registration removal; logout deletes the Keychain session and
73
+ `projects.yml`.
74
+
75
+ ## Auth — Auth0 device login (recommended)
76
+
77
+ ```bash
78
+ ermes login # Auth0 device flow + auto-discover projects
79
+ ermes projects list # show cached projects; keys stay masked
80
+ ermes use <project-name-or-id> # interactive active-project selection
81
+ ermes whoami # local selection only; not API liveness
82
+ ermes projects refresh # re-sync after admins add you to new projects
83
+ ermes logout # delete Keychain tokens + projects.yml
84
+ ```
85
+
86
+ `ermes login` runs an Auth0 Device Authorization Flow (RFC 8628) against the
87
+ shared `dev-hqiwr-re.us.auth0.com` tenant (same one publinker-cli uses, so the
88
+ authenticated user is your Auth0 identity). After authentication:
89
+
90
+ 1. Trades the Auth0 access token for an Ermes admin JWT via `/api/cli/exchange`
91
+ (the account's email must be verified in Auth0).
92
+ 2. Lists every project you can access via `/api/admin/projects/all`.
93
+ 3. Caches the list in `~/.ermes/projects.yml` (chmod 600) — **no keys are minted at login**.
94
+ 4. Auto-selects the first project as active.
95
+ 5. On the first command against a project, mints **your own** `sk_` key for it via
96
+ `POST /api/admin/projects/:id/cli-key`, named `cli-<email>-<hostname>` and bound to your
97
+ admin account (revoked when you are removed from the project). Keys from the old
98
+ `cli-managed-<hostname>` scheme are ignored.
99
+
100
+ Auth0 tokens live in the OS keychain (`ermes-cli` service). On a headless box with no keyring
101
+ backend they fall back to `~/.ermes/auth0-tokens.json` (chmod 600). Per-project `sk_` keys live
102
+ in `~/.ermes/projects.yml`.
103
+
104
+ For interactive work, `ermes use <project-name-or-id>` persists the active project. For every script
105
+ or agent command, pin scope without changing that shared default:
106
+
107
+ ```bash
108
+ ERMES_PROJECT=<project-name-or-id> ermes --json campaigns list --limit 1
109
+ ```
110
+
111
+ Resolution order: `--profile`, `ERMES_API_KEY`, `ERMES_PROJECT`, the active project, then the legacy
112
+ `ERMES_PROFILE` / `config.yml` default. `ERMES_PROFILE` is deprecated and prints a warning; it can no
113
+ longer override an explicit project or `projects.yml`.
114
+
115
+ For CI or a machine without a browser, skip `login` and pass a key directly:
116
+
117
+ ```bash
118
+ ERMES_API_KEY=sk_... ERMES_BASE_URL=https://ermes.publinker.com ermes --json campaigns list
119
+ ```
120
+
121
+ `ermes whoami` reports the local selection; `ermes whoami --check` additionally makes one
122
+ authenticated read and exits 1 if the key is rejected.
123
+
124
+ > **Requires:** `AUTH_CLI_AUDIENCE=https://ermes.publinker.com/api` set on the
125
+ > Ermes API (SSM env-file). Without it, `/api/cli/exchange` returns 404 and
126
+ > `ermes login` errors out with a clear hint.
127
+
128
+ ### Legacy: single-profile manual setup
129
+
130
+ `ermes config init` (or `ermes config add`) still works for the one-project,
131
+ manual-paste flow. It writes `~/.ermes/config.yml` with the legacy profile
132
+ shape; `resolve_profile()` falls back to it when no `projects.yml` exists. The
133
+ command now emits a deprecation warning pointing at `ermes login`.
134
+
135
+ ## Write-mode safety (default-blocked mutations)
136
+
137
+ Every `POST` / `PATCH` / `PUT` / `DELETE` through the CLI is **blocked by
138
+ default**. Reads work freely; mutations require an explicit unlock. These interactive commands act on
139
+ the project deliberately selected with `ermes use`:
140
+
141
+ ```bash
142
+ ermes write status # show current state
143
+ ermes write allow # unlock for 30 minutes
144
+ ermes write allow --minutes 5 # custom window (1-1440 min)
145
+ ermes write allow --persistent # no expiry (until `write deny`)
146
+ ermes write deny # close the window immediately
147
+ ```
148
+
149
+ For one reviewed scripted mutation, keep both project and write authorization process-scoped:
150
+
151
+ ```bash
152
+ ERMES_PROJECT=<owned-project-name-or-id> ERMES_ALLOW_WRITES=1 \
153
+ ermes --json <resource-group> <mutation> <arguments>
154
+ ```
155
+
156
+ Ermes has no general server-side dry run. Read the current resource before the mutation, keep the
157
+ scope to an owned project, then read it again to verify the requested state. Never leave
158
+ `ERMES_ALLOW_WRITES` exported in a shared shell.
159
+
160
+ State lives in `~/.ermes/state.yml` (chmod 600), separate from `projects.yml`
161
+ so refreshing projects doesn't clobber the write window. `ermes whoami` shows
162
+ the current write status.
163
+
164
+ The optional compatibility MCP remains fixed at 10 read-only tools. Mutations stay CLI-only so every
165
+ agent write must carry explicit project scope and one-process `ERMES_ALLOW_WRITES=1` authorization.
166
+
167
+ ## Metrics
168
+
169
+ These examples assume that you deliberately selected an interactive project with `ermes use`. In
170
+ scripts, prefix the same commands with `ERMES_PROJECT=<project-name-or-id>`.
171
+
172
+ ```bash
173
+ ermes metrics # last 7 days, type=trigger, channel=email (UI default)
174
+ ermes metrics --type blast --days 30 # last 30 days, blast campaigns only
175
+ ermes metrics --type all --start 2026-05-01 --end 2026-05-31
176
+ ermes metrics --channel push --days 7 # push campaigns
177
+ ermes metrics --channel all --days 7 # both, as two labelled blocks
178
+ ermes --json metrics --type all # raw JSON for piping
179
+ ```
180
+
181
+ Per-campaign breakdown: sends, opens, clicks, bounces, complaints, plus
182
+ computed open/click/CTOR rates. Aggregate summary row at the bottom. Server
183
+ caps at 100 rows / 90-day range; the CLI surfaces both as warnings.
184
+
185
+ ### Channels
186
+
187
+ `--channel / -c` takes `email` (default) | `push` | `all`. The API accepts only
188
+ `email` and `push`, lowercase — the CLI folds the case so `--channel PUSH`
189
+ works instead of 400ing, and defaults to `email`, which is also what the server
190
+ assumes when the param is omitted, so the default output is unchanged.
191
+
192
+ There is **no combined mode server-side**: `--channel all` issues one request
193
+ per channel and prints two labelled blocks (`--json` nests them under
194
+ `channels`). The two are never summed — push has no opens or bounces, so the
195
+ rates aren't comparable.
196
+
197
+ > **`--json` is a ROOT flag, not a `metrics` flag** — `ermes --json metrics …`,
198
+ > never `ermes metrics --json` (that exits with `No such option: --json`). This
199
+ > catches people out because every example that shows JSON also shows a
200
+ > subcommand. The per-campaign rows land under **`results`** (not `campaigns`
201
+ > or `rows`), alongside `channel`, `summary`, `truncated` and
202
+ > `aggregated_through`:
203
+ >
204
+ > ```json
205
+ > {"channel": "push", "results": [{"campaign_id": 156139, "name": "...",
206
+ > "sends": 61, "failed": 0, "clicks": 0, "click_rate": 0, "opens": 0}],
207
+ > "summary": {"campaign_count": 13, "sends": 499, "failed": 67, "clicks": 25},
208
+ > "truncated": false, "aggregated_through": "2026-08-04"}
209
+ > ```
210
+ >
211
+ > For `--channel all` the shape nests instead: `{"channel": "all",
212
+ > "channels": {"email": {…}, "push": {…}}}`, so a consumer must branch on
213
+ > `channel` before reading `results`.
214
+
215
+ Push renders the UI's columns — `Sent` (= `sends + failed`), `Delivered`
216
+ (= `sends`), `Failed`, `Clicks`, `Click %`. Opens / Open % / CTOR / Bounces /
217
+ Spam are omitted: push has no open or bounce event, so they are always zero and
218
+ only read as breakage.
219
+
220
+ `aggregated_through` in the response is the newest local day with data for that
221
+ channel+type, capped at the day the hourly rollup has marked complete for the
222
+ project. Today, and yesterday until the first run past local midnight, are
223
+ returned as partial "so far" numbers, so the default window ends yesterday,
224
+ like the UI; pass `--end` to include today. The empty-state message prints the frontier so an empty window
225
+ is legible.
226
+
227
+ ## Tool surface
228
+
229
+ ### CLI (`ermes ...`)
230
+
231
+ | Group | Commands |
232
+ |---|---|
233
+ | `campaigns` | list, get, create, update, delete, trigger (`--external-id`, optional `--email`/`--phone`/`--device-token`/`--locale`/`--timezone`) |
234
+ | `templates` | list, get, create, update, delete |
235
+ | `lists` | list, get, create, update, delete |
236
+ | `journeys` | list, get, create, update, delete |
237
+ | `users` | list, get, delete, events, lists |
238
+ | `keys` | list, get, create, update, delete — uses your Auth0 admin session, never a project key |
239
+ | `subscriptions`, `tags`, `providers`, `ingest`, `export`, `config` | … |
240
+ | `login`, `logout`, `whoami`, `use`, `projects` | Auth0 device-flow auth + multi-project context |
241
+ | `metrics` | Campaign metrics, email or push (mirrors UI Metrics view) |
242
+ | `write allow / deny / status` | Control the write-mode guard (mutations blocked by default) |
243
+
244
+ Full surface: `ermes --help`.
245
+
246
+ ### Optional compatibility MCP (`ermes-mcp`)
247
+
248
+ The 10-tool read-only MCP remains for temporary Claude Code rollback only. Normal sessions use the
249
+ CLI so they can pin project scope per command without loading fixed schemas at startup.
250
+ `install-mcp.sh --project=<name-or-id>` writes an explicit `ERMES_PROJECT`; `ermes use` doesn't
251
+ retarget that process.
252
+
253
+ The CLI covers every compatibility read and is the preferred path:
254
+
255
+ | Tool | Purpose |
256
+ |---|---|
257
+ | `whoami` | Current profile + base URL (no API call) |
258
+ | `list_campaigns(limit?, status?)` | Campaigns; use CLI `campaigns list --status` when filtering |
259
+ | `get_campaign(campaign_id)` | One campaign |
260
+ | `list_templates(limit?)` | Templates |
261
+ | `get_template(template_id)` | One template |
262
+ | `list_lists(limit?)` | Contact lists |
263
+ | `list_journeys(limit?)` | Automation journeys |
264
+ | `list_users(limit?, email?)` | Users; use CLI `users list --q` for exact search |
265
+ | `get_user(user_id)` | One user; the CLI argument is the external ID |
266
+ | `get_user_events(user_id, limit?)` | Recent events; the CLI argument is the external ID |
267
+
268
+ ## Where in the wider stack
269
+
270
+ Ermes is a CLI-first integration in the
271
+ [`Martech-DM/martech-mcps`](https://github.com/Martech-DM/martech-mcps) registry.
272
+ That repo's `ermes/README.md` and `ermes/skills/ermes-ops/SKILL.md` are the
273
+ cross-fleet setup and operating runbooks. The compatibility MCP stays
274
+ unregistered during normal operation.
275
+
276
+ ## CLI source history
277
+
278
+ The `ermes_cli/` package was originally bundled inside the legacy
279
+ `ermes-skill`. It now lives here as the single code source of truth. The
280
+ versioned operating skill lives at `martech-mcps/ermes/skills/ermes-ops` and
281
+ invokes this installed package rather than carrying a copy.
@@ -0,0 +1,37 @@
1
+ ermes_cli/__init__.py,sha256=J-j-u0itpEFT6irdmWmixQqYMadNl1X91TxUmoiLHMI,22
2
+ ermes_cli/admin_api.py,sha256=J03HgaVCCS-PCL4ELvOiVSoyhwCl-OFxZBM3OBD-De8,9944
3
+ ermes_cli/auth0.py,sha256=5wkwL2hBfePqgnJJkZA2_7yNP_sJUZ2FJ1AFiSIainM,7365
4
+ ermes_cli/client.py,sha256=tgV2zKux-zq9qGSZLwMNv9O-OeztKJqnSZd3NpaXeMc,3899
5
+ ermes_cli/config.py,sha256=CeeWvoDCFX8zPkr5UIWzq_7O6iGd2sbYcf19n4Bwuro,12950
6
+ ermes_cli/errors.py,sha256=wdXrJT1rnWZo5tShpNg1SQANZI7mOGN6A3kXMxPe3Tk,1147
7
+ ermes_cli/export_utils.py,sha256=X6_wh0fcgnwp27r0jVMrgMsKYAIq4ILgnMm1pwUuq_I,7954
8
+ ermes_cli/keyring_store.py,sha256=_96PtrPZ0V5KZ-Bb6dve4b9kfF4HKKtbyMNas7f57J8,5355
9
+ ermes_cli/main.py,sha256=hk1SAXGBJfYyZ1P_AmUEGZpG1E5MedSeACnM9TZxW7g,7005
10
+ ermes_cli/manifest.py,sha256=Waxg5QTIOkjOLRf-7sq-KWAV0RRPgCSrRm3P1uzj7fs,7458
11
+ ermes_cli/mcp_server.py,sha256=VyEXhzmeiwHh1Fd2gmUttHlm-c_B-eRQmlTto8-sj-s,5208
12
+ ermes_cli/output.py,sha256=Bwcx5Z8UkGIqJA6k0pCyoj5V3yPmP_Jm9twO4HqTk-c,1569
13
+ ermes_cli/rules.py,sha256=sEBjNPRvVsNxk_ay_bWuuub5DSPENM4hMDi1VAyK_Bc,2308
14
+ ermes_cli/write_mode.py,sha256=p7bifKmX_HfNg85vle4TdhOXNc4GLU14piRG3J-jV_E,5457
15
+ ermes_cli/commands/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
16
+ ermes_cli/commands/auth_cmds.py,sha256=iO8uY5aElJ76QgnFqb4rMbXevqeAEyvaaD6o7HAz9JE,15121
17
+ ermes_cli/commands/campaigns.py,sha256=0h9jScKGavFX_7Y0cfFJSh1URhaofh5p0KHA0oGqFQs,11516
18
+ ermes_cli/commands/config_cmd.py,sha256=2ThBd-h4dfm8bDko0NkQYjFbI6xUdLEZU9_Sm9DG0EM,3122
19
+ ermes_cli/commands/data_paths.py,sha256=Dukvzf2WqCQAk1Y_1CHsctiONQJdI1xqNqD9l0cHCwk,3838
20
+ ermes_cli/commands/export.py,sha256=JRuBXXuIU8Vv9g_Xmg4B9sGzN79pTaMQYFN0unQDD6c,6872
21
+ ermes_cli/commands/ingest.py,sha256=B2Xu6dppe5ol3F-EVu7Z5fQIkUqft8CWLt3DkF55umQ,3997
22
+ ermes_cli/commands/journeys.py,sha256=bdC6QhxUHe-usSMbKKyx7-rCO0WcQQSDttmbtLiDukY,6746
23
+ ermes_cli/commands/keys.py,sha256=Lctv0I5BNHzALWm8wYF-2LYMbdAN0qnTMuGkOf3JvPk,4296
24
+ ermes_cli/commands/lists.py,sha256=HGReU0xZfwpfWaeIIAzHgjJMWmX1I-UGICIhmc-CC4Y,4726
25
+ ermes_cli/commands/metrics.py,sha256=uRENV6xPmb3-w4PD9rOSF0mvNbsWl1o86vaxnDf-xDw,14890
26
+ ermes_cli/commands/projects.py,sha256=H9T6MA_v-TYrDg-HULQY-B_xyJ3XA7nCQlLQJkgHrsw,9931
27
+ ermes_cli/commands/providers.py,sha256=dkSB33-CMlNuQHEFPWjMg3j8cmCPsQNc7aKBM1e4Gqw,6493
28
+ ermes_cli/commands/subscriptions.py,sha256=r0u138xCGcxlnqEIYWqwjmCr9jeiihTXGchkpyvFcUM,2599
29
+ ermes_cli/commands/tags.py,sha256=brvNoGF_W0Z-6FCtZUc5OaEjjrs16USm7DIOi7lFb7Q,2641
30
+ ermes_cli/commands/templates.py,sha256=Tj2laP7PjrE1OV1Ogfd2S9VcH6Eez66DBYN3mn6PE7Q,5020
31
+ ermes_cli/commands/users.py,sha256=kSO0MaezDzoaxun8uMdWqxC-NzK2F8EuZ4ZgFFMjAcg,4897
32
+ ermes_cli/commands/write_cmds.py,sha256=432YRNrOzqYEIgpuxGDzxM0qWQtKbLY0MvXtCyH0OYA,2582
33
+ ermes_cli/routes.yaml,sha256=XRcSfZrqxFALFJ-aYPymQU1IPRyUn3dLrUiMcsvdTug,3943
34
+ ermes_cli-1.0.0.dist-info/METADATA,sha256=_O72O1qn1fcJBIcYs6K5j1_dWVfzfdWn0MvmFn-UgXs,12706
35
+ ermes_cli-1.0.0.dist-info/WHEEL,sha256=W3fkpkm7-wf9vBI5Z-7s0eWkeM-spu78I8Neb98DeEg,87
36
+ ermes_cli-1.0.0.dist-info/entry_points.txt,sha256=a_UUhy06iPI04PaU1q1kiWLBIu3rEzXrkdNxq1HL85Q,83
37
+ ermes_cli-1.0.0.dist-info/RECORD,,
@@ -0,0 +1,4 @@
1
+ Wheel-Version: 1.0
2
+ Generator: hatchling 1.32.4
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
@@ -0,0 +1,3 @@
1
+ [console_scripts]
2
+ ermes = ermes_cli.main:app
3
+ ermes-mcp = ermes_cli.mcp_server:main