Current Version: v1.1.1

yeci-notify

A remote control for the agents the vendors left out

yeci-notify mirrors approvals, questions, and task updates from Claude Code, Codex, and Gemini CLI straight to your phone. Built for what the vendors' own remote features don't reach: Codex and Gemini CLI on a Linux server, and Claude Code when Remote Control isn't available to you. One static binary, zero dependencies.

$ curl -fsSL https://notify.yeci.org/install.sh | bash

Installs a single binary to ~/.local/bin. Nothing runs and nothing is configured until you activate it.

When to use this — and when not to

Use the official one if you can. If you run Claude Code with a claude.ai subscription on a direct connection to api.anthropic.com, use Anthropic's own Remote Control (claude --remote-control). It gives you the full conversation, @ file-path completion, image attachments, two-way sync and first-party push. yeci-notify does none of that, and does not intend to.

yeci-notify exists for the two cases that leaves behind.

1

Codex & Gemini CLI on a Linux server

Codex remote connections only support a macOS or Windows host: your phone talks to the ChatGPT desktop app, which then reaches your project over SSH. The chain is phone → your computer → SSH → server, and it needs your computer awake, online and signed in. Which is exactly what you don't have once you've walked away from your desk. Linux hosts are unsupported; Gemini CLI has no equivalent at all.

2

Claude Code, when Remote Control can't reach you

Remote Control is simply unavailable in all of these cases:

  • API key auth (including setup-token / CLAUDE_CODE_OAUTH_TOKEN)
  • ANTHROPIC_BASE_URL pointing at a relay or self-hosted gateway
  • Amazon Bedrock, Google Vertex, Microsoft Foundry
  • Orgs under Zero Data Retention or similar compliance policy
  • Team / Enterprise where an Owner hasn't enabled the toggle
  • DISABLE_TELEMETRY / DO_NOT_TRACK and friends

And one trade-off worth naming

Remote Control takes over the session — the connection becomes part of your task. Its own docs state that if the machine is awake but offline for more than about 10 minutes, the session times out and the process exits. For a long job left running overnight on a VPS, a network blip can kill it.

yeci-notify is a side channel. If the hook can't report, it exits silently; if the daemon is down, the socket is gone, or a payload won't parse, the agent simply falls back to its native terminal interaction. Your task can never stall or fail because of yeci-notify. The price is a lower ceiling: notify, approve, answer, steer — not full session takeover. That is a design choice, not a gap.

In one line

Claude Code + claude.ai subscription + direct connectionOfficial Remote Control
Claude Code + API key / gateway / Bedrock / ZDR / org disabledyeci-notify
Codex or Gemini CLI on a Linux serveryeci-notify
Several agents across several machines, one place to watch themyeci-notify
You won't accept "10 minutes offline kills the task"yeci-notify

Core Features

Notify

Get pinged the instant an agent needs a permission or finishes a task — foreground, tmux, nohup, or over SSH.

Approve

The terminal dialog, mirrored one-to-one: Allow, Deny, Deny with a note — plus the agent's own "always allow / don't ask again" options rendered as buttons.

Answer questions

When Claude asks a multiple-choice question, the options arrive as buttons. Tap one, or send your own answer with /send — the "something else" path, remotely.

Terminal & phone in parallel

Dialogs pop in the terminal instantly as always, while Telegram gets a full mirror with buttons. Handle it on either side — the other side is safely invalidated. No priority, no timeouts, no guessing where you are.

Remote steer

Inject instructions into a live session from your phone — every session notification carries the exact /send command to copy. Needs tmux; degrades gracefully to notify-only.

Multi-agent

Claude Code, Codex, and Gemini CLI unified behind one adapter layer, with defensive parsing across versions.

Single binary

One static CGO-free executable, no runtime dependencies. Present but absent until you run init — no processes, no config changes.

Secure by design

Outbound long-polling only — no public port or webhook. A single allowlisted Telegram ID; the token never touches logs or messages.

Agents & Platforms

Claude Code Codex Gemini CLI Telegram Linux macOS amd64 · arm64 systemd launchd tmux

Install

The install script detects your OS and architecture and drops one static binary into ~/.local/bin. Linux and macOS, amd64 and arm64.

# One-line install
curl -fsSL https://notify.yeci.org/install.sh | bash

# Custom install directory
YECI_BIN_DIR=/usr/local/bin curl -fsSL https://notify.yeci.org/install.sh | bash

Four static CGO_ENABLED=0 binaries per release, verified against checksums.txt.

Quick start

yeci-notify currently delivers notifications and interactive approvals through a Telegram bot only. Additional channels are on the roadmap.

One command activates everything. It is fully idempotent — re-run it any time to update.

yeci-notify init
  1. 1 Validate your Telegram Bot Token (from @BotFather) and capture your telegram_id (from @GotIDBot).
  2. 2 Detect installed agents and wire up their hooks — deep-merged, never overwriting your existing config.
  3. 3 Pick a launcher: systemd / launchd / shell lazy-start / manual, based on your environment.
  4. 4 Send a test notification to confirm the loop.

yeci-notify remove returns everything to factory state and restores every file it touched.

Telegram Bot Commands

/lsList sessions and each one's most recent event
/send <text>Inject text into the only active tmux session (lists candidates when there are several)
/send <session> <text>Inject text into a specific tmux pane
/pendingList pending approvals and re-render their buttons
/mute · /unmuteTemporarily silence notifications (approvals unaffected)
/statusRuntime status summary

Every message ends with a ready-to-copy command — the bot does not act on replies at all. /send <session> <text> steers a session; while an approval is open it denies with that note, and while a question is open it becomes your own answer. A Telegram reply points at one message, but a session keeps sending new ones — a command carries the session name, so it is never ambiguous.

Parallel by design

The terminal is the single source of truth: hooks never block it, and every dialog is mirrored to Telegram in full. Buttons and replies act on the terminal via tmux key injection — after verifying on screen that the dialog is still there.

Terminal acts first

You answer the dialog locally

The Telegram message flips to "handled in the terminal"; a late button tap is safely rejected instead of double-acting.

Phone acts first

You tap a button or send /send in Telegram

The choice is injected into the terminal dialog — visible and auditable in the session. The agent carries on immediately.

Neither yet

You are busy elsewhere

No timeouts: the buttons stay valid as long as the dialog is open in the terminal. Answer whenever you get to it.

Sending into a session that is already running on fresh input? The bot holds your message and asks before sending, so both ends never answer twice. Remote actions require tmux; without it, mirrors still arrive and you decide in the terminal.

Capabilities

The notification mainline (every row without a requirement) holds unconditionally, for every user and every way of running an agent. Remote decisions inject keys through tmux — the deliberate trade for a terminal that stays the single source of truth.

Feature Requires Without it
Done / idle / error notifications (reply text in full, auto-split if long)Always available
Approval / question mirror notification (full command & option text)Always available
Approval remote decision (allow / deny-with-note / "always allow" options)tmuxMirror still sent; buttons say "handle in the terminal"
Questions (AskUserQuestion) answered remotely (single / multi / custom)tmuxMirror still sent; answer in the terminal
/send session injectiontmux"Session not injectable"
/ls session listtmuxLists sessions seen in the event stream

Security

config.toml is forced to 0600. The token supports ${ENV_VAR} expansion, so it need never hit disk.

The token never appears in hook configs, logs, Telegram messages, or any distributed artifact.

A single Telegram ID allowlist is a hard check — non-matching updates are dropped and never answered.

Outbound long-polling only. No public port, webhook, or HTTPS certificate required.

Dangerous commands render a reject-only prompt by default — high-risk actions keep the friction of returning to the terminal.

Your bot token is a remote for your dev machine. Guard it accordingly; after remove, revoke it via @BotFather.