ermes-cli 1.1.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.
Files changed (72) hide show
  1. {ermes_cli-1.1.0 → ermes_cli-1.2.0}/PKG-INFO +15 -3
  2. {ermes_cli-1.1.0 → ermes_cli-1.2.0}/README.md +14 -2
  3. ermes_cli-1.2.0/ermes_cli/__init__.py +1 -0
  4. ermes_cli-1.2.0/ermes_cli/commands/skill.py +40 -0
  5. {ermes_cli-1.1.0 → ermes_cli-1.2.0}/ermes_cli/main.py +2 -0
  6. ermes_cli-1.2.0/ermes_cli/skill/SKILL.md +69 -0
  7. {ermes_cli-1.1.0 → ermes_cli-1.2.0}/pyproject.toml +1 -1
  8. ermes_cli-1.2.0/tests/test_skill_command.py +20 -0
  9. ermes_cli-1.1.0/ermes_cli/__init__.py +0 -1
  10. {ermes_cli-1.1.0 → ermes_cli-1.2.0}/.gitignore +0 -0
  11. {ermes_cli-1.1.0 → ermes_cli-1.2.0}/ermes_cli/admin_api.py +0 -0
  12. {ermes_cli-1.1.0 → ermes_cli-1.2.0}/ermes_cli/auth0.py +0 -0
  13. {ermes_cli-1.1.0 → ermes_cli-1.2.0}/ermes_cli/client.py +0 -0
  14. {ermes_cli-1.1.0 → ermes_cli-1.2.0}/ermes_cli/commands/__init__.py +0 -0
  15. {ermes_cli-1.1.0 → ermes_cli-1.2.0}/ermes_cli/commands/auth_cmds.py +0 -0
  16. {ermes_cli-1.1.0 → ermes_cli-1.2.0}/ermes_cli/commands/campaigns.py +0 -0
  17. {ermes_cli-1.1.0 → ermes_cli-1.2.0}/ermes_cli/commands/config_cmd.py +0 -0
  18. {ermes_cli-1.1.0 → ermes_cli-1.2.0}/ermes_cli/commands/data_paths.py +0 -0
  19. {ermes_cli-1.1.0 → ermes_cli-1.2.0}/ermes_cli/commands/export.py +0 -0
  20. {ermes_cli-1.1.0 → ermes_cli-1.2.0}/ermes_cli/commands/ingest.py +0 -0
  21. {ermes_cli-1.1.0 → ermes_cli-1.2.0}/ermes_cli/commands/journeys.py +0 -0
  22. {ermes_cli-1.1.0 → ermes_cli-1.2.0}/ermes_cli/commands/keys.py +0 -0
  23. {ermes_cli-1.1.0 → ermes_cli-1.2.0}/ermes_cli/commands/lists.py +0 -0
  24. {ermes_cli-1.1.0 → ermes_cli-1.2.0}/ermes_cli/commands/metrics.py +0 -0
  25. {ermes_cli-1.1.0 → ermes_cli-1.2.0}/ermes_cli/commands/projects.py +0 -0
  26. {ermes_cli-1.1.0 → ermes_cli-1.2.0}/ermes_cli/commands/providers.py +0 -0
  27. {ermes_cli-1.1.0 → ermes_cli-1.2.0}/ermes_cli/commands/subscriptions.py +0 -0
  28. {ermes_cli-1.1.0 → ermes_cli-1.2.0}/ermes_cli/commands/tags.py +0 -0
  29. {ermes_cli-1.1.0 → ermes_cli-1.2.0}/ermes_cli/commands/templates.py +0 -0
  30. {ermes_cli-1.1.0 → ermes_cli-1.2.0}/ermes_cli/commands/users.py +0 -0
  31. {ermes_cli-1.1.0 → ermes_cli-1.2.0}/ermes_cli/commands/write_cmds.py +0 -0
  32. {ermes_cli-1.1.0 → ermes_cli-1.2.0}/ermes_cli/config.py +0 -0
  33. {ermes_cli-1.1.0 → ermes_cli-1.2.0}/ermes_cli/errors.py +0 -0
  34. {ermes_cli-1.1.0 → ermes_cli-1.2.0}/ermes_cli/export_utils.py +0 -0
  35. {ermes_cli-1.1.0 → ermes_cli-1.2.0}/ermes_cli/keyring_store.py +0 -0
  36. {ermes_cli-1.1.0 → ermes_cli-1.2.0}/ermes_cli/output.py +0 -0
  37. {ermes_cli-1.1.0 → ermes_cli-1.2.0}/ermes_cli/rules.py +0 -0
  38. {ermes_cli-1.1.0 → ermes_cli-1.2.0}/ermes_cli/update.py +0 -0
  39. {ermes_cli-1.1.0 → ermes_cli-1.2.0}/ermes_cli/write_mode.py +0 -0
  40. {ermes_cli-1.1.0 → ermes_cli-1.2.0}/scripts/install-cli.sh +0 -0
  41. {ermes_cli-1.1.0 → ermes_cli-1.2.0}/tests/__init__.py +0 -0
  42. {ermes_cli-1.1.0 → ermes_cli-1.2.0}/tests/conftest.py +0 -0
  43. {ermes_cli-1.1.0 → ermes_cli-1.2.0}/tests/test_admin_api.py +0 -0
  44. {ermes_cli-1.1.0 → ermes_cli-1.2.0}/tests/test_auth0.py +0 -0
  45. {ermes_cli-1.1.0 → ermes_cli-1.2.0}/tests/test_auth_cmds.py +0 -0
  46. {ermes_cli-1.1.0 → ermes_cli-1.2.0}/tests/test_client.py +0 -0
  47. {ermes_cli-1.1.0 → ermes_cli-1.2.0}/tests/test_commands/__init__.py +0 -0
  48. {ermes_cli-1.1.0 → ermes_cli-1.2.0}/tests/test_commands/test_campaigns.py +0 -0
  49. {ermes_cli-1.1.0 → ermes_cli-1.2.0}/tests/test_commands/test_data_paths.py +0 -0
  50. {ermes_cli-1.1.0 → ermes_cli-1.2.0}/tests/test_commands/test_export.py +0 -0
  51. {ermes_cli-1.1.0 → ermes_cli-1.2.0}/tests/test_commands/test_ingest.py +0 -0
  52. {ermes_cli-1.1.0 → ermes_cli-1.2.0}/tests/test_commands/test_journeys.py +0 -0
  53. {ermes_cli-1.1.0 → ermes_cli-1.2.0}/tests/test_commands/test_keys.py +0 -0
  54. {ermes_cli-1.1.0 → ermes_cli-1.2.0}/tests/test_commands/test_lists.py +0 -0
  55. {ermes_cli-1.1.0 → ermes_cli-1.2.0}/tests/test_commands/test_metrics.py +0 -0
  56. {ermes_cli-1.1.0 → ermes_cli-1.2.0}/tests/test_commands/test_projects.py +0 -0
  57. {ermes_cli-1.1.0 → ermes_cli-1.2.0}/tests/test_commands/test_providers.py +0 -0
  58. {ermes_cli-1.1.0 → ermes_cli-1.2.0}/tests/test_commands/test_subscriptions.py +0 -0
  59. {ermes_cli-1.1.0 → ermes_cli-1.2.0}/tests/test_commands/test_tags.py +0 -0
  60. {ermes_cli-1.1.0 → ermes_cli-1.2.0}/tests/test_commands/test_templates.py +0 -0
  61. {ermes_cli-1.1.0 → ermes_cli-1.2.0}/tests/test_commands/test_users.py +0 -0
  62. {ermes_cli-1.1.0 → ermes_cli-1.2.0}/tests/test_config.py +0 -0
  63. {ermes_cli-1.1.0 → ermes_cli-1.2.0}/tests/test_export_utils.py +0 -0
  64. {ermes_cli-1.1.0 → ermes_cli-1.2.0}/tests/test_keyring_store.py +0 -0
  65. {ermes_cli-1.1.0 → ermes_cli-1.2.0}/tests/test_output.py +0 -0
  66. {ermes_cli-1.1.0 → ermes_cli-1.2.0}/tests/test_projects_config.py +0 -0
  67. {ermes_cli-1.1.0 → ermes_cli-1.2.0}/tests/test_rules.py +0 -0
  68. {ermes_cli-1.1.0 → ermes_cli-1.2.0}/tests/test_update.py +0 -0
  69. {ermes_cli-1.1.0 → ermes_cli-1.2.0}/tests/test_version.py +0 -0
  70. {ermes_cli-1.1.0 → ermes_cli-1.2.0}/tests/test_whoami.py +0 -0
  71. {ermes_cli-1.1.0 → ermes_cli-1.2.0}/tests/test_write_cmds.py +0 -0
  72. {ermes_cli-1.1.0 → ermes_cli-1.2.0}/tests/test_write_mode.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: ermes-cli
3
- Version: 1.1.0
3
+ Version: 1.2.0
4
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
@@ -31,8 +31,9 @@ Description-Content-Type: text/markdown
31
31
 
32
32
  This package gives Ermes operators a CLI surface for explicit-project API reads and guarded writes.
33
33
 
34
- Version 1.1.0. It removed the `ermes-mcp` compatibility server that 1.0.0 shipped (DEV-2326); a
35
- machine that registered it must drop that registration (e.g. `claude mcp remove ermes`).
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`).
36
37
 
37
38
  ## Install the CLI
38
39
 
@@ -53,6 +54,16 @@ Releases are cut by pushing a tag `ermes-cli-vX.Y.Z` equal to `cli/pyproject.tom
53
54
  (`.github/workflows/publish-cli.yml` runs the tests, builds the wheel, publishes to PyPI and attaches
54
55
  it to a GitHub Release). `install-cli.sh` is idempotent and safe to re-run for upgrades.
55
56
 
57
+ ## Teach your AI assistant
58
+
59
+ ```bash
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
62
+ ```
63
+
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
+
56
67
  ## Update the CLI
57
68
 
58
69
  **Automatic prompt.** In an interactive terminal, the CLI checks PyPI for a newer `ermes-cli` at
@@ -259,6 +270,7 @@ is legible.
259
270
  | `subscriptions`, `tags`, `providers`, `ingest`, `export`, `config` | … |
260
271
  | `login`, `logout`, `whoami`, `use`, `projects` | Auth0 device-flow auth + multi-project context |
261
272
  | `metrics` | Campaign metrics, email or push (mirrors UI Metrics view) |
273
+ | `skill install / path` | Install the packaged agent skill for Claude Code / Codex |
262
274
  | `write allow / deny / status` | Control the write-mode guard (mutations blocked by default) |
263
275
 
264
276
  Full surface: `ermes --help`.
@@ -2,8 +2,9 @@
2
2
 
3
3
  This package gives Ermes operators a CLI surface for explicit-project API reads and guarded writes.
4
4
 
5
- Version 1.1.0. It removed the `ermes-mcp` compatibility server that 1.0.0 shipped (DEV-2326); a
6
- machine that registered it must drop that registration (e.g. `claude mcp remove ermes`).
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`).
7
8
 
8
9
  ## Install the CLI
9
10
 
@@ -24,6 +25,16 @@ Releases are cut by pushing a tag `ermes-cli-vX.Y.Z` equal to `cli/pyproject.tom
24
25
  (`.github/workflows/publish-cli.yml` runs the tests, builds the wheel, publishes to PyPI and attaches
25
26
  it to a GitHub Release). `install-cli.sh` is idempotent and safe to re-run for upgrades.
26
27
 
28
+ ## Teach your AI assistant
29
+
30
+ ```bash
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
+ ```
34
+
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
+
27
38
  ## Update the CLI
28
39
 
29
40
  **Automatic prompt.** In an interactive terminal, the CLI checks PyPI for a newer `ermes-cli` at
@@ -230,6 +241,7 @@ is legible.
230
241
  | `subscriptions`, `tags`, `providers`, `ingest`, `export`, `config` | … |
231
242
  | `login`, `logout`, `whoami`, `use`, `projects` | Auth0 device-flow auth + multi-project context |
232
243
  | `metrics` | Campaign metrics, email or push (mirrors UI Metrics view) |
244
+ | `skill install / path` | Install the packaged agent skill for Claude Code / Codex |
233
245
  | `write allow / deny / status` | Control the write-mode guard (mutations blocked by default) |
234
246
 
235
247
  Full surface: `ermes --help`.
@@ -0,0 +1 @@
1
+ __version__ = "1.2.0"
@@ -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())
@@ -219,6 +219,7 @@ from ermes_cli.commands.auth_cmds import login as _login, logout as _logout
219
219
  from ermes_cli.commands.projects import projects_app, use as _use
220
220
  from ermes_cli.commands.write_cmds import write_app
221
221
  from ermes_cli.commands.metrics import metrics_app
222
+ from ermes_cli.commands.skill import skill_app
222
223
 
223
224
  app.add_typer(campaigns_app, name="campaigns")
224
225
  app.add_typer(templates_app, name="templates")
@@ -236,6 +237,7 @@ app.add_typer(export_app, name="export")
236
237
  app.add_typer(projects_app, name="projects")
237
238
  app.add_typer(write_app, name="write")
238
239
  app.add_typer(metrics_app, name="metrics")
240
+ app.add_typer(skill_app, name="skill")
239
241
  app.command(name="login", help="Auth0 device login + auto-discover projects")(_login)
240
242
  app.command(name="logout", help="Forget Auth0 tokens + cached projects")(_logout)
241
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.
@@ -5,7 +5,7 @@ build-backend = "hatchling.build"
5
5
  [project]
6
6
  name = "ermes-cli"
7
7
  # Keep in lockstep with ermes_cli/__init__.py::__version__ — tests/test_version.py fails if they drift.
8
- version = "1.1.0"
8
+ version = "1.2.0"
9
9
  description = "CLI-first access to Ermes marketing automation with Auth0 multi-project auth."
10
10
  readme = "README.md"
11
11
  requires-python = ">=3.10"
@@ -0,0 +1,20 @@
1
+ """Tests for `ermes skill install|path`."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from pathlib import Path
6
+
7
+
8
+ def test_install_writes_the_packaged_skill_for_claude_and_codex(invoke, tmp_path, monkeypatch) -> None:
9
+ monkeypatch.setenv("HOME", str(tmp_path))
10
+ result = invoke(["skill", "install"])
11
+ assert result.exit_code == 0, result.output
12
+ for root in (".claude/skills", ".agents/skills"):
13
+ skill = (tmp_path / root / "ermes" / "SKILL.md").read_text()
14
+ assert skill.startswith("---\nname: ermes\n")
15
+
16
+
17
+ def test_path_prints_an_existing_skill_file(invoke) -> None:
18
+ result = invoke(["skill", "path"])
19
+ assert result.exit_code == 0, result.output
20
+ assert Path(result.output.strip()).is_file()
@@ -1 +0,0 @@
1
- __version__ = "1.1.0"
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes