From 3cbd41a7d96f61d672cb062c226a39c7f01803b0 Mon Sep 17 00:00:00 2001 From: Bobyue0118 <465741287@qq.com> Date: Fri, 10 Apr 2026 12:59:43 +0800 Subject: [PATCH 1/9] docs: add macos mobile codex design spec --- .../2026-04-10-macos-mobile-codex-design.md | 313 ++++++++++++++++++ 1 file changed, 313 insertions(+) create mode 100644 docs/superpowers/specs/2026-04-10-macos-mobile-codex-design.md diff --git a/docs/superpowers/specs/2026-04-10-macos-mobile-codex-design.md b/docs/superpowers/specs/2026-04-10-macos-mobile-codex-design.md new file mode 100644 index 0000000..c38b10a --- /dev/null +++ b/docs/superpowers/specs/2026-04-10-macos-mobile-codex-design.md @@ -0,0 +1,313 @@ +# macOS Mobile Codex Control Design + +**Date:** 2026-04-10 + +**Status:** Approved in chat, awaiting final spec review before implementation planning + +## Goal + +Adapt this repo from its current Windows-first operational model into a macOS-hosted, mobile-first Codex control surface where: + +- Codex runs on the Mac +- the iPhone is the primary control UI +- remote access happens through a private Tailscale URL +- new iPhones require explicit approval before they become trusted + +The intended day-to-day workflow is that the user manages Codex sessions from the iPhone, while the Mac stays in the background as the execution host and source of truth for session history. + +## Current Repo Context + +The repo already contains the core web-control concept: + +- a patched `claudecodeui` layer under [upstream-overrides/claudecodeui-1.25.2](/Users/bobyue/Documents/GitHub/mobileCodexHelper/upstream-overrides/claudecodeui-1.25.2) +- Codex session discovery and resume support in [upstream-overrides/claudecodeui-1.25.2/server/openai-codex.js](/Users/bobyue/Documents/GitHub/mobileCodexHelper/upstream-overrides/claudecodeui-1.25.2/server/openai-codex.js) +- project and session browsing in [upstream-overrides/claudecodeui-1.25.2/server/projects.js](/Users/bobyue/Documents/GitHub/mobileCodexHelper/upstream-overrides/claudecodeui-1.25.2/server/projects.js) +- login and trusted-device persistence in [upstream-overrides/claudecodeui-1.25.2/server/routes/auth.js](/Users/bobyue/Documents/GitHub/mobileCodexHelper/upstream-overrides/claudecodeui-1.25.2/server/routes/auth.js) and [upstream-overrides/claudecodeui-1.25.2/server/database/db.js](/Users/bobyue/Documents/GitHub/mobileCodexHelper/upstream-overrides/claudecodeui-1.25.2/server/database/db.js) + +The repo is still operationally Windows-first: + +- PowerShell scripts under [scripts](/Users/bobyue/Documents/GitHub/mobileCodexHelper/scripts) +- a Windows desktop helper in [mobile_codex_control.py](/Users/bobyue/Documents/GitHub/mobileCodexHelper/mobile_codex_control.py) +- Windows/nginx deployment assumptions in [README.md](/Users/bobyue/Documents/GitHub/mobileCodexHelper/README.md) and [docs/DEPLOYMENT.md](/Users/bobyue/Documents/GitHub/mobileCodexHelper/docs/DEPLOYMENT.md) + +The macOS work should preserve the existing trust model and Codex web-control behavior, while removing the Windows helper as a runtime dependency. + +## User-Approved Product Direction + +The user approved the following decisions: + +- use the repo's server-managed Codex session model, not remote control of an existing desktop app window +- make the iPhone the primary control surface +- require remote access from anywhere through a private tunnel such as Tailscale +- keep feature scope similar to the existing repo +- prefer browser-based approval on the Mac over a desktop helper or CLI-only approval flow + +## Non-Goals + +This design does not attempt to: + +- remote-control a native Codex desktop app window on macOS +- expose a raw shell or PTY to the iPhone +- support multi-user collaboration +- expose the app directly to the public internet +- preserve the Windows desktop helper UX on macOS + +## Recommended Architecture + +### Runtime Shape + +The recommended macOS runtime shape is: + +```text +iPhone browser + ↓ +Tailscale private HTTPS + ↓ +localhost-only mobile Codex web app on the Mac + ↓ +Mac-hosted Codex sessions and workspace state +``` + +The patched app remains bound to `127.0.0.1:3001` on the Mac. Tailscale exposes that local service privately to the iPhone. The Mac remains the only execution environment for Codex. + +### Why This Architecture + +This is the smallest viable adaptation that satisfies the user's actual goal: + +- mobile-first Codex control from iPhone +- no dependency on remote desktop +- no dependency on a native macOS control app +- private remote access with a narrower attack surface + +Compared with porting the Windows helper or keeping a local reverse proxy layer, this path has fewer moving parts and a smaller macOS-specific support burden. + +## Functional Scope + +### Required Phone Capabilities + +The iPhone web UI must support the normal Codex workflow end to end: + +- register and log in +- wait for first-device approval when the iPhone is not yet trusted +- browse workspaces/projects +- browse sessions inside a project +- open complete session history +- start a new Codex session in a selected workspace +- resume an existing Codex session +- send follow-up prompts and stream output live +- interrupt an in-progress Codex run +- rename sessions +- switch between sessions +- create a new workspace under a configured safe root +- import an existing workspace from a configured safe root + +### Explicitly Excluded Phone Capabilities + +The phone UI should not expose: + +- general-purpose shell access +- non-Codex providers in hardened mode +- arbitrary filesystem traversal outside a configured safe root + +This preserves the repo's "view and chat-control" model while still making the phone the primary interface for Codex work. + +## Security Model + +### Core Trust Rules + +The macOS port keeps these rules: + +1. the app binds only to localhost on the Mac +2. remote access happens only through Tailscale +3. the system is single-user +4. unknown devices cannot log in until explicitly approved +5. Codex execution stays on the Mac + +### Trusted Device Flow + +The existing trust tables already support: + +- approved device persistence +- pending approval requests +- approval status polling +- device revocation + +The missing piece is not the data model but the admin surface that makes approval usable without the Windows helper. + +## macOS Admin Surface + +### Purpose + +The macOS port needs a browser-based local admin page served by the app itself. This replaces the Windows desktop helper for the critical ownership and approval workflows. + +### Required Admin Functions + +The Mac-local admin page should provide: + +- app/service status +- current local URL +- current Tailscale/private access URL +- pending device approval requests +- trusted device list +- approve action for pending devices +- reject action for pending devices +- revoke action for approved devices +- visibility into the configured workspace root + +### Access Rules + +The admin page should only be usable from the Mac-local session. It is not intended to be a remote self-approval page for new devices. + +At a minimum, the implementation should ensure: + +- approval actions require an authenticated owner session +- phone-originated unauthenticated users cannot approve themselves +- the page is reachable from localhost for the owner during setup and administration + +## Workspace Safety Model + +### Safe Root + +Workspace creation and import should be limited to a configured `WORKSPACES_ROOT` on the Mac. + +This root serves two purposes: + +- it defines the area the phone UI is allowed to manage +- it avoids exposing arbitrary absolute path selection from the iPhone + +### Allowed Operations + +Within `WORKSPACES_ROOT`, the phone may: + +- create a new folder-backed workspace +- clone a repository into a new folder +- add an existing folder as a managed workspace + +Outside `WORKSPACES_ROOT`, these operations must be rejected. + +## System Components + +### 1. Patched App Runtime + +The upstream override layer remains the main application. The macOS adaptation should continue to use the patched app as the single control plane for: + +- auth +- trusted devices +- projects/workspaces +- Codex session execution and streaming + +### 2. macOS Runtime Scripts + +New macOS scripts should replace the Windows-first operational scripts. They should cover: + +- applying upstream overrides +- checking runtime requirements +- starting the app service +- stopping the app service +- discovering or enabling the Tailscale private URL +- optionally installing and removing a `launchd` agent + +### 3. Local Admin UI + +The admin UI is a thin operational layer, not a new product surface. Its job is to expose existing trust and status data that already lives in the server/database layer, plus any new service state needed for macOS operation. + +## Main User Flows + +### First-Time Setup On Mac + +1. Install dependencies on macOS. +2. Download upstream `claudecodeui` into `vendor/claudecodeui-1.25.2`. +3. Apply this repo's overrides. +4. Start the localhost app service on the Mac. +5. Open the local web UI on the Mac and register the single user account. +6. Sign into Tailscale on the Mac and confirm a private remote URL exists. + +### First iPhone Login + +1. The iPhone opens the private Tailscale URL. +2. The user attempts login. +3. The server detects that the device is not yet trusted. +4. The phone enters waiting-for-approval state. +5. The Mac admin page shows the pending request with device metadata. +6. The user approves the request on the Mac. +7. The iPhone auto-retries and completes login. + +### Normal Mobile Workflow + +1. Open the private URL from the iPhone. +2. Browse or create/import a workspace under the safe root. +3. Start a new Codex session or resume an existing one. +4. Send prompts and observe streaming output. +5. Interrupt if needed. +6. Reopen the same session later from the phone or from the Mac-hosted web UI. + +## Error Handling + +The macOS adaptation should make operational failures explicit in both docs and UI: + +- if upstream source is missing, the scripts should fail with a direct path-specific message +- if `codex` is not installed or not on `PATH`, the UI should surface that clearly +- if Tailscale is unavailable, local access should still work and remote setup should show a targeted error +- if a workspace path is outside `WORKSPACES_ROOT`, the request should fail with a clear boundary message +- if a device request has already been resolved, approval polling should return the current status cleanly + +## Testing Strategy + +### Local Verification + +The local macOS verification baseline is: + +1. local browser can open `http://127.0.0.1:3001` +2. first account registration succeeds +3. project list loads +4. a Codex session can be started locally +5. session history persists and can be reopened + +### Remote Verification + +The remote mobile verification baseline is: + +1. Tailscale exposes a private URL to the same localhost app +2. a new iPhone enters pending approval state on first login +3. the Mac admin page can approve that device +4. the iPhone finishes login after approval +5. the iPhone can create or import a workspace under the safe root +6. the iPhone can start a new Codex session +7. the iPhone can resume a previous session +8. the iPhone can interrupt a running Codex request + +## Rollout Strategy + +The work should be done in small phases: + +1. add macOS runtime and setup path +2. expose local admin/approval APIs and UI +3. enforce workspace-root boundaries for phone-managed creation/import +4. update docs for macOS setup and mobile-first use +5. verify the full Tailscale-backed iPhone flow + +This ordering delivers usable value early while keeping the diff scoped to the approved product direction. + +## Files Likely To Change In Implementation + +The design suggests likely changes in: + +- [README.md](/Users/bobyue/Documents/GitHub/mobileCodexHelper/README.md) +- [docs/DEPLOYMENT.md](/Users/bobyue/Documents/GitHub/mobileCodexHelper/docs/DEPLOYMENT.md) +- [upstream-overrides/claudecodeui-1.25.2/server/routes/auth.js](/Users/bobyue/Documents/GitHub/mobileCodexHelper/upstream-overrides/claudecodeui-1.25.2/server/routes/auth.js) +- [upstream-overrides/claudecodeui-1.25.2/server/database/db.js](/Users/bobyue/Documents/GitHub/mobileCodexHelper/upstream-overrides/claudecodeui-1.25.2/server/database/db.js) +- frontend files under [upstream-overrides/claudecodeui-1.25.2/src](/Users/bobyue/Documents/GitHub/mobileCodexHelper/upstream-overrides/claudecodeui-1.25.2/src) +- new macOS scripts under [scripts](/Users/bobyue/Documents/GitHub/mobileCodexHelper/scripts) + +The design intentionally does not require a native macOS desktop control app. + +## Acceptance Criteria + +This design is successful when all of the following are true: + +- the repo supports a documented macOS setup path without the Windows helper +- the Mac-hosted app can be reached privately from the iPhone through Tailscale +- first-time iPhone approval can be completed from a browser on the Mac +- the iPhone can manage Codex sessions as the primary day-to-day interface +- session history created from the iPhone remains available on the Mac-hosted system +- workspace creation/import from the iPhone is restricted to a configured safe root From 26fd08672abf8c922626b70e15910e9fae76d730 Mon Sep 17 00:00:00 2001 From: Bobyue0118 <465741287@qq.com> Date: Fri, 10 Apr 2026 13:08:25 +0800 Subject: [PATCH 2/9] chore: ignore local worktrees --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index 7d0d0bf..d92620e 100644 --- a/.gitignore +++ b/.gitignore @@ -8,6 +8,7 @@ __pycache__/ # Local runtime/output .runtime/ tmp/ +.worktrees/ build/ dist/ *.log From e578a09bc82b955905c85fd29b225936c974f386 Mon Sep 17 00:00:00 2001 From: Bobyue0118 <465741287@qq.com> Date: Fri, 10 Apr 2026 13:48:42 +0800 Subject: [PATCH 3/9] feat: add owner admin backend APIs --- .../claudecodeui-1.25.2/server/database/db.js | 14 ++ .../claudecodeui-1.25.2/server/routes/auth.js | 121 ++++++++++++++++++ .../utils/__tests__/owner-admin.test.mjs | 65 ++++++++++ .../server/utils/owner-admin.js | 73 +++++++++++ 4 files changed, 273 insertions(+) create mode 100644 upstream-overrides/claudecodeui-1.25.2/server/utils/__tests__/owner-admin.test.mjs create mode 100644 upstream-overrides/claudecodeui-1.25.2/server/utils/owner-admin.js diff --git a/upstream-overrides/claudecodeui-1.25.2/server/database/db.js b/upstream-overrides/claudecodeui-1.25.2/server/database/db.js index 9b492e8..956539f 100644 --- a/upstream-overrides/claudecodeui-1.25.2/server/database/db.js +++ b/upstream-overrides/claudecodeui-1.25.2/server/database/db.js @@ -599,6 +599,20 @@ const trustedDevicesDb = { } }, + listPendingApprovalRequestsByUser: (userId) => { + try { + return db.prepare(` + SELECT dar.*, u.username + FROM device_approval_requests dar + LEFT JOIN users u ON u.id = dar.user_id + WHERE dar.status = 'pending' AND dar.user_id = ? + ORDER BY dar.created_at DESC + `).all(userId); + } catch (err) { + throw err; + } + }, + resolveApprovalRequest: (requestToken, status, note = null) => { try { const nowColumn = status === 'approved' ? 'approved_at' : 'rejected_at'; diff --git a/upstream-overrides/claudecodeui-1.25.2/server/routes/auth.js b/upstream-overrides/claudecodeui-1.25.2/server/routes/auth.js index b9b9f20..5bfa6d4 100644 --- a/upstream-overrides/claudecodeui-1.25.2/server/routes/auth.js +++ b/upstream-overrides/claudecodeui-1.25.2/server/routes/auth.js @@ -8,6 +8,11 @@ import { generateToken, setAuthCookie, } from '../middleware/auth.js'; +import { + buildOwnerAdminStatus, + isLoopbackAddress, + loadTailscaleAdminState, +} from '../utils/owner-admin.js'; const router = express.Router(); const sanitizeUser = (user) => ({ id: user.id, username: user.username }); @@ -52,6 +57,22 @@ const buildApprovalPayload = (request, message = '新设备需要在电脑端批 deviceName: request.device_name || request.device_id, }); +const requireLocalOwnerSession = (req, res, next) => { + const candidates = [ + typeof req.headers['x-forwarded-for'] === 'string' + ? req.headers['x-forwarded-for'].split(',')[0].trim() + : null, + req.ip, + req.socket?.remoteAddress, + ].filter(Boolean); + + if (!candidates.some((value) => isLoopbackAddress(value))) { + return res.status(403).json({ error: 'Owner admin is only available from the Mac local browser.' }); + } + + return next(); +}; + const issueAuthSession = (req, res, user, deviceMetadata = null) => { const token = generateToken(user, { deviceId: deviceMetadata?.deviceId || null, @@ -109,6 +130,106 @@ router.get('/device-approval/:requestToken', async (req, res) => { } }); +router.get('/owner-admin/status', authenticateToken, requireLocalOwnerSession, async (req, res) => { + try { + const tailscaleState = await loadTailscaleAdminState(); + return res.json(buildOwnerAdminStatus({ + workspacesRoot: process.env.WORKSPACES_ROOT || process.env.HOME || null, + tailscaleState, + port: Number(process.env.PORT || 3001), + })); + } catch (error) { + console.error('Owner admin status error:', error); + return res.status(500).json({ error: '服务器内部错误' }); + } +}); + +router.get('/owner-admin/pending-devices', authenticateToken, requireLocalOwnerSession, (req, res) => { + try { + const requests = trustedDevicesDb.listPendingApprovalRequestsByUser(req.user.id); + return res.json({ requests }); + } catch (error) { + console.error('Owner admin pending devices error:', error); + return res.status(500).json({ error: '服务器内部错误' }); + } +}); + +router.get('/owner-admin/trusted-devices', authenticateToken, requireLocalOwnerSession, (req, res) => { + try { + const devices = trustedDevicesDb.listApprovedDevices(req.user.id); + return res.json({ devices }); + } catch (error) { + console.error('Owner admin trusted devices error:', error); + return res.status(500).json({ error: '服务器内部错误' }); + } +}); + +router.post('/owner-admin/pending-devices/:requestToken/approve', authenticateToken, requireLocalOwnerSession, (req, res) => { + try { + const requestToken = normalizeTextField(req.params.requestToken, 128); + if (!requestToken) { + return res.status(400).json({ error: '审批令牌无效' }); + } + + const request = trustedDevicesDb.getApprovalRequestByToken(requestToken); + if (!request || request.user_id !== req.user.id || request.status !== 'pending') { + return res.status(404).json({ error: 'Pending approval request not found.' }); + } + + trustedDevicesDb.approveDevice(request.user_id, request.device_id, { + deviceName: request.device_name, + platform: request.platform, + appType: request.app_type, + ip: request.requested_ip, + userAgent: request.requested_user_agent, + }); + trustedDevicesDb.resolveApprovalRequest(requestToken, 'approved'); + return res.json({ success: true }); + } catch (error) { + console.error('Owner admin approve device error:', error); + return res.status(500).json({ error: '服务器内部错误' }); + } +}); + +router.post('/owner-admin/pending-devices/:requestToken/reject', authenticateToken, requireLocalOwnerSession, (req, res) => { + try { + const requestToken = normalizeTextField(req.params.requestToken, 128); + if (!requestToken) { + return res.status(400).json({ error: '审批令牌无效' }); + } + + const request = trustedDevicesDb.getApprovalRequestByToken(requestToken); + if (!request || request.user_id !== req.user.id || request.status !== 'pending') { + return res.status(404).json({ error: 'Pending approval request not found.' }); + } + + trustedDevicesDb.resolveApprovalRequest(requestToken, 'rejected'); + return res.json({ success: true }); + } catch (error) { + console.error('Owner admin reject device error:', error); + return res.status(500).json({ error: '服务器内部错误' }); + } +}); + +router.delete('/owner-admin/trusted-devices/:deviceId', authenticateToken, requireLocalOwnerSession, (req, res) => { + try { + const deviceId = normalizeTextField(req.params.deviceId, 128); + if (!deviceId) { + return res.status(400).json({ error: '设备标识无效' }); + } + + const success = trustedDevicesDb.deactivateDevice(req.user.id, deviceId); + if (!success) { + return res.status(404).json({ error: 'Trusted device not found.' }); + } + + return res.json({ success: true }); + } catch (error) { + console.error('Owner admin revoke device error:', error); + return res.status(500).json({ error: '服务器内部错误' }); + } +}); + // User registration (setup) - only allowed if no users exist router.post('/register', async (req, res) => { try { diff --git a/upstream-overrides/claudecodeui-1.25.2/server/utils/__tests__/owner-admin.test.mjs b/upstream-overrides/claudecodeui-1.25.2/server/utils/__tests__/owner-admin.test.mjs new file mode 100644 index 0000000..ff2d13e --- /dev/null +++ b/upstream-overrides/claudecodeui-1.25.2/server/utils/__tests__/owner-admin.test.mjs @@ -0,0 +1,65 @@ +import test from 'node:test'; +import assert from 'node:assert/strict'; +import { + buildOwnerAdminStatus, + isLoopbackAddress, + loadTailscaleAdminState, +} from '../owner-admin.js'; + +test('isLoopbackAddress accepts Express loopback variants', () => { + assert.equal(isLoopbackAddress('127.0.0.1'), true); + assert.equal(isLoopbackAddress('::1'), true); + assert.equal(isLoopbackAddress('::ffff:127.0.0.1'), true); + assert.equal(isLoopbackAddress('100.101.102.103'), false); +}); + +test('buildOwnerAdminStatus returns local and remote URLs', () => { + const payload = buildOwnerAdminStatus({ + workspacesRoot: '/Users/bobyue/Code', + tailscaleState: { + installed: true, + running: true, + backendState: 'Running', + dnsName: 'bobyue-mac.tail123.ts.net', + remoteUrl: 'https://bobyue-mac.tail123.ts.net', + authUrl: null, + }, + port: 3001, + }); + + assert.deepEqual(payload, { + localUrl: 'http://127.0.0.1:3001', + remoteUrl: 'https://bobyue-mac.tail123.ts.net', + workspacesRoot: '/Users/bobyue/Code', + tailscale: { + installed: true, + running: true, + backendState: 'Running', + dnsName: 'bobyue-mac.tail123.ts.net', + authUrl: null, + }, + }); +}); + +test('loadTailscaleAdminState handles login-required status', async () => { + const state = await loadTailscaleAdminState({ + tailscalePath: 'tailscale', + execFileImpl: async () => ({ + stdout: JSON.stringify({ + BackendState: 'NeedsLogin', + AuthURL: 'https://login.tailscale.example/device', + Self: {}, + }), + stderr: '', + }), + }); + + assert.deepEqual(state, { + installed: true, + running: false, + backendState: 'NeedsLogin', + dnsName: null, + remoteUrl: null, + authUrl: 'https://login.tailscale.example/device', + }); +}); diff --git a/upstream-overrides/claudecodeui-1.25.2/server/utils/owner-admin.js b/upstream-overrides/claudecodeui-1.25.2/server/utils/owner-admin.js new file mode 100644 index 0000000..b639b79 --- /dev/null +++ b/upstream-overrides/claudecodeui-1.25.2/server/utils/owner-admin.js @@ -0,0 +1,73 @@ +import { execFile } from 'node:child_process'; +import { promisify } from 'node:util'; + +const execFileAsync = promisify(execFile); + +export function isLoopbackAddress(address) { + if (!address) { + return false; + } + + const normalized = String(address).trim().toLowerCase(); + return normalized === '127.0.0.1' + || normalized === '::1' + || normalized === '::ffff:127.0.0.1' + || normalized === 'localhost'; +} + +export function buildOwnerAdminStatus({ workspacesRoot, tailscaleState, port = 3001 }) { + return { + localUrl: `http://127.0.0.1:${port}`, + remoteUrl: tailscaleState.remoteUrl, + workspacesRoot, + tailscale: { + installed: tailscaleState.installed, + running: tailscaleState.running, + backendState: tailscaleState.backendState, + dnsName: tailscaleState.dnsName, + authUrl: tailscaleState.authUrl, + }, + }; +} + +export async function loadTailscaleAdminState({ + tailscalePath = process.env.MOBILE_CODEX_TAILSCALE || 'tailscale', + execFileImpl = execFileAsync, +} = {}) { + try { + const { stdout } = await execFileImpl(tailscalePath, ['status', '--json']); + const parsed = JSON.parse(stdout || '{}'); + const dnsName = typeof parsed?.Self?.DNSName === 'string' + ? parsed.Self.DNSName.replace(/\.$/, '') + : null; + + return { + installed: true, + running: parsed?.BackendState === 'Running', + backendState: parsed?.BackendState || 'Unknown', + dnsName, + remoteUrl: dnsName ? `https://${dnsName}` : null, + authUrl: parsed?.AuthURL || null, + }; + } catch (error) { + if (error?.code === 'ENOENT') { + return { + installed: false, + running: false, + backendState: 'NotInstalled', + dnsName: null, + remoteUrl: null, + authUrl: null, + }; + } + + return { + installed: true, + running: false, + backendState: 'Error', + dnsName: null, + remoteUrl: null, + authUrl: null, + }; + } +} From b0abbbcdb52008d501f8c5ded054212e113b4423 Mon Sep 17 00:00:00 2001 From: Bobyue0118 <465741287@qq.com> Date: Fri, 10 Apr 2026 13:51:02 +0800 Subject: [PATCH 4/9] feat: add macos runtime shell foundation --- scripts/apply-upstream-overrides.sh | 22 +++++++++++++ scripts/check-mobile-codex-runtime.sh | 18 ++++++++++ scripts/lib-mobile-codex.sh | 47 +++++++++++++++++++++++++++ scripts/test-macos-runtime.sh | 18 ++++++++++ 4 files changed, 105 insertions(+) create mode 100755 scripts/apply-upstream-overrides.sh create mode 100755 scripts/check-mobile-codex-runtime.sh create mode 100755 scripts/lib-mobile-codex.sh create mode 100755 scripts/test-macos-runtime.sh diff --git a/scripts/apply-upstream-overrides.sh b/scripts/apply-upstream-overrides.sh new file mode 100755 index 0000000..3ad78ba --- /dev/null +++ b/scripts/apply-upstream-overrides.sh @@ -0,0 +1,22 @@ +#!/usr/bin/env bash +set -euo pipefail + +source "$(dirname "$0")/lib-mobile-codex.sh" + +workspace="$(workspace_dir)" +source_root="$workspace/upstream-overrides/claudecodeui-1.25.2" +target_root="$(resolve_upstream_dir)" + +require_path "$source_root" "Override source not found" +require_path "$target_root" "Upstream checkout not found" + +copied=0 +while IFS= read -r source_file; do + relative_path="${source_file#$source_root/}" + destination="$target_root/$relative_path" + mkdir -p "$(dirname "$destination")" + cp "$source_file" "$destination" + copied=$((copied + 1)) +done < <(find "$source_root" -type f | sort) + +echo "Applied $copied override files to $target_root" diff --git a/scripts/check-mobile-codex-runtime.sh b/scripts/check-mobile-codex-runtime.sh new file mode 100755 index 0000000..551a672 --- /dev/null +++ b/scripts/check-mobile-codex-runtime.sh @@ -0,0 +1,18 @@ +#!/usr/bin/env bash +set -euo pipefail + +source "$(dirname "$0")/lib-mobile-codex.sh" + +workspace="$(workspace_dir)" +upstream_dir="$(resolve_upstream_dir)" +node_bin="$(resolve_node || true)" +tailscale_bin="$(resolve_tailscale || true)" + +cat </dev/null 2>&1 + pwd +} + +resolve_upstream_dir() { + if [[ -n "${MOBILE_CODEX_UPSTREAM_DIR:-}" ]]; then + printf '%s\n' "$MOBILE_CODEX_UPSTREAM_DIR" + return 0 + fi + + printf '%s/vendor/claudecodeui-1.25.2\n' "$(workspace_dir)" +} + +require_path() { + local target="$1" + local message="$2" + + [[ -e "$target" ]] || { + echo "$message: $target" >&2 + exit 1 + } +} + +resolve_node() { + if [[ -n "${MOBILE_CODEX_NODE:-}" ]]; then + printf '%s\n' "$MOBILE_CODEX_NODE" + return 0 + fi + + command -v node +} + +resolve_tailscale() { + if [[ -n "${MOBILE_CODEX_TAILSCALE:-}" ]]; then + printf '%s\n' "$MOBILE_CODEX_TAILSCALE" + return 0 + fi + + command -v tailscale +} + +logs_dir() { + printf '%s/tmp/logs\n' "$(workspace_dir)" +} diff --git a/scripts/test-macos-runtime.sh b/scripts/test-macos-runtime.sh new file mode 100755 index 0000000..80e35ba --- /dev/null +++ b/scripts/test-macos-runtime.sh @@ -0,0 +1,18 @@ +#!/usr/bin/env bash +set -euo pipefail + +scripts=( + "scripts/lib-mobile-codex.sh" + "scripts/apply-upstream-overrides.sh" + "scripts/check-mobile-codex-runtime.sh" +) + +for script in "${scripts[@]}"; do + [[ -f "$script" ]] || { + echo "Missing required macOS script: $script" >&2 + exit 1 + } + bash -n "$script" +done + +echo "macOS runtime foundation scripts look valid" From 4eb94ce6968bb615ff537effd3259f194618d4e5 Mon Sep 17 00:00:00 2001 From: Bobyue0118 <465741287@qq.com> Date: Fri, 10 Apr 2026 14:20:04 +0800 Subject: [PATCH 5/9] feat: add macos owner admin panel --- .../src/components/admin/OwnerAdminPanel.tsx | 133 ++++++++++++++++++ .../components/admin/useOwnerAdminState.ts | 113 +++++++++++++++ .../utils/__tests__/ownerAdminAccess.test.mjs | 19 +++ .../admin/utils/ownerAdminAccess.js | 9 ++ .../src/components/app/AppContent.tsx | 22 ++- .../claudecodeui-1.25.2/src/utils/api.js | 17 +++ 6 files changed, 312 insertions(+), 1 deletion(-) create mode 100644 upstream-overrides/claudecodeui-1.25.2/src/components/admin/OwnerAdminPanel.tsx create mode 100644 upstream-overrides/claudecodeui-1.25.2/src/components/admin/useOwnerAdminState.ts create mode 100644 upstream-overrides/claudecodeui-1.25.2/src/components/admin/utils/__tests__/ownerAdminAccess.test.mjs create mode 100644 upstream-overrides/claudecodeui-1.25.2/src/components/admin/utils/ownerAdminAccess.js diff --git a/upstream-overrides/claudecodeui-1.25.2/src/components/admin/OwnerAdminPanel.tsx b/upstream-overrides/claudecodeui-1.25.2/src/components/admin/OwnerAdminPanel.tsx new file mode 100644 index 0000000..3364884 --- /dev/null +++ b/upstream-overrides/claudecodeui-1.25.2/src/components/admin/OwnerAdminPanel.tsx @@ -0,0 +1,133 @@ +import { X } from 'lucide-react'; +import { useOwnerAdminState } from './useOwnerAdminState'; + +type OwnerAdminPanelProps = { + isOpen: boolean; + onClose: () => void; +}; + +export default function OwnerAdminPanel({ isOpen, onClose }: OwnerAdminPanelProps) { + const { + status, + pendingDevices, + trustedDevices, + isLoading, + error, + approveDevice, + rejectDevice, + revokeDevice, + } = useOwnerAdminState(isOpen); + + if (!isOpen) { + return null; + } + + return ( +
+
+
+
+

Owner Admin

+

+ Approve iPhones, inspect Tailscale access, and manage trusted devices. +

+
+ +
+ +
+
+
+
Access
+
Local: {status?.localUrl || 'Loading status'}
+
Remote: {status?.remoteUrl || 'Not available yet'}
+
+ Workspace root: {status?.workspacesRoot || 'Loading workspace root'} +
+
+ +
+
Pending devices
+
+ {pendingDevices.map((device) => ( +
+
{device.device_name || device.device_id}
+
+ {device.platform || 'Unknown platform'} · {device.requested_ip || 'Unknown IP'} +
+
+ + +
+
+ ))} + {!pendingDevices.length &&
No pending devices.
} +
+
+
+ +
+
+
Tailscale
+
+ State: {status?.tailscale?.backendState || 'Loading state'} +
+
+ DNS: {status?.tailscale?.dnsName || 'Unavailable'} +
+ {status?.tailscale?.authUrl && ( + + Finish Tailscale Login + + )} +
+ +
+
Trusted devices
+
+ {trustedDevices.map((device) => ( +
+
+
{device.device_name || device.device_id}
+
{device.platform || 'Unknown platform'}
+
+ +
+ ))} + {!trustedDevices.length &&
No trusted devices yet.
} +
+
+ + {(isLoading || error) && ( +
+ {error || 'Refreshing owner admin data'} +
+ )} +
+
+
+
+ ); +} diff --git a/upstream-overrides/claudecodeui-1.25.2/src/components/admin/useOwnerAdminState.ts b/upstream-overrides/claudecodeui-1.25.2/src/components/admin/useOwnerAdminState.ts new file mode 100644 index 0000000..7b93948 --- /dev/null +++ b/upstream-overrides/claudecodeui-1.25.2/src/components/admin/useOwnerAdminState.ts @@ -0,0 +1,113 @@ +import { useCallback, useEffect, useState } from 'react'; +import { api } from '../../utils/api'; + +type OwnerAdminStatus = { + localUrl: string; + remoteUrl: string | null; + workspacesRoot: string | null; + tailscale: { + installed: boolean; + running: boolean; + backendState: string; + dnsName: string | null; + authUrl: string | null; + }; +}; + +type PendingDevice = { + request_token: string; + device_id: string; + device_name: string | null; + platform: string | null; + requested_ip: string | null; +}; + +type TrustedDevice = { + device_id: string; + device_name: string | null; + platform: string | null; +}; + +async function parseJsonOrThrow(response: Response) { + const payload = await response.json().catch(() => null); + if (!response.ok) { + throw new Error(payload?.error || 'Request failed'); + } + + return payload; +} + +export function useOwnerAdminState(isOpen: boolean) { + const [status, setStatus] = useState(null); + const [pendingDevices, setPendingDevices] = useState([]); + const [trustedDevices, setTrustedDevices] = useState([]); + const [isLoading, setIsLoading] = useState(false); + const [error, setError] = useState(null); + + const refresh = useCallback(async () => { + setIsLoading(true); + setError(null); + + try { + const [statusResponse, pendingResponse, trustedResponse] = await Promise.all([ + api.ownerAdmin.status(), + api.ownerAdmin.pendingDevices(), + api.ownerAdmin.trustedDevices(), + ]); + + const [statusPayload, pendingPayload, trustedPayload] = await Promise.all([ + parseJsonOrThrow(statusResponse), + parseJsonOrThrow(pendingResponse), + parseJsonOrThrow(trustedResponse), + ]); + + setStatus(statusPayload as OwnerAdminStatus); + setPendingDevices((pendingPayload?.requests || []) as PendingDevice[]); + setTrustedDevices((trustedPayload?.devices || []) as TrustedDevice[]); + } catch (caughtError) { + setError(caughtError instanceof Error ? caughtError.message : 'Failed to load owner admin data.'); + } finally { + setIsLoading(false); + } + }, []); + + useEffect(() => { + if (!isOpen) { + return undefined; + } + + void refresh(); + const timer = window.setInterval(() => { + void refresh(); + }, 3000); + + return () => window.clearInterval(timer); + }, [isOpen, refresh]); + + const approveDevice = useCallback(async (requestToken: string) => { + await parseJsonOrThrow(await api.ownerAdmin.approveDevice(requestToken)); + await refresh(); + }, [refresh]); + + const rejectDevice = useCallback(async (requestToken: string) => { + await parseJsonOrThrow(await api.ownerAdmin.rejectDevice(requestToken)); + await refresh(); + }, [refresh]); + + const revokeDevice = useCallback(async (deviceId: string) => { + await parseJsonOrThrow(await api.ownerAdmin.revokeDevice(deviceId)); + await refresh(); + }, [refresh]); + + return { + status, + pendingDevices, + trustedDevices, + isLoading, + error, + refresh, + approveDevice, + rejectDevice, + revokeDevice, + }; +} diff --git a/upstream-overrides/claudecodeui-1.25.2/src/components/admin/utils/__tests__/ownerAdminAccess.test.mjs b/upstream-overrides/claudecodeui-1.25.2/src/components/admin/utils/__tests__/ownerAdminAccess.test.mjs new file mode 100644 index 0000000..1e42e00 --- /dev/null +++ b/upstream-overrides/claudecodeui-1.25.2/src/components/admin/utils/__tests__/ownerAdminAccess.test.mjs @@ -0,0 +1,19 @@ +import test from 'node:test'; +import assert from 'node:assert/strict'; +import { + isLoopbackHost, + shouldShowOwnerAdminLauncher, +} from '../ownerAdminAccess.js'; + +test('isLoopbackHost accepts macOS local browser hostnames', () => { + assert.equal(isLoopbackHost('127.0.0.1'), true); + assert.equal(isLoopbackHost('localhost'), true); + assert.equal(isLoopbackHost('::1'), true); + assert.equal(isLoopbackHost('bobyue-mac.tail123.ts.net'), false); +}); + +test('shouldShowOwnerAdminLauncher requires a user on a loopback host', () => { + assert.equal(shouldShowOwnerAdminLauncher({ hostname: 'localhost', hasUser: true }), true); + assert.equal(shouldShowOwnerAdminLauncher({ hostname: 'localhost', hasUser: false }), false); + assert.equal(shouldShowOwnerAdminLauncher({ hostname: 'bobyue-mac.tail123.ts.net', hasUser: true }), false); +}); diff --git a/upstream-overrides/claudecodeui-1.25.2/src/components/admin/utils/ownerAdminAccess.js b/upstream-overrides/claudecodeui-1.25.2/src/components/admin/utils/ownerAdminAccess.js new file mode 100644 index 0000000..39e000c --- /dev/null +++ b/upstream-overrides/claudecodeui-1.25.2/src/components/admin/utils/ownerAdminAccess.js @@ -0,0 +1,9 @@ +const LOOPBACK_HOSTS = new Set(['localhost', '127.0.0.1', '::1', '[::1]']); + +export function isLoopbackHost(hostname) { + return LOOPBACK_HOSTS.has(String(hostname || '').trim().toLowerCase()); +} + +export function shouldShowOwnerAdminLauncher({ hostname, hasUser }) { + return Boolean(hasUser) && isLoopbackHost(hostname); +} diff --git a/upstream-overrides/claudecodeui-1.25.2/src/components/app/AppContent.tsx b/upstream-overrides/claudecodeui-1.25.2/src/components/app/AppContent.tsx index 3a3a3b4..056690f 100644 --- a/upstream-overrides/claudecodeui-1.25.2/src/components/app/AppContent.tsx +++ b/upstream-overrides/claudecodeui-1.25.2/src/components/app/AppContent.tsx @@ -1,4 +1,4 @@ -import { useEffect, useRef } from 'react'; +import { useEffect, useRef, useState } from 'react'; import { useNavigate, useParams } from 'react-router-dom'; import { useTranslation } from 'react-i18next'; import Sidebar from '../sidebar/view/Sidebar'; @@ -9,14 +9,19 @@ import { useDeviceSettings } from '../../hooks/useDeviceSettings'; import { useSessionProtection } from '../../hooks/useSessionProtection'; import { useProjectsState } from '../../hooks/useProjectsState'; import MobileNav from './MobileNav'; +import OwnerAdminPanel from '../admin/OwnerAdminPanel'; +import { useAuth } from '../auth/context/AuthContext'; +import { shouldShowOwnerAdminLauncher } from '../admin/utils/ownerAdminAccess.js'; export default function AppContent() { const navigate = useNavigate(); const { sessionId } = useParams<{ sessionId?: string }>(); const { t } = useTranslation('common'); + const { user } = useAuth(); const { isMobile } = useDeviceSettings({ trackPWA: false }); const { ws, sendMessage, latestMessage, isConnected } = useWebSocket(); const wasConnectedRef = useRef(false); + const [showOwnerAdmin, setShowOwnerAdmin] = useState(false); const { activeSessions, @@ -91,6 +96,11 @@ export default function AppContent() { } }, [isConnected, selectedSession?.id, sendMessage]); + const canShowOwnerAdmin = shouldShowOwnerAdminLauncher({ + hostname: window.location.hostname, + hasUser: Boolean(user), + }); + return (
{!isMobile ? ( @@ -159,6 +169,16 @@ export default function AppContent() { /> )} + {canShowOwnerAdmin && ( + + )} + + setShowOwnerAdmin(false)} />
); } diff --git a/upstream-overrides/claudecodeui-1.25.2/src/utils/api.js b/upstream-overrides/claudecodeui-1.25.2/src/utils/api.js index b0d41ac..cf0438e 100644 --- a/upstream-overrides/claudecodeui-1.25.2/src/utils/api.js +++ b/upstream-overrides/claudecodeui-1.25.2/src/utils/api.js @@ -50,6 +50,23 @@ export const api = { user: () => authenticatedFetch('/api/auth/user'), logout: () => authenticatedFetch('/api/auth/logout', { method: 'POST' }), }, + ownerAdmin: { + status: () => authenticatedFetch('/api/auth/owner-admin/status'), + pendingDevices: () => authenticatedFetch('/api/auth/owner-admin/pending-devices'), + trustedDevices: () => authenticatedFetch('/api/auth/owner-admin/trusted-devices'), + approveDevice: (requestToken) => + authenticatedFetch(`/api/auth/owner-admin/pending-devices/${encodeURIComponent(requestToken)}/approve`, { + method: 'POST', + }), + rejectDevice: (requestToken) => + authenticatedFetch(`/api/auth/owner-admin/pending-devices/${encodeURIComponent(requestToken)}/reject`, { + method: 'POST', + }), + revokeDevice: (deviceId) => + authenticatedFetch(`/api/auth/owner-admin/trusted-devices/${encodeURIComponent(deviceId)}`, { + method: 'DELETE', + }), + }, // Protected endpoints // config endpoint removed - no longer needed (frontend uses window.location) From 5b7d43dec30d84b36dcb253357439009d6e145cc Mon Sep 17 00:00:00 2001 From: Bobyue0118 <465741287@qq.com> Date: Fri, 10 Apr 2026 15:29:31 +0800 Subject: [PATCH 6/9] feat: add macos runtime and launchd scripts --- scripts/check-mobile-codex-runtime.sh | 2 + scripts/enable-mobile-codex-remote.sh | 58 +++++++++++++++++++++ scripts/install-mobile-codex-launchd.sh | 34 +++++++++++++ scripts/lib-mobile-codex.sh | 61 +++++++++++++++++++++-- scripts/start-mobile-codex-stack.sh | 4 ++ scripts/start-mobile-codex.sh | 55 ++++++++++++++++++++ scripts/stop-mobile-codex-stack.sh | 4 ++ scripts/stop-mobile-codex.sh | 30 +++++++++++ scripts/test-macos-runtime.sh | 27 +++++++++- scripts/uninstall-mobile-codex-launchd.sh | 7 +++ 10 files changed, 278 insertions(+), 4 deletions(-) create mode 100755 scripts/enable-mobile-codex-remote.sh create mode 100755 scripts/install-mobile-codex-launchd.sh create mode 100755 scripts/start-mobile-codex-stack.sh create mode 100755 scripts/start-mobile-codex.sh create mode 100755 scripts/stop-mobile-codex-stack.sh create mode 100755 scripts/stop-mobile-codex.sh create mode 100755 scripts/uninstall-mobile-codex-launchd.sh diff --git a/scripts/check-mobile-codex-runtime.sh b/scripts/check-mobile-codex-runtime.sh index 551a672..3ffb2cc 100755 --- a/scripts/check-mobile-codex-runtime.sh +++ b/scripts/check-mobile-codex-runtime.sh @@ -7,6 +7,7 @@ workspace="$(workspace_dir)" upstream_dir="$(resolve_upstream_dir)" node_bin="$(resolve_node || true)" tailscale_bin="$(resolve_tailscale || true)" +db_path="$(database_path)" cat <&2 + exit 1 +fi + +if ! curl -sf http://127.0.0.1:3001/health >/dev/null 2>&1; then + echo "Local mobile Codex app is not reachable at http://127.0.0.1:3001/health" >&2 + exit 1 +fi + +if ! status_json="$("$tailscale_bin" status --json 2>&1)"; then + echo "Failed to read Tailscale status: $status_json" >&2 + exit 1 +fi + +backend_state="$(python3 - <<'PY' "$status_json" +import json, sys +payload = json.loads(sys.argv[1]) +print(payload.get("BackendState", "Unknown")) +PY +)" + +if [[ "$backend_state" != "Running" ]]; then + auth_url="$(python3 - <<'PY' "$status_json" +import json, sys +payload = json.loads(sys.argv[1]) +print(payload.get("AuthURL", "")) +PY +)" + if [[ -n "$auth_url" ]]; then + echo "Tailscale login required: $auth_url" + exit 1 + fi + echo "Tailscale is not running" + exit 1 +fi + +"$tailscale_bin" serve --bg http://127.0.0.1:3001 + +dns_name="$(python3 - <<'PY' "$status_json" +import json, sys +payload = json.loads(sys.argv[1]) +print((payload.get("Self", {}) or {}).get("DNSName", "").rstrip(".")) +PY +)" + +if [[ -z "$dns_name" ]]; then + echo "Tailscale is running, but no device DNS name is available yet" >&2 + exit 1 +fi + +echo "Private remote URL: https://$dns_name" diff --git a/scripts/install-mobile-codex-launchd.sh b/scripts/install-mobile-codex-launchd.sh new file mode 100755 index 0000000..63a7796 --- /dev/null +++ b/scripts/install-mobile-codex-launchd.sh @@ -0,0 +1,34 @@ +#!/usr/bin/env bash +set -euo pipefail + +source "$(dirname "$0")/lib-mobile-codex.sh" + +workspace="$(workspace_dir)" +launch_agent="$HOME/Library/LaunchAgents/com.mobilecodexhelper.agent.plist" +mkdir -p "$(dirname "$launch_agent")" "$(logs_dir)" + +cat >"$launch_agent" < + + + + Label + com.mobilecodexhelper.agent + ProgramArguments + + /bin/bash + $workspace/scripts/start-mobile-codex.sh + + RunAtLoad + + StandardOutPath + $(stdout_log) + StandardErrorPath + $(stderr_log) + + +PLIST + +launchctl unload "$launch_agent" >/dev/null 2>&1 || true +launchctl load "$launch_agent" +echo "Installed launch agent at $launch_agent" diff --git a/scripts/lib-mobile-codex.sh b/scripts/lib-mobile-codex.sh index d4eeaed..8b3ac70 100755 --- a/scripts/lib-mobile-codex.sh +++ b/scripts/lib-mobile-codex.sh @@ -1,8 +1,10 @@ #!/usr/bin/env bash +MOBILE_CODEX_LIB_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" >/dev/null 2>&1 && pwd)" +MOBILE_CODEX_WORKSPACE_DIR="$(cd "$MOBILE_CODEX_LIB_DIR/.." >/dev/null 2>&1 && pwd)" + workspace_dir() { - cd "$(dirname "${BASH_SOURCE[0]}")/.." >/dev/null 2>&1 - pwd + printf '%s\n' "$MOBILE_CODEX_WORKSPACE_DIR" } resolve_upstream_dir() { @@ -30,7 +32,31 @@ resolve_node() { return 0 fi - command -v node + local upstream_dir probe_target candidate current_node + upstream_dir="$(resolve_upstream_dir)" + if [[ -d "$upstream_dir/node_modules/better-sqlite3" ]]; then + probe_target="$upstream_dir/node_modules/better-sqlite3" + else + probe_target="" + fi + + current_node="$(command -v node 2>/dev/null || true)" + if [[ -n "$current_node" ]]; then + if [[ -z "$probe_target" ]] || "$current_node" -e 'const Database=require(process.argv[1]); const db=new Database(":memory:"); db.close();' "$probe_target" >/dev/null 2>&1; then + printf '%s\n' "$current_node" + return 0 + fi + fi + + while IFS= read -r candidate; do + [[ -n "$candidate" && -x "$candidate" ]] || continue + if [[ -z "$probe_target" ]] || "$candidate" -e 'const Database=require(process.argv[1]); const db=new Database(":memory:"); db.close();' "$probe_target" >/dev/null 2>&1; then + printf '%s\n' "$candidate" + return 0 + fi + done < <(find "$HOME/.nvm/versions/node" -mindepth 3 -maxdepth 3 -path '*/bin/node' -type f 2>/dev/null | sort -Vr) + + return 1 } resolve_tailscale() { @@ -45,3 +71,32 @@ resolve_tailscale() { logs_dir() { printf '%s/tmp/logs\n' "$(workspace_dir)" } + +runtime_dir() { + printf '%s/.runtime\n' "$(workspace_dir)" +} + +database_dir() { + printf '%s/auth\n' "$(runtime_dir)" +} + +database_path() { + if [[ -n "${MOBILE_CODEX_DATABASE_PATH:-}" ]]; then + printf '%s\n' "$MOBILE_CODEX_DATABASE_PATH" + return 0 + fi + + printf '%s/auth.db\n' "$(database_dir)" +} + +pid_file() { + printf '%s/mobile-codex.pid\n' "$(runtime_dir)" +} + +stdout_log() { + printf '%s/mobile-codex-app.stdout.log\n' "$(logs_dir)" +} + +stderr_log() { + printf '%s/mobile-codex-app.stderr.log\n' "$(logs_dir)" +} diff --git a/scripts/start-mobile-codex-stack.sh b/scripts/start-mobile-codex-stack.sh new file mode 100755 index 0000000..44f627a --- /dev/null +++ b/scripts/start-mobile-codex-stack.sh @@ -0,0 +1,4 @@ +#!/usr/bin/env bash +set -euo pipefail + +"$(dirname "$0")/start-mobile-codex.sh" diff --git a/scripts/start-mobile-codex.sh b/scripts/start-mobile-codex.sh new file mode 100755 index 0000000..38414a0 --- /dev/null +++ b/scripts/start-mobile-codex.sh @@ -0,0 +1,55 @@ +#!/usr/bin/env bash +set -euo pipefail + +source "$(dirname "$0")/lib-mobile-codex.sh" + +upstream_dir="$(resolve_upstream_dir)" +node_bin="$(resolve_node || true)" +db_path="$(database_path)" + +require_path "$upstream_dir" "Upstream checkout not found" +require_path "$node_bin" "Node executable not found" +mkdir -p "$(logs_dir)" "$(runtime_dir)" "$(database_dir)" +: >"$(stdout_log)" +: >"$(stderr_log)" + +if [[ -f "$(pid_file)" ]]; then + existing_pid="$(cat "$(pid_file)")" + if kill -0 "$existing_pid" >/dev/null 2>&1; then + echo "Mobile Codex app already running with PID $existing_pid" + exit 0 + fi + + rm -f "$(pid_file)" +fi + +( + cd "$upstream_dir" + export NODE_ENV=production + export HOST=127.0.0.1 + export PORT=3001 + export DATABASE_PATH="$db_path" + export CODEX_ONLY_HARDENED_MODE=true + export VITE_CODEX_ONLY_HARDENED_MODE=true + nohup "$node_bin" server/index.js >>"$(stdout_log)" 2>>"$(stderr_log)" & + echo $! >"$(pid_file)" +) + +pid="$(cat "$(pid_file)")" +for _ in {1..20}; do + if ! kill -0 "$pid" >/dev/null 2>&1; then + rm -f "$(pid_file)" + echo "Mobile Codex app exited before becoming healthy. Check $(stderr_log)" >&2 + exit 1 + fi + + if curl -sf http://127.0.0.1:3001/health >/dev/null 2>&1; then + echo "Started mobile Codex app with PID $pid" + exit 0 + fi + + sleep 1 +done + +echo "Mobile Codex app is still starting, but /health did not respond within 20s. Check $(stdout_log) and $(stderr_log)" >&2 +exit 1 diff --git a/scripts/stop-mobile-codex-stack.sh b/scripts/stop-mobile-codex-stack.sh new file mode 100755 index 0000000..423817a --- /dev/null +++ b/scripts/stop-mobile-codex-stack.sh @@ -0,0 +1,4 @@ +#!/usr/bin/env bash +set -euo pipefail + +"$(dirname "$0")/stop-mobile-codex.sh" diff --git a/scripts/stop-mobile-codex.sh b/scripts/stop-mobile-codex.sh new file mode 100755 index 0000000..a67a53e --- /dev/null +++ b/scripts/stop-mobile-codex.sh @@ -0,0 +1,30 @@ +#!/usr/bin/env bash +set -euo pipefail + +source "$(dirname "$0")/lib-mobile-codex.sh" + +if [[ ! -f "$(pid_file)" ]]; then + echo "No PID file found" + exit 0 +fi + +pid="$(cat "$(pid_file)")" +if kill -0 "$pid" >/dev/null 2>&1; then + kill "$pid" + + for _ in {1..10}; do + if ! kill -0 "$pid" >/dev/null 2>&1; then + rm -f "$(pid_file)" + echo "Stopped mobile Codex app PID $pid" + exit 0 + fi + sleep 1 + done + + echo "Process $pid did not stop within 10s" >&2 + exit 1 +else + echo "Process $pid is not running" +fi + +rm -f "$(pid_file)" diff --git a/scripts/test-macos-runtime.sh b/scripts/test-macos-runtime.sh index 80e35ba..f8998a1 100755 --- a/scripts/test-macos-runtime.sh +++ b/scripts/test-macos-runtime.sh @@ -5,6 +5,13 @@ scripts=( "scripts/lib-mobile-codex.sh" "scripts/apply-upstream-overrides.sh" "scripts/check-mobile-codex-runtime.sh" + "scripts/start-mobile-codex.sh" + "scripts/stop-mobile-codex.sh" + "scripts/start-mobile-codex-stack.sh" + "scripts/stop-mobile-codex-stack.sh" + "scripts/enable-mobile-codex-remote.sh" + "scripts/install-mobile-codex-launchd.sh" + "scripts/uninstall-mobile-codex-launchd.sh" ) for script in "${scripts[@]}"; do @@ -15,4 +22,22 @@ for script in "${scripts[@]}"; do bash -n "$script" done -echo "macOS runtime foundation scripts look valid" +repo_root="$(pwd)" +path_report="$(bash -lc "cd /tmp && source \"$repo_root/scripts/lib-mobile-codex.sh\" && printf 'workspace=%s\nruntime=%s\ndatabase=%s\n' \"\$(workspace_dir)\" \"\$(runtime_dir)\" \"\$(database_path)\"")" + +[[ "$path_report" == *"workspace=$repo_root"* ]] || { + echo "workspace_dir did not stay anchored to the repo root" >&2 + exit 1 +} + +[[ "$path_report" == *"runtime=$repo_root/.runtime"* ]] || { + echo "runtime_dir did not resolve under the repo root" >&2 + exit 1 +} + +[[ "$path_report" == *"database=$repo_root/.runtime/auth/auth.db"* ]] || { + echo "database_path did not resolve under the repo runtime directory" >&2 + exit 1 +} + +echo "all macOS runtime scripts look valid" diff --git a/scripts/uninstall-mobile-codex-launchd.sh b/scripts/uninstall-mobile-codex-launchd.sh new file mode 100755 index 0000000..4ba77cf --- /dev/null +++ b/scripts/uninstall-mobile-codex-launchd.sh @@ -0,0 +1,7 @@ +#!/usr/bin/env bash +set -euo pipefail + +launch_agent="$HOME/Library/LaunchAgents/com.mobilecodexhelper.agent.plist" +launchctl unload "$launch_agent" >/dev/null 2>&1 || true +rm -f "$launch_agent" +echo "Removed launch agent $launch_agent" From 9ce2c4b761e3f8fef6b6be36b96c70801ef2bde4 Mon Sep 17 00:00:00 2001 From: Bobyue0118 <465741287@qq.com> Date: Fri, 10 Apr 2026 15:38:19 +0800 Subject: [PATCH 7/9] docs: add macos mobile deployment guide --- README.en.md | 7 +++++++ README.md | 8 ++++++++ docs/DEPLOYMENT-macos.md | 29 +++++++++++++++++++++++++++++ docs/DEPLOYMENT-macos.zh-CN.md | 29 +++++++++++++++++++++++++++++ docs/DEPLOYMENT.md | 3 +++ docs/DEPLOYMENT.zh-CN.md | 3 +++ 6 files changed, 79 insertions(+) create mode 100644 docs/DEPLOYMENT-macos.md create mode 100644 docs/DEPLOYMENT-macos.zh-CN.md diff --git a/README.en.md b/README.en.md index b7ffd70..c6c801c 100644 --- a/README.en.md +++ b/README.en.md @@ -114,6 +114,13 @@ powershell -ExecutionPolicy Bypass -File scripts/enable-mobile-codex-remote.ps1 At that point, you can usually continue controlling Codex from the phone. +## macOS mobile-first deployment + +- English: `docs/DEPLOYMENT-macos.md` +- 中文:`docs/DEPLOYMENT-macos.zh-CN.md` + +This path is for the workflow where Codex stays on the Mac, `./scripts/start-mobile-codex.sh` runs the local service, `./scripts/enable-mobile-codex-remote.sh` exposes the private Tailscale path, and the local Owner Admin panel approves the iPhone. + ## Step 1: Download this project Put this repository in a working directory, for example: diff --git a/README.md b/README.md index 335a57b..b5ee152 100644 --- a/README.md +++ b/README.md @@ -146,6 +146,14 @@ Tailscale 私网 HTTPS - 中文:`docs/DEPLOYMENT.zh-CN.md` - English: `docs/DEPLOYMENT.md` +## macOS 手机优先部署 + +- English: `docs/DEPLOYMENT-macos.md` +- 中文:`docs/DEPLOYMENT-macos.zh-CN.md` + +这条路线对应的是“Mac 上运行 Codex,iPhone 通过私有网页继续控制”的工作方式。 +关键命令是 `./scripts/start-mobile-codex.sh`、`./scripts/enable-mobile-codex-remote.sh`,以及本地页面里的 Owner Admin 面板。 + ## 部署成功的判断标准 如果下面这些都满足,说明部署基本成功: diff --git a/docs/DEPLOYMENT-macos.md b/docs/DEPLOYMENT-macos.md new file mode 100644 index 0000000..9387e02 --- /dev/null +++ b/docs/DEPLOYMENT-macos.md @@ -0,0 +1,29 @@ +# macOS Deployment Guide + +This guide is for the macOS mobile-first workflow. +The Mac remains the only machine that runs Codex. Your iPhone uses a private web UI to view sessions, send prompts, and approve new devices through the local Owner Admin panel. + +## Required software + +- Node.js 22 LTS recommended before `npm install` +- Git +- Tailscale for private remote access from anywhere +- Codex already installed and working on the Mac + +## Quick start + +1. Place upstream `claudecodeui v1.25.2` in `vendor/claudecodeui-1.25.2` +2. Run `./scripts/apply-upstream-overrides.sh` +3. Run `./scripts/check-mobile-codex-runtime.sh` +4. Go to `vendor/claudecodeui-1.25.2` and run `npm install` +5. Run `./scripts/start-mobile-codex.sh` +6. Open `http://127.0.0.1:3001` in a Mac browser and register your account +7. Run `./scripts/enable-mobile-codex-remote.sh` +8. Open the Owner Admin panel from the local app, approve the iPhone, and keep using Codex from the phone + +## Notes + +- Local-only testing works without Tailscale. Remote phone access does not. +- `./scripts/check-mobile-codex-runtime.sh` prints the upstream path, resolved Node path, Tailscale path, and workspace-local database path. +- `./scripts/start-mobile-codex.sh` keeps the app bound to `127.0.0.1:3001` and stores auth data under `.runtime/auth/auth.db`. +- `./scripts/install-mobile-codex-launchd.sh` installs a per-user `launchd` agent if you want the Mac service to start automatically. diff --git a/docs/DEPLOYMENT-macos.zh-CN.md b/docs/DEPLOYMENT-macos.zh-CN.md new file mode 100644 index 0000000..e626581 --- /dev/null +++ b/docs/DEPLOYMENT-macos.zh-CN.md @@ -0,0 +1,29 @@ +# macOS 部署说明 + +这份文档对应的是 macOS 手机优先工作流。 +Codex 只在 Mac 上运行,iPhone 通过私有网页查看会话、继续发消息,并在本地 Owner Admin 面板里完成新设备审批。 + +## 需要的软件 + +- 建议在执行 `npm install` 前准备好 Node.js 22 LTS +- Git +- Tailscale,用于随时随地的私有远程访问 +- Mac 上已经安装并能正常使用的 Codex + +## 快速开始 + +1. 把上游 `claudecodeui v1.25.2` 放到 `vendor/claudecodeui-1.25.2` +2. 运行 `./scripts/apply-upstream-overrides.sh` +3. 运行 `./scripts/check-mobile-codex-runtime.sh` +4. 进入 `vendor/claudecodeui-1.25.2` 并执行 `npm install` +5. 运行 `./scripts/start-mobile-codex.sh` +6. 在 Mac 浏览器打开 `http://127.0.0.1:3001`,注册你的账号 +7. 运行 `./scripts/enable-mobile-codex-remote.sh` +8. 在本地页面打开 Owner Admin 面板,批准 iPhone,然后继续用手机控制 Codex + +## 说明 + +- 不装 Tailscale 也可以做本地测试,但不能实现随时随地的私有远程访问。 +- `./scripts/check-mobile-codex-runtime.sh` 会打印上游目录、实际 Node 路径、Tailscale 路径,以及工作区内的数据库路径。 +- `./scripts/start-mobile-codex.sh` 会把应用固定绑定到 `127.0.0.1:3001`,并把认证数据写到 `.runtime/auth/auth.db`。 +- 如果你希望服务自动随登录启动,可以运行 `./scripts/install-mobile-codex-launchd.sh` 安装当前用户的 `launchd` agent。 diff --git a/docs/DEPLOYMENT.md b/docs/DEPLOYMENT.md index 9884734..3cf0e5e 100644 --- a/docs/DEPLOYMENT.md +++ b/docs/DEPLOYMENT.md @@ -5,6 +5,9 @@ This guide is written for first-time users. The goal is simple: get the stack running on a Windows PC and make it reachable from your phone. +This document remains the Windows-first deployment path. +For the macOS mobile-first path, use `docs/DEPLOYMENT-macos.md`. + ## Expected result After deployment, you should be able to: diff --git a/docs/DEPLOYMENT.zh-CN.md b/docs/DEPLOYMENT.zh-CN.md index 5ec43e1..68ca68f 100644 --- a/docs/DEPLOYMENT.zh-CN.md +++ b/docs/DEPLOYMENT.zh-CN.md @@ -5,6 +5,9 @@ 这份文档是给第一次部署的人看的。 目标不是解释内部实现,而是帮助你一步一步把它跑起来,并让手机能够访问。 +这份文档仍然是 Windows 优先部署路线。 +如果你要走 macOS 手机优先路线,请改看 `docs/DEPLOYMENT-macos.zh-CN.md`。 + ## 目标效果 部署完成后,你应该能做到这些事: From a38d5c465cdb63b3a987f0c71fc02a59aa78c461 Mon Sep 17 00:00:00 2001 From: Bobyue0118 <465741287@qq.com> Date: Fri, 10 Apr 2026 16:22:53 +0800 Subject: [PATCH 8/9] docs: rewrite README for macos beginners --- README.en.md | 472 +++++++++++++++++++-------------------------------- README.md | 399 ++++++++++++++++++++++--------------------- 2 files changed, 384 insertions(+), 487 deletions(-) diff --git a/README.en.md b/README.en.md index c6c801c..239204b 100644 --- a/README.en.md +++ b/README.en.md @@ -2,419 +2,309 @@ [中文](README.md) | [English](README.en.md) -Turn the Codex sessions running on your computer into a private, phone-friendly web control panel. +Turn the Codex sessions running on your Mac into a private, iPhone-friendly web control panel. -This project is built for a simple use case: +The goal of this fork is simple: -- Codex runs locally on your PC -- you want to view projects, sessions, and messages from your phone -- you want to send follow-up prompts from your phone and let Codex continue on the PC -- you want private-by-default access, with first-time device approval from the desktop +- Codex keeps running on the Mac +- the iPhone uses a private web UI to view projects, sessions, messages, and history +- the iPhone can send follow-up prompts and let the Mac continue the work +- first-time devices must be approved from the Mac locally -If you are not familiar with this kind of setup, that is fine. This README is written as a practical deployment guide. +## How this fork differs from the original Windows-first version -## Interface preview +| Topic | Original Windows-first path | This fork's macOS mobile-first path | +| --- | --- | --- | +| Main platform | Windows PC | macOS + iPhone | +| Main control surface | Windows desktop tool, portable EXE, PowerShell | local browser UI, Owner Admin panel, Bash scripts | +| Local service management | `start-mobile-codex-stack.ps1`, nginx, desktop UI | `start-mobile-codex.sh`, optional `launchd`, local browser | +| Remote phone access | enabled from the desktop tool | `./scripts/enable-mobile-codex-remote.sh` + Tailscale Serve | +| First-device approval | approved from the Windows desktop tool | approved from the local Owner Admin panel on the Mac | +| Best for | people who want a Windows portable workflow | people who want Codex on a Mac and control from an iPhone | -The screenshot below shows the Windows desktop control tool: +If you want the original Windows route, use: -![Mobile Codex control console preview](docs/assets/mobile-codex-control-console.png) +- Windows English deployment: `docs/DEPLOYMENT.md` +- Windows 中文部署:`docs/DEPLOYMENT.zh-CN.md` + +If you want the Mac + iPhone workflow, keep reading this README. -## What it does +## The 3 things beginners should remember first -- view Codex projects and sessions from a phone browser -- send messages from the phone to continue controlling Codex on the PC -- require desktop approval before a new device can log in -- provide a Windows desktop tool to monitor: - - local service health - - remote publish state - - trusted device whitelist - - pending approval requests +1. The Mac uses `http://127.0.0.1:3001` +2. The iPhone uses a Tailscale URL like `https://.ts.net` +3. The iPhone must not use `127.0.0.1`, and the first login may require approval on the Mac + +## Who this is for + +- you already use Codex successfully on a Mac +- you want to view history and continue sessions from an iPhone +- you want private-by-default access through Tailscale +- you are setting up a single-user workflow, not a shared service ## What it is not -- not a multi-user SaaS system -- not intended for exposing the Node app directly to the public internet +- not a multi-user SaaS +- not a public internet deployment target - not a full remote desktop or full remote IDE -- focused on “phone view + chat control”, not every high-risk capability -## Recommended architecture +## Workflow at a glance ```text -Phone browser +iPhone Safari + ↓ +Tailscale private HTTPS URL ↓ -Tailscale private HTTPS +Mac local web service (127.0.0.1:3001) ↓ -Local nginx reverse proxy +Codex sessions running on the Mac + +Mac local browser ↓ -Local claudecodeui with this project's patches +The same local web service ↓ -Codex sessions on your PC +Owner Admin panel for approving new devices ``` -## Prerequisites +## Interface preview -Prepare the following on your Windows PC: +The screenshot below comes from the original Windows control-console preview. +The main change in this fork is not a brand-new UI, but the completed macOS + iPhone workflow around it. -### Required +![Mobile Codex control console preview](docs/assets/mobile-codex-control-console.png) -- Python 3.11+ -- Node.js 22 LTS -- Git -- nginx for Windows -- a working local Codex environment +## Detailed beginner setup -### Strongly recommended +### Step 0: Prepare these first +- a Mac where Codex already works +- Git +- Node.js 22 LTS - Tailscale +- an iPhone +- upstream `claudecodeui v1.25.2` -Why: - -- it is the easiest way to make this “private access for yourself only” -- much safer than direct public exposure - -## Fastest path to deployment +The upstream directory must be placed at: -If you do not want to read everything first, follow this shortest path: +```text +vendor/claudecodeui-1.25.2 +``` -### On the PC +### Step 1: Apply the upstream patch layer -1. Install Python 3.11+, Node.js 22, nginx, and Tailscale -2. Put upstream `claudecodeui v1.25.2` into `vendor/claudecodeui-1.25.2` -3. Run: +Run from the repo root: -```powershell -powershell -ExecutionPolicy Bypass -File scripts/apply-upstream-overrides.ps1 +```bash +./scripts/apply-upstream-overrides.sh +./scripts/check-mobile-codex-runtime.sh cd vendor/claudecodeui-1.25.2 npm install -cd ..\.. -powershell -ExecutionPolicy Bypass -File scripts/start-mobile-codex-stack.ps1 -python mobile_codex_control.py +cd ../.. ``` -4. Open this in a desktop browser: +What to check: -```text -http://127.0.0.1:3001 -``` +- `UpstreamExists=true` +- `UpstreamPath` points to `vendor/claudecodeui-1.25.2` +- `Node=` has a value +- if Tailscale is installed already, `Tailscale=` should also have a value -5. Complete the first registration +### Step 2: Start the local Mac service -### On the phone - -1. Install and log into Tailscale -2. On the PC, run: +Run: -```powershell -powershell -ExecutionPolicy Bypass -File scripts/enable-mobile-codex-remote.ps1 +```bash +./scripts/start-mobile-codex.sh ``` -3. Open the private HTTPS address shown by Tailscale -4. Log in with the account you just created -5. If the phone waits for approval, approve the device in the desktop tool - -At that point, you can usually continue controlling Codex from the phone. +Then open this in a browser on the Mac: -## macOS mobile-first deployment - -- English: `docs/DEPLOYMENT-macos.md` -- 中文:`docs/DEPLOYMENT-macos.zh-CN.md` +```text +http://127.0.0.1:3001 +``` -This path is for the workflow where Codex stays on the Mac, `./scripts/start-mobile-codex.sh` runs the local service, `./scripts/enable-mobile-codex-remote.sh` exposes the private Tailscale path, and the local Owner Admin panel approves the iPhone. +On first use: -## Step 1: Download this project +1. register your account +2. log in +3. confirm the local page works +4. find the Owner Admin panel entry -Put this repository in a working directory, for example: +The Owner Admin panel is where you approve first-time devices. -```text -D:\mobileCodexHelper -``` +### Step 3: Enable the private iPhone URL -## Step 2: Download upstream claudecodeui +Make sure: -This project is not a full replacement for upstream. It is a hardened and phone-control layer on top of upstream. +- Tailscale is installed and logged in on the Mac +- Tailscale is installed and logged in on the iPhone +- both devices are in the same tailnet -Download upstream `siteboon/claudecodeui` `v1.25.2` into: +Then run on the Mac: -```text -vendor/claudecodeui-1.25.2 +```bash +./scripts/enable-mobile-codex-remote.sh ``` -Expected layout: +You should get output like: ```text -mobileCodexHelper/ -├─ vendor/ -│ └─ claudecodeui-1.25.2/ -├─ upstream-overrides/ -├─ scripts/ -├─ deploy/ -└─ mobile_codex_control.py -``` - -## Step 3: Apply this project's override layer - -Run: - -```powershell -powershell -ExecutionPolicy Bypass -File scripts/apply-upstream-overrides.ps1 +Private remote URL: https://your-mac-name.example.ts.net ``` -This copies the files from `upstream-overrides/claudecodeui-1.25.2/` into the upstream checkout. - -## Step 4: Install upstream dependencies +That `Private remote URL` is the real iPhone address. -Go into the upstream directory: +Do not use these on the iPhone: -```powershell -cd vendor/claudecodeui-1.25.2 -npm install -``` +- `http://127.0.0.1:3001` +- the one-time Tailscale admin/login URL shown while enabling Serve -If you only want to run the project and not package the desktop tool, Python usually does not need extra third-party packages. +### Step 4: Log in from the iPhone for the first time -If you want to build the Windows desktop tool as an `.exe`, run: +On the iPhone: -```powershell -pip install -r requirements.txt -``` +1. open Safari +2. open the `Private remote URL` +3. log in with the account you created on the Mac -## Step 5: Check your local environment +Two normal outcomes: -Back in the project root, run: +- it opens directly because the device is already trusted +- it waits for approval because this is a new device -```powershell -powershell -ExecutionPolicy Bypass -File scripts/check-mobile-codex-runtime.ps1 -``` +If it waits for approval, go back to the Mac: -Important fields to confirm: +1. open `http://127.0.0.1:3001` +2. open the Owner Admin panel +3. find the pending iPhone +4. approve it -- `UpstreamExists = True` -- `Node` is present -- `Nginx` is present -- if you want private remote access, `Tailscale` should also be present +After approval, the iPhone should continue automatically. -## Step 6: Start the local stack +### Step 5: Daily usage after setup -Run: +On the Mac: -```powershell -powershell -ExecutionPolicy Bypass -File scripts/start-mobile-codex-stack.ps1 +```bash +./scripts/start-mobile-codex.sh ``` -This starts: +On the iPhone: -1. the local `claudecodeui` service -2. the local nginx reverse proxy +1. open the same Tailscale private URL +2. browse session history +3. reopen an existing session or start a new one +4. send prompts and let the Mac continue the Codex work -Default ports: +When you are done, stop the local service on the Mac: -- app: `127.0.0.1:3001` -- proxy: `127.0.0.1:8080` +```bash +./scripts/stop-mobile-codex.sh +``` -## Step 7: Launch the desktop control tool +### Step 6: Optional auto-start on login -Run either: +Install the per-user `launchd` agent: -```powershell -python mobile_codex_control.py +```bash +./scripts/install-mobile-codex-launchd.sh ``` -or: +Remove it later if you do not want auto-start: -```powershell -scripts\launch-mobile-codex-control.cmd +```bash +./scripts/uninstall-mobile-codex-launchd.sh ``` -The desktop tool shows: +## The 5 most common beginner mistakes -- PC app service status -- nginx status -- Tailscale login state -- remote publish state -- phone device presence -- pending device approvals +### 1. Using the wrong address on the iPhone -## Step 8: First account registration - -Open the local page in a desktop browser: +The iPhone should use: ```text -http://127.0.0.1:3001 +https://.ts.net ``` -Complete the first account registration. - -Notes: +Not: -- this is a single-user system -- the first registered account becomes your main account - -## Step 9: Phone access - -### Local testing first - -First test the login flow from the desktop browser. - -### Private remote access through Tailscale - -If both your PC and phone are logged into the same Tailscale network, run: - -```powershell -powershell -ExecutionPolicy Bypass -File scripts/enable-mobile-codex-remote.ps1 +```text +http://127.0.0.1:3001 ``` -Then check the remote publish state in the desktop control tool. - -Recommended phone clients: - -- a normal mobile browser -- or your own WebView / wrapper app - -## Step 10: First-time device approval - -This is one of the key security features. +### 2. Trying to log in on the iPhone before creating the account on the Mac -When a new phone or new WebView logs in for the first time: +Create the account locally on the Mac first. Then use that same account on the iPhone. -1. the phone page shows “waiting for desktop approval” -2. the desktop tool shows a pending device -3. you verify device name, platform, user agent, and IP -4. you click approve on the PC -5. the phone automatically continues login +### 3. Forgetting first-device approval -Benefits: - -- even if account credentials leak, an unknown device still cannot log in directly -- you control which phones enter the trusted-device whitelist - -## What success looks like - -If all of the following are true, the deployment is basically working: - -- `http://127.0.0.1:3001` opens on the PC -- the desktop tool shows both the app service and nginx as healthy -- the phone can open the private HTTPS address -- the desktop tool shows a pending device on first login -- after approval, the phone enters the project and session list -- sending a message from the phone continues the Codex run on the PC - -## The 3 most common failure points - -If your first deployment fails, start with these three checks: - -### 1. Wrong upstream version or folder path - -You need: - -- upstream version: `v1.25.2` -- folder path: `vendor/claudecodeui-1.25.2` - -If you are not sure the override flow really worked, run: - -```powershell -powershell -ExecutionPolicy Bypass -File scripts/smoke-test-override-flow.ps1 -UpstreamZip -``` +If the iPhone waits for approval, do not assume the password is wrong first. +Check the Owner Admin panel on the Mac. -### 2. Local dependencies were not discovered correctly +### 4. Wrong upstream folder or version -The usual missing executables are: +The expected path is: -- `node.exe` -- `nginx.exe` -- `tailscale.exe` - -Run: - -```powershell -powershell -ExecutionPolicy Bypass -File scripts/check-mobile-codex-runtime.ps1 +```text +vendor/claudecodeui-1.25.2 ``` -If any important field is empty, fix that first. - -### 3. Your wrapped phone app is not WebView-compatible enough +### 5. Tailscale is not logged in on both devices -If a normal phone browser works but your wrapper app fails, suspect the wrapper first, not the account credentials. - -Recommended order: - -- validate the full flow in a normal mobile browser first -- test the wrapper app second -- confirm support for `localStorage`, cookies, `Authorization` headers, and WebSocket +Both the Mac and the iPhone must be logged into the same tailnet. ## Common commands -### Start everything +### Check the runtime -```powershell -powershell -ExecutionPolicy Bypass -File scripts/start-mobile-codex-stack.ps1 +```bash +./scripts/check-mobile-codex-runtime.sh ``` -### Stop everything +### Apply upstream overrides -```powershell -powershell -ExecutionPolicy Bypass -File scripts/stop-mobile-codex-stack.ps1 +```bash +./scripts/apply-upstream-overrides.sh ``` -### Check Tailscale status +### Start the local service -```powershell -powershell -ExecutionPolicy Bypass -File scripts/check-tailscale-status.ps1 +```bash +./scripts/start-mobile-codex.sh ``` -### Package the desktop tool +### Stop the local service -```powershell -scripts\package-mobile-codex-control.cmd +```bash +./scripts/stop-mobile-codex.sh ``` -### Smoke-test the override flow +### Enable the private iPhone URL -```powershell -powershell -ExecutionPolicy Bypass -File scripts/smoke-test-override-flow.ps1 -UpstreamZip +```bash +./scripts/enable-mobile-codex-remote.sh ``` -## Troubleshooting - -### 1. The phone can open the page, but nothing happens after login - -Check: - -- whether the desktop tool shows a pending device -- whether this is the first login for the device -- whether the desktop-side services are still running - -### 2. Browser login works, but a wrapped app fails +### Install auto-start -This project includes WebView compatibility work, but wrapper quality varies a lot. - -Check whether the wrapper allows: - -- `localStorage` -- `Authorization` headers -- WebSocket -- cookie behavior required by the app - -If the browser works but the wrapper app does not, the issue is often the wrapper capability, not the account itself. - -### 3. You see 502 errors - -Check: - -- `tmp/logs/mobile-codex-app.stdout.log` -- `tmp/logs/mobile-codex-app.stderr.log` -- nginx logs +```bash +./scripts/install-mobile-codex-launchd.sh +``` -### 4. Why not expose it directly to the public internet? +### Remove auto-start -Because this project controls local Codex sessions on your PC, which is a high-trust environment. -The intended setup is private network + reverse proxy + device approval, not direct public exposure. +```bash +./scripts/uninstall-mobile-codex-launchd.sh +``` -## Recommended reading +## More documentation -- Deployment guide: [`docs/DEPLOYMENT.md`](docs/DEPLOYMENT.md) +- macOS English deployment: [`docs/DEPLOYMENT-macos.md`](docs/DEPLOYMENT-macos.md) +- macOS 中文部署:[`docs/DEPLOYMENT-macos.zh-CN.md`](docs/DEPLOYMENT-macos.zh-CN.md) +- Windows English deployment: [`docs/DEPLOYMENT.md`](docs/DEPLOYMENT.md) +- Windows 中文部署:[`docs/DEPLOYMENT.zh-CN.md`](docs/DEPLOYMENT.zh-CN.md) - Architecture: [`docs/ARCHITECTURE.md`](docs/ARCHITECTURE.md) - Security policy: [`SECURITY.md`](SECURITY.md) -- Open-source release checklist: [`docs/OPEN_SOURCE_RELEASE_CHECKLIST.md`](docs/OPEN_SOURCE_RELEASE_CHECKLIST.md) ## Upstream and license @@ -423,11 +313,3 @@ This project builds on upstream `siteboon/claudecodeui`. Please keep: - upstream attribution - the included license - a clear description of local modifications - -## Before you publish your own fork - -At minimum: - -1. run `scripts/check-open-source-tree.ps1` -2. run `scripts/smoke-test-override-flow.ps1` -3. review [`SECURITY.md`](SECURITY.md) diff --git a/README.md b/README.md index b5ee152..c02f02d 100644 --- a/README.md +++ b/README.md @@ -2,302 +2,325 @@ [中文](README.md) | [English](README.en.md) -把你电脑上本地运行的 Codex 会话,变成一个可以在手机上访问和控制的私有网页面板。 +把 Mac 上本地运行的 Codex 会话,变成一个可以被 iPhone 私有访问和继续控制的网页面板。 -这个项目适合下面这种需求: -- 你平时在电脑上跑 Codex -- 你想在手机上随时查看项目、会话、消息 -- 你想从手机继续发消息,让电脑上的 Codex 接着执行 -- 你希望默认是私有访问,并且新手机第一次登录要经过电脑批准 +这个 fork 的目标不是做“远程桌面”,而是把你的日常 Codex 工作方式变成: -安全上的考虑: -- 仅允许你本人账号登录 -- 首次新设备登录需电脑端授权 -- 支持设备白名单,陌生设备不能直接进入 -- 手机只发消息控制,不开放电脑桌面 -- 远程访问建议走 Tailscale 等加密通道,降低暴露风险 +- Codex 始终跑在 Mac 上 +- iPhone 通过私有网页查看项目、会话、消息和历史 +- iPhone 可以继续发 prompt,让 Mac 上的 Codex 接着执行 +- 新设备第一次登录必须在 Mac 本地批准 +## 这个 fork 和原始 Windows 支持版有什么不同 -如果你不熟悉这类项目,也没关系。只要你有codex,部署方案:在你电脑codex新起一个线程,复制本项目链接地址:https://github.com/StarsTom/mobileCodexHelper, -然后分析依赖并安装调试运行。 +| 对比项 | 原始 Windows 优先路线 | 这个 fork 的 macOS 手机优先路线 | +| --- | --- | --- | +| 主要平台 | Windows PC | macOS + iPhone | +| 核心入口 | Windows 桌面控制工具、便携版 EXE、PowerShell | 本地网页、Owner Admin 面板、Bash 脚本 | +| 本地服务管理 | `start-mobile-codex-stack.ps1`、nginx、桌面工具 | `start-mobile-codex.sh`、可选 `launchd`、本地浏览器 | +| 手机远程入口 | 桌面工具里开启手机访问 | `./scripts/enable-mobile-codex-remote.sh` + Tailscale Serve | +| 新设备批准 | Windows 桌面工具审批 | Mac 本地网页里的 Owner Admin 面板审批 | +| 推荐对象 | 想要 Windows 便携打包版的人 | 想在 Mac 上跑 Codex,并从 iPhone 随时继续控制的人 | -如果觉得还有点用,请帮忙star一下,谢谢啦~ +如果你要的是原来的 Windows 工作方式,请看: -## 界面预览 +- Windows 中文部署:`docs/DEPLOYMENT.zh-CN.md` +- Windows English deployment: `docs/DEPLOYMENT.md` -下面这张图是 Windows 桌面控制工具的公开 README 预览图: +如果你要的是“Mac 上跑 Codex,iPhone 远程控制”,继续读这份 README。 -![移动 Codex 控制台预览](docs/assets/mobile-codex-control-console.png) +## 新手先记住 3 件事 -## 它能做什么 +1. Mac 自己访问的地址是 `http://127.0.0.1:3001` +2. iPhone 访问的地址是 Tailscale 提供的 `https://<你的机器名>.ts.net` +3. iPhone 不能使用 `127.0.0.1:3001`,第一次登录新设备通常还要回到 Mac 上批准 -- 在手机浏览器中查看 Codex 项目和会话 -- 在手机上发送消息,继续控制电脑上的 Codex -- 首次登录新设备时,必须由电脑端批准 -- 在 Windows 桌面工具中查看: - - 本地服务状态 - - 手机访问开关状态 - - 已批准设备白名单 - - 待审批设备列表 +## 它适合什么人 -## 它不能做什么 +- 你已经能在 Mac 上正常使用 Codex +- 你希望在手机上查看历史、继续发消息、恢复会话 +- 你接受默认走 Tailscale 私网,而不是直接暴露公网 +- 你是单用户使用,不是多人共享服务 -- 不适合多人协作 -- 不建议把 Node 服务直接暴露到公网 -- 默认不是远程桌面,也不是完整 IDE -- 重点是“手机查看和聊天控制”,不是开放所有高风险能力 +## 它不适合什么人 -## 整体工作方式 +- 想做多人协作 SaaS +- 想把高权限 Codex 服务直接暴露到公网 +- 想要完整远程桌面或完整远程 IDE -推荐架构如下: +## 工作方式一图看懂 ```text -手机浏览器 +iPhone Safari + ↓ +Tailscale 私有 HTTPS 地址 ↓ -Tailscale 私网 HTTPS +Mac 本地网页服务(127.0.0.1:3001) ↓ -本机 nginx 代理 +Mac 上正在运行的 Codex 会话 + +Mac 本地浏览器 ↓ -本机网页控制服务 +同一个本地网页服务 ↓ -电脑上的 Codex 会话 +Owner Admin 面板(审批新设备) ``` -## 你需要准备什么 +## 界面预览 + +下面这张图来自原始 Windows 控制台预览。 +这个 fork 的重点变化不是换掉核心界面,而是把 macOS + iPhone 的工作流补齐。 -请先在 Windows 电脑上准备: +![移动 Codex 控制台预览](docs/assets/mobile-codex-control-console.png) -### 必装 +## 新手详细上手 -- Python 3.11 或更高 -- Node.js 22 LTS +### 第 0 步:你需要先准备好这些东西 + +- 一台已经能正常运行 Codex 的 Mac - Git -- nginx for Windows -- 一个可正常使用的 Codex 本地环境 +- Node.js 22 LTS +- Tailscale +- 一台 iPhone +- 上游 `claudecodeui v1.25.2` -### 强烈推荐 +上游目录必须放在这里: -- Tailscale +```text +vendor/claudecodeui-1.25.2 +``` + +如果目录名不对,后面的脚本会直接找不到它。 -原因很简单: +### 第 1 步:把上游补丁应用好 -- 这是最容易做成“只有你自己能访问”的远程方案 -- 比直接公网暴露安全得多 +在仓库根目录运行: -## 更省事的使用方式 +```bash +./scripts/apply-upstream-overrides.sh +./scripts/check-mobile-codex-runtime.sh +cd vendor/claudecodeui-1.25.2 +npm install +cd ../.. +``` -如果你是普通使用者,不想自己手动执行很多命令,推荐直接使用发布页里的便携版: +你应该重点确认: -- 解压整个发布目录 -- 双击 `MobileCodexControl.exe` -- 正常情况下,不需要你自己再准备额外的程序源码或压缩包 +- `UpstreamExists=true` +- `UpstreamPath` 指向 `vendor/claudecodeui-1.25.2` +- `Node=` 有值 +- 如果已经安装 Tailscale,`Tailscale=` 也应该有值 -首次打开后,桌面工具会自动弹出“首次初始化向导”。 -你只需要: +说明: -1. 确认 `node.exe`、`nginx.exe`、`tailscale.exe` 路径 -2. 点击“`一键初始化并启动`” +- `apply-upstream-overrides.sh` 会把本仓库里的覆盖文件应用到上游目录 +- `check-mobile-codex-runtime.sh` 会告诉你脚本实际找到的 Node、Tailscale、数据库路径 +- `npm install` 必须在 `vendor/claudecodeui-1.25.2` 目录里执行 -向导会自动帮你完成: +### 第 2 步:启动 Mac 本地服务 -- 保存本机路径配置 -- 识别便携包中已经内置的运行环境 -- 启动本地服务 +运行: -只有在排查问题或维护发布包时,才可能需要打开“故障处理工具”并手动选择程序目录或压缩包。 +```bash +./scripts/start-mobile-codex.sh +``` -## 最快部署路线 +然后在 Mac 浏览器打开: -如果你不想一开始看太多说明,可以直接按这个顺序做: +```text +http://127.0.0.1:3001 +``` -1. 安装 Python 3.11+、Node.js 22、nginx、Tailscale -2. 从发布页下载便携版,完整解压到一个固定目录 -3. 双击 `MobileCodexControl.exe` -4. 在初始化向导里确认 `node.exe`、`nginx.exe`、`tailscale.exe` 路径 -5. 点击“`一键初始化并启动`” -6. 在电脑浏览器打开 `http://127.0.0.1:3001`,完成第一次注册 -7. 在桌面工具里点击“开启手机访问” -8. 让手机和电脑登录同一个 Tailscale 网络 -9. 用手机打开桌面工具里显示的“手机访问地址” -10. 首次登录新设备时,在电脑端批准这台设备 +第一次使用时,你应该: -做到这里,你通常已经可以从手机继续控制电脑上的 Codex 了。 +1. 注册账号 +2. 登录 +3. 确认本地页面已经正常打开 +4. 找到 Owner Admin 面板入口 -## 首次设备批准 +Owner Admin 面板的作用是: -这是本项目最重要的安全机制之一。 +- 查看当前本地服务状态 +- 看待批准设备 +- 批准 iPhone 这类新设备 -当一个新手机或新 WebView 第一次登录时: +### 第 3 步:给 iPhone 打开私有访问入口 -1. 手机端会提示“等待电脑端批准” -2. 电脑端桌面控制工具会出现待审批设备 -3. 你核对设备名、平台、UA、IP 后点击“批准所选” -4. 手机端自动继续登录 +先确保: -这样做的好处是: +- Mac 上已经安装并登录 Tailscale +- iPhone 上也安装并登录了同一个 Tailnet -- 即使账号密码泄露,未知设备也不能直接登录 -- 你可以控制哪些手机被加入白名单 +然后在 Mac 上运行: -## 可选:自己从源码构建 +```bash +./scripts/enable-mobile-codex-remote.sh +``` -如果你想自己维护、二次开发,或者重新生成便携发布目录,再看源码部署路线: +你会看到类似输出: -- 中文:`docs/DEPLOYMENT.zh-CN.md` -- English: `docs/DEPLOYMENT.md` +```text +Private remote URL: https://your-mac-name.example.ts.net +``` -## macOS 手机优先部署 +真正给 iPhone 用的是这一行里的 `Private remote URL`。 -- English: `docs/DEPLOYMENT-macos.md` -- 中文:`docs/DEPLOYMENT-macos.zh-CN.md` +不要把下面这些地址拿去给 iPhone: -这条路线对应的是“Mac 上运行 Codex,iPhone 通过私有网页继续控制”的工作方式。 -关键命令是 `./scripts/start-mobile-codex.sh`、`./scripts/enable-mobile-codex-remote.sh`,以及本地页面里的 Owner Admin 面板。 +- `http://127.0.0.1:3001` +- Tailscale 的一次性网页登录地址 -## 部署成功的判断标准 +### 第 4 步:第一次让 iPhone 登录 -如果下面这些都满足,说明部署基本成功: +在 iPhone 上: -- 电脑端 `http://127.0.0.1:3001` 能打开 -- 桌面控制工具里 PC 应用服务和 nginx 都是正常 -- 手机能打开私有 HTTPS 地址 -- 手机首次登录时,电脑端能看到待审批设备 -- 你批准后,手机能进入项目和会话列表 -- 手机发送消息后,电脑上的 Codex 会继续执行 +1. 打开 Safari +2. 访问刚才的 `Private remote URL` +3. 使用你在 Mac 本地页面里刚注册的账号登录 -## 最容易失败的 3 个点 +可能出现两种情况: -如果你第一次部署就遇到问题,通常优先排查这 3 个地方: +- 直接进入页面:说明这台设备已经被信任 +- 显示等待批准:这是正常的首次设备审批流程 -### 1. 程序目录不完整或位置被改动 +如果 iPhone 显示等待批准,就回到 Mac: -先确认这两件事: +1. 打开本地页面 `http://127.0.0.1:3001` +2. 进入 Owner Admin 面板 +3. 找到待批准的 iPhone +4. 点击批准 -- 你解压的是整个便携目录,而不是只拿出了其中的 `MobileCodexControl.exe` -- 程序目录里没有误删 `vendor/claudecodeui-1.25.2` +批准后,iPhone 会自动继续登录。 -如果你是自己从源码构建的维护者,再额外确认: +### 第 5 步:以后每天怎么用 -- 使用的是 `claudecodeui v1.25.2` -- 目录名是 `vendor/claudecodeui-1.25.2` +每天使用通常只需要: -如果你想做源码覆盖自检,可以运行: +在 Mac 上: -```powershell -powershell -ExecutionPolicy Bypass -File scripts/smoke-test-override-flow.ps1 -UpstreamZip <你的程序源码压缩包路径> +```bash +./scripts/start-mobile-codex.sh ``` -### 2. 本机依赖路径没有被脚本找到 - -最常见的是: +在 iPhone 上: -- `node.exe` -- `nginx.exe` -- `tailscale.exe` +1. 打开之前的 Tailscale 私有地址 +2. 查看会话历史 +3. 打开已有 session,或者新建 session +4. 继续发 prompt,让 Mac 上的 Codex 接着跑 -先运行: +结束时,在 Mac 上停止服务: -```powershell -powershell -ExecutionPolicy Bypass -File scripts/check-mobile-codex-runtime.ps1 +```bash +./scripts/stop-mobile-codex.sh ``` -只要里面有空值,就先不要继续下一步。 +### 第 6 步:如果你希望 Mac 登录后自动启动 -### 3. 手机封装壳不兼容 +安装当前用户的 `launchd` agent: -如果手机浏览器能登录,但封装成 App 的 WebView 不行,先默认怀疑是壳兼容问题,不要先怀疑账号密码。 +```bash +./scripts/install-mobile-codex-launchd.sh +``` -优先建议: +如果以后不想自动启动了: -- 先用手机浏览器打通全流程 -- 再测试封装 App -- 确认壳支持 `localStorage`、Cookie、`Authorization` 请求头和 WebSocket +```bash +./scripts/uninstall-mobile-codex-launchd.sh +``` -## 常用命令 +## 新手最容易踩的 5 个坑 -### 启动整套服务 +### 1. 把错误的地址发给 iPhone -```powershell -powershell -ExecutionPolicy Bypass -File scripts/start-mobile-codex-stack.ps1 +iPhone 用的是: + +```text +https://<你的机器名>.ts.net ``` -### 停止整套服务 +不是: -```powershell -powershell -ExecutionPolicy Bypass -File scripts/stop-mobile-codex-stack.ps1 +```text +http://127.0.0.1:3001 ``` -### 检查 Tailscale 状态 +### 2. 没有先在 Mac 本地注册账号 -```powershell -powershell -ExecutionPolicy Bypass -File scripts/check-tailscale-status.ps1 -``` +第一次账号注册应该先在 Mac 本地页面完成,再让 iPhone 用这个账号登录。 -### 维护者:打包桌面工具 +### 3. 忘了批准第一次登录的新设备 -```powershell -scripts\package-mobile-codex-control.cmd -``` +如果 iPhone 一直卡在等待批准,不要先怀疑密码。 +先去 Mac 本地页面里的 Owner Admin 面板看待审批设备。 + +### 4. 上游目录不对 -### 维护者:生成便携发布目录 +目录必须是: -```powershell -scripts\package-mobile-codex-helper.cmd +```text +vendor/claudecodeui-1.25.2 ``` -说明: +不是别的版本,也不是别的目录名。 -- 当前推荐发布形态就是“带内置运行环境的便携目录 + `MobileCodexControl.exe`” -- 不再要求额外制作安装包 +### 5. Tailscale 没有在两台设备上同时登录 -### 维护者:源码覆盖自测 +Mac 和 iPhone 必须同时登录同一个 Tailnet。 +否则 iPhone 打不开私有 URL,即使本地 `127.0.0.1:3001` 在 Mac 上是正常的。 -```powershell -powershell -ExecutionPolicy Bypass -File scripts/smoke-test-override-flow.ps1 -UpstreamZip <你的程序源码压缩包路径> -``` +## 常用命令 -## 常见问题 +### 检查运行环境 -### 1. 手机能打开页面,但登录后没反应 +```bash +./scripts/check-mobile-codex-runtime.sh +``` -优先检查: +### 应用上游覆盖文件 -- 桌面工具里是否出现待审批设备 -- 手机是不是第一次登录新设备 -- 电脑端服务是否还在运行 +```bash +./scripts/apply-upstream-overrides.sh +``` -### 2. 手机浏览器能登录,封装 App 登录失败 +### 启动本地服务 -本项目已经针对 WebView 做了兼容处理,但不同壳的实现质量差异很大。 +```bash +./scripts/start-mobile-codex.sh +``` -建议依次检查: +### 停止本地服务 -- 壳是否允许 `localStorage` -- 壳是否允许 `Authorization` 请求头 -- 壳是否允许 WebSocket -- 壳是否拦截 Cookie 或跨域行为 +```bash +./scripts/stop-mobile-codex.sh +``` -如果浏览器可以、壳不可以,通常是壳自身 WebView 能力不足,而不是账号密码错误。 +### 开启 iPhone 私有访问入口 -### 3. 出现 502 +```bash +./scripts/enable-mobile-codex-remote.sh +``` -优先检查这些日志: +### 安装自动启动 -- `tmp/logs/mobile-codex-app.stdout.log` -- `tmp/logs/mobile-codex-app.stderr.log` -- nginx 日志目录 +```bash +./scripts/install-mobile-codex-launchd.sh +``` -### 4. 为什么不直接公网暴露? +### 卸载自动启动 -因为这个项目控制的是你电脑上的本地 Codex,会话权限很高。 -推荐私网、反向代理、设备白名单三层一起用,不建议直接裸露到公网。 +```bash +./scripts/uninstall-mobile-codex-launchd.sh +``` -## 推荐阅读 +## 更详细的文档 -- 部署说明:[`docs/DEPLOYMENT.zh-CN.md`](docs/DEPLOYMENT.zh-CN.md) -- 架构说明:[`docs/ARCHITECTURE.zh-CN.md`](docs/ARCHITECTURE.zh-CN.md) +- macOS 中文部署:[`docs/DEPLOYMENT-macos.zh-CN.md`](docs/DEPLOYMENT-macos.zh-CN.md) +- macOS English deployment: [`docs/DEPLOYMENT-macos.md`](docs/DEPLOYMENT-macos.md) +- Windows 中文部署:[`docs/DEPLOYMENT.zh-CN.md`](docs/DEPLOYMENT.zh-CN.md) +- Windows English deployment: [`docs/DEPLOYMENT.md`](docs/DEPLOYMENT.md) +- 中文架构说明:[`docs/ARCHITECTURE.zh-CN.md`](docs/ARCHITECTURE.zh-CN.md) - 安全策略:[`SECURITY.zh-CN.md`](SECURITY.zh-CN.md) -- 开源发布检查清单:[`docs/OPEN_SOURCE_RELEASE_CHECKLIST.zh-CN.md`](docs/OPEN_SOURCE_RELEASE_CHECKLIST.zh-CN.md) ## 上游与许可证 @@ -306,11 +329,3 @@ powershell -ExecutionPolicy Bypass -File scripts/smoke-test-override-flow.ps1 -U - 上游归属说明 - 本仓库中的许可证 - 对上游改动的说明 - -## 发布前建议 - -如果你打算把你自己的改动再公开发布,至少先做这三件事: - -1. 运行 `scripts/check-open-source-tree.ps1` -2. 运行 `scripts/smoke-test-override-flow.ps1` -3. 再通读一次 [`SECURITY.zh-CN.md`](SECURITY.zh-CN.md) From 459b5217c133aa1f97906290fa74e8d2452948f3 Mon Sep 17 00:00:00 2001 From: Bobyue0118 <465741287@qq.com> Date: Fri, 10 Apr 2026 17:01:55 +0800 Subject: [PATCH 9/9] fix: redact personal examples from tests and spec --- .../2026-04-10-macos-mobile-codex-design.md | 26 +++++++++--------- scripts/__tests__/privacy-redaction.test.mjs | 27 +++++++++++++++++++ .../utils/__tests__/owner-admin.test.mjs | 12 ++++----- .../utils/__tests__/ownerAdminAccess.test.mjs | 4 +-- 4 files changed, 48 insertions(+), 21 deletions(-) create mode 100644 scripts/__tests__/privacy-redaction.test.mjs diff --git a/docs/superpowers/specs/2026-04-10-macos-mobile-codex-design.md b/docs/superpowers/specs/2026-04-10-macos-mobile-codex-design.md index c38b10a..bd891e7 100644 --- a/docs/superpowers/specs/2026-04-10-macos-mobile-codex-design.md +++ b/docs/superpowers/specs/2026-04-10-macos-mobile-codex-design.md @@ -19,16 +19,16 @@ The intended day-to-day workflow is that the user manages Codex sessions from th The repo already contains the core web-control concept: -- a patched `claudecodeui` layer under [upstream-overrides/claudecodeui-1.25.2](/Users/bobyue/Documents/GitHub/mobileCodexHelper/upstream-overrides/claudecodeui-1.25.2) -- Codex session discovery and resume support in [upstream-overrides/claudecodeui-1.25.2/server/openai-codex.js](/Users/bobyue/Documents/GitHub/mobileCodexHelper/upstream-overrides/claudecodeui-1.25.2/server/openai-codex.js) -- project and session browsing in [upstream-overrides/claudecodeui-1.25.2/server/projects.js](/Users/bobyue/Documents/GitHub/mobileCodexHelper/upstream-overrides/claudecodeui-1.25.2/server/projects.js) -- login and trusted-device persistence in [upstream-overrides/claudecodeui-1.25.2/server/routes/auth.js](/Users/bobyue/Documents/GitHub/mobileCodexHelper/upstream-overrides/claudecodeui-1.25.2/server/routes/auth.js) and [upstream-overrides/claudecodeui-1.25.2/server/database/db.js](/Users/bobyue/Documents/GitHub/mobileCodexHelper/upstream-overrides/claudecodeui-1.25.2/server/database/db.js) +- a patched `claudecodeui` layer under `upstream-overrides/claudecodeui-1.25.2` +- Codex session discovery and resume support in `upstream-overrides/claudecodeui-1.25.2/server/openai-codex.js` +- project and session browsing in `upstream-overrides/claudecodeui-1.25.2/server/projects.js` +- login and trusted-device persistence in `upstream-overrides/claudecodeui-1.25.2/server/routes/auth.js` and `upstream-overrides/claudecodeui-1.25.2/server/database/db.js` The repo is still operationally Windows-first: -- PowerShell scripts under [scripts](/Users/bobyue/Documents/GitHub/mobileCodexHelper/scripts) -- a Windows desktop helper in [mobile_codex_control.py](/Users/bobyue/Documents/GitHub/mobileCodexHelper/mobile_codex_control.py) -- Windows/nginx deployment assumptions in [README.md](/Users/bobyue/Documents/GitHub/mobileCodexHelper/README.md) and [docs/DEPLOYMENT.md](/Users/bobyue/Documents/GitHub/mobileCodexHelper/docs/DEPLOYMENT.md) +- PowerShell scripts under `scripts` +- a Windows desktop helper in `mobile_codex_control.py` +- Windows/nginx deployment assumptions in `README.md` and `docs/DEPLOYMENT.md` The macOS work should preserve the existing trust model and Codex web-control behavior, while removing the Windows helper as a runtime dependency. @@ -292,12 +292,12 @@ This ordering delivers usable value early while keeping the diff scoped to the a The design suggests likely changes in: -- [README.md](/Users/bobyue/Documents/GitHub/mobileCodexHelper/README.md) -- [docs/DEPLOYMENT.md](/Users/bobyue/Documents/GitHub/mobileCodexHelper/docs/DEPLOYMENT.md) -- [upstream-overrides/claudecodeui-1.25.2/server/routes/auth.js](/Users/bobyue/Documents/GitHub/mobileCodexHelper/upstream-overrides/claudecodeui-1.25.2/server/routes/auth.js) -- [upstream-overrides/claudecodeui-1.25.2/server/database/db.js](/Users/bobyue/Documents/GitHub/mobileCodexHelper/upstream-overrides/claudecodeui-1.25.2/server/database/db.js) -- frontend files under [upstream-overrides/claudecodeui-1.25.2/src](/Users/bobyue/Documents/GitHub/mobileCodexHelper/upstream-overrides/claudecodeui-1.25.2/src) -- new macOS scripts under [scripts](/Users/bobyue/Documents/GitHub/mobileCodexHelper/scripts) +- `README.md` +- `docs/DEPLOYMENT.md` +- `upstream-overrides/claudecodeui-1.25.2/server/routes/auth.js` +- `upstream-overrides/claudecodeui-1.25.2/server/database/db.js` +- frontend files under `upstream-overrides/claudecodeui-1.25.2/src` +- new macOS scripts under `scripts` The design intentionally does not require a native macOS desktop control app. diff --git a/scripts/__tests__/privacy-redaction.test.mjs b/scripts/__tests__/privacy-redaction.test.mjs new file mode 100644 index 0000000..71b0096 --- /dev/null +++ b/scripts/__tests__/privacy-redaction.test.mjs @@ -0,0 +1,27 @@ +import test from 'node:test'; +import assert from 'node:assert/strict'; +import fs from 'node:fs'; +import path from 'node:path'; +import { fileURLToPath } from 'node:url'; + +const __filename = fileURLToPath(import.meta.url); +const __dirname = path.dirname(__filename); +const repoRoot = path.resolve(__dirname, '../..'); + +function readRepoFile(relativePath) { + return fs.readFileSync(path.join(repoRoot, relativePath), 'utf8'); +} + +test('checked-in examples do not contain personal hostnames or home-directory paths', () => { + const files = [ + 'upstream-overrides/claudecodeui-1.25.2/server/utils/__tests__/owner-admin.test.mjs', + 'upstream-overrides/claudecodeui-1.25.2/src/components/admin/utils/__tests__/ownerAdminAccess.test.mjs', + 'docs/superpowers/specs/2026-04-10-macos-mobile-codex-design.md', + ]; + + for (const file of files) { + const contents = readRepoFile(file); + assert.equal(/\/Users\/[A-Za-z0-9._-]+/.test(contents), false, `${file} should not contain a real home-directory path`); + assert.equal(/\btail123\.ts\.net\b/i.test(contents), false, `${file} should not contain a real tailnet host`); + } +}); diff --git a/upstream-overrides/claudecodeui-1.25.2/server/utils/__tests__/owner-admin.test.mjs b/upstream-overrides/claudecodeui-1.25.2/server/utils/__tests__/owner-admin.test.mjs index ff2d13e..eb3bb4b 100644 --- a/upstream-overrides/claudecodeui-1.25.2/server/utils/__tests__/owner-admin.test.mjs +++ b/upstream-overrides/claudecodeui-1.25.2/server/utils/__tests__/owner-admin.test.mjs @@ -15,13 +15,13 @@ test('isLoopbackAddress accepts Express loopback variants', () => { test('buildOwnerAdminStatus returns local and remote URLs', () => { const payload = buildOwnerAdminStatus({ - workspacesRoot: '/Users/bobyue/Code', + workspacesRoot: '/workspace/projects', tailscaleState: { installed: true, running: true, backendState: 'Running', - dnsName: 'bobyue-mac.tail123.ts.net', - remoteUrl: 'https://bobyue-mac.tail123.ts.net', + dnsName: 'codex-host.tailnet.ts.net', + remoteUrl: 'https://codex-host.tailnet.ts.net', authUrl: null, }, port: 3001, @@ -29,13 +29,13 @@ test('buildOwnerAdminStatus returns local and remote URLs', () => { assert.deepEqual(payload, { localUrl: 'http://127.0.0.1:3001', - remoteUrl: 'https://bobyue-mac.tail123.ts.net', - workspacesRoot: '/Users/bobyue/Code', + remoteUrl: 'https://codex-host.tailnet.ts.net', + workspacesRoot: '/workspace/projects', tailscale: { installed: true, running: true, backendState: 'Running', - dnsName: 'bobyue-mac.tail123.ts.net', + dnsName: 'codex-host.tailnet.ts.net', authUrl: null, }, }); diff --git a/upstream-overrides/claudecodeui-1.25.2/src/components/admin/utils/__tests__/ownerAdminAccess.test.mjs b/upstream-overrides/claudecodeui-1.25.2/src/components/admin/utils/__tests__/ownerAdminAccess.test.mjs index 1e42e00..0cacc67 100644 --- a/upstream-overrides/claudecodeui-1.25.2/src/components/admin/utils/__tests__/ownerAdminAccess.test.mjs +++ b/upstream-overrides/claudecodeui-1.25.2/src/components/admin/utils/__tests__/ownerAdminAccess.test.mjs @@ -9,11 +9,11 @@ test('isLoopbackHost accepts macOS local browser hostnames', () => { assert.equal(isLoopbackHost('127.0.0.1'), true); assert.equal(isLoopbackHost('localhost'), true); assert.equal(isLoopbackHost('::1'), true); - assert.equal(isLoopbackHost('bobyue-mac.tail123.ts.net'), false); + assert.equal(isLoopbackHost('codex-host.tailnet.ts.net'), false); }); test('shouldShowOwnerAdminLauncher requires a user on a loopback host', () => { assert.equal(shouldShowOwnerAdminLauncher({ hostname: 'localhost', hasUser: true }), true); assert.equal(shouldShowOwnerAdminLauncher({ hostname: 'localhost', hasUser: false }), false); - assert.equal(shouldShowOwnerAdminLauncher({ hostname: 'bobyue-mac.tail123.ts.net', hasUser: true }), false); + assert.equal(shouldShowOwnerAdminLauncher({ hostname: 'codex-host.tailnet.ts.net', hasUser: true }), false); });