feat(skill): add emcli Agent Skill (agentskills.io standard)
skills/emcli/ — an Agent Skill teaching an agent to read and send mail through
emcli's JSON agent commands:
- SKILL.md: name/description (what + when + trigger keywords), compatibility,
metadata; body covers the security model (agent-only commands, never touch
EMCLI_KEY), setup, the list→get→ack workflow, sending, and enforcement
awareness. Frontmatter validated against the spec (name matches dir; desc
574/1024; compatibility 239/500); body 146 lines (<500).
- scripts/install.sh: detects OS/arch, downloads the release binary, verifies
the sha256 checksum when present, fails gracefully. Release tag/assets
(v0.4.0, emcli_<ver>_<os>_<arch>) are placeholders until the first release.
- references/{commands.md,install.md}: full agent command reference (flags, JSON
shapes, error codes, enforcement) and install options, loaded on demand.
README links to the skill.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,146 @@
|
||||
---
|
||||
name: emcli
|
||||
description: Read and send email through emcli, a guard-railed CLI gateway that mediates IMAP/SMTP so the agent never holds mail credentials. Use when a task involves checking or reading an inbox, listing or searching messages, fetching a message or its attachments, marking mail as processed, or sending or replying to email on the user's behalf. Works with Gmail and any IMAP/SMTP account; every command prints a single JSON object. Triggers include: check my email, read the inbox, list/search messages, get or download a message, reply to that email, send an email, process new mail.
|
||||
compatibility: Requires the emcli binary (run scripts/install.sh to fetch it; needs curl or wget and a Linux/macOS/Windows shell) and the EMCLI_KEY environment variable, which the orchestrator provides. Needs network access to the configured mail server.
|
||||
metadata:
|
||||
author: steve
|
||||
version: "0.4.0"
|
||||
homepage: "https://gitea.dcglab.co.uk/steve/emcli"
|
||||
---
|
||||
|
||||
# emcli — email for agents
|
||||
|
||||
`emcli` is a command-line gateway for email. You (the agent) call its **agent commands** to read
|
||||
and send mail; the program holds the credentials and enforces the user's rules, so you never see a
|
||||
password and cannot bypass the limits. Every agent command prints exactly **one line of JSON** and
|
||||
sets its exit code to match.
|
||||
|
||||
## Security model — read this first
|
||||
|
||||
- **You only run agent commands:** `list`, `get`, `search`, `ack`, `send`. Account setup,
|
||||
passwords, whitelists, and config are the **user's** job (admin commands) — do not run or suggest
|
||||
running `account`, `whitelist`, `config`, `init`, or `doctor` unless the user explicitly asks you
|
||||
to help administer.
|
||||
- **Never touch the secret key.** `EMCLI_KEY` is supplied in the environment by whoever launched
|
||||
you. Do not read it, print it, log it, pass it as an argument, or try to generate one. If it is
|
||||
missing, stop and tell the user (see Setup).
|
||||
- **Some mail is intentionally invisible.** The user may restrict which senders you can see and who
|
||||
you can email. Blocked or filtered results are normal — handle them, don't try to work around
|
||||
them (see Enforcement).
|
||||
|
||||
## Setup (do this once per session, before the first command)
|
||||
|
||||
1. **Check the binary is available.** Run `emcli version`. If the command is not found, install it:
|
||||
|
||||
```bash
|
||||
bash scripts/install.sh
|
||||
```
|
||||
|
||||
This downloads the binary from the project's releases and puts it on your PATH
|
||||
(`~/.local/bin` by default). See [references/install.md](references/install.md) for options.
|
||||
|
||||
2. **Check the key is present.** Confirm the `EMCLI_KEY` environment variable is set (e.g.
|
||||
`test -n "$EMCLI_KEY"`). **Do not print its value.** If it is empty, do not proceed — tell the
|
||||
user: "emcli needs the EMCLI_KEY environment variable set by your orchestrator; I can't read or
|
||||
create it for you."
|
||||
|
||||
3. **Find out which account(s) exist.** Ask the user for the account name (e.g. `gmail`, `work`),
|
||||
or, if permitted, run `emcli doctor` once to see configured accounts and that they connect.
|
||||
|
||||
## How to read every result
|
||||
|
||||
Each command prints one JSON object:
|
||||
|
||||
```json
|
||||
{ "error": false, "error_detail": {}, "data": { } }
|
||||
```
|
||||
|
||||
Always check `error` first.
|
||||
- `error: false` → use `data`.
|
||||
- `error: true` → read `error_detail.code` and `error_detail.message`. The exit code is also
|
||||
non-zero.
|
||||
|
||||
With `jq`:
|
||||
|
||||
```bash
|
||||
out=$(emcli list --account gmail --new) || true
|
||||
echo "$out" | jq -e '.error == false' >/dev/null && echo "$out" | jq '.data.messages'
|
||||
```
|
||||
|
||||
Error codes you may get back: `config`, `db`, `network`, `auth`, `policy`, `not_found`, `usage`.
|
||||
A `policy` error means the user's rules blocked the action; `not_found` is returned both for
|
||||
missing mail **and** for mail you are not allowed to see — treat them the same.
|
||||
|
||||
## The core workflow: process new mail
|
||||
|
||||
```bash
|
||||
ACC=gmail # the account name the user gave you
|
||||
|
||||
# 1. See what's new (unprocessed). Headers only — cheap.
|
||||
emcli list --account "$ACC" --new --limit 20
|
||||
|
||||
# 2. For a message of interest, fetch the full body + attachments.
|
||||
emcli get --account "$ACC" --uid 70314
|
||||
|
||||
# 3. Do the work (summarize, extract, draft a reply, etc.).
|
||||
|
||||
# 4. Mark it processed so it stops showing under --new. This is deliberate — do it
|
||||
# only when you've actually handled the message.
|
||||
emcli ack --account "$ACC" --uid-list 70314
|
||||
```
|
||||
|
||||
Acking is the **only** command that changes state; `list`/`get`/`search` never do. Ack is safe to
|
||||
repeat and order-independent (you can ack several UIDs: `--uid-list 70314,70315,70320`).
|
||||
|
||||
## Sending mail
|
||||
|
||||
```bash
|
||||
emcli send --account "$ACC" \
|
||||
--to alice@example.com --subject "Hello" --body "Plain-text body."
|
||||
|
||||
# multiple recipients (repeat or comma-separate), cc/bcc, attachments:
|
||||
emcli send --account "$ACC" --to a@x.com --to b@x.com --cc boss@x.com \
|
||||
--subject "Report" --body "see attached" --attach ./report.pdf
|
||||
|
||||
# reply that threads correctly off a message you can see:
|
||||
emcli send --account "$ACC" --to a@x.com --subject "Re: Hi" --body "thanks" \
|
||||
--reply-to 70314 --folder INBOX
|
||||
```
|
||||
|
||||
Sending only works on read-write accounts. If you get `policy` / `ro_mode`, the account is
|
||||
read-only — tell the user; do not attempt another account without their say-so.
|
||||
|
||||
## Command quick reference
|
||||
|
||||
| Command | Purpose |
|
||||
|---|---|
|
||||
| `emcli list --account A [--folder F] [--new] [--limit N] [--before U] [--since U]` | Message headers, newest first |
|
||||
| `emcli get --account A [--folder F] --uid U` | One full message (body + attachments) |
|
||||
| `emcli search --account A [--folder F] [--from X] [--subject-contains X] [--text X] [--since-date D] [--before-date D]` | Server-side search |
|
||||
| `emcli ack --account A [--folder F] --uid-list U1,U2` | Mark message(s) processed |
|
||||
| `emcli send --account A --to X [--cc X] [--bcc X] --subject S --body B [--attach P]… [--reply-to U]` | Send / reply |
|
||||
|
||||
Defaults: `--folder INBOX`, `--limit 50` (max 500). Dates are RFC 3339 (e.g.
|
||||
`2026-06-01T00:00:00Z`). UIDs come from `list`/`search` output.
|
||||
|
||||
**Full reference** (every flag, exact JSON shapes for each command, attachment encoding, error
|
||||
codes, and the enforcement rules): [references/commands.md](references/commands.md).
|
||||
|
||||
## Enforcement awareness — work *with* the rules
|
||||
|
||||
The user configures these; you cannot change them and shouldn't try.
|
||||
- **Read-only (RO) accounts** reject `send` (`policy` / `ro_mode`).
|
||||
- **Inbound whitelist / subject filter:** mail from disallowed senders (or non-matching subjects)
|
||||
is invisible — it won't appear in `list`/`search`, and `get`/`ack` on it return `not_found`.
|
||||
- **Outbound whitelist:** if any recipient isn't allowed, the **whole** send is blocked
|
||||
(`policy` / `whitelist_out`) — nothing is sent. Don't retry by dropping recipients silently;
|
||||
surface it to the user.
|
||||
|
||||
## Do / Don't
|
||||
|
||||
- ✅ Check `error` on every call; report `policy`/`not_found`/`auth` outcomes plainly to the user.
|
||||
- ✅ `get` to read, then `ack` only after you've truly processed a message.
|
||||
- ✅ Ask the user for the account name; keep bodies plain text.
|
||||
- ❌ Don't read, print, or invent `EMCLI_KEY` or any password.
|
||||
- ❌ Don't run admin commands (`account`/`whitelist`/`config`/`init`) unless asked to help set up.
|
||||
- ❌ Don't treat a blocked send or filtered message as a bug to route around — it's the user's policy.
|
||||
@@ -0,0 +1,172 @@
|
||||
# emcli agent command reference
|
||||
|
||||
The five agent commands you may use. Each prints **one** JSON object to stdout and sets a matching
|
||||
exit code (0 success, non-zero error). All take `--account <name>`; most take `--folder` (default
|
||||
`INBOX`).
|
||||
|
||||
## The JSON envelope
|
||||
|
||||
```json
|
||||
{ "error": false, "error_detail": {}, "data": { } }
|
||||
```
|
||||
|
||||
- `error` — boolean. Check it first.
|
||||
- `error_detail` — `{}` on success; `{ "code": "...", "message": "..." }` on failure.
|
||||
- `data` — command-specific payload (below).
|
||||
|
||||
**Error codes:** `config` (key/config), `db`, `network`, `auth`, `policy` (blocked by a rule),
|
||||
`not_found` (missing **or** filtered/invisible mail), `usage` (bad/missing flag).
|
||||
|
||||
---
|
||||
|
||||
## `list` — headers, newest first
|
||||
|
||||
```
|
||||
emcli list --account A [--folder F] [--new] [--limit N] [--before U] [--since U]
|
||||
```
|
||||
|
||||
| Flag | Default | Meaning |
|
||||
|---|---|---|
|
||||
| `--folder` | `INBOX` | Mailbox |
|
||||
| `--new` | off | Only messages not yet `ack`ed |
|
||||
| `--limit` | `50` | Max results (capped at 500) |
|
||||
| `--before <uid>` | — | Only UIDs lower than this (page to older mail) |
|
||||
| `--since <uid>` | — | Only UIDs higher than this (page to newer mail) |
|
||||
|
||||
`data`:
|
||||
```json
|
||||
{ "messages": [
|
||||
{ "uid": 70314,
|
||||
"from": "\"Boss\" <boss@example.com>",
|
||||
"to": "<you@example.com>",
|
||||
"subject": "Hello",
|
||||
"date": "Mon, 22 Jun 2026 17:00:30 +0000",
|
||||
"message_id": "abc@example.com",
|
||||
"has_attachments": false } ] }
|
||||
```
|
||||
|
||||
Headers only — no body is downloaded. `data.messages` is `[]` when nothing matches.
|
||||
|
||||
---
|
||||
|
||||
## `get` — one full message
|
||||
|
||||
```
|
||||
emcli get --account A [--folder F] --uid U
|
||||
```
|
||||
|
||||
`data`:
|
||||
```json
|
||||
{ "uid": 70314,
|
||||
"from": "\"Boss\" <boss@example.com>",
|
||||
"to": "<you@example.com>",
|
||||
"subject": "Hello",
|
||||
"date": "Mon, 22 Jun 2026 17:00:30 +0000",
|
||||
"message_id": "abc@example.com",
|
||||
"body_text": "the decoded plain-text body…",
|
||||
"attachments": [
|
||||
{ "name": "report.pdf", "size": 20480, "mime": "application/pdf", "content_b64": "JVBERi0…" } ] }
|
||||
```
|
||||
|
||||
- `body_text` is the decoded plain-text part.
|
||||
- Each attachment's bytes are base64 in `content_b64`; decode to recover the file
|
||||
(`echo "$b64" | base64 -d > report.pdf`). `size` is the decoded byte length.
|
||||
- `get` does **not** acknowledge the message.
|
||||
- A filtered/invisible or missing UID returns `error: true`, code `not_found`.
|
||||
|
||||
---
|
||||
|
||||
## `search` — server-side search (whole folder)
|
||||
|
||||
```
|
||||
emcli search --account A [--folder F] [--from X] [--subject-contains X] [--text X] \
|
||||
[--since-date D] [--before-date D] [--limit N]
|
||||
```
|
||||
|
||||
| Flag | Meaning |
|
||||
|---|---|
|
||||
| `--from` | Sender contains |
|
||||
| `--subject-contains` | Subject contains |
|
||||
| `--text` | Full-text |
|
||||
| `--since-date` / `--before-date` | RFC 3339 bounds, e.g. `2026-06-01T00:00:00Z` |
|
||||
| `--limit` | Max results (default 50) |
|
||||
|
||||
`data` shape is identical to `list` (`{ "messages": [ … ] }`). Searches the whole folder regardless
|
||||
of new/acked state. Filtered mail never appears.
|
||||
|
||||
---
|
||||
|
||||
## `ack` — mark message(s) processed
|
||||
|
||||
```
|
||||
emcli ack --account A [--folder F] --uid-list U1,U2,U3
|
||||
```
|
||||
|
||||
`data`:
|
||||
```json
|
||||
{ "acked": [70314, 70315, 70320] }
|
||||
```
|
||||
|
||||
- The **only** command that changes state. Call it after you've actually handled a message.
|
||||
- Idempotent and order-independent; batch multiple UIDs comma-separated.
|
||||
- After ack, those UIDs no longer appear under `list --new`.
|
||||
- You cannot ack a message you aren't allowed to see — returns `not_found`.
|
||||
|
||||
---
|
||||
|
||||
## `send` — send or reply (RW accounts only)
|
||||
|
||||
```
|
||||
emcli send --account A --to X [--cc X] [--bcc X] --subject S --body B \
|
||||
[--attach P]… [--reply-to U [--folder F]]
|
||||
```
|
||||
|
||||
| Flag | Meaning |
|
||||
|---|---|
|
||||
| `--to` / `--cc` / `--bcc` | Recipients — repeat the flag or comma-separate (`--to a@x,b@x`) |
|
||||
| `--subject` | Subject |
|
||||
| `--body` | Plain-text body |
|
||||
| `--attach` | File path to attach (repeatable) |
|
||||
| `--reply-to <uid>` | Thread the reply onto this source message |
|
||||
| `--folder` | Folder of the `--reply-to` source (default `INBOX`) |
|
||||
|
||||
`data`:
|
||||
```json
|
||||
{ "sent": true, "recipients": ["alice@example.com", "boss@example.com"] }
|
||||
```
|
||||
|
||||
Blocked sends return `error: true`, code `policy`:
|
||||
- `ro_mode` — the account is read-only; it cannot send.
|
||||
- `whitelist_out` — a recipient isn't on the outbound whitelist; the **whole** send is blocked and
|
||||
nothing was sent. Don't silently drop recipients — tell the user.
|
||||
|
||||
`--reply-to` reads the source message's `Message-ID`/`References` so the reply threads. The source
|
||||
is subject to the inbound filter: a filtered/missing source returns `not_found`.
|
||||
|
||||
---
|
||||
|
||||
## Enforcement rules (set by the user; you can't change them)
|
||||
|
||||
- **Mode:** `RO` accounts reject `send`. `RW` can read and send.
|
||||
- **Inbound whitelist / subject filter:** disallowed mail is invisible everywhere (`list`/`search`
|
||||
omit it; `get`/`ack` return `not_found`). You can't tell a filtered message from a non-existent
|
||||
one — by design.
|
||||
- **Outbound whitelist:** every recipient (to+cc+bcc) must match, or the send is blocked whole.
|
||||
- **Address matching:** case-insensitive; an entry `@domain.com` matches any address at that
|
||||
domain; otherwise an exact-address match.
|
||||
|
||||
## Parsing tips
|
||||
|
||||
```bash
|
||||
# Guard on success, then read data:
|
||||
out=$(emcli list --account gmail --new --limit 10)
|
||||
if echo "$out" | jq -e '.error == false' >/dev/null; then
|
||||
echo "$out" | jq -r '.data.messages[] | "\(.uid)\t\(.subject)"'
|
||||
else
|
||||
echo "$out" | jq -r '.error_detail | "\(.code): \(.message)"' >&2
|
||||
fi
|
||||
|
||||
# Save an attachment from get:
|
||||
emcli get --account gmail --uid 70314 \
|
||||
| jq -r '.data.attachments[0].content_b64' | base64 -d > report.pdf
|
||||
```
|
||||
@@ -0,0 +1,62 @@
|
||||
# Installing the emcli binary
|
||||
|
||||
The skill's `scripts/install.sh` downloads a prebuilt binary from the project's release assets.
|
||||
|
||||
## Quick install
|
||||
|
||||
```bash
|
||||
bash scripts/install.sh
|
||||
```
|
||||
|
||||
It detects your OS (`linux`/`darwin`/`windows`) and architecture (`amd64`/`arm64`), downloads the
|
||||
matching asset, verifies its SHA-256 checksum when a `checksums.txt` is published, makes it
|
||||
executable, and confirms it runs.
|
||||
|
||||
## Options (environment variables)
|
||||
|
||||
| Variable | Default | Purpose |
|
||||
|---|---|---|
|
||||
| `EMCLI_VERSION` | `v0.4.0` | Release tag to fetch |
|
||||
| `EMCLI_BASE_URL` | `https://gitea.dcglab.co.uk/steve/emcli` | Repo base URL |
|
||||
| `EMCLI_INSTALL_DIR` | `$HOME/.local/bin` | Install location |
|
||||
|
||||
Example — install a specific version to a system directory:
|
||||
|
||||
```bash
|
||||
EMCLI_VERSION=v0.4.0 EMCLI_INSTALL_DIR=/usr/local/bin bash scripts/install.sh
|
||||
```
|
||||
|
||||
## Release asset naming
|
||||
|
||||
The release publishes one binary per platform plus a checksum file:
|
||||
|
||||
```
|
||||
emcli_0.4.0_linux_amd64
|
||||
emcli_0.4.0_linux_arm64
|
||||
emcli_0.4.0_darwin_amd64
|
||||
emcli_0.4.0_darwin_arm64
|
||||
emcli_0.4.0_windows_amd64.exe
|
||||
checksums.txt # sha256, one "<sum> <asset>" line per asset
|
||||
```
|
||||
|
||||
> `v0.4.0` and these assets are placeholders until the first tagged release exists. Update
|
||||
> `EMCLI_VERSION` (or the default in `install.sh`) once a real release is cut.
|
||||
|
||||
## Building from source instead
|
||||
|
||||
If you have Go and prefer to build rather than download:
|
||||
|
||||
```bash
|
||||
git clone https://gitea.dcglab.co.uk/steve/emcli
|
||||
cd emcli
|
||||
CGO_ENABLED=0 go build -o emcli ./cmd/emcli
|
||||
# then move ./emcli onto your PATH
|
||||
```
|
||||
|
||||
## After installing
|
||||
|
||||
`emcli` needs the `EMCLI_KEY` environment variable (a base64-encoded 32-byte AES key) to touch its
|
||||
database. For agent use, the **orchestrator provides this** — the agent should not generate or read
|
||||
it. A human setting up emcli for the first time generates one with
|
||||
`head -c 32 /dev/urandom | base64` and saves it securely. See the project User Manual for full admin
|
||||
setup.
|
||||
Executable
+91
@@ -0,0 +1,91 @@
|
||||
#!/usr/bin/env bash
|
||||
#
|
||||
# install.sh — download the emcli binary from the project's release assets and
|
||||
# put it on your PATH. Detects OS/arch, verifies the checksum when available.
|
||||
#
|
||||
# Usage:
|
||||
# bash install.sh
|
||||
#
|
||||
# Environment overrides:
|
||||
# EMCLI_VERSION release tag to fetch (default: v0.4.0)
|
||||
# EMCLI_BASE_URL repo base URL (default: https://gitea.dcglab.co.uk/steve/emcli)
|
||||
# EMCLI_INSTALL_DIR where to put the binary (default: $HOME/.local/bin)
|
||||
#
|
||||
# NOTE: v0.4.0 and its release assets are placeholders until the first tagged
|
||||
# release is published. The asset naming below is the scheme the release will use:
|
||||
# emcli_<version>_<os>_<arch>[.exe] e.g. emcli_0.4.0_linux_amd64
|
||||
# checksums.txt (sha256, one "<sum> <asset>" line per asset)
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
VERSION="${EMCLI_VERSION:-v0.4.0}"
|
||||
BASE_URL="${EMCLI_BASE_URL:-https://gitea.dcglab.co.uk/steve/emcli}"
|
||||
INSTALL_DIR="${EMCLI_INSTALL_DIR:-$HOME/.local/bin}"
|
||||
|
||||
die() { printf 'install.sh: %s\n' "$1" >&2; exit 1; }
|
||||
|
||||
# --- detect OS -------------------------------------------------------------
|
||||
case "$(uname -s)" in
|
||||
Linux) OS=linux ;;
|
||||
Darwin) OS=darwin ;;
|
||||
MINGW*|MSYS*|CYGWIN*) OS=windows ;;
|
||||
*) die "unsupported OS: $(uname -s)" ;;
|
||||
esac
|
||||
|
||||
# --- detect arch -----------------------------------------------------------
|
||||
case "$(uname -m)" in
|
||||
x86_64|amd64) ARCH=amd64 ;;
|
||||
arm64|aarch64) ARCH=arm64 ;;
|
||||
*) die "unsupported architecture: $(uname -m)" ;;
|
||||
esac
|
||||
|
||||
EXT=""
|
||||
[ "$OS" = windows ] && EXT=".exe"
|
||||
|
||||
VER_NO_V="${VERSION#v}"
|
||||
ASSET="emcli_${VER_NO_V}_${OS}_${ARCH}${EXT}"
|
||||
URL="${BASE_URL}/releases/download/${VERSION}/${ASSET}"
|
||||
DEST="${INSTALL_DIR}/emcli${EXT}"
|
||||
|
||||
# --- pick a downloader -----------------------------------------------------
|
||||
download() { # download <url> <dest>
|
||||
if command -v curl >/dev/null 2>&1; then
|
||||
curl -fSL "$1" -o "$2"
|
||||
elif command -v wget >/dev/null 2>&1; then
|
||||
wget -qO "$2" "$1"
|
||||
else
|
||||
die "need curl or wget to download"
|
||||
fi
|
||||
}
|
||||
|
||||
mkdir -p "$INSTALL_DIR"
|
||||
|
||||
printf 'Downloading %s\n from %s\n' "$ASSET" "$URL"
|
||||
download "$URL" "$DEST" || die "download failed (is ${VERSION} published yet?)"
|
||||
chmod +x "$DEST"
|
||||
|
||||
# --- verify checksum if a checksums.txt is published -----------------------
|
||||
if command -v sha256sum >/dev/null 2>&1; then
|
||||
sums="$(mktemp)"
|
||||
if download "${BASE_URL}/releases/download/${VERSION}/checksums.txt" "$sums" 2>/dev/null; then
|
||||
want="$(awk -v a="$ASSET" '$2 == a || $2 == "*"a {print $1}' "$sums" | head -n1)"
|
||||
if [ -n "$want" ]; then
|
||||
got="$(sha256sum "$DEST" | awk '{print $1}')"
|
||||
[ "$want" = "$got" ] || die "checksum mismatch for ${ASSET} (expected ${want}, got ${got})"
|
||||
echo "checksum ok"
|
||||
fi
|
||||
fi
|
||||
rm -f "$sums"
|
||||
fi
|
||||
|
||||
printf 'Installed emcli to %s\n' "$DEST"
|
||||
|
||||
# --- PATH hint -------------------------------------------------------------
|
||||
case ":$PATH:" in
|
||||
*":$INSTALL_DIR:"*) ;;
|
||||
*) printf 'Note: %s is not on your PATH. Add it, e.g.:\n export PATH="%s:$PATH"\n' "$INSTALL_DIR" "$INSTALL_DIR" ;;
|
||||
esac
|
||||
|
||||
# --- confirm it runs -------------------------------------------------------
|
||||
"$DEST" version || die "binary downloaded but failed to run"
|
||||
echo "Done. Remember: emcli needs EMCLI_KEY set in the environment to do anything."
|
||||
Reference in New Issue
Block a user