From a64c6b8c1a82c0d46379dd1e0eb42825173c70af Mon Sep 17 00:00:00 2001 From: Daniel Frysinger Date: Tue, 21 Jul 2026 20:04:59 -0600 Subject: [PATCH] Add Codex CLI as a third first-class backend Extends the multi-call wrapper so `co ` / `codex-agent ` launches Codex CLI alongside the existing `ca` (Copilot) and `cc` (Claude Code) commands. Same shape as the other two: its own workspace root (CODEX_WORKSPACE_BASE, default ~/Library/CloudStorage/Dropbox/codex-workspace), agent-/ subdirs, and a `codex-` tmux session prefix so `ca alpha`, `cc alpha`, and `co alpha` coexist on one tmux server. Resume strategy mirrors the Claude backend since Codex CLI has no session-id flag: if any rollout under ~/.codex/sessions has a session_meta line whose cwd matches $DIR, launch with `codex resume --last`; otherwise fresh `codex`. `resume --last` already filters by cwd, so we only need a boolean "has this agent run before" check. Scan uses ripgrep when available (~350ms across ~1000 sessions here) and falls back to `grep -rl` when it isn't. `ALLOW_ALL=true` appends `--dangerously-bypass-approvals-and-sandbox`, Codex's analogue of Copilot's `--allow-all` and Claude's `--dangerously-skip-permissions`. install.sh gains `--codex-workspace-base`, prompts for the codex root via the same _resolve_workspace_base helper, symlinks `codex-agent` and `co` alongside the existing four names, writes CODEX_WORKSPACE_BASE into the config, and reports `codex --version` in the backend-CLI detection block. uninstall.sh removes the new symlinks with the same exact-match collision guard. README rewrites the intro, "What you get", installer summary, first-launch, workspace-locations, config, and roadmap sections to name Codex as a third backend rather than a future one. --- README.md | 101 ++++++++++++++++++++++++++++++--------------------- bin/agent | 72 ++++++++++++++++++++++++++++++++---- install.sh | 72 ++++++++++++++++++++++++++++-------- uninstall.sh | 7 +++- 4 files changed, 186 insertions(+), 66 deletions(-) diff --git a/README.md b/README.md index 993b663..90a3467 100644 --- a/README.md +++ b/README.md @@ -2,13 +2,14 @@ A macOS bootstrap that turns one Mac into a always-reachable host for running named AI agent sessions — [GitHub Copilot -CLI](https://github.com/github/copilot-cli) and [Claude Code -CLI](https://docs.claude.com/en/docs/claude-code) are both first-class +CLI](https://github.com/github/copilot-cli), [Claude Code +CLI](https://docs.claude.com/en/docs/claude-code), and [Codex +CLI](https://developers.openai.com/codex/cli/) are all first-class backends, chosen per-command (`ca alpha` for Copilot, `cc alpha` for -Claude) — and reach them from any device (phone, tablet, another -laptop) over [Tailscale SSH](https://tailscale.com/kb/1193/tailscale-ssh) -and `tmux`, with [Termius](https://termius.com) as the terminal -client. +Claude, `co alpha` for Codex) — and reach them from any device (phone, +tablet, another laptop) over [Tailscale +SSH](https://tailscale.com/kb/1193/tailscale-ssh) and `tmux`, with +[Termius](https://termius.com) as the terminal client. The end state: tap a host in Termius on your phone, land in the same agent CLI conversation your laptop left mid-thought. @@ -36,19 +37,21 @@ section you need from the table of contents. ## What you get -- A single multi-call wrapper (`bin/agent`) installed under four names +- A single multi-call wrapper (`bin/agent`) installed under six names — `ca` / `copilot-agent` for the Copilot backend, `cc` / - `claude-agent` for the Claude Code backend — that launches or - reattaches to a named agent session. Keychain unlock, `tmux` session - management, and best-effort resumption of the previous CLI session - for that name (Copilot: stable UUID via `--session-id`; Claude: - `--continue` when the workspace has an existing session) are all - handled. + `claude-agent` for the Claude Code backend, `co` / `codex-agent` for + the Codex backend — that launches or reattaches to a named agent + session. Keychain unlock, `tmux` session management, and best-effort + resumption of the previous CLI session for that name (Copilot: stable + UUID via `--session-id`; Claude: `--continue` when the workspace has + an existing session; Codex: `resume --last` when the workspace has an + existing session) are all handled. Each backend has its own workspace root and its own tmux session - namespace, so the two never collide: + namespace, so the three never collide: - `ca alpha` → workspace `$COPILOT_WORKSPACE_BASE/agent-alpha`, tmux `alpha` - `cc alpha` → workspace `$CLAUDE_WORKSPACE_BASE/agent-alpha`, tmux `claude-alpha` + - `co alpha` → workspace `$CODEX_WORKSPACE_BASE/agent-alpha`, tmux `codex-alpha` - A one-shot installer for OS-level prerequisites: Homebrew, `tmux`, Tailscale (CLI build), and the MagicDNS resolver fix that Homebrew's Tailscale formula leaves out. @@ -143,9 +146,9 @@ The installer is idempotent — safe to re-run. It: - Installs Homebrew (if missing), `tmux`, and the Tailscale CLI build. - Writes `/etc/resolver/ts.net` (the MagicDNS resolver fix). -- Symlinks four names into `/usr/local/bin` — `copilot-agent`, - `ca`, `claude-agent`, `cc` — all pointing at the same multi-call - wrapper (`bin/agent`). +- Symlinks six names into `/usr/local/bin` — `copilot-agent`, + `ca`, `claude-agent`, `cc`, `codex-agent`, `co` — all pointing at + the same multi-call wrapper (`bin/agent`). - Stamps a managed block into `~/.tmux.conf` that hides tmux's status bar (the agents are full-screen TUIs and your Termius tabs already label each session). Toggle it back per-session with `prefix + b`. @@ -260,22 +263,29 @@ Install whichever backend CLI(s) you plan to use: The first launch opens an in-terminal login flow — follow the prompts to authenticate; the token lands under `~/.claude/`. -You can install one, the other, or both. The wrapper enforces the +- **Codex CLI** — per [the Codex CLI + install](https://developers.openai.com/codex/cli/) (`brew install + --cask codex` or the tarball). Run `codex login` and follow the + ChatGPT auth flow; state lands under `~/.codex/`. + +You can install any subset of the three. The wrapper enforces the presence of the backend binary in PATH only at launch time. ### 1.8 — Workspace locations Each backend has its own root directory; inside each, agents are `agent-/` subdirs (e.g., `agent-alpha/`, `agent-bravo/`). The -installer asks for both roots and auto-picks smart defaults: +installer asks for all three roots and auto-picks smart defaults: - **With Dropbox installed** (recommended — workspaces sync across Macs): - Copilot: `~/Library/CloudStorage/Dropbox/copilot-workspace` - Claude: `~/Library/CloudStorage/Dropbox/claude-workspace` + - Codex: `~/Library/CloudStorage/Dropbox/codex-workspace` - **Without Dropbox**: - Copilot: `~/copilot-workspace` - Claude: `~/claude-workspace` + - Codex: `~/codex-workspace` Press Enter to accept a default, or type any path (`~`, `$HOME`, and shell expansions all work). Re-running `install.sh` later keeps @@ -286,13 +296,14 @@ To pick paths non-interactively (or from a script): ```bash ./install.sh \ --copilot-workspace-base ~/code/copilot-workspace \ - --claude-workspace-base ~/code/claude-workspace + --claude-workspace-base ~/code/claude-workspace \ + --codex-workspace-base ~/code/codex-workspace ``` -This writes `COPILOT_WORKSPACE_BASE` and `CLAUDE_WORKSPACE_BASE` into -`~/.config/remote-agent-stack/config`. Edit that file directly any -time to move a workspace base later (you'll have to move existing -`/` directories under it by hand). +This writes `COPILOT_WORKSPACE_BASE`, `CLAUDE_WORKSPACE_BASE`, and +`CODEX_WORKSPACE_BASE` into `~/.config/remote-agent-stack/config`. Edit +that file directly any time to move a workspace base later (you'll +have to move existing `/` directories under it by hand). ### 1.9 — Test the wrapper locally @@ -308,6 +319,12 @@ Claude Code backend: cc alpha # tmux session `claude-alpha`, workspace `$CLAUDE_WORKSPACE_BASE/agent-alpha/` ``` +Codex backend: + +```bash +co alpha # tmux session `codex-alpha`, workspace `$CODEX_WORKSPACE_BASE/agent-alpha/` +``` + You should land inside the matching `tmux` session with the backend CLI running. Detach with `Ctrl-b d` (you're back at the Mac shell). Reattach with the same command again — should be instant. Now the Mac @@ -574,21 +591,24 @@ file looks roughly like: ```sh COPILOT_WORKSPACE_BASE="/Users/you/.../Dropbox/copilot-workspace" # set during install CLAUDE_WORKSPACE_BASE="/Users/you/.../Dropbox/claude-workspace" # set during install +CODEX_WORKSPACE_BASE="/Users/you/.../Dropbox/codex-workspace" # set during install MAILBOX_INTEGRATION="false" # off by default ALLOW_ALL="false" # off by default # COPILOT_BIN="copilot" # CLAUDE_BIN="claude" +# CODEX_BIN="codex" # AGENT_DIR_PREFIX="agent-" ``` -`COPILOT_WORKSPACE_BASE` and `CLAUDE_WORKSPACE_BASE` are whatever you -picked at install time (Dropbox defaults if Dropbox is installed, -otherwise `$HOME/copilot-workspace` and `$HOME/claude-workspace`). -Inside each root, agents live in `agent-/` subdirs (change the -prefix with `AGENT_DIR_PREFIX` if you want a different convention). -Re-run `./install.sh` with `--copilot-workspace-base PATH` / -`--claude-workspace-base PATH` or edit the file directly to move a -root. +`COPILOT_WORKSPACE_BASE`, `CLAUDE_WORKSPACE_BASE`, and +`CODEX_WORKSPACE_BASE` are whatever you picked at install time (Dropbox +defaults if Dropbox is installed, otherwise `$HOME/copilot-workspace`, +`$HOME/claude-workspace`, and `$HOME/codex-workspace`). Inside each +root, agents live in `agent-/` subdirs (change the prefix with +`AGENT_DIR_PREFIX` if you want a different convention). Re-run +`./install.sh` with `--copilot-workspace-base PATH` / +`--claude-workspace-base PATH` / `--codex-workspace-base PATH` or edit +the file directly to move a root. `MAILBOX_INTEGRATION` enables the cross-session [mailbox](https://github.com/dfrysinger/skills) skill — `ca` pokes the @@ -597,14 +617,13 @@ unless you have the skill installed. `ALLOW_ALL` makes the wrapper skip permission prompts on new-session launch — Copilot: `--allow-all` (auto-approves tools, paths, URLs); -Claude Code: `--dangerously-skip-permissions`. Personal-machine +Claude Code: `--dangerously-skip-permissions`; Codex: +`--dangerously-bypass-approvals-and-sandbox`. Personal-machine convenience; do not enable in shared environments. -`COPILOT_BIN` / `CLAUDE_BIN` let you point the wrapper at a specific -backend binary (e.g., `/opt/homebrew/bin/copilot` or a nightly build). - -Additional agent backends (Codex CLI, etc.) are on the roadmap — see -[Status](#status--roadmap). +`COPILOT_BIN` / `CLAUDE_BIN` / `CODEX_BIN` let you point the wrapper +at a specific backend binary (e.g., `/opt/homebrew/bin/copilot` or a +nightly build). ### tmux keychain bootstrap LaunchAgent @@ -665,9 +684,9 @@ place — see the uninstaller output for the manual cleanup commands. ## Status & roadmap Single-machine, single-user, macOS arm64. Tested on macOS Tahoe. -Copilot CLI and Claude Code CLI are both first-class backends today. -Linux support and a Codex CLI backend are [open -issues](https://github.com/dfrysinger/remote-agent-stack/issues) — +Copilot CLI, Claude Code CLI, and Codex CLI are all first-class +backends today. Linux support is an [open +issue](https://github.com/dfrysinger/remote-agent-stack/issues) — patches welcome. ## Other docs diff --git a/bin/agent b/bin/agent index e12ba97..eddb632 100755 --- a/bin/agent +++ b/bin/agent @@ -1,34 +1,40 @@ #!/usr/bin/env bash -# agent — launch or attach to a named agent session (Copilot or Claude Code) +# agent — launch or attach to a named agent session +# (Copilot CLI, Claude Code CLI, or Codex CLI) # -# This is a multi-call script: the backend (Copilot CLI vs Claude Code CLI) -# is chosen by the name it's invoked as. install.sh symlinks four names in -# /usr/local/bin: +# This is a multi-call script: the backend is chosen by the name it's +# invoked as. install.sh symlinks six names in /usr/local/bin: # copilot-agent (or ca) → Copilot CLI backend # claude-agent (or cc) → Claude Code CLI backend +# codex-agent (or co) → Codex CLI backend # # Usage: # copilot-agent (or: ca ) # claude-agent (or: cc ) +# codex-agent (or: co ) # # Layout: each backend has its own workspace root; inside each, agents # live in AGENT_DIR_PREFIX-prefixed subdirs so they're easy to spot as # agent workspaces alongside other project dirs: # copilot: $COPILOT_WORKSPACE_BASE/agent- (tmux session: ) # claude : $CLAUDE_WORKSPACE_BASE/agent- (tmux session: claude-) +# codex : $CODEX_WORKSPACE_BASE/agent- (tmux session: codex-) # -# The claude tmux session name is prefixed only so `ca alpha` and `cc alpha` -# can coexist on the same tmux server without collision. +# The claude/codex tmux session names are prefixed only so `ca alpha`, +# `cc alpha`, and `co alpha` can coexist on the same tmux server. # # Configuration (env or ~/.config/remote-agent-stack/config): # COPILOT_WORKSPACE_BASE default: ~/Library/CloudStorage/Dropbox/copilot-workspace # CLAUDE_WORKSPACE_BASE default: ~/Library/CloudStorage/Dropbox/claude-workspace +# CODEX_WORKSPACE_BASE default: ~/Library/CloudStorage/Dropbox/codex-workspace # COPILOT_BIN default: copilot # CLAUDE_BIN default: claude +# CODEX_BIN default: codex # AGENT_DIR_PREFIX default: agent- # ALLOW_ALL default: false # copilot: --allow-all # claude : --dangerously-skip-permissions +# codex : --dangerously-bypass-approvals-and-sandbox # MAILBOX_INTEGRATION default: false set -euo pipefail @@ -38,6 +44,7 @@ set -euo pipefail case "$(basename "$0")" in ca|copilot-agent) BACKEND="copilot" ;; cc|claude-agent) BACKEND="claude" ;; + co|codex-agent) BACKEND="codex" ;; agent) BACKEND="copilot" ;; # fallback for direct invocation *) BACKEND="copilot" ;; esac @@ -60,8 +67,10 @@ fi : "${COPILOT_WORKSPACE_BASE:=$HOME/Library/CloudStorage/Dropbox/copilot-workspace}" : "${CLAUDE_WORKSPACE_BASE:=$HOME/Library/CloudStorage/Dropbox/claude-workspace}" +: "${CODEX_WORKSPACE_BASE:=$HOME/Library/CloudStorage/Dropbox/codex-workspace}" : "${COPILOT_BIN:=copilot}" : "${CLAUDE_BIN:=claude}" +: "${CODEX_BIN:=codex}" : "${AGENT_DIR_PREFIX:=agent-}" case "$BACKEND" in @@ -75,6 +84,11 @@ case "$BACKEND" in WORKSPACE_BASE="$CLAUDE_WORKSPACE_BASE" SESSION_PREFIX="claude-" ;; + codex) + AGENT_BIN="$CODEX_BIN" + WORKSPACE_BASE="$CODEX_WORKSPACE_BASE" + SESSION_PREFIX="codex-" + ;; esac # ---- args ------------------------------------------------------------------ @@ -93,8 +107,9 @@ Options: Config: ~/.config/remote-agent-stack/config - Env: COPILOT_WORKSPACE_BASE, CLAUDE_WORKSPACE_BASE, COPILOT_BIN, - CLAUDE_BIN, AGENT_DIR_PREFIX, ALLOW_ALL, MAILBOX_INTEGRATION + Env: COPILOT_WORKSPACE_BASE, CLAUDE_WORKSPACE_BASE, CODEX_WORKSPACE_BASE, + COPILOT_BIN, CLAUDE_BIN, CODEX_BIN, AGENT_DIR_PREFIX, ALLOW_ALL, + MAILBOX_INTEGRATION USAGE } @@ -279,6 +294,47 @@ case "$BACKEND" in CMD="$CMD --dangerously-skip-permissions" fi ;; + + codex) + # Codex CLI stores each session as a `rollout-*.jsonl` file under + # ~/.codex/sessions/YYYY/MM/DD/ + # The first line is a `session_meta` record whose `cwd` field is the + # working directory Codex was launched from. `codex resume --last` picks + # the most recent session whose cwd matches the current cwd (unless + # --all is passed), so we only need a yes/no answer to "has this agent + # ever run?". If yes → `codex resume --last`; else → fresh `codex`. + # + # Scan strategy: prefer `rg` (fast, common) and fall back to `grep -r` + # so the wrapper still works when ripgrep isn't installed. We match the + # exact `"cwd":"$DIR"` JSON substring against every rollout file, but + # stop at the first hit — session_meta is always on line 1, and the + # search prunes irrelevant files quickly. Local timing: ~350ms for + # ~1000 sessions with rg, well under the human-perceptible threshold. + HAS_SESSION=false + CODEX_SESSIONS_DIR="$HOME/.codex/sessions" + if [ -d "$CODEX_SESSIONS_DIR" ]; then + _cwd_pat="\"cwd\":\"$DIR\"" + if command -v rg >/dev/null 2>&1; then + if rg -l --max-count 1 -F "$_cwd_pat" "$CODEX_SESSIONS_DIR" \ + >/dev/null 2>&1; then + HAS_SESSION=true + fi + else + if grep -rl --include='*.jsonl' -F "$_cwd_pat" \ + "$CODEX_SESSIONS_DIR" 2>/dev/null | head -n1 | grep -q .; then + HAS_SESSION=true + fi + fi + fi + + CMD="$AGENT_BIN" + if [ "$HAS_SESSION" = "true" ]; then + CMD="$CMD resume --last" + fi + if [ "${ALLOW_ALL:-false}" = "true" ]; then + CMD="$CMD --dangerously-bypass-approvals-and-sandbox" + fi + ;; esac [ -n "$MAILBOX_POKE" ] && ( "$MAILBOX_POKE" "$SESSION" --wait ) & diff --git a/install.sh b/install.sh index 2419425..4973242 100755 --- a/install.sh +++ b/install.sh @@ -24,6 +24,7 @@ set -euo pipefail COPILOT_WORKSPACE_BASE_ARG="" CLAUDE_WORKSPACE_BASE_ARG="" +CODEX_WORKSPACE_BASE_ARG="" usage() { cat </ subdirs). --claude-workspace-base PATH Where claude agent workspaces live (parent dir for agent-/ subdirs). + --codex-workspace-base PATH Where codex agent workspaces live + (parent dir for agent-/ subdirs). - If either flag is omitted, the installer auto-detects Dropbox and + If any flag is omitted, the installer auto-detects Dropbox and prompts interactively with smart defaults. -h, --help Show this help and exit. @@ -54,6 +57,11 @@ while [ $# -gt 0 ]; do CLAUDE_WORKSPACE_BASE_ARG="$2"; shift 2 ;; --claude-workspace-base=*) CLAUDE_WORKSPACE_BASE_ARG="${1#*=}"; shift ;; + --codex-workspace-base) + [ $# -ge 2 ] || { echo "--codex-workspace-base requires a path" >&2; exit 2; } + CODEX_WORKSPACE_BASE_ARG="$2"; shift 2 ;; + --codex-workspace-base=*) + CODEX_WORKSPACE_BASE_ARG="${1#*=}"; shift ;; -h|--help) usage; exit 0 ;; *) @@ -70,6 +78,8 @@ WRAPPER_DST="/usr/local/bin/copilot-agent" WRAPPER_SHORT_DST="/usr/local/bin/ca" CLAUDE_WRAPPER_DST="/usr/local/bin/claude-agent" CLAUDE_WRAPPER_SHORT_DST="/usr/local/bin/cc" +CODEX_WRAPPER_DST="/usr/local/bin/codex-agent" +CODEX_WRAPPER_SHORT_DST="/usr/local/bin/co" SS_SRC="$REPO_ROOT/bin/ss" SS_DST="/usr/local/bin/ss" VNCFIX_SRC="$REPO_ROOT/bin/vncfix" @@ -240,11 +250,12 @@ else NEED_SYMLINK=true fi -# Same collision-avoidance for the claude-agent long name and the 'cc' short -# alias. All four names ('copilot-agent', 'ca', 'claude-agent', 'cc') point at -# the same multi-call wrapper — backend is chosen from $0's basename inside -# the script. -for _dst in "$CLAUDE_WRAPPER_DST" "$CLAUDE_WRAPPER_SHORT_DST"; do +# Same collision-avoidance for the claude/codex long names and their short +# aliases. All six names ('copilot-agent', 'ca', 'claude-agent', 'cc', +# 'codex-agent', 'co') point at the same multi-call wrapper — backend is +# chosen from $0's basename inside the script. +for _dst in "$CLAUDE_WRAPPER_DST" "$CLAUDE_WRAPPER_SHORT_DST" \ + "$CODEX_WRAPPER_DST" "$CODEX_WRAPPER_SHORT_DST"; do if [ -L "$_dst" ] && [ "$(readlink "$_dst")" = "$WRAPPER_SRC" ]; then : # already correct elif [ -e "$_dst" ]; then @@ -282,6 +293,7 @@ if $NEED_TAILSCALED_START || $NEED_RESOLVER_WRITE || $NEED_USRLOCALBIN_MKDIR || $NEED_USRLOCALBIN_MKDIR && echo " • create /usr/local/bin" $NEED_SYMLINK && echo " • symlink $WRAPPER_DST, $WRAPPER_SHORT_DST -> $WRAPPER_SRC" $NEED_SYMLINK && echo " • symlink $CLAUDE_WRAPPER_DST, $CLAUDE_WRAPPER_SHORT_DST -> $WRAPPER_SRC" + $NEED_SYMLINK && echo " • symlink $CODEX_WRAPPER_DST, $CODEX_WRAPPER_SHORT_DST -> $WRAPPER_SRC" $NEED_SYMLINK && echo " • symlink $SS_DST, $VNCFIX_DST -> bin/{ss,vncfix}" echo echo " ============================================================" @@ -302,12 +314,14 @@ if $NEED_TAILSCALED_START || $NEED_RESOLVER_WRITE || $NEED_USRLOCALBIN_MKDIR || WRAPPER_SHORT_DST="$WRAPPER_SHORT_DST" \ CLAUDE_WRAPPER_DST="$CLAUDE_WRAPPER_DST" \ CLAUDE_WRAPPER_SHORT_DST="$CLAUDE_WRAPPER_SHORT_DST" \ + CODEX_WRAPPER_DST="$CODEX_WRAPPER_DST" \ + CODEX_WRAPPER_SHORT_DST="$CODEX_WRAPPER_SHORT_DST" \ SS_SRC="$SS_SRC" \ SS_DST="$SS_DST" \ VNCFIX_SRC="$VNCFIX_SRC" \ VNCFIX_DST="$VNCFIX_DST" \ BREW_BIN="$(command -v brew)" \ - sudo --preserve-env=NEED_TAILSCALED_START,NEED_RESOLVER_WRITE,NEED_USRLOCALBIN_MKDIR,NEED_SYMLINK,RESOLVER_SRC,RESOLVER_DST,WRAPPER_SRC,WRAPPER_DST,WRAPPER_SHORT_DST,CLAUDE_WRAPPER_DST,CLAUDE_WRAPPER_SHORT_DST,SS_SRC,SS_DST,VNCFIX_SRC,VNCFIX_DST,BREW_BIN \ + sudo --preserve-env=NEED_TAILSCALED_START,NEED_RESOLVER_WRITE,NEED_USRLOCALBIN_MKDIR,NEED_SYMLINK,RESOLVER_SRC,RESOLVER_DST,WRAPPER_SRC,WRAPPER_DST,WRAPPER_SHORT_DST,CLAUDE_WRAPPER_DST,CLAUDE_WRAPPER_SHORT_DST,CODEX_WRAPPER_DST,CODEX_WRAPPER_SHORT_DST,SS_SRC,SS_DST,VNCFIX_SRC,VNCFIX_DST,BREW_BIN \ bash -euo pipefail <<'PRIVILEGED_BLOCK' if [ "$NEED_TAILSCALED_START" = "true" ]; then echo " → starting tailscaled" @@ -328,11 +342,13 @@ if $NEED_TAILSCALED_START || $NEED_RESOLVER_WRITE || $NEED_USRLOCALBIN_MKDIR || rm -f "$WRAPPER_DST" ln -s "$WRAPPER_SRC" "$WRAPPER_DST" - # Short 'ca' alias + claude-agent names: only create/replace if absent, + # Short 'ca' alias + claude/codex names: only create/replace if absent, # or already a symlink pointing at THIS wrapper. Leave any pre-existing - # real file OR foreign symlink alone (collision avoidance). All four + # real file OR foreign symlink alone (collision avoidance). All six # names point at the same multi-call wrapper. - for _alias_dst in "$WRAPPER_SHORT_DST" "$CLAUDE_WRAPPER_DST" "$CLAUDE_WRAPPER_SHORT_DST"; do + for _alias_dst in "$WRAPPER_SHORT_DST" \ + "$CLAUDE_WRAPPER_DST" "$CLAUDE_WRAPPER_SHORT_DST" \ + "$CODEX_WRAPPER_DST" "$CODEX_WRAPPER_SHORT_DST"; do if [ ! -e "$_alias_dst" ] || { [ -L "$_alias_dst" ] && [ "$(readlink "$_alias_dst")" = "$WRAPPER_SRC" ]; }; then echo " → symlinking $_alias_dst" rm -f "$_alias_dst" @@ -359,6 +375,8 @@ PRIVILEGED_BLOCK $NEED_SYMLINK && [ -L "$WRAPPER_SHORT_DST" ] && [ "$(readlink "$WRAPPER_SHORT_DST")" = "$WRAPPER_SRC" ] && ok "$WRAPPER_SHORT_DST -> $WRAPPER_SRC" || true $NEED_SYMLINK && [ -L "$CLAUDE_WRAPPER_DST" ] && [ "$(readlink "$CLAUDE_WRAPPER_DST")" = "$WRAPPER_SRC" ] && ok "$CLAUDE_WRAPPER_DST -> $WRAPPER_SRC" || true $NEED_SYMLINK && [ -L "$CLAUDE_WRAPPER_SHORT_DST" ] && [ "$(readlink "$CLAUDE_WRAPPER_SHORT_DST")" = "$WRAPPER_SRC" ] && ok "$CLAUDE_WRAPPER_SHORT_DST -> $WRAPPER_SRC" || true + $NEED_SYMLINK && [ -L "$CODEX_WRAPPER_DST" ] && [ "$(readlink "$CODEX_WRAPPER_DST")" = "$WRAPPER_SRC" ] && ok "$CODEX_WRAPPER_DST -> $WRAPPER_SRC" || true + $NEED_SYMLINK && [ -L "$CODEX_WRAPPER_SHORT_DST" ] && [ "$(readlink "$CODEX_WRAPPER_SHORT_DST")" = "$WRAPPER_SRC" ] && ok "$CODEX_WRAPPER_SHORT_DST -> $WRAPPER_SRC" || true $NEED_SYMLINK && [ -L "$SS_DST" ] && [ "$(readlink "$SS_DST")" = "$SS_SRC" ] && ok "$SS_DST -> $SS_SRC" || true $NEED_SYMLINK && [ -L "$VNCFIX_DST" ] && [ "$(readlink "$VNCFIX_DST")" = "$VNCFIX_SRC" ] && ok "$VNCFIX_DST -> $VNCFIX_SRC" || true else @@ -388,6 +406,7 @@ _read_config_var() { EXISTING_COPILOT_WORKSPACE_BASE="$(_read_config_var COPILOT_WORKSPACE_BASE)" EXISTING_CLAUDE_WORKSPACE_BASE="$(_read_config_var CLAUDE_WORKSPACE_BASE)" +EXISTING_CODEX_WORKSPACE_BASE="$(_read_config_var CODEX_WORKSPACE_BASE)" EXISTING_LEGACY_WORKSPACE_BASE="$(_read_config_var WORKSPACE_BASE)" if [ -z "$EXISTING_COPILOT_WORKSPACE_BASE" ] && [ -n "$EXISTING_LEGACY_WORKSPACE_BASE" ]; then @@ -398,10 +417,12 @@ fi if [ -d "$HOME/Library/CloudStorage/Dropbox" ]; then COPILOT_SMART_DEFAULT="$HOME/Library/CloudStorage/Dropbox/copilot-workspace" CLAUDE_SMART_DEFAULT="$HOME/Library/CloudStorage/Dropbox/claude-workspace" + CODEX_SMART_DEFAULT="$HOME/Library/CloudStorage/Dropbox/codex-workspace" SMART_DEFAULT_REASON="Dropbox detected — workspaces will sync across Macs" else COPILOT_SMART_DEFAULT="$HOME/copilot-workspace" CLAUDE_SMART_DEFAULT="$HOME/claude-workspace" + CODEX_SMART_DEFAULT="$HOME/codex-workspace" SMART_DEFAULT_REASON="no Dropbox found" fi @@ -445,6 +466,10 @@ _resolve_workspace_base claude "$CLAUDE_WORKSPACE_BASE_ARG" \ "$EXISTING_CLAUDE_WORKSPACE_BASE" "$CLAUDE_SMART_DEFAULT" CLAUDE_WORKSPACE_BASE_RESOLVED="$RESOLVED_WS" +_resolve_workspace_base codex "$CODEX_WORKSPACE_BASE_ARG" \ + "$EXISTING_CODEX_WORKSPACE_BASE" "$CODEX_SMART_DEFAULT" +CODEX_WORKSPACE_BASE_RESOLVED="$RESOLVED_WS" + # ---- mailbox integration prompt ------------------------------------------- # # The optional dfrysinger-skills `mailbox` skill lets one named agent send @@ -551,24 +576,27 @@ bold "Configuration" mkdir -p "$CONFIG_DIR" -# Always (re-)write the config so both workspace bases match what we resolved. -# COPILOT_BIN and AGENT_DIR_PREFIX stay as commented defaults — the wrapper +# Always (re-)write the config so every workspace base matches what we resolved. +# The *_BIN and AGENT_DIR_PREFIX vars stay as commented defaults — the wrapper # falls back to its own defaults if they're absent. cat > "$CONFIG_DIR/config" < Copilot CLI (workspace: \$COPILOT_WORKSPACE_BASE/agent-) # claude-agent / cc -> Claude Code CLI (workspace: \$CLAUDE_WORKSPACE_BASE/agent-) +# codex-agent / co -> Codex CLI (workspace: \$CODEX_WORKSPACE_BASE/agent-) # Where each backend's agent working directories live. COPILOT_WORKSPACE_BASE="$COPILOT_WORKSPACE_BASE_RESOLVED" CLAUDE_WORKSPACE_BASE="$CLAUDE_WORKSPACE_BASE_RESOLVED" +CODEX_WORKSPACE_BASE="$CODEX_WORKSPACE_BASE_RESOLVED" # Backend CLI binaries (must be in PATH). Uncomment to override. # COPILOT_BIN="copilot" # CLAUDE_BIN="claude" +# CODEX_BIN="codex" # AGENT_DIR_PREFIX: prefix for per-agent working directories under each # backend's workspace root. Uncomment to override. @@ -581,7 +609,10 @@ CLAUDE_WORKSPACE_BASE="$CLAUDE_WORKSPACE_BASE_RESOLVED" MAILBOX_INTEGRATION="$MAILBOX_INTEGRATION_RESOLVED" # ALLOW_ALL: when "true", the wrapper skips permission prompts on -# new-session launch (copilot: --allow-all; claude: --dangerously-skip-permissions). +# new-session launch: +# copilot: --allow-all +# claude : --dangerously-skip-permissions +# codex : --dangerously-bypass-approvals-and-sandbox # Personal-machine convenience; do NOT enable in shared environments. ALLOW_ALL="$ALLOW_ALL_RESOLVED" EOF @@ -780,6 +811,13 @@ else echo " Install per https://docs.claude.com/en/docs/claude-code/quickstart" fi +if have codex; then + ok "codex CLI found ($(codex --version 2>&1 | head -1))" +else + warn "codex CLI not found in PATH — 'co' / 'codex-agent' will error until installed." + echo " Install per https://developers.openai.com/codex/cli/" +fi + # ---- manual steps --------------------------------------------------------- # # FDA paths must be the REAL binary (the Cellar path), not the symlink @@ -836,12 +874,16 @@ cat <