ermes-cli 1.0.0__tar.gz → 1.2.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.
- {ermes_cli-1.0.0 → ermes_cli-1.2.0}/PKG-INFO +51 -42
- {ermes_cli-1.0.0 → ermes_cli-1.2.0}/README.md +49 -39
- ermes_cli-1.2.0/ermes_cli/__init__.py +1 -0
- {ermes_cli-1.0.0 → ermes_cli-1.2.0}/ermes_cli/commands/auth_cmds.py +1 -1
- {ermes_cli-1.0.0 → ermes_cli-1.2.0}/ermes_cli/commands/projects.py +1 -1
- ermes_cli-1.2.0/ermes_cli/commands/skill.py +40 -0
- {ermes_cli-1.0.0 → ermes_cli-1.2.0}/ermes_cli/config.py +1 -1
- {ermes_cli-1.0.0 → ermes_cli-1.2.0}/ermes_cli/main.py +50 -0
- ermes_cli-1.2.0/ermes_cli/skill/SKILL.md +69 -0
- ermes_cli-1.2.0/ermes_cli/update.py +225 -0
- {ermes_cli-1.0.0 → ermes_cli-1.2.0}/ermes_cli/write_mode.py +1 -5
- {ermes_cli-1.0.0 → ermes_cli-1.2.0}/pyproject.toml +2 -7
- {ermes_cli-1.0.0 → ermes_cli-1.2.0}/scripts/install-cli.sh +1 -11
- ermes_cli-1.2.0/tests/test_skill_command.py +20 -0
- ermes_cli-1.2.0/tests/test_update.py +366 -0
- ermes_cli-1.0.0/ermes_cli/__init__.py +0 -1
- ermes_cli-1.0.0/ermes_cli/manifest.py +0 -218
- ermes_cli-1.0.0/ermes_cli/mcp_server.py +0 -167
- ermes_cli-1.0.0/ermes_cli/routes.yaml +0 -163
- ermes_cli-1.0.0/scripts/install-mcp.sh +0 -348
- ermes_cli-1.0.0/tests/test_install_mcp_script.py +0 -256
- ermes_cli-1.0.0/tests/test_install_mcp_uninstall.py +0 -168
- ermes_cli-1.0.0/tests/test_manifest.py +0 -229
- ermes_cli-1.0.0/tests/test_mcp_server.py +0 -160
- {ermes_cli-1.0.0 → ermes_cli-1.2.0}/.gitignore +0 -0
- {ermes_cli-1.0.0 → ermes_cli-1.2.0}/ermes_cli/admin_api.py +0 -0
- {ermes_cli-1.0.0 → ermes_cli-1.2.0}/ermes_cli/auth0.py +0 -0
- {ermes_cli-1.0.0 → ermes_cli-1.2.0}/ermes_cli/client.py +0 -0
- {ermes_cli-1.0.0 → ermes_cli-1.2.0}/ermes_cli/commands/__init__.py +0 -0
- {ermes_cli-1.0.0 → ermes_cli-1.2.0}/ermes_cli/commands/campaigns.py +0 -0
- {ermes_cli-1.0.0 → ermes_cli-1.2.0}/ermes_cli/commands/config_cmd.py +0 -0
- {ermes_cli-1.0.0 → ermes_cli-1.2.0}/ermes_cli/commands/data_paths.py +0 -0
- {ermes_cli-1.0.0 → ermes_cli-1.2.0}/ermes_cli/commands/export.py +0 -0
- {ermes_cli-1.0.0 → ermes_cli-1.2.0}/ermes_cli/commands/ingest.py +0 -0
- {ermes_cli-1.0.0 → ermes_cli-1.2.0}/ermes_cli/commands/journeys.py +0 -0
- {ermes_cli-1.0.0 → ermes_cli-1.2.0}/ermes_cli/commands/keys.py +0 -0
- {ermes_cli-1.0.0 → ermes_cli-1.2.0}/ermes_cli/commands/lists.py +0 -0
- {ermes_cli-1.0.0 → ermes_cli-1.2.0}/ermes_cli/commands/metrics.py +0 -0
- {ermes_cli-1.0.0 → ermes_cli-1.2.0}/ermes_cli/commands/providers.py +0 -0
- {ermes_cli-1.0.0 → ermes_cli-1.2.0}/ermes_cli/commands/subscriptions.py +0 -0
- {ermes_cli-1.0.0 → ermes_cli-1.2.0}/ermes_cli/commands/tags.py +0 -0
- {ermes_cli-1.0.0 → ermes_cli-1.2.0}/ermes_cli/commands/templates.py +0 -0
- {ermes_cli-1.0.0 → ermes_cli-1.2.0}/ermes_cli/commands/users.py +0 -0
- {ermes_cli-1.0.0 → ermes_cli-1.2.0}/ermes_cli/commands/write_cmds.py +0 -0
- {ermes_cli-1.0.0 → ermes_cli-1.2.0}/ermes_cli/errors.py +0 -0
- {ermes_cli-1.0.0 → ermes_cli-1.2.0}/ermes_cli/export_utils.py +0 -0
- {ermes_cli-1.0.0 → ermes_cli-1.2.0}/ermes_cli/keyring_store.py +0 -0
- {ermes_cli-1.0.0 → ermes_cli-1.2.0}/ermes_cli/output.py +0 -0
- {ermes_cli-1.0.0 → ermes_cli-1.2.0}/ermes_cli/rules.py +0 -0
- {ermes_cli-1.0.0 → ermes_cli-1.2.0}/tests/__init__.py +0 -0
- {ermes_cli-1.0.0 → ermes_cli-1.2.0}/tests/conftest.py +0 -0
- {ermes_cli-1.0.0 → ermes_cli-1.2.0}/tests/test_admin_api.py +0 -0
- {ermes_cli-1.0.0 → ermes_cli-1.2.0}/tests/test_auth0.py +0 -0
- {ermes_cli-1.0.0 → ermes_cli-1.2.0}/tests/test_auth_cmds.py +0 -0
- {ermes_cli-1.0.0 → ermes_cli-1.2.0}/tests/test_client.py +0 -0
- {ermes_cli-1.0.0 → ermes_cli-1.2.0}/tests/test_commands/__init__.py +0 -0
- {ermes_cli-1.0.0 → ermes_cli-1.2.0}/tests/test_commands/test_campaigns.py +0 -0
- {ermes_cli-1.0.0 → ermes_cli-1.2.0}/tests/test_commands/test_data_paths.py +0 -0
- {ermes_cli-1.0.0 → ermes_cli-1.2.0}/tests/test_commands/test_export.py +0 -0
- {ermes_cli-1.0.0 → ermes_cli-1.2.0}/tests/test_commands/test_ingest.py +0 -0
- {ermes_cli-1.0.0 → ermes_cli-1.2.0}/tests/test_commands/test_journeys.py +0 -0
- {ermes_cli-1.0.0 → ermes_cli-1.2.0}/tests/test_commands/test_keys.py +0 -0
- {ermes_cli-1.0.0 → ermes_cli-1.2.0}/tests/test_commands/test_lists.py +0 -0
- {ermes_cli-1.0.0 → ermes_cli-1.2.0}/tests/test_commands/test_metrics.py +0 -0
- {ermes_cli-1.0.0 → ermes_cli-1.2.0}/tests/test_commands/test_projects.py +0 -0
- {ermes_cli-1.0.0 → ermes_cli-1.2.0}/tests/test_commands/test_providers.py +0 -0
- {ermes_cli-1.0.0 → ermes_cli-1.2.0}/tests/test_commands/test_subscriptions.py +0 -0
- {ermes_cli-1.0.0 → ermes_cli-1.2.0}/tests/test_commands/test_tags.py +0 -0
- {ermes_cli-1.0.0 → ermes_cli-1.2.0}/tests/test_commands/test_templates.py +0 -0
- {ermes_cli-1.0.0 → ermes_cli-1.2.0}/tests/test_commands/test_users.py +0 -0
- {ermes_cli-1.0.0 → ermes_cli-1.2.0}/tests/test_config.py +0 -0
- {ermes_cli-1.0.0 → ermes_cli-1.2.0}/tests/test_export_utils.py +0 -0
- {ermes_cli-1.0.0 → ermes_cli-1.2.0}/tests/test_keyring_store.py +0 -0
- {ermes_cli-1.0.0 → ermes_cli-1.2.0}/tests/test_output.py +0 -0
- {ermes_cli-1.0.0 → ermes_cli-1.2.0}/tests/test_projects_config.py +0 -0
- {ermes_cli-1.0.0 → ermes_cli-1.2.0}/tests/test_rules.py +0 -0
- {ermes_cli-1.0.0 → ermes_cli-1.2.0}/tests/test_version.py +0 -0
- {ermes_cli-1.0.0 → ermes_cli-1.2.0}/tests/test_whoami.py +0 -0
- {ermes_cli-1.0.0 → ermes_cli-1.2.0}/tests/test_write_cmds.py +0 -0
- {ermes_cli-1.0.0 → ermes_cli-1.2.0}/tests/test_write_mode.py +0 -0
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: ermes-cli
|
|
3
|
-
Version: 1.
|
|
4
|
-
Summary: CLI-first access to Ermes marketing automation
|
|
3
|
+
Version: 1.2.0
|
|
4
|
+
Summary: CLI-first access to Ermes marketing automation with Auth0 multi-project auth.
|
|
5
5
|
Project-URL: Homepage, https://ermes.publinker.com
|
|
6
6
|
Project-URL: Repository, https://github.com/Martech-DM/ermes
|
|
7
7
|
Author: Martech-DM
|
|
@@ -14,7 +14,6 @@ Classifier: Programming Language :: Python :: 3
|
|
|
14
14
|
Classifier: Programming Language :: Python :: 3 :: Only
|
|
15
15
|
Classifier: Topic :: Communications :: Email
|
|
16
16
|
Requires-Python: >=3.10
|
|
17
|
-
Requires-Dist: fastmcp>=2.0
|
|
18
17
|
Requires-Dist: httpx>=0.28
|
|
19
18
|
Requires-Dist: keyring>=24.0
|
|
20
19
|
Requires-Dist: pyyaml>=6.0
|
|
@@ -28,13 +27,13 @@ Requires-Dist: pytest>=8.0; extra == 'dev'
|
|
|
28
27
|
Requires-Dist: responses>=0.25; extra == 'dev'
|
|
29
28
|
Description-Content-Type: text/markdown
|
|
30
29
|
|
|
31
|
-
# Ermes CLI
|
|
30
|
+
# Ermes CLI
|
|
32
31
|
|
|
33
|
-
This package gives Ermes operators a CLI
|
|
34
|
-
writes. It also retains a 10-tool read-only MCP for temporary compatibility, but normal agent
|
|
35
|
-
sessions leave that server unregistered.
|
|
32
|
+
This package gives Ermes operators a CLI surface for explicit-project API reads and guarded writes.
|
|
36
33
|
|
|
37
|
-
Version 1.0.
|
|
34
|
+
Version 1.2.0. It adds `ermes skill install` (DEV-2329). 1.1.0 removed the `ermes-mcp`
|
|
35
|
+
compatibility server that 1.0.0 shipped (DEV-2326); a machine that registered it must drop that
|
|
36
|
+
registration (e.g. `claude mcp remove ermes`).
|
|
38
37
|
|
|
39
38
|
## Install the CLI
|
|
40
39
|
|
|
@@ -55,22 +54,54 @@ Releases are cut by pushing a tag `ermes-cli-vX.Y.Z` equal to `cli/pyproject.tom
|
|
|
55
54
|
(`.github/workflows/publish-cli.yml` runs the tests, builds the wheel, publishes to PyPI and attaches
|
|
56
55
|
it to a GitHub Release). `install-cli.sh` is idempotent and safe to re-run for upgrades.
|
|
57
56
|
|
|
58
|
-
|
|
59
|
-
explicit project:
|
|
57
|
+
## Teach your AI assistant
|
|
60
58
|
|
|
61
59
|
```bash
|
|
62
|
-
|
|
60
|
+
ermes skill install # writes ~/.claude/skills/ermes/SKILL.md (Claude Code) and ~/.agents/skills/ermes/SKILL.md (Codex)
|
|
61
|
+
ermes skill path # prints the packaged SKILL.md, for any other assistant
|
|
63
62
|
```
|
|
64
63
|
|
|
65
|
-
|
|
66
|
-
|
|
64
|
+
The skill ships inside the package, so it matches the installed version. Re-run `ermes skill
|
|
65
|
+
install` after `ermes update`, then restart the assistant.
|
|
66
|
+
|
|
67
|
+
## Update the CLI
|
|
68
|
+
|
|
69
|
+
**Automatic prompt.** In an interactive terminal, the CLI checks PyPI for a newer `ermes-cli` at
|
|
70
|
+
most once every 24 hours (2-second timeout, cached in `~/.ermes/update-check.json`; failed checks
|
|
71
|
+
are cached too, so an offline laptop is not slowed down). When one exists it asks before running
|
|
72
|
+
your command:
|
|
73
|
+
|
|
74
|
+
```
|
|
75
|
+
ermes-cli 1.2.0 is available (you have 1.1.0). Update now? [Y]es / [n]ot now / [s]kip this version
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
- **Yes** (Enter): runs `pipx upgrade ermes-cli`, checks the new version in a fresh process, then
|
|
79
|
+
re-runs your command on it.
|
|
80
|
+
- **Not now**: runs your command; asks again on the next run.
|
|
81
|
+
- **Skip this version**: never asks about that version again; a newer release asks once more.
|
|
82
|
+
|
|
83
|
+
A failed upgrade or Ctrl-C at the prompt prints one line and runs your command on the current
|
|
84
|
+
version. The check never runs with `--json`, when `CI` is set, when stdin/stdout/stderr is not a
|
|
85
|
+
TTY, before `ermes version` / `ermes update`, or with `ERMES_NO_UPDATE_CHECK=1`.
|
|
86
|
+
|
|
87
|
+
**Manual.**
|
|
67
88
|
|
|
68
89
|
```bash
|
|
69
|
-
|
|
90
|
+
ermes update # check, then ask before upgrading
|
|
91
|
+
ermes update --check # report only, never install
|
|
92
|
+
ermes update --yes # upgrade without asking
|
|
93
|
+
ermes --json update [--yes] # --json installs only with --yes
|
|
70
94
|
```
|
|
71
95
|
|
|
72
|
-
|
|
73
|
-
`
|
|
96
|
+
**Status values** (`--json` `status`): `up_to_date`, `ahead`, `update_available`, `updated`,
|
|
97
|
+
`lookup_failed` (exit 1), `update_failed` (exit 1). Installer output goes to stderr; stdout stays
|
|
98
|
+
one JSON document.
|
|
99
|
+
|
|
100
|
+
**Supported install.** Only the pipx install of the PyPI package (`install-cli.sh`, or
|
|
101
|
+
`pipx install ermes-cli`) upgrades itself. `pipx upgrade` keeps the old venv if pip fails, so a
|
|
102
|
+
failed upgrade never leaves you without a CLI. Editable checkouts (`git pull`), git or local-dir
|
|
103
|
+
installs, non-pipx environments and an `ermes` that is not the venv pipx manages get the manual
|
|
104
|
+
command instead of a prompt. Keychain tokens and `~/.ermes` are untouched.
|
|
74
105
|
|
|
75
106
|
## Auth — Auth0 device login (recommended)
|
|
76
107
|
|
|
@@ -161,8 +192,8 @@ State lives in `~/.ermes/state.yml` (chmod 600), separate from `projects.yml`
|
|
|
161
192
|
so refreshing projects doesn't clobber the write window. `ermes whoami` shows
|
|
162
193
|
the current write status.
|
|
163
194
|
|
|
164
|
-
|
|
165
|
-
|
|
195
|
+
Every agent write must carry explicit project scope and one-process `ERMES_ALLOW_WRITES=1`
|
|
196
|
+
authorization.
|
|
166
197
|
|
|
167
198
|
## Metrics
|
|
168
199
|
|
|
@@ -239,39 +270,17 @@ is legible.
|
|
|
239
270
|
| `subscriptions`, `tags`, `providers`, `ingest`, `export`, `config` | … |
|
|
240
271
|
| `login`, `logout`, `whoami`, `use`, `projects` | Auth0 device-flow auth + multi-project context |
|
|
241
272
|
| `metrics` | Campaign metrics, email or push (mirrors UI Metrics view) |
|
|
273
|
+
| `skill install / path` | Install the packaged agent skill for Claude Code / Codex |
|
|
242
274
|
| `write allow / deny / status` | Control the write-mode guard (mutations blocked by default) |
|
|
243
275
|
|
|
244
276
|
Full surface: `ermes --help`.
|
|
245
277
|
|
|
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
278
|
## Where in the wider stack
|
|
269
279
|
|
|
270
280
|
Ermes is a CLI-first integration in the
|
|
271
281
|
[`Martech-DM/martech-mcps`](https://github.com/Martech-DM/martech-mcps) registry.
|
|
272
282
|
That repo's `ermes/README.md` and `ermes/skills/ermes-ops/SKILL.md` are the
|
|
273
|
-
cross-fleet setup and operating runbooks.
|
|
274
|
-
unregistered during normal operation.
|
|
283
|
+
cross-fleet setup and operating runbooks.
|
|
275
284
|
|
|
276
285
|
## CLI source history
|
|
277
286
|
|
|
@@ -1,10 +1,10 @@
|
|
|
1
|
-
# Ermes CLI
|
|
1
|
+
# Ermes CLI
|
|
2
2
|
|
|
3
|
-
This package gives Ermes operators a CLI
|
|
4
|
-
writes. It also retains a 10-tool read-only MCP for temporary compatibility, but normal agent
|
|
5
|
-
sessions leave that server unregistered.
|
|
3
|
+
This package gives Ermes operators a CLI surface for explicit-project API reads and guarded writes.
|
|
6
4
|
|
|
7
|
-
Version 1.0.
|
|
5
|
+
Version 1.2.0. It adds `ermes skill install` (DEV-2329). 1.1.0 removed the `ermes-mcp`
|
|
6
|
+
compatibility server that 1.0.0 shipped (DEV-2326); a machine that registered it must drop that
|
|
7
|
+
registration (e.g. `claude mcp remove ermes`).
|
|
8
8
|
|
|
9
9
|
## Install the CLI
|
|
10
10
|
|
|
@@ -25,22 +25,54 @@ Releases are cut by pushing a tag `ermes-cli-vX.Y.Z` equal to `cli/pyproject.tom
|
|
|
25
25
|
(`.github/workflows/publish-cli.yml` runs the tests, builds the wheel, publishes to PyPI and attaches
|
|
26
26
|
it to a GitHub Release). `install-cli.sh` is idempotent and safe to re-run for upgrades.
|
|
27
27
|
|
|
28
|
-
|
|
29
|
-
explicit project:
|
|
28
|
+
## Teach your AI assistant
|
|
30
29
|
|
|
31
30
|
```bash
|
|
32
|
-
|
|
31
|
+
ermes skill install # writes ~/.claude/skills/ermes/SKILL.md (Claude Code) and ~/.agents/skills/ermes/SKILL.md (Codex)
|
|
32
|
+
ermes skill path # prints the packaged SKILL.md, for any other assistant
|
|
33
33
|
```
|
|
34
34
|
|
|
35
|
-
|
|
36
|
-
|
|
35
|
+
The skill ships inside the package, so it matches the installed version. Re-run `ermes skill
|
|
36
|
+
install` after `ermes update`, then restart the assistant.
|
|
37
|
+
|
|
38
|
+
## Update the CLI
|
|
39
|
+
|
|
40
|
+
**Automatic prompt.** In an interactive terminal, the CLI checks PyPI for a newer `ermes-cli` at
|
|
41
|
+
most once every 24 hours (2-second timeout, cached in `~/.ermes/update-check.json`; failed checks
|
|
42
|
+
are cached too, so an offline laptop is not slowed down). When one exists it asks before running
|
|
43
|
+
your command:
|
|
44
|
+
|
|
45
|
+
```
|
|
46
|
+
ermes-cli 1.2.0 is available (you have 1.1.0). Update now? [Y]es / [n]ot now / [s]kip this version
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
- **Yes** (Enter): runs `pipx upgrade ermes-cli`, checks the new version in a fresh process, then
|
|
50
|
+
re-runs your command on it.
|
|
51
|
+
- **Not now**: runs your command; asks again on the next run.
|
|
52
|
+
- **Skip this version**: never asks about that version again; a newer release asks once more.
|
|
53
|
+
|
|
54
|
+
A failed upgrade or Ctrl-C at the prompt prints one line and runs your command on the current
|
|
55
|
+
version. The check never runs with `--json`, when `CI` is set, when stdin/stdout/stderr is not a
|
|
56
|
+
TTY, before `ermes version` / `ermes update`, or with `ERMES_NO_UPDATE_CHECK=1`.
|
|
57
|
+
|
|
58
|
+
**Manual.**
|
|
37
59
|
|
|
38
60
|
```bash
|
|
39
|
-
|
|
61
|
+
ermes update # check, then ask before upgrading
|
|
62
|
+
ermes update --check # report only, never install
|
|
63
|
+
ermes update --yes # upgrade without asking
|
|
64
|
+
ermes --json update [--yes] # --json installs only with --yes
|
|
40
65
|
```
|
|
41
66
|
|
|
42
|
-
|
|
43
|
-
`
|
|
67
|
+
**Status values** (`--json` `status`): `up_to_date`, `ahead`, `update_available`, `updated`,
|
|
68
|
+
`lookup_failed` (exit 1), `update_failed` (exit 1). Installer output goes to stderr; stdout stays
|
|
69
|
+
one JSON document.
|
|
70
|
+
|
|
71
|
+
**Supported install.** Only the pipx install of the PyPI package (`install-cli.sh`, or
|
|
72
|
+
`pipx install ermes-cli`) upgrades itself. `pipx upgrade` keeps the old venv if pip fails, so a
|
|
73
|
+
failed upgrade never leaves you without a CLI. Editable checkouts (`git pull`), git or local-dir
|
|
74
|
+
installs, non-pipx environments and an `ermes` that is not the venv pipx manages get the manual
|
|
75
|
+
command instead of a prompt. Keychain tokens and `~/.ermes` are untouched.
|
|
44
76
|
|
|
45
77
|
## Auth — Auth0 device login (recommended)
|
|
46
78
|
|
|
@@ -131,8 +163,8 @@ State lives in `~/.ermes/state.yml` (chmod 600), separate from `projects.yml`
|
|
|
131
163
|
so refreshing projects doesn't clobber the write window. `ermes whoami` shows
|
|
132
164
|
the current write status.
|
|
133
165
|
|
|
134
|
-
|
|
135
|
-
|
|
166
|
+
Every agent write must carry explicit project scope and one-process `ERMES_ALLOW_WRITES=1`
|
|
167
|
+
authorization.
|
|
136
168
|
|
|
137
169
|
## Metrics
|
|
138
170
|
|
|
@@ -209,39 +241,17 @@ is legible.
|
|
|
209
241
|
| `subscriptions`, `tags`, `providers`, `ingest`, `export`, `config` | … |
|
|
210
242
|
| `login`, `logout`, `whoami`, `use`, `projects` | Auth0 device-flow auth + multi-project context |
|
|
211
243
|
| `metrics` | Campaign metrics, email or push (mirrors UI Metrics view) |
|
|
244
|
+
| `skill install / path` | Install the packaged agent skill for Claude Code / Codex |
|
|
212
245
|
| `write allow / deny / status` | Control the write-mode guard (mutations blocked by default) |
|
|
213
246
|
|
|
214
247
|
Full surface: `ermes --help`.
|
|
215
248
|
|
|
216
|
-
### Optional compatibility MCP (`ermes-mcp`)
|
|
217
|
-
|
|
218
|
-
The 10-tool read-only MCP remains for temporary Claude Code rollback only. Normal sessions use the
|
|
219
|
-
CLI so they can pin project scope per command without loading fixed schemas at startup.
|
|
220
|
-
`install-mcp.sh --project=<name-or-id>` writes an explicit `ERMES_PROJECT`; `ermes use` doesn't
|
|
221
|
-
retarget that process.
|
|
222
|
-
|
|
223
|
-
The CLI covers every compatibility read and is the preferred path:
|
|
224
|
-
|
|
225
|
-
| Tool | Purpose |
|
|
226
|
-
|---|---|
|
|
227
|
-
| `whoami` | Current profile + base URL (no API call) |
|
|
228
|
-
| `list_campaigns(limit?, status?)` | Campaigns; use CLI `campaigns list --status` when filtering |
|
|
229
|
-
| `get_campaign(campaign_id)` | One campaign |
|
|
230
|
-
| `list_templates(limit?)` | Templates |
|
|
231
|
-
| `get_template(template_id)` | One template |
|
|
232
|
-
| `list_lists(limit?)` | Contact lists |
|
|
233
|
-
| `list_journeys(limit?)` | Automation journeys |
|
|
234
|
-
| `list_users(limit?, email?)` | Users; use CLI `users list --q` for exact search |
|
|
235
|
-
| `get_user(user_id)` | One user; the CLI argument is the external ID |
|
|
236
|
-
| `get_user_events(user_id, limit?)` | Recent events; the CLI argument is the external ID |
|
|
237
|
-
|
|
238
249
|
## Where in the wider stack
|
|
239
250
|
|
|
240
251
|
Ermes is a CLI-first integration in the
|
|
241
252
|
[`Martech-DM/martech-mcps`](https://github.com/Martech-DM/martech-mcps) registry.
|
|
242
253
|
That repo's `ermes/README.md` and `ermes/skills/ermes-ops/SKILL.md` are the
|
|
243
|
-
cross-fleet setup and operating runbooks.
|
|
244
|
-
unregistered during normal operation.
|
|
254
|
+
cross-fleet setup and operating runbooks.
|
|
245
255
|
|
|
246
256
|
## CLI source history
|
|
247
257
|
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__version__ = "1.2.0"
|
|
@@ -80,7 +80,7 @@ def _hostname() -> str:
|
|
|
80
80
|
def ensure_project_key(project_id: int) -> str:
|
|
81
81
|
"""Return the cached CLI key for `project_id`, minting it on first use.
|
|
82
82
|
|
|
83
|
-
Raises `ConfigError` (never SystemExit
|
|
83
|
+
Raises `ConfigError` (never SystemExit)
|
|
84
84
|
when there is no Auth0 session or the server refuses.
|
|
85
85
|
"""
|
|
86
86
|
payload = load_projects_file()
|
|
@@ -246,7 +246,7 @@ def use(
|
|
|
246
246
|
..., help="Project name or numeric id (from `ermes projects list`)"
|
|
247
247
|
),
|
|
248
248
|
) -> None:
|
|
249
|
-
"""Switch the active project. Affects every subsequent CLI
|
|
249
|
+
"""Switch the active project. Affects every subsequent CLI call."""
|
|
250
250
|
projects = get_projects()
|
|
251
251
|
if not projects:
|
|
252
252
|
console.print("[yellow]No cached projects.[/yellow] Run `ermes login` first.")
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
"""`ermes skill` — install the agent skill that teaches AI assistants the CLI.
|
|
2
|
+
|
|
3
|
+
The skill is a plain SKILL.md packaged with the CLI, so it always matches the
|
|
4
|
+
installed version. `install` copies it to the user-level skill folders that
|
|
5
|
+
Claude Code and Codex read; `path` prints it for any other assistant.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from __future__ import annotations
|
|
9
|
+
|
|
10
|
+
from importlib import resources
|
|
11
|
+
from pathlib import Path
|
|
12
|
+
|
|
13
|
+
import typer
|
|
14
|
+
|
|
15
|
+
skill_app = typer.Typer(help="Install the Ermes skill for Claude Code and Codex.", no_args_is_help=True)
|
|
16
|
+
|
|
17
|
+
SKILL_NAME = "ermes"
|
|
18
|
+
# User-level skill folders: Claude Code reads ~/.claude/skills, Codex reads
|
|
19
|
+
# ~/.agents/skills. Both take <name>/SKILL.md.
|
|
20
|
+
TARGETS = (Path(".claude/skills"), Path(".agents/skills"))
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def packaged_skill() -> Path:
|
|
24
|
+
return Path(str(resources.files("ermes_cli") / "skill" / "SKILL.md"))
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
@skill_app.command("install", help="Copy the skill into ~/.claude/skills and ~/.agents/skills.")
|
|
28
|
+
def install_cmd() -> None:
|
|
29
|
+
text = packaged_skill().read_text(encoding="utf-8")
|
|
30
|
+
for target in TARGETS:
|
|
31
|
+
dest = Path.home() / target / SKILL_NAME / "SKILL.md"
|
|
32
|
+
dest.parent.mkdir(parents=True, exist_ok=True)
|
|
33
|
+
dest.write_text(text, encoding="utf-8")
|
|
34
|
+
typer.echo(f"installed {dest}")
|
|
35
|
+
typer.echo("Restart Claude Code / Codex to pick it up.")
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
@skill_app.command("path", help="Print the packaged SKILL.md path (for other assistants).")
|
|
39
|
+
def path_cmd() -> None:
|
|
40
|
+
typer.echo(packaged_skill())
|
|
@@ -304,7 +304,7 @@ def get_auth0_email() -> str | None:
|
|
|
304
304
|
|
|
305
305
|
|
|
306
306
|
# ---------------------------------------------------------------------------
|
|
307
|
-
# Profile resolution — what the CLI
|
|
307
|
+
# Profile resolution — what the CLI actually consumes at runtime
|
|
308
308
|
# ---------------------------------------------------------------------------
|
|
309
309
|
|
|
310
310
|
|
|
@@ -32,11 +32,15 @@ atexit.register(_close_client)
|
|
|
32
32
|
|
|
33
33
|
@app.callback()
|
|
34
34
|
def main(
|
|
35
|
+
ctx: typer.Context,
|
|
35
36
|
profile: Optional[str] = typer.Option(None, "--profile", "-p", help="Profile name"),
|
|
36
37
|
use_json: bool = typer.Option(False, "--json", help="Output as JSON"),
|
|
37
38
|
):
|
|
38
39
|
state.profile_name = profile
|
|
39
40
|
state.use_json = use_json
|
|
41
|
+
from ermes_cli.update import maybe_prompt
|
|
42
|
+
|
|
43
|
+
maybe_prompt(use_json, ctx.invoked_subcommand)
|
|
40
44
|
|
|
41
45
|
|
|
42
46
|
def get_client() -> ErmesClient:
|
|
@@ -61,6 +65,50 @@ def version():
|
|
|
61
65
|
typer.echo(f"ermes-cli {__version__}")
|
|
62
66
|
|
|
63
67
|
|
|
68
|
+
@app.command()
|
|
69
|
+
def update(
|
|
70
|
+
check: bool = typer.Option(False, "--check", help="Only report whether a newer version exists."),
|
|
71
|
+
yes: bool = typer.Option(False, "--yes", "-y", help="Upgrade without asking."),
|
|
72
|
+
):
|
|
73
|
+
"""Check PyPI for a newer ermes-cli and upgrade the pipx install. Disable the daily prompt with ERMES_NO_UPDATE_CHECK=1."""
|
|
74
|
+
import json
|
|
75
|
+
import time
|
|
76
|
+
|
|
77
|
+
from ermes_cli import update as upd
|
|
78
|
+
|
|
79
|
+
def done(status: str, code: int = 0, **extra) -> None:
|
|
80
|
+
if state.use_json:
|
|
81
|
+
print(json.dumps({"status": status, "installed": __version__, **extra}))
|
|
82
|
+
raise typer.Exit(code=code)
|
|
83
|
+
|
|
84
|
+
try:
|
|
85
|
+
latest = upd.fetch_latest()
|
|
86
|
+
except upd.UpdateError as e:
|
|
87
|
+
typer.echo(f"Update check failed: {e}", err=True)
|
|
88
|
+
done("lookup_failed", 1, error=str(e))
|
|
89
|
+
upd.write_cache(checked_at=time.time(), latest=latest, error=None)
|
|
90
|
+
|
|
91
|
+
if not upd.is_newer(latest, __version__):
|
|
92
|
+
status = "ahead" if upd.is_newer(__version__, latest) else "up_to_date"
|
|
93
|
+
if not state.use_json:
|
|
94
|
+
typer.echo(f"ermes-cli {__version__} is {'newer than PyPI ' + latest if status == 'ahead' else 'up to date'}.")
|
|
95
|
+
done(status, latest=latest)
|
|
96
|
+
if check or (state.use_json and not yes):
|
|
97
|
+
if not state.use_json:
|
|
98
|
+
typer.echo(f"ermes-cli {latest} is available (you have {__version__}). Run `ermes update` to install.")
|
|
99
|
+
done("update_available", latest=latest)
|
|
100
|
+
if not yes and not typer.confirm(f"Upgrade ermes-cli {__version__} -> {latest}?", default=True, err=True):
|
|
101
|
+
done("update_available", latest=latest)
|
|
102
|
+
try:
|
|
103
|
+
installed = upd.upgrade(upd.detect_install(), latest)
|
|
104
|
+
except upd.UpdateError as e:
|
|
105
|
+
typer.echo(f"Update failed: {e}", err=True)
|
|
106
|
+
done("update_failed", 1, latest=latest, error=str(e))
|
|
107
|
+
if not state.use_json:
|
|
108
|
+
typer.echo(f"Updated ermes-cli to {installed}.")
|
|
109
|
+
done("updated", latest=latest)
|
|
110
|
+
|
|
111
|
+
|
|
64
112
|
@app.command()
|
|
65
113
|
def whoami(
|
|
66
114
|
check: bool = typer.Option(
|
|
@@ -171,6 +219,7 @@ from ermes_cli.commands.auth_cmds import login as _login, logout as _logout
|
|
|
171
219
|
from ermes_cli.commands.projects import projects_app, use as _use
|
|
172
220
|
from ermes_cli.commands.write_cmds import write_app
|
|
173
221
|
from ermes_cli.commands.metrics import metrics_app
|
|
222
|
+
from ermes_cli.commands.skill import skill_app
|
|
174
223
|
|
|
175
224
|
app.add_typer(campaigns_app, name="campaigns")
|
|
176
225
|
app.add_typer(templates_app, name="templates")
|
|
@@ -188,6 +237,7 @@ app.add_typer(export_app, name="export")
|
|
|
188
237
|
app.add_typer(projects_app, name="projects")
|
|
189
238
|
app.add_typer(write_app, name="write")
|
|
190
239
|
app.add_typer(metrics_app, name="metrics")
|
|
240
|
+
app.add_typer(skill_app, name="skill")
|
|
191
241
|
app.command(name="login", help="Auth0 device login + auto-discover projects")(_login)
|
|
192
242
|
app.command(name="logout", help="Forget Auth0 tokens + cached projects")(_logout)
|
|
193
243
|
app.command(name="use", help="Switch active project (by name or id)")(_use)
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: ermes
|
|
3
|
+
description: Read and manage Ermes marketing-automation data (campaigns, journeys, lists, templates, users, events, metrics, subscriptions) with the `ermes` CLI. Use when the user asks about their Ermes projects, campaigns, email metrics, contacts or lists, or mentions ermes.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Ermes CLI
|
|
7
|
+
|
|
8
|
+
The `ermes` command talks to the Ermes API for one project at a time. It only
|
|
9
|
+
sees the projects the logged-in user has access to.
|
|
10
|
+
|
|
11
|
+
## Before anything
|
|
12
|
+
|
|
13
|
+
1. `ermes whoami` shows the selected project (it does not prove the login is
|
|
14
|
+
still valid). If there is no login, ask the user to run `ermes login`
|
|
15
|
+
themselves (it opens a browser to confirm a code). Never run `ermes login`
|
|
16
|
+
for them, and never print API keys or the contents of `~/.ermes`.
|
|
17
|
+
2. `ermes projects list` to see the projects. Name the project on every
|
|
18
|
+
command with `ERMES_PROJECT=<name-or-id>` in front of it. Do not run
|
|
19
|
+
`ermes use` to switch projects: it changes the user's shared default.
|
|
20
|
+
|
|
21
|
+
```bash
|
|
22
|
+
ERMES_PROJECT=<project> ermes --json campaigns list --limit 5
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
## Find the right command
|
|
26
|
+
|
|
27
|
+
Every group and command has `--help`; read it instead of guessing flags.
|
|
28
|
+
|
|
29
|
+
```bash
|
|
30
|
+
ermes --help
|
|
31
|
+
ermes campaigns --help
|
|
32
|
+
ermes metrics --help
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
`--json` is a root option: `ermes --json campaigns list`, not
|
|
36
|
+
`ermes campaigns list --json`. Bound list reads with `--limit`.
|
|
37
|
+
|
|
38
|
+
## Common reads
|
|
39
|
+
|
|
40
|
+
```bash
|
|
41
|
+
ERMES_PROJECT=<project> ermes --json campaigns get <id>
|
|
42
|
+
ERMES_PROJECT=<project> ermes --json journeys list --limit 20
|
|
43
|
+
ERMES_PROJECT=<project> ermes --json users get <external-id>
|
|
44
|
+
ERMES_PROJECT=<project> ermes --json users events <external-id> --limit 20
|
|
45
|
+
ERMES_PROJECT=<project> ermes --json metrics --help
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
User commands take the user's `external_id`, not an email address.
|
|
49
|
+
|
|
50
|
+
## Changes (writes)
|
|
51
|
+
|
|
52
|
+
Commands that create, change or delete data are blocked by default. Before a write:
|
|
53
|
+
|
|
54
|
+
1. Read the current value first, then tell the user exactly what will change
|
|
55
|
+
and get a clear yes for that exact command.
|
|
56
|
+
2. Run that one command with `ERMES_ALLOW_WRITES=1` and an explicit project:
|
|
57
|
+
`ERMES_PROJECT=<project> ERMES_ALLOW_WRITES=1 ermes --json campaigns update <id> ...`
|
|
58
|
+
3. Never `export ERMES_ALLOW_WRITES` and never run `ermes write allow`;
|
|
59
|
+
nothing should stay unlocked afterwards.
|
|
60
|
+
4. Read the value again and show the user the result. If a write times out,
|
|
61
|
+
read before retrying; it may have gone through.
|
|
62
|
+
|
|
63
|
+
## Errors
|
|
64
|
+
|
|
65
|
+
- `401` / no login: the user runs `ermes login`.
|
|
66
|
+
- `403` or a missing project: the user has no access to it, or runs
|
|
67
|
+
`ermes projects refresh` after being added. Do not retry on other projects.
|
|
68
|
+
- A newer version is announced: tell the user to run `ermes update`, then
|
|
69
|
+
`ermes skill install` to refresh this skill.
|