Skip to content

Add MyCodex: a cross-platform desktop app for the @openai/codex engine#1

Open
devin-ai-integration[bot] wants to merge 2 commits into
devfrom
devin/1781369527-mycodex-desktop
Open

Add MyCodex: a cross-platform desktop app for the @openai/codex engine#1
devin-ai-integration[bot] wants to merge 2 commits into
devfrom
devin/1781369527-mycodex-desktop

Conversation

@devin-ai-integration

@devin-ai-integration devin-ai-integration Bot commented Jun 13, 2026

Copy link
Copy Markdown

Summary

MyCodex is a cross-platform Electron desktop app (isolated top-level mycodex/ dir, not a bun workspace, so it does not touch opencode's install/typecheck) that wraps the official open-source @openai/codex engine. Rather than reimplementing Codex's chat/approvals/diff UI, it embeds the real Codex TUI in an xterm.js terminal driven by node-pty — so every acceptance feature (send a message, file-edit approval, diff, interrupt, resume) is the engine's own native behavior rendered inside a desktop shell with a session sidebar, working-folder picker, and an Interrupt button.

Context: the original ask was a macOS pixel-copy of Codex.app. This VM is Linux and none of the referenced assets (Codex.app, reference screenshots, ~/.codex/auth.json) exist here, so — per agreement in the session — this is a cross-platform build that runs/tests on Linux, is not pixel-matched, and uses codex login for auth.

How it works

  • Engine spawn (src/main/main.js, src/main/codex.js): pty.spawn(codexBinary, resumeId ? ["resume", resumeId] : [], { env }). Output streams to the renderer via pty:data → term.write; keystrokes go term.onData → writePty → engine stdin. The Interrupt button sends ESC: pty:interrupt → term.write("\x1b").
  • Three documented pitfalls handled:
    1. GUI launch has crippled PATH / no TERMsrc/main/env.js resolves a full login-shell env ($SHELL -ilc) before spawning, merges PATH, sets TERM=xterm-256color.
    2. asar:false in package.json build config so the bundled engine binary is spawnable.
    3. node-pty spawn-helper needs chmod +xscripts/fix-pty-permissions.js runs at postinstall and app startup (no-op on Linux which uses forkpty; needed on macOS).
  • Sessions/restore: codex.js listSessions() reads ~/.codex/sessions/*.jsonl; clicking a session calls launch({resumeId})codex resume <id>. Working folder + last session persist in localStorage and reload on init.

Packaging fixes in this PR (commit e216a4e)

Two real bugs found by testing the packaged build (not just the dev tree):

  • Nested-binary resolution: electron-builder's production install nests the platform package under @openai/codex/node_modules/@openai/codex-linux-x64 instead of hoisting it. codex.js now searches both hoisted and nested roots (candidateNodeModuleRoots + recursive searchBinary fallback).
  • Missing scripts/ in the bundle: scripts/**/* was absent from the electron-builder files glob, so the packaged app crashed with Cannot find module '../../scripts/fix-pty-permissions'. Added to files.

Testing status

Tested the packaged Linux build (release/linux-unpacked/mycodex, asar:false) on the VM GUI. A recorded walkthrough of six auth-free tests all passed: launch + engine detection (engine: codex ✓), working-folder picker, real Codex TUI rendering through the PTY, bidirectional keystrokes, Interrupt-button ESC delivery, and working-folder persistence across a full restart.

Untested (auth skipped by request): the credentialed agent flow — send a message, file-edit approval, diff view, functional turn-interrupt, real codex resume. These mechanisms are wired (paths above) but were not exercised live; they "should work in theory" but I have not verified them.

Known minor cosmetic quirk (not functional): the sidebar folder box renders ~/Projects/mycodex as Projects/mycodex/~ because .folder-path uses direction: rtl (styles.css:115) for tail-truncation. The stored value is correct (top-bar shows ~/Projects/mycodex; folder persists correctly). One-line CSS follow-up available.

See the test-results comment below for screenshots and the recording.

Link to Devin session: https://app.devin.ai/sessions/a2330658806b42acab0d0b0ce33ddecf
Requested by: @01clauding

…/codex engine

MyCodex wraps the official Codex engine in an Electron desktop shell with a
sessions sidebar, working-folder picker, interrupt control, and the engine
running in an embedded xterm.js terminal driven by node-pty.

Handles the three GUI-packaging pitfalls: login-shell PATH/TERM reconstruction
before spawning the engine, asar:false so the bundled binary is spawnable, and
chmod +x on node-pty's spawn-helper / the engine binary.

Isolated under mycodex/ (not a bun workspace), so it does not affect the
opencode monorepo's install or typecheck.
@devin-ai-integration

Copy link
Copy Markdown
Author

🤖 Devin AI Engineer

I'll be helping with this pull request! Here's what you should know:

✅ I will automatically:

  • Address comments on this PR. Add '(aside)' to your comment to have me ignore it.
  • Look at CI failures and help fix them

Note: I can only respond to comments from users who have write access to this repository.

⚙️ Control Options:

  • Disable automatic comment, CI, and merge conflict monitoring

Found while testing the electron-builder output:
- The platform engine package is hoisted differently when installed by the
  packager (nested under @openai/codex/node_modules), so findCodexBinary now
  searches both the top-level and nested roots plus a bounded recursive fallback.
- Add scripts/**/* to the build files glob (fix-pty-permissions.js is required
  at startup and was missing from the package, crashing the packaged app).
- Send electron-builder output to release/ so it cannot recursively include the
  renderer's dist/.
@devin-ai-integration

Copy link
Copy Markdown
Author

Runtime test results — packaged Linux build

Tested the packaged build (release/linux-unpacked/mycodex, electron-builder, asar:false) on a Linux GUI. Recorded walkthrough of six auth-free tests — all passed. The credentialed agent flow is untested because auth was skipped by request.

Demo recording (T1→T6):

MyCodex demo

Test Result
T1 — Launch + engine detection (engine: codex ✓) ✅ passed
T2 — Working-folder picker (native dialog) ✅ passed
T3 — Real Codex TUI renders through the PTY ✅ passed
T4 — Keystrokes flow to engine (bidirectional PTY) ✅ passed
T5 — Interrupt button delivers ESC ✅ passed
T6 — Working folder persists across full restart ✅ passed
Credentialed flow (send/approve/diff/functional-interrupt/resume) ⚪ untested (auth skipped)
T3 — Real Codex TUI renders through the PTY (the core integration)

"Start a session" spawns the real @openai/codex engine via node-pty and renders its actual TUI (starburst logo, "Welcome to Codex…", the 3 sign-in options). The top-bar title becomes ~/Projects/mycodex. This single screen proves pitfall #1 (login-shell PATH/TERM), pitfall anomalyco#2 (asar:false), nested-binary resolution, the node-pty spawn, and xterm rendering all work together.

T3 real TUI

T1 — Launch + engine detection

engine: codex ✓ (green) proves the packaged app resolves the bundled binary — the pre-fix nested-binary bug produced engine: missing. auth: run codex login (red) is expected with no auth.

T1 launch

T2 — Working-folder picker

Native GTK dialog opens; selecting ~/Projects/mycodex updates the working folder.

T2 dialog
T2 set

T4 — Bidirectional PTY (keystrokes)

Down moved the > selector 1→3, Up moved 3→2 — input reaches the engine and its redraw streams back.

T4 down
T4 up

T5 — Interrupt button delivers ESC

On the API-key sub-screen ("Press esc to go back"), clicking Interrupt returned to the main menu — confirming the button delivers ESC (pty:interrupt → term.write("\x1b")). Interrupting a running turn needs auth → untested.

T5 before
T5 after

T6 — Persistence across full restart

Killed the process and relaunched; the working folder restored to ~/Projects/mycodex (from localStorage), not the default ~. The relaunch also re-detected the engine.

T6 persist

Notes — untested flows, a cosmetic quirk, and CI

Untested (auth skipped): send a message, file-edit approval, diff view, functional turn-interrupt, real codex resume. Mechanisms are wired (launch({resumeId}) → codex resume <id>, renderer.js:234-236; engine renders approvals/diffs in the same PTY) but not exercised live — "should work in theory", not verified.

Cosmetic quirk (not functional): the sidebar folder box shows Projects/mycodex/~ instead of ~/Projects/mycodex due to direction: rtl on .folder-path (styles.css:115). Stored value is correct (top-bar + persistence confirm it). One-line CSS follow-up available.

CI: 0 checks ran — GitHub Actions isn't triggered for this branch on the fork, and MyCodex is an isolated top-level dir (not a bun workspace), so it doesn't affect opencode's own pipeline. Not a failure.

Tested by Devin — session: https://app.devin.ai/sessions/a2330658806b42acab0d0b0ce33ddecf

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant