Universal local CLI for sending files from Codex projects to messenger targets.
This utility was generated entirely by AI during an interactive Codex session. Review the code, configuration, and security behavior before relying on it for sensitive workflows.
0BSD. See LICENSE.
The project is intentionally small and conservative:
- secrets live outside git in
~/.courier-cli/bot-credits; - every send operation requires interactive confirmation;
- providers are implemented separately;
- Telegram is implemented;
- XMPP with HTTP File Upload is implemented.
This repository contains two related parts:
courier-cli: the Python command line tool installed ascourier;skills/send-with-courier: a Codex skill that teaches Codex agents how to usecouriersafely.
Install and configure the CLI first. Then install the Codex skill if you want Codex agents to understand requests like "send this file to Telegram" or "отправь файл в Monal".
- Python 3.11+
- Network access during installation, because dependencies are installed from PyPI
pipxis recommended for a user-wide CLI install
If your system python3 is older than 3.11, use an explicit python3.11 executable or the full path to a Python 3.11+ interpreter during installation.
This is the preferred setup when Codex or any terminal should be able to run courier from any project.
-
Install
pipxif it is not installed yet.macOS with Homebrew:
brew install pipx pipx ensurepath
Generic Python install:
python3.11 -m pip install --user pipx python3.11 -m pipx ensurepath
-
Restart the terminal, or source your shell config.
For zsh:
source ~/.zshrc
For bash:
source ~/.bashrc
-
Clone the repository:
git clone <repo-url> cd courier-cli
-
Install
courier-cli:pipx install --python python3.11 . -
Check that the command is available without activating a virtual environment:
command -v courier courier --help courier doctor
If command -v courier prints a path, most Codex terminal sessions should also be able to find the command through PATH. If Codex still cannot find courier, run command -v courier inside the Codex terminal and adjust the shell or app environment that Codex actually uses.
After pulling new code:
cd courier-cli
git pull
pipx install --force --python python3.11 .Check again:
courier --help
courier doctorUse this when you do not want to use pipx.
-
Clone the repository:
git clone <repo-url> cd courier-cli
-
Create a virtual environment:
python3.11 -m venv .venv
-
Install the package and its dependencies into that virtual environment:
.venv/bin/pip install -e . -
Add the virtual environment's
bindirectory to your shellPATH.For zsh:
echo 'export PATH="$HOME/path/to/courier-cli/.venv/bin:$PATH"' >> ~/.zshrc source ~/.zshrc
For bash:
echo 'export PATH="$HOME/path/to/courier-cli/.venv/bin:$PATH"' >> ~/.bashrc source ~/.bashrc
Replace
"$HOME/path/to/courier-cli"with the real clone path. -
Check that
courieris available globally:command -v courier courier --help courier doctor
For local development inside this repository:
python3.11 -m venv .venv
source .venv/bin/activate
pip install -e .
courier --help
courier doctorThis makes courier available while the virtual environment is active. For Codex usage across projects, prefer the pipx install or add .venv/bin to PATH.
-
Install Python 3.11+.
-
Install
pipx, or decide to use the venv + PATH method. -
Clone this repository.
-
Install the package with
pipx install --python python3.11 .or.venv/bin/pip install -e .. -
Confirm
command -v courierprints a path. -
Create
~/.courier-cli/bot-credits. -
Configure at least one target, for example
telegram.meorxmpp.me. -
Run:
courier doctor courier config check
-
Send a test file.
-
If this machine will be used with Codex, install the bundled Codex skill from skills/send-with-courier.
Codex skills and agents expect courier to be available through PATH. They should not rely on project-specific absolute paths.
Run this from any project directory:
command -v courier
courier doctorIf command -v courier prints nothing, install with pipx or add the venv bin directory to PATH.
The bundled Codex skill is optional for manual terminal usage, but recommended when Codex agents should send files for you. The skill does not contain secrets. It only contains instructions for agents.
Install and verify courier first:
command -v courier
courier doctor
courier config checkThen install the skill from this repository:
cd courier-cli
CODEX_HOME="${CODEX_HOME:-$HOME/.codex}"
mkdir -p "$CODEX_HOME/skills"
rm -rf "$CODEX_HOME/skills/send-with-courier"
cp -R skills/send-with-courier "$CODEX_HOME/skills/"The install command replaces any existing installed copy of send-with-courier with the version from this repository.
Restart Codex after installing or updating the skill. New Codex chats should then be able to see send-with-courier when you type $.
To test natural triggering, open a new Codex chat in any project and ask something like:
Отправь README.md мне в Monal
or:
Send README.md to me in Telegram
The skill tells the agent to:
- use only
courierfound throughPATH; - ask for confirmation before sending;
- run network commands with network access/escalation in sandboxed Codex environments;
- run
courier sendinteractively and writeyto stdin only after the user confirms; - avoid showing CLI implementation details in normal user-facing chat.
If courier was installed with pipx, pipx ensurepath usually handles PATH setup. Verify with:
command -v courier
courier doctorIf you installed into a project venv, add that venv's bin directory to PATH as shown in Alternative Install With venv and PATH.
GUI apps may not always inherit the same PATH as an interactive terminal. When troubleshooting Codex, run command -v courier in the Codex terminal, not only in your regular shell.
Create the secrets directory:
mkdir -p ~/.courier-cli
chmod 700 ~/.courier-cliCreate:
~/.courier-cli/bot-credits
Example TOML:
[telegram.me]
bot_token = "123456:telegram-token"
chat_id = "123456789"
[xmpp.me]
jid = "pavel@example.org"
password = "app-password"
recipient_jid = "pavel@example.org"Protect the file:
chmod 600 ~/.courier-cli/bot-creditsNever commit this file to git.
-
Open Telegram and find the verified
@BotFatheraccount. -
Send:
/newbot -
Choose a display name for the bot.
-
Choose a username ending with
bot, for examplemy_courier_bot. -
BotFather will return a bot token. Keep it private.
-
Send any message to your new bot from the Telegram account that should receive files.
-
Add the token to
~/.courier-cli/bot-creditswith a temporary chat id:[telegram.me] bot_token = "123456:telegram-token" chat_id = "TODO"
-
Ask
courierto list chats seen by the bot:courier telegram chats --target telegram.me
The output should look like:
123456789 [private] (your_name) -
Replace
TODOwith the printed chat id:[telegram.me] bot_token = "123456:telegram-token" chat_id = "123456789"
Manual chat_id lookup alternative:
curl "https://api.telegram.org/bot<YOUR_BOT_TOKEN>/getUpdates"In the JSON response, find:
"chat":{"id":123456789}- Check the config:
courier config check
courier targets list-
Send a test file:
courier send ./README.md --target telegram.me
The command will ask for confirmation before sending.
XMPP file delivery uses XEP-0363 HTTP File Upload:
- The CLI logs in as the configured XMPP account.
- It discovers an HTTP File Upload service.
- It uploads the file through that service.
- It sends a chat message to
recipient_jidcontaining the uploaded file URL.
Add an XMPP target to ~/.courier-cli/bot-credits:
[xmpp.me]
jid = "pavel@example.org"
password = "app-password"
recipient_jid = "pavel@example.org"jid is the sender account used by the CLI. password is the password or app password for that sender account. recipient_jid is the account that receives the message with the uploaded file URL. If you send files to yourself, jid and recipient_jid can be the same.
If DNS SRV discovery is unavailable or you want to force a server address, add:
host = "xmpp.example.org"
port = "5222"Start without host and port when normal XMPP DNS discovery works. Add them only if your client needs a manually configured server address or if the JID domain is different from the actual XMPP host.
Check the config:
courier config check
courier targets listCheck XMPP login and HTTP File Upload discovery:
courier xmpp doctor --target xmpp.meSuccessful output looks like:
XMPP OK.
HTTP File Upload service: upload.example.org
Send a file:
courier send ./README.md --target xmpp.meThe command will ask for confirmation before connecting and uploading.
XMPP support depends on the server. The server must support XEP-0363 HTTP File Upload, and the upload endpoint must be reachable from this machine.
Every send operation asks for confirmation:
About to send:
File: /absolute/path/to/file.pdf
Size: 248.0 KB
Via: telegram
To: me
Proceed? [y/N]
The default answer is No. The file is sent only after an explicit y or yes.
During Telegram and XMPP uploads, the CLI periodically prints progress to stderr:
XMPP upload: starting upload (24.0 MB)
XMPP upload: 4.0 MB / 24.0 MB (16.7%)
XMPP upload: 8.0 MB / 24.0 MB (33.3%)
XMPP upload: upload complete, waiting for server response
Progress is reported for bytes read from the local file and passed to the HTTP upload request. After the upload reaches 100%, the command may still wait for the messenger or upload server to finish processing the request.
Uploads use a 10 minute timeout. If a file is too large or the network is too slow, retry on a faster connection or split the file.
The CLI refuses obvious sensitive files by name:
.env
.env.*
*.pem
*.key
id_rsa
id_ed25519
*.sqlite
*.db
*.kdbx
This denylist is only a guardrail. Always review the file path shown in the confirmation prompt before sending.
courier --help
courier doctor
courier config check
courier targets list
courier telegram chats --target telegram.me
courier xmpp doctor --target xmpp.me
courier send ./file.pdf --target telegram.me
courier send ./file.pdf --target xmpp.meThe send command validates the file, loads the target, asks for confirmation, and then dispatches through the selected provider.
Telegram and XMPP delivery are implemented.
If courier telegram chats --target telegram.me prints no chats, send a new normal message to the bot and run the command again. /start is often enough, but a regular text message is more reliable for getUpdates.
If courier config check reports a missing value, check the matching table in ~/.courier-cli/bot-credits.
If the CLI warns that bot-credits is too permissive, run:
chmod 600 ~/.courier-cli/bot-creditsIf courier xmpp doctor --target xmpp.me cannot find an HTTP File Upload service, check your XMPP server configuration for XEP-0363 support. Common server-side modules include Prosody mod_http_upload and ejabberd mod_http_upload.
python3.11 -m unittest discover -v