diff --git a/README.md b/README.md index d62b5f5..9657e41 100644 --- a/README.md +++ b/README.md @@ -48,3 +48,31 @@ Internal macOS menu bar app for secure multi-account switching. ./.build/arm64-apple-macosx/release/jnslayer2-helper doctor --json ./.build/arm64-apple-macosx/release/jnslayer2-helper relogin --stale --json ``` + +## Remote-Safe Worker Mode + +Keep the desktop Codex App and ChatGPT mobile remote-control pinned to the main +`~/.codex/auth.json` account. For quota-heavy work on another stored account, +run Codex through an isolated worker home instead of switching the shared auth: + +```bash +codex-worker --email worker@example.com -- login status +codex-worker --email worker@example.com -- exec -C "$PWD" "summarize this repo" +``` + +`codex-worker` materializes `~/.jnslayer2/codex-homes//auth.json` from +the stored account file and sets `CODEX_HOME` only for that process. It does not +modify `~/.codex/auth.json`, restart Codex.app, or affect ChatGPT mobile +remote-control presence. + +To protect the mobile remote-control account from accidental shared-auth +switches, run the guard with the ChatGPT account used on the phone: + +```bash +CODEX_REMOTE_MAIN_EMAIL=main@example.com ./Scripts/codex-remote-main-guard +``` + +If `~/.codex/auth.json` no longer matches that account, the guard restores the +stored auth from `~/.jnslayer2/accounts//auth.json` and re-enables the +Codex app-server remote-control daemon. Run it from launchd or another scheduler +if you want continuous protection. diff --git a/Scripts/codex-remote-main-guard b/Scripts/codex-remote-main-guard new file mode 100755 index 0000000..7239c3f --- /dev/null +++ b/Scripts/codex-remote-main-guard @@ -0,0 +1,72 @@ +#!/usr/bin/env bash +set -euo pipefail + +PINNED_EMAIL="${CODEX_REMOTE_MAIN_EMAIL:-}" +HOME_DIR="${HOME:?HOME is required}" +ACTIVE_AUTH="$HOME_DIR/.codex/auth.json" +PINNED_AUTH="$HOME_DIR/.jnslayer2/accounts/$PINNED_EMAIL/auth.json" +LOCK_DIR="/tmp/codex-remote-main-guard.lock" +CODEX_BIN="/Applications/Codex.app/Contents/Resources/codex" + +mkdir -p "$HOME_DIR/.codex" + +if [[ -z "$PINNED_EMAIL" ]]; then + echo "codex-remote-main-guard: set CODEX_REMOTE_MAIN_EMAIL to the ChatGPT account used by mobile remote-control" >&2 + exit 2 +fi + +if [[ ! -f "$PINNED_AUTH" ]]; then + echo "codex-remote-main-guard: pinned auth missing: $PINNED_AUTH" >&2 + exit 1 +fi + +active_account_id() { + /usr/bin/python3 - "$1" <<'PY' +import json +import sys +try: + with open(sys.argv[1], "r", encoding="utf-8") as handle: + auth = json.load(handle) + print((auth.get("tokens") or {}).get("account_id") or "") +except Exception: + print("") +PY +} + +if [[ -e "$LOCK_DIR" && ! -d "$LOCK_DIR" ]]; then + rm -f "$LOCK_DIR" +elif [[ -d "$LOCK_DIR" ]]; then + rmdir "$LOCK_DIR" 2>/dev/null || true +fi + +if ! mkdir "$LOCK_DIR" 2>/dev/null; then + exit 0 +fi +trap 'rmdir "$LOCK_DIR" 2>/dev/null || true' EXIT + +pinned_id="$(active_account_id "$PINNED_AUTH")" +current_id="" +if [[ -f "$ACTIVE_AUTH" ]]; then + current_id="$(active_account_id "$ACTIVE_AUTH")" +fi + +if [[ -z "$pinned_id" ]]; then + echo "codex-remote-main-guard: pinned auth has no account_id" >&2 + exit 1 +fi + +if [[ "$current_id" != "$pinned_id" ]]; then + install -m 600 "$PINNED_AUTH" "$ACTIVE_AUTH" + echo "codex-remote-main-guard: restored Codex remote account to $PINNED_EMAIL" + "$CODEX_BIN" app-server daemon restart \ + --enable remote_control \ + --enable hooks \ + --enable goals \ + --enable prevent_idle_sleep >/dev/null +fi + +"$CODEX_BIN" app-server daemon enable-remote-control \ + --enable remote_control \ + --enable hooks \ + --enable goals \ + --enable prevent_idle_sleep >/dev/null diff --git a/Scripts/codex-worker b/Scripts/codex-worker new file mode 100755 index 0000000..01c3421 --- /dev/null +++ b/Scripts/codex-worker @@ -0,0 +1,90 @@ +#!/usr/bin/env bash +set -euo pipefail + +usage() { + cat <<'EOF' +Usage: + codex-worker --email -- + +Examples: + codex-worker --email worker@example.com -- login status + codex-worker --email worker@example.com -- exec -C "$PWD" "summarize this repo" + +This runs Codex with an isolated CODEX_HOME under ~/.jnslayer2/codex-homes. +It does not modify ~/.codex/auth.json, so Codex App remote-control keeps using +the main desktop account. +EOF +} + +email="" +while [[ $# -gt 0 ]]; do + case "$1" in + --email) + if [[ $# -lt 2 ]]; then + echo "codex-worker: --email requires a value" >&2 + exit 2 + fi + email="$2" + shift 2 + ;; + --help|-h) + usage + exit 0 + ;; + --) + shift + break + ;; + *) + echo "codex-worker: unknown argument before --: $1" >&2 + usage >&2 + exit 2 + ;; + esac +done + +if [[ -z "$email" ]]; then + echo "codex-worker: missing --email" >&2 + usage >&2 + exit 2 +fi + +if [[ $# -eq 0 ]]; then + echo "codex-worker: missing codex arguments after --" >&2 + usage >&2 + exit 2 +fi + +account_auth="$HOME/.jnslayer2/accounts/$email/auth.json" +if [[ ! -f "$account_auth" ]]; then + echo "codex-worker: stored auth not found for $email" >&2 + echo "expected: $account_auth" >&2 + exit 1 +fi + +safe_name="$(printf '%s' "$email" | tr -cs 'A-Za-z0-9._@-' '_' | sed 's/^_//; s/_$//')" +worker_home="$HOME/.jnslayer2/codex-homes/$safe_name" +mkdir -p "$worker_home" +chmod 700 "$worker_home" + +install -m 600 "$account_auth" "$worker_home/auth.json" + +if [[ -f "$HOME/.codex/config.toml" && ! -f "$worker_home/config.toml" ]]; then + install -m 600 "$HOME/.codex/config.toml" "$worker_home/config.toml" +fi + +codex_bin="${JNSLAYER2_CODEX:-$HOME/.local/bin/codex}" +if [[ ! -x "$codex_bin" ]]; then + codex_bin="/Applications/Codex.app/Contents/Resources/codex" +fi +if [[ ! -x "$codex_bin" ]]; then + codex_bin="/opt/homebrew/bin/codex" +fi +if [[ ! -x "$codex_bin" ]]; then + echo "codex-worker: no executable codex binary found" >&2 + exit 1 +fi + +export CODEX_HOME="$worker_home" +export PATH="$HOME/.local/bin:/opt/homebrew/bin:/opt/homebrew/sbin:/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin" +exec "$codex_bin" "$@"