Language: English | 简体中文
MyCodex is a Telegram-driven remote coding gateway for Linux servers.
It manages multiple Git repositories inside one workspace and uses the locally installed codex CLI to provide isolated Codex runtimes and multiple conversation threads per repository.
- Run on Linux servers.
- Control Codex remotely through Telegram direct messages.
- Support multiple first-level repositories inside one workspace.
- Keep each repository isolated at the Codex runtime boundary.
- Allow multiple Codex threads per repository.
- Let the user switch repositories, switch threads, clone repositories, create new threads, and approve commands or patches from Telegram.
Internally, the system is built around three layers:
workspace- A container directory for repositories, such as
/srv/workspace - Only first-level child directories are scanned
- A container directory for repositories, such as
repo- The real working unit
- Each repo has its own Codex runtime boundary
- Switching repos does not reuse another repo's runtime
thread- A Codex conversation inside a repo
- One repo can have multiple threads
- One repo has exactly one active thread at a time
This means:
- Repo A and repo B do not share the same Codex process context
- Repo A can keep multiple historical threads
- When you switch back to repo A, you resume repo A's active thread instead of inheriting repo B's context
Current MVP capabilities:
- Telegram long polling
- Single-user allowlist
- Workspace first-level repository scan
- Clone new repositories into the workspace
/repo useto switch the active repo/thread newand/thread useinside each repo- Plain text messages routed to the active thread of the active repo
- Codex command approvals
- Codex file change approvals
- Local state persistence
This is still an MVP. Current constraints:
- Telegram only
- Single user only
- Only scans first-level directories under the workspace
- No nested repo, submodule, or git worktree support
cloneonly uses the default branch- Does not manage Codex login flows
- No web UI
- No multi-user isolation
Before deployment, prepare:
- A Linux server
- A working
codexCLI installation - Valid Codex authentication on that machine
- A Telegram bot token
- A working
gitsetup on the server - A workspace directory that will contain multiple repos
Recommended sanity checks:
codex --version
codex app-server --help
git --versionMyCodex does not manage Codex login. It only uses whatever authentication is already available on the server.
The recommended option is an environment variable:
export OPENAI_API_KEY=...If your server already has a working codex login state, that can be reused as well. The check command validates:
- Telegram token availability
codex app-serverinitialization- Workspace existence
- Repository scanning inside the workspace
Start from the example:
Example:
[workspace]
root = "/srv/workspace"
[telegram]
bot_token = "123456:replace-me"
access_mode = "pairing"
poll_timeout_seconds = 30
[codex]
bin = "codex"
# model = "gpt-5.1-codex"
[state]
dir = "/var/lib/mycodex"
[ui]
stream_edit_interval_ms = 1200
max_inline_diff_chars = 6000
[git]
clone_timeout_sec = 600
allow_ssh = true
allow_https = trueworkspace.root- Repository container directory
- Example:
/srv/workspace
telegram.bot_token- Telegram bot token
telegram.access_mode- Default recommended value is
pairing - In pairing mode, unknown users receive a pairing code instead of controlling the bot directly
- Default recommended value is
codex.bin- Path or command name for the
codexexecutable
- Path or command name for the
codex.model- Optional explicit model override for turns
state.dir- MyCodex state directory
- Stores repo/thread mappings, active repo/thread, temporary patch files, and related state
ui.stream_edit_interval_ms- Telegram streaming edit throttle interval
ui.max_inline_diff_chars- Diffs above this size are sent as
.patchfiles instead of inline text
- Diffs above this size are sent as
git.clone_timeout_sec- Timeout for
git clone
- Timeout for
git.allow_ssh- Whether SSH URLs are allowed
git.allow_https- Whether HTTPS URLs are allowed
For local development:
cargo build --release
./target/release/mycodex check --config ./config/config.example.toml
./target/release/mycodex serve --config ./config/config.example.tomlcheck performs startup validation. serve starts the Telegram polling loop and Codex event loop.
There are two separate installers in this repository:
- scripts/install.sh
- For users who already cloned the repository
- Builds from the local source tree and installs MyCodex
- public/install.sh
- For a website-hosted one-line installer
- Downloads a prebuilt release binary and installs MyCodex
These two entry points are intentionally separate.
Use this when:
- You already cloned the repository
- You want to build from the current source tree
- You are developing, testing, or doing a manual deployment
Example:
./scripts/install.sh \
--install-systemdThis mode will:
- Run
cargo build --releasefrom the current source tree - Install the release binary
- Generate config and environment templates
- Optionally install the systemd unit
- Print the next onboarding command
If an existing installation is detected, the script automatically switches to update mode:
- It keeps the existing config and env files
- It reuses the current systemd choice
- It restarts the service automatically if that service is already active
Use this when:
- Users do not want to clone the repository
- You want a single install command
- You publish release binaries to GitHub Releases or another download URL
Recommended one-line install:
curl -fsSL https://raw.githubusercontent.com/LeoGray/mycodex/main/public/install.sh | bashThis installs the binary, prepares config/state directories, optionally installs systemd, and then hands off to onboarding.
Advanced example:
curl -fsSL https://raw.githubusercontent.com/LeoGray/mycodex/main/public/install.sh | bash -s -- \
--install-systemdYou can also provide a full asset URL:
curl -fsSL https://raw.githubusercontent.com/LeoGray/mycodex/main/public/install.sh | bash -s -- \
--asset-url https://example.com/mycodex-x86_64-unknown-linux-gnu.tar.gzThis mode will:
- Infer the Linux target triple from the current machine
- Download the matching release archive
- Extract the
mycodexbinary - Continue with the same install-only flow used by the source installer
If an existing installation is detected, the public installer automatically switches to update mode:
- It keeps the current install layout
- It does not ask again whether systemd should be installed
- It restarts the service automatically if it is already active
For this open-source setup, you can use GitHub Raw directly:
curl -fsSL https://raw.githubusercontent.com/LeoGray/mycodex/main/public/install.sh | bashThe public installer already defaults to LeoGray/mycodex. If you fork or rebrand the project, update the default GitHub repo near the top of public/install.sh.
Both installers default to:
- Running the service as the current user instead of forcing a dedicated system user
- Reusing the current user's existing:
- Codex auth state
- Git credentials
- SSH keys
- Using these default paths:
- Binary:
/usr/local/bin/mycodex - Config:
/etc/mycodex/config.toml - Environment file:
/etc/mycodex/mycodex.env - Service:
/etc/systemd/system/mycodex.service - Workspace:
/srv/workspace - State dir:
/var/lib/mycodex
- Binary:
The source installer supports:
--update--run-user--run-group--workspace-root--state-dir--install-bin--config-path--env-path--service-path--install-systemd--skip-systemd--skip-build
The public installer supports:
--update--github-repo--release-version--asset-url--target-triple--run-user--run-group--workspace-root--state-dir--install-bin--config-path--env-path--service-path--install-systemd--skip-systemd
Supported installer environment variables:
MYCODEX_TELEGRAM_BOT_TOKENMYCODEX_RELEASE_GITHUB_REPOMYCODEX_RELEASE_VERSIONMYCODEX_RELEASE_ASSET_URLMYCODEX_RELEASE_TARGET_TRIPLEOPENAI_API_KEY
To support prebuilt release installs, the repo also includes:
Examples:
./scripts/package-release.sh --target x86_64-unknown-linux-gnu
./scripts/package-release.sh --target aarch64-unknown-linux-muslThis generates archives such as:
dist/mycodex-x86_64-unknown-linux-gnu.tar.gzdist/mycodex-aarch64-unknown-linux-musl.tar.gz
The release installer resolves assets using this naming convention by default.
The repository now includes two GitHub Actions workflows:
- ci.yml
- Runs
cargo checkandcargo teston GitHub-hostedubuntu-latest
- Runs
- release.yml
- Builds Linux release artifacts when you push a tag
- Currently produces:
mycodex-x86_64-unknown-linux-gnu.tar.gzmycodex-x86_64-unknown-linux-musl.tar.gz
- Uploads them to GitHub Releases
This is the missing piece that makes the public installer practical.
After installation, run:
mycodex onboardThe onboarding flow is interactive and currently handles:
- Telegram bot token validation via
getMe - Workspace root selection
- default:
$HOME/workspace - custom path allowed
- create-if-missing flow
- default:
- Optional
OPENAI_API_KEY - Optional start of an installed systemd service
The intended product flow is now:
curl -fsSL https://raw.githubusercontent.com/LeoGray/mycodex/main/public/install.sh | bash
mycodex onboardMyCodex now defaults to telegram.access_mode = "pairing".
That means:
- Unknown Telegram users do not control the bot directly
- They receive a pairing code
- An operator approves the code on the server
Server-side commands:
mycodex pairing list
mycodex pairing approve <CODE>
mycodex pairing reject <CODE>Typical first-time flow:
- Install MyCodex
- Run
mycodex onboard - Send a message to the bot from Telegram
- Receive a pairing code
- Approve it on the server with
mycodex pairing approve <CODE>
Use this template as a starting point:
deploy/systemd/mycodex.service
Suggested directory layout:
/usr/local/bin/mycodex/etc/mycodex/config.toml/etc/mycodex/mycodex.env/var/lib/mycodex/srv/workspace
sudo mkdir -p /etc/mycodex
sudo mkdir -p /var/lib/mycodex
sudo mkdir -p /srv/workspace
sudo cp ./target/release/mycodex /usr/local/bin/mycodex
sudo cp ./config/config.example.toml /etc/mycodex/config.tomlExample:
sudo tee /etc/mycodex/mycodex.env >/dev/null <<'EOF'
OPENAI_API_KEY=replace-me
EOFsudo cp ./deploy/systemd/mycodex.service /etc/systemd/system/mycodex.service
sudo systemctl daemon-reload
sudo systemctl enable --now mycodexsudo journalctl -u mycodex -f/start- Show help
/status- Show the active repo, active thread, runtime status, active turn, and pending approval
/abort- Interrupt the active turn
/repo list- List registered repos
/repo use <name>- Switch the active repo
/repo clone <git_url> [dir_name]- Clone a new repo into the workspace
/repo status- Show the current repo state
/repo rescan- Rescan existing repos under the workspace
/thread list- List threads inside the current repo
/thread new- Create a new thread in the current repo
/thread use <thread>- Switch to an older thread in the current repo
<thread>can be a list index or a local thread ID prefix
/thread status- Show the active thread in the current repo
Plain text is always routed to:
- The current active repo
- The current active thread
If the current repo does not yet have a thread, the first plain text message creates one automatically.
/repo rescan
/repo use repo-a
Fix the CI failure in this repository
What happens:
- MyCodex starts the Codex runtime for repo A
- If repo A has no thread yet, one is created automatically
- Further text messages go to repo A's active thread
/thread new
Let's rethink the implementation from scratch
What happens:
- You stay inside repo A
- No second repo runtime is started
- A new Codex thread is created under repo A
/repo use repo-b
Review the deployment scripts in this repository
What happens:
- Repo A's runtime is stopped
- Repo B's runtime is started
- Repo B uses its own thread set
- Repo A context is not reused
When Codex wants to run a higher-risk command or apply file changes, MyCodex sends an approval message in Telegram.
The message includes:
- Repo name
- Thread title
- CWD
- Command
- Reason
Buttons:
Approve onceDeclineAbort turn
The message includes:
- Repo name
- Thread title
- Changed paths
- Diff preview
If the diff is too large, a .patch file is sent as well.
Buttons:
Approve patchDecline patch
MyCodex stores the following under state.dir:
- Repo catalog
- Thread catalog for each repo
- Current active repo
- Current active thread
- Current pending approval
- Current progress message ID
After a service restart:
- Repo and thread mappings are preserved
- The active repo is restored when possible
- Stale active turns are not restored
- Stale pending approvals are not restored
On startup and during /repo rescan, MyCodex only scans first-level directories under workspace.root.
Example:
/srv/workspace
├── repo-a
├── repo-b
└── repo-c
Only directories like repo-a, repo-b, and repo-c are recognized.
Current clone behavior:
- Supports
https://... - Supports
ssh://... - Supports scp-style SSH URLs such as
git@host:org/repo.git - Uses the default branch
- Uses the repo basename as the default target directory
- Rejects the operation if the target directory already exists
- Relies on the server's existing SSH keys or credential helper for Git auth
Check:
- Whether the Telegram token is correct
- Whether
codexis onPATH - Whether
codex app-servercan start - Whether
OPENAI_API_KEYor existing login state is valid - Whether the workspace and state directories exist and are writable
Check:
- Whether the bot token is correct
- Whether the service was onboarded successfully
- Whether pairing requests exist via
mycodex pairing list - Whether the logs show
telegram polling failed
Check:
- Whether the server can access the remote Git host
- Whether SSH keys or credentials are valid
- Whether the target directory already exists
- Whether the URL is blocked by
git.allow_sshorgit.allow_https
Check:
codex --versioncodex app-server --helpOPENAI_API_KEYjournalctl -u mycodex -f