Eppie CLI agent skill
Use this skill when an autonomous or semi-autonomous agent must operate eppie-console directly.
eppie-console is a console mail client that can work with:
- Gmail,
- Outlook,
- Proton Mail,
- regular IMAP/SMTP email services,
- decentralized peer-to-peer Eppie mail.
Purpose
Use eppie-console in a predictable, automation-friendly way to:
- inspect accounts,
- inspect folders,
- read messages,
- send messages,
- sync folders,
- initialize, unlock, and reset the local vault when needed by the task.
This skill focuses on deterministic command execution, structured output handling, and explicit standard-input contracts.
For end-to-end validation, coverage, and reproducible test scenarios, use eppie-cli-agent-test-plan.md.
How to use this file
Treat sections marked Normative as the required contract for agent execution. Treat sections marked Reference as supporting material, examples, or quick navigation aids.
Normative sections in this file:
Normative defaults for agentsNormative rulesNormative command patterns for agentsNormative stdin contractsNormative execution policy for autonomous agentsNormative error handling
Reference sections in this file:
Reference: preferred installationReference: required inputsReference: launch modesReference: decision guideReference: end-to-end agent scenariosReference: command-specific notes and examplesReference: recommended agent workflowReference: known limitationsReference: contributing
Normative defaults for agents
When the task requires executing eppie-console, use these defaults unless the task explicitly requires otherwise:
- use
--non-interactive=true - use
--output=jsonfor agent automation unless text output is explicitly required - use
--unlock-password-stdin=truefor stateful commands that need an existing vault - use
--assume-yes=truefor destructive automation - provide
stdinexactly in the required order - use one consistent working directory per vault
Canonical command patterns and exact stdin contracts are defined later in this file. Use those sections as the single source of truth for agent execution.
Use this skill when
- the task requires executing
eppie-console - the agent must inspect accounts, folders, messages, or contacts
- the agent must send a message
- the agent must synchronize a folder
- the agent must initialize or reset the local vault
- the agent must work with machine-readable CLI output
Do not use this skill when
- the task does not require calling
eppie-console - the task is only to explain behavior conceptually without running commands
- the task is primarily about test coverage or validation workflows; use
eppie-cli-agent-test-plan.md - an interactive REPL session is not explicitly required but would be the only execution path
- the required executable or working directory is unavailable
Reference: preferred installation
Prefer the executable published in GitHub Releases. You can also build from source.
Latest release page:
- https://github.com/Eppie-io/Eppie-CLI/releases/latest
Direct download links:
Linux
- https://github.com/Eppie-io/Eppie-CLI/releases/latest/download/Eppie.CLI-linux-x64.tar.gz
- https://github.com/Eppie-io/Eppie-CLI/releases/latest/download/Eppie.CLI-linux-arm64.tar.gz
macOS
- https://github.com/Eppie-io/Eppie-CLI/releases/latest/download/Eppie.CLI-osx-x64.tar.gz
- https://github.com/Eppie-io/Eppie-CLI/releases/latest/download/Eppie.CLI-osx-arm64.tar.gz
Windows
- https://github.com/Eppie-io/Eppie-CLI/releases/latest/download/Eppie.CLI-win-x64.zip
- https://github.com/Eppie-io/Eppie-CLI/releases/latest/download/Eppie.CLI-win-arm64.zip
After extracting the archive, use the eppie-console executable directly.
Reference: required inputs
Common inputs
- path to
eppie-console - working directory
- whether the task requires stateful access to an existing vault
- whether structured JSON output is required
Vault access inputs
- vault password
- whether the vault must be initialized first
Reading inputs
- account identifier for
-a; for agent workflows, use the account address string returned bylist-accountsindata[].address - folder name when folder-specific access is needed
- message
id - message
pk - page size and total limit for paged commands
Sending inputs
- sender address
- receiver address
- subject
- message body
Account creation inputs
- account type
- vault password
- structured JSON payload when required
- provider-specific secrets only through standard input when supported
Reference: launch modes
Interactive mode
Use only when a REPL session is explicitly needed.
Example:
eppie-console
Typical sequence:
open- vault password
list-accountsexit
open is useful in interactive mode to open the local vault inside the current process.
Non-interactive mode
Prefer this mode for agents.
Use:
eppie-console --non-interactive=true <command> [command options]
To avoid passing the same startup flags on every call, you can put them in appsettings.json next to eppie-console, for example:
{ "non-interactive": true, "output": "json", "unlock-password-stdin": true }
eppie-console also reads configuration from environment variables.
When the default .NET host configuration pipeline is used, environment variables override appsettings.json. Command-line arguments override both.
For structured machine-readable output, also use:
--output=json
JSON responses use a normalized envelope:
type:result,status,warning, orerrorcode: stable machine-readable outcome codedata: payload forresultandstatusresponsesmeta: optional metadata forresultresponses; usuallynull, but paged read commands can return objects such as{"header":"All messages:","returned":5,"hasMore":true}message: human-readable warning/error text
In --non-interactive=true --output=json mode, structured responses are emitted on stdout without a preceding stack trace on stderr for handled command failures such as unhandledException.
In non-interactive mode, do not use open as part of the agent workflow. It does not establish reusable state for later process launches. For stateful non-interactive commands, use --unlock-password-stdin=true instead.
For all agent examples in this file, <account> means the account address returned by list-accounts in data[].address. Prefer that address string for -a instead of the numeric id, unless a command explicitly documents another identifier format.
Common success-output examples:
--non-interactive=true --output=json list-accounts(with accounts):
- {"type":"result","code":"accounts","data":[{"id":1,"address":"<address>","accountType":"Dec"}],"meta":null}
--non-interactive=true --assume-yes=true --output=json reset:
- {"type":"status","code":"reset","data":null}
--non-interactive=true --unlock-password-stdin=true --output=json send ...:
- {"type":"status","code":"messageSent","data":{"subject":"<subject>","to":"<receiver>","from":"<sender>"}}
--non-interactive=true --unlock-password-stdin=true --output=json sync-folder ...:
- {"type":"status","code":"folderSynced","data":{"account":"<account>","folder":"Inbox"}}
--non-interactive=true --unlock-password-stdin=true --output=json show-all-messages ...:
- {"type":"result","code":"messages","data":[{"id":1,"pk":1,"to":["<receiver>"],"from":["<sender>"],"folder":"Inbox","subject":"<subject>"}],"meta":{"header":"All messages:","returned":1,"hasMore":false}}
Additional command-specific JSON result shapes are documented later in Reference: command-specific notes and examples.
Common text success-output examples:
open:
- The application was opened successfully.
reset:
- The application has just been reset.
add-account -t dec:
- Account <generated-address> added (Dec).
send:
- Message sent from <sender> to <receiver>. Subject: <subject>
sync-folder -f Inbox:
- Folder 'Inbox' for account <account> synchronized.
Normative rules
- Treat
Normative command patterns for agentsandNormative stdin contractsas normative for agent workflows. - Put global launch options before
--and put the startup command after--. - Do not assume
opencreates a reusable session for future process launches. - Synchronize the relevant folder before reading when the task requires newly arrived messages.
- Do not guess provider-specific folder names; call
list-foldersfirst. - Use
-l/--limitwhen predictable payload size matters. - Do not pass account secrets in command-line arguments when structured standard input is supported.
eppie-console open only opens the vault inside the current process. It does not create a reusable session for future process launches.
Normative command patterns for agents
Use this section as the single source of truth for agent command lines. Unless text output is explicitly required, all agent examples below use --output=json.
| Task | Canonical command |
|---|---|
| Initialize a vault | --non-interactive=true --output=json -- init |
| List accounts | --non-interactive=true --unlock-password-stdin=true --output=json -- list-accounts |
| List folders | --non-interactive=true --unlock-password-stdin=true --output=json -- list-folders -a <account> |
| Show all messages | --non-interactive=true --unlock-password-stdin=true --output=json -- show-all-messages -s 10 -l 10 |
| Show one message | --non-interactive=true --unlock-password-stdin=true --output=json -- show-message -a <account> -f <folder> -i <id> -k <pk> |
| Delete one message | --non-interactive=true --unlock-password-stdin=true --output=json -- delete-message -a <account> -f <folder> -i <id> -k <pk> |
| List contacts | --non-interactive=true --unlock-password-stdin=true --output=json -- list-contacts -s 10 -l 10 |
| Show contact messages | --non-interactive=true --unlock-password-stdin=true --output=json -- show-contact-messages -c <contact> -s 10 -l 10 |
| Show folder messages | --non-interactive=true --unlock-password-stdin=true --output=json -- show-folder-messages -a <account> -f <folder> -s 10 -l 10 |
| Sync a folder | --non-interactive=true --unlock-password-stdin=true --output=json -- sync-folder -a <account> -f <folder> |
| Send a message | --non-interactive=true --unlock-password-stdin=true --output=json -- send -s <sender> -r <receiver> -t "<subject>" |
| Reset local data | --non-interactive=true --assume-yes=true --output=json -- reset |
| Add a DEC account | --non-interactive=true --unlock-password-stdin=true --output=json -- add-account -t dec |
| Add a regular IMAP/SMTP account | --non-interactive=true --unlock-password-stdin=true --output=json -- add-account -t email --input-json-stdin |
| Add a Proton Mail account | --non-interactive=true --unlock-password-stdin=true --output=json -- add-account -t proton --input-json-stdin |
Normative stdin contracts
Provide stdin exactly in the documented order. Unless a command explicitly reads until end-of-stream, stop after the documented input has been written.
| Command | Exact stdin contract | EOF required |
|---|---|---|
--non-interactive=true --output=json -- init | line 1: new vault password | no |
--non-interactive=true --unlock-password-stdin=true --output=json -- list-accounts | line 1: vault password | no |
--non-interactive=true --unlock-password-stdin=true --output=json -- list-folders -a <account> | line 1: vault password | no |
--non-interactive=true --unlock-password-stdin=true --output=json -- show-all-messages -s <page-size> -l <limit> | line 1: vault password | no |
--non-interactive=true --unlock-password-stdin=true --output=json -- show-message -a <account> -f <folder> -i <id> -k <pk> | line 1: vault password | no |
--non-interactive=true --unlock-password-stdin=true --output=json -- delete-message -a <account> -f <folder> -i <id> -k <pk> | line 1: vault password | no |
--non-interactive=true --unlock-password-stdin=true --output=json -- list-contacts -s <page-size> -l <limit> | line 1: vault password | no |
--non-interactive=true --unlock-password-stdin=true --output=json -- show-contact-messages -c <contact> -s <page-size> -l <limit> | line 1: vault password | no |
--non-interactive=true --unlock-password-stdin=true --output=json -- show-folder-messages -a <account> -f <folder> -s <page-size> -l <limit> | line 1: vault password | no |
--non-interactive=true --unlock-password-stdin=true --output=json -- sync-folder -a <account> -f <folder> | line 1: vault password | no |
--non-interactive=true --unlock-password-stdin=true --output=json -- send -s <sender> -r <receiver> -t "<subject>" | line 1: vault password; line 2 and later: message body; then close stdin | yes |
--non-interactive=true --assume-yes=true --output=json -- reset | no stdin | no |
--non-interactive=true --unlock-password-stdin=true --output=json -- add-account -t dec | line 1: vault password | no |
--non-interactive=true --unlock-password-stdin=true --output=json -- add-account -t email --input-json-stdin | line 1: vault password; remaining bytes: one JSON object | yes |
--non-interactive=true --unlock-password-stdin=true --output=json -- add-account -t proton --input-json-stdin | line 1: vault password; remaining bytes: one JSON object | yes |
Command summary
| Command group | Needs existing vault | Use --unlock-password-stdin=true | Prefer --output=json | Notes |
|---|---|---|---|---|
init | no | no | yes for agent workflows | writes a new local vault password from stdin |
reset | no | no | yes | destructive; also use --assume-yes=true |
list-accounts, list-folders | yes | yes | yes | inspect vault content |
show-all-messages, show-message, show-folder-messages, show-contact-messages | yes | yes | yes | use -l when payload size matters |
sync-folder | yes | yes | yes | call before reading when fresh messages are required |
send | yes | yes | yes | password first in stdin, then body until EOF |
add-account -t dec | yes | yes | yes | simple vault-backed account creation |
add-account -t email, add-account -t proton | yes | yes | yes | use --input-json-stdin; do not pass secrets in arguments |
Avoid these mistakes
- Do not use
openin a non-interactive workflow. - Do not guess provider-specific folder names; call
list-foldersfirst. - Do not rely only on process exit code when JSON output is available.
- Do not omit
-lwhen an agent needs a predictable maximum payload size. - Do not pass secrets in command-line arguments when structured standard input is supported.
Normative execution policy for autonomous agents
Output handling
- Follow the
Normative defaults for agentsdefaults unless the task explicitly requires otherwise. - Parse
type,code,data,meta, andmessagefrom the normalized envelope. - Do not rely only on process exit code when structured output is available.
- Treat
warninganderrorresponses as non-success outcomes even when the process exits cleanly.
Standard input handling
- Follow
Normative stdin contractsexactly. - For
send, closestdinafter the last body line. - For commands using
--input-json-stdin, pass the password first when required, then the JSON payload. - Do not prepend labels, prompts, or extra blank lines to the documented
stdincontract.
Working directory handling
- Use one consistent working directory for related commands that operate on the same local vault.
- Use an isolated working directory when the run is disposable or destructive.
Account identifier handling
- For agent workflows, pass the account address from
list-accountsdata[].addressto-a. - Do not assume the numeric
idis accepted interchangeably by all commands. - Re-read the current address from
list-accountswhen a workflow depends on a newly created account.
Pagination handling
- Use both
-s/--page-sizeand-l/--limitin agent workflows when payload size must be predictable. - Treat
-sas page size only. Treat-las the total maximum number of records returned by the command. - If
-lis omitted, paged read commands return up to 20 records by default. - Use
meta.returnedto confirm how many records were actually returned. - Use
meta.hasMoreto decide whether another read with a higher-lor a narrower filter is needed. - Do not assume provider folders or message volume are small enough to omit
-l.
Reference: decision guide
Use Normative command patterns for agents for the exact command line. This guide is only for choosing the next command.
- inspect available accounts →
list-accounts - inspect available folders for an account →
list-folders - inspect recent messages across folders → sync the relevant folders when new messages are required, then use
show-all-messages - inspect messages in a specific folder →
list-folders, thensync-folder, thenshow-folder-messageswhen new messages are required - inspect one message →
show-message - delete one message →
delete-message - inspect contacts →
list-contacts - inspect messages for one contact →
show-contact-messages - send a message →
send - sync one folder →
list-folders, thensync-folder - initialize a vault →
init - reset local data →
reset
Reference: end-to-end agent scenarios
This section is informative. Use Normative command patterns for agents for the exact command line and Normative stdin contracts for the exact stdin shape.
Initialize a new vault, add an account, then verify it
Use this flow when the working directory does not yet contain the required local vault.
- initialize the vault with
init - add the required account with
add-account - verify the created account with
list-accounts - use the returned
data[].addressas<account>in later commands
Typical sequences:
- DEC account:
init→add-account -t dec→list-accounts - regular IMAP/SMTP account:
init→add-account -t email→list-accounts - Proton Mail account:
init→add-account -t proton→list-accounts
Agent notes:
- keep one consistent working directory across the whole sequence so all commands operate on the same local vault
- for
add-account -t emailandadd-account -t proton, provide the structured payload through standard input after the vault password as documented inNormative stdin contracts - after account creation, re-read the current address from
list-accountsinstead of assuming another identifier format
Read the latest message from a specific folder
Use this flow when the task requires a concrete folder such as inbox or sent mail and the messages may have changed since the last sync.
- inspect accounts with
list-accounts - inspect folders for the chosen account with
list-folders - choose the correct folder name from
data[].fullNameordata[].roles - synchronize that folder with
sync-folder - read a bounded message list with
show-folder-messages - open one returned message with
show-messageusing itsidandpk
Typical sequence:
list-accounts→list-folders→sync-folder→show-folder-messages→show-message
Agent notes:
- do not guess the folder name; use the
fullNamereturned bylist-folders - use
-sand-lonshow-folder-messageswhen predictable payload size matters - when
meta.hasMoreistrue, treat the result as partial and increase-lonly if the task requires a larger set
Send a message from an existing account
Use this flow when the local vault already contains the sender account and the task is to deliver one message deterministically.
- inspect accounts with
list-accounts - choose the sender address from
data[].address - call
sendwith the sender, receiver, and subject - pass the vault password on the first
stdinline - pass the message body starting from line 2, then close
stdin
Typical sequence:
list-accounts→send
Agent notes:
- if the sender is unknown or the workflow depends on a newly created account, refresh the account list before sending
- in
--non-interactive=truemode,sendreads the body until end-of-stream, so the agent should closestdinafter the last body line - prefer
--output=jsonand verify themessageSentstatus response instead of relying only on the process exit code
Reference: command-specific notes and examples
The canonical command lines are defined in Normative command patterns for agents, and the exact inputs are defined in Normative stdin contracts. This section keeps only command-specific notes, result shapes, and examples that are easy to reference during agent execution.
Init local vault
Notes:
- writes a new local vault password from
stdin
Show all messages
Notes:
-s/--page-sizecontrols only the page size-l/--limitcaps the total number of returned records and should be used by agents to keep payload size predictable- if
-lis omitted, paged read commands return up to 20 records by default - message items use the same address shape as
show-message:toandfromare arrays - paged result metadata can include
returnedandhasMore - agents should check
meta.hasMorebefore assuming the returned list is complete
List folders
Expected result shape:
{"type":"result","code":"folders","data":[{"fullName":"Inbox","unreadCount":0,"totalCount":0,"roles":["inbox"]}],"meta":{"account":"<account>"}}
Notes:
<account>should be the account address fromlist-accountsdata[].address- use this command before
show-folder-messagesorsync-folderinstead of guessing provider-specific folder names - Proton may expose names such as
All Sent; rely on the returnedfullName - use
roleswhen you need a machine-readable way to find folders such as inbox or sent mail
Delete one message
Notes:
- when called for a message outside
Trash, the command moves the message toTrash - when called for a message already in
Trash, the command deletes it permanently
Show contact messages
Notes:
- paged result metadata can include
returnedandhasMore
Show folder messages
Notes:
- paged result metadata can include
returnedandhasMore
Sync folder
Expected result shape:
{"type":"status","code":"folderSynced","data":{"account":"<account>","folder":"Inbox"}}
Send message
Examples:
vault passwordHello from automationSecond body line- close
stdin
Notes:
- in
--non-interactive=truemode,sendreads the message body until end-of-stream - JSON success shape:
{"type":"status","code":"messageSent","data":{"subject":"<subject>","to":"<receiver>","from":"<sender>"}}
PowerShell example with the command line and exact stdin in one block:
@"
vault password
Hello from automation
Second body line
"@ | .\eppie-console --non-interactive=true --unlock-password-stdin=true --output=json -- send -s sender@example.com -r receiver@example.com -t "Hello from automation"Linux / bash example with the command line and exact stdin in one block:
cat <<'EOF_INPUT' | ./eppie-console --non-interactive=true --unlock-password-stdin=true --output=json -- send -s sender@example.com -r receiver@example.com -t "Hello from automation"
vault password
Hello from automation
Second body line
EOF_INPUTNotes:
- on Linux and macOS, use
./eppie-consoleafter extracting the archive and making sure the file is executable.
Open local vault
Use open only in interactive mode when the agent is already inside a REPL session and must open the local vault in that same process.
Notes:
- do not use
openas part of the non-interactive agent workflow
Reset local data
Notes:
resetis a destructive command and should be called with--assume-yes=truein automation or other non-interactive scripts.
Add DEC account
Expected result shape:
{"type":"result","code":"accountAdded","data":{"address":"<generated-address>","accountType":"Dec"},"meta":null}
Add regular IMAP/SMTP account
Examples:
JSON object:
{
"email": "user@example.com",
"accountPassword": "account password",
"imapServer": "imap.example.com",
"imapPort": 993,
"smtpServer": "smtp.example.com",
"smtpPort": 465
}stdin shape:
- first line:
vault password - remaining bytes:
{"email":"user@example.com","accountPassword":"account password","imapServer":"imap.example.com","imapPort":993,"smtpServer":"smtp.example.com","smtpPort":465}Notes:
- use this mode for regular IMAP/SMTP providers when browser-based OAuth is not needed
- do not pass account secrets in command-line arguments
- required structured properties are
email,accountPassword,imapServer,imapPort,smtpServer, andsmtpPort - invalid structured input returns one of these machine-readable errors:
- structuredStandardInputInvalidJson - structuredStandardInputMissingProperty
Add Proton Mail account
Examples:
JSON object:
{
"email": "user@proton.me",
"accountPassword": "account password",
"mailboxPassword": "mailbox password",
"twoFactorCode": "123456"
}stdin shape:
- first line:
vault password - remaining bytes:
{"email":"user@proton.me","accountPassword":"account password","mailboxPassword":"mailbox password"}Notes:
- do not pass Proton secrets in command-line arguments
emailandaccountPasswordare required in structured inputmailboxPasswordis required only if the Proton flow asks for ittwoFactorCodeis required only if the Proton flow asks for it- if the mailbox password is the same as the account password, repeat the same value in
mailboxPassword - invalid structured input returns one of these machine-readable errors:
- structuredStandardInputInvalidJson - structuredStandardInputMissingProperty
Reference: recommended agent workflow
- Start from the defaults in
Normative defaults for agents. - Choose the exact command line from
Normative command patterns for agents. - Provide
stdinin the exact shape defined inNormative stdin contracts. - Use the account address from
list-accountsdata[].addressfor-a. - Use an isolated working directory when the run is disposable or destructive.
- For message reading:
- start with list-folders if the next step requires a specific folder name, - synchronize the relevant folder before reading when you need newly arrived messages, - then use show-all-messages when you need recent messages across all folders, - then call show-message or delete-message with the returned id and pk.
- In
--non-interactive=truemode text output stops after the first page instead of prompting. - For common task chains, use
Reference: end-to-end agent scenariosas a quick reference.
Reference: known limitations
Some commands still require explicit non-interactive inputs
--non-interactive=true suppresses interactive prompts, but it does not invent missing data.
Commands that normally ask follow-up questions still need explicit arguments or stdin data. The main examples are already covered above:
sendreads the message body until end-of-stream in--non-interactive=truemodeadd-account -t emailandadd-account -t protonshould use--input-json-stdin- destructive commands such as
resetstill need--assume-yes=true - paged read commands still default to 20 records when
-lis omitted
Normative error handling
For agent automation, prefer deterministic handling over implicit retries.
Interpret the normalized envelope
When --output=json is enabled, handle responses by type first:
type | Meaning | Agent action |
|---|---|---|
result | command returned data | parse data and meta |
status | command completed successfully without a list-style result | read data when present |
warning | handled problem or non-success condition | inspect code; do not assume success; stop unless the workflow explicitly defines a recovery step |
error | handled failure | inspect code, message, and data; stop unless the input can be corrected deterministically |
Common machine-readable codes
code | Meaning | Typical agent response |
|---|---|---|
invalidPassword | vault password was rejected | stop; do not retry automatically with the same password |
structuredStandardInputInvalidJson | structured payload is not valid JSON | fix serialization and retry once with corrected JSON |
structuredStandardInputMissingProperty | required JSON property is missing or empty | provide the missing property and retry once |
unhandledException | command failed and returned exception details | inspect data.exceptionType and command context; do not retry blindly |
Agent response rules
- Parse
typefirst, thencode. - Do not treat
warningas success. - Do not retry
invalidPasswordautomatically. - Retry only when the failure is deterministic and local to the request, such as malformed JSON or a missing structured property.
- When retrying after
structuredStandardInputInvalidJsonorstructuredStandardInputMissingProperty, change only the invalid input and keep the command line unchanged. - For
unhandledException, record the command, the parsedcode, anddata.exceptionTypewhen present. - When a paged read returns
meta.hasMore: true, treat that as an incomplete result set rather than an error.
Error-output examples
Invalid password example:
{"type":"warning","code":"invalidPassword","message":"Warning: Invalid password.","data":null}
Unknown sender account example for send:
{"type":"error","code":"unhandledException","message":"An error has occurred: ...","data":{"exceptionType":"Tuvi.Core.Entities.AccountIsNotExistInDatabaseException"}}
Invalid Proton structured input example:
{"type":"error","code":"structuredStandardInputInvalidJson","message":"Error: The remaining standard input for the 'add-account' command is not valid JSON.","data":{"commandName":"add-account"}}
Missing Proton structured property example:
{"type":"error","code":"structuredStandardInputMissingProperty","message":"Error: The structured standard input for the 'add-account' command must contain a non-empty 'email' property.","data":{"commandName":"add-account","propertyName":"email"}}
Reference: contributing
Contributions are welcome.
Please open issues for bugs, UX problems, and feature ideas. Pull requests on GitHub are also welcome.
Repository:
- https://github.com/Eppie-io/Eppie-CLI