Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 10 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,9 @@ This preview requires macOS 13 or later. It is ad-hoc signed and not notarized y
| **Quit Menu Bar** | Closes only the menu app; the proxy and current route keep running. |
| **Stop CodexCommander and Quit…** | Restores native Codex, stops the proxy and service, then quits the menu app. |

Here, **Restore Native** means removing CodexCommander-owned routing. A user-managed external Codex
provider is left unchanged.

Route changes show a spinner, elapsed time, and the real **Changing route → Confirming route**
phases. After a confirmed route change, quit ChatGPT completely, reopen it, and start a new task.

Expand Down Expand Up @@ -118,11 +121,13 @@ Codex tasks, history, or authentication, and it does not require a repair comman
database. Generated catalogs and caches may remain on disk, but native Codex no longer references
them.

On its first launch, the app enables **Launch at Login** so the
menu icon returns after sign-in. The startup row exposes the actual mode: **Desktop** launches the
menu app, **Headless** leaves only an installed background service at login, and **Off** starts
neither automatically. Rebuilt source apps refresh their login registration in place; they are
never copied into Application Support. Full
On its first launch, the app enables **Launch at Login** so the menu icon returns after sign-in.
On every new manual or Login Item launch, the app performs an explicit **Start**: it starts or
attaches to the proxy, then routes managed Codex through it. An external user-managed Codex provider
is preserved. The startup row exposes the actual mode: **Desktop**
performs this app-managed start, **Headless** leaves only an installed background service at login,
and **Off** starts neither automatically. Rebuilt source apps refresh their login registration in
place; they are never copied into Application Support. Full
setup, Gatekeeper, release packaging, and troubleshooting details are in the
[macOS menu bar guide](docs-site/src/content/docs/guides/macos-menu-bar.md).

Expand Down
19 changes: 19 additions & 0 deletions app/Sources/MenuBarCore/ActionCoordinator.swift
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,25 @@ public enum CodexCatalogApplyOutcome: Equatable, Sendable {
case failed(String)
}

public let companionPassiveLaunchArgument = "--ccx-passive-launch"

/// A direct or Login Item launch is an explicit product Start. A CLI that only
/// ensures the shared proxy passes the fixed passive marker so opening the menu
/// surface cannot override an intentional Native route.
public enum CompanionLaunchPolicy {
public static func run(
using actions: ActionCoordinator?,
arguments: [String] = CommandLine.arguments
) async -> ProxyControlOutcome {
guard let actions else { return .failed("Lifecycle control is unavailable.") }
return if arguments.dropFirst().contains(companionPassiveLaunchArgument) {
await actions.ensure()
} else {
await actions.start()
}
}
}

/// Executes confirm-gated lifecycle actions through the fixed structured helper.
public actor ActionCoordinator {
private let lifecycle: any LifecycleCommandRunning
Expand Down
29 changes: 29 additions & 0 deletions app/Sources/MenuBarCoreTests/ActionSuite.swift
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,35 @@ enum ActionSuite {
)
}

t.test("lifecycle: direct companion launch starts routing; passive CLI launch only ensures") {
let lifecycle = FakeLifecycleRunner(results: [
LifecycleCommandResult(
action: .start, ok: true, state: .running,
changed: true, pid: 41, port: 10100, message: "running"
),
LifecycleCommandResult(
action: .ensure, ok: true, state: .running,
changed: false, pid: 41, port: 10100, message: "running"
),
])
let coordinator = ActionCoordinator(lifecycle: lifecycle)
t.equal(
sync { await CompanionLaunchPolicy.run(
using: coordinator,
arguments: ["CodexCommanderMenuBar"]
) },
.running
)
t.equal(
sync { await CompanionLaunchPolicy.run(
using: coordinator,
arguments: ["CodexCommanderMenuBar", companionPassiveLaunchArgument]
) },
.running
)
t.equal(sync { await lifecycle.recordedActions() }, [.start, .ensure])
}

t.test("lifecycle: a helper refusal is surfaced without changing its message") {
let lifecycle = FakeLifecycleRunner(results: [
LifecycleCommandResult(
Expand Down
12 changes: 6 additions & 6 deletions app/Sources/MenuBarUI/AppDelegate.swift
Original file line number Diff line number Diff line change
Expand Up @@ -164,7 +164,7 @@ public final class AppDelegate: NSObject, NSApplicationDelegate, NSMenuItemValid
)
)
startCompanionHeartbeat()
ensureProxyOnLaunch()
startProxyOnLaunch()
}

public func applicationDidBecomeActive(_ notification: Notification) {
Expand Down Expand Up @@ -378,16 +378,16 @@ public final class AppDelegate: NSObject, NSApplicationDelegate, NSMenuItemValid
return true
}

/// Finder launch is the app-level start contract. It uses the fixed TS helper and
/// keeps the menu app alive even when startup fails, so Start remains available.
private func ensureProxyOnLaunch() {
/// Manual and Launch-at-Login openings share the explicit Start contract. A failed
/// start leaves the menu app alive so its diagnostics and Start control remain usable.
private func startProxyOnLaunch() {
guard !lifecycleInFlight, !restartInFlight, !catalogActionInFlight else { return }
lifecycleInFlight = true
updateApplicationMenu()
controller.setLifecycleControlsEnabled(false)
refreshCatalogApplyAvailability()
Task { [actions, coordinator] in
let outcome = await actions?.ensure() ?? .failed("Lifecycle control is unavailable.")
let outcome = await CompanionLaunchPolicy.run(using: actions)
await coordinator?.forceRefresh()
await MainActor.run { [weak self] in
guard let self else { return }
Expand All @@ -399,7 +399,7 @@ public final class AppDelegate: NSObject, NSApplicationDelegate, NSMenuItemValid
self.companionHeartbeat?.reportNow()
case .catalogUpdateReady(let count):
// The proxy is running with a pending catalog refresh; report now so
// a failed pre-ensure report is retried right after startup.
// a failed pre-start report is retried right after startup.
self.companionHeartbeat?.reportNow()
self.presentCatalogUpdate(staleWorkerCount: count)
case .stopped:
Expand Down
26 changes: 17 additions & 9 deletions docs-site/src/content/docs/guides/macos-menu-bar.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,12 +24,17 @@ To build from source instead, follow [Build from source](#build-from-source) bel

The panel has one **Launch at Login** switch and reports the resulting mode:

- **Desktop** — the CodexCommander menu app launches when you sign in and ensures or attaches to exactly
one server. This is the default desktop experience and is reported as **App-managed** in Startup.
- **Desktop** — the CodexCommander menu app launches when you sign in, performs an explicit Start,
starts or attaches to exactly one proxy, and routes managed Codex through it. An external
user-managed Codex provider is preserved. This is the default desktop experience and is reported
as **App-managed** in Startup.
- **Headless** — the menu app is not a login item, but an independently installed
`ccx service` continues starting and supervising the server.
- **Off** — neither the menu app nor a background service starts automatically; open the app or run
`ccx start` manually.
- **Off** — neither the menu app nor a background service starts automatically. A new manual app
launch performs the same explicit Start-and-route transition as `ccx start`.

Throughout this guide, **restore native** means removing CodexCommander-owned routing. An external
user-managed Codex provider is left unchanged.

The visible app and background server remain separate internally. With the CodexCommander panel active,
**Quit Menu Bar** (`⌘Q`) closes only the companion UI and deliberately leaves routing active.
Expand All @@ -40,8 +45,9 @@ therefore list CodexCommander under both **Open at Login** and **Allow in the Ba
responsibilities of one installation, not duplicate app copies. Turning off Launch at Login never
installs, removes, starts, or stops the background service.

App-managed startup and the background service solve different problems. The app starts the proxy at
sign-in, which is enough for normal desktop use. The optional background service additionally
App-managed startup and the background service solve different problems. The app starts or attaches
to the proxy at sign-in and routes managed Codex through it, which is enough for normal desktop use. The
optional background service additionally
supervises the proxy and restarts it after a crash, so the dashboard labels it **Background
recovery** instead of presenting it as a requirement. The companion periodically reports its current
Launch at Login state to the local proxy; that short-lived report is kept only in memory and is used
Expand Down Expand Up @@ -217,9 +223,11 @@ open dist/macos/CodexCommander.app

The development app is exactly `dist/macos/CodexCommander.app`. Every build embeds the Bun runtime and
CodexCommander server resources inside the app bundle; the running app never executes `src/` from the
checkout. Rebuild the app to pick up source changes. Double-clicking it attempts to ensure the proxy,
but an offline failure or failed start does not close the app: its status panel remains available and
**Start** can be retried. This source workflow does not install or copy the app into Application
checkout. Rebuild the app to pick up source changes. Double-clicking it to launch a new app process
performs an explicit Start: it starts or attaches to the proxy and routes managed Codex through it.
An external user-managed provider is preserved. An offline failure or failed start
does not close the app: its status panel remains available and **Start** can be retried. This source
workflow does not install or copy the app into Application
Support. A rebuild at the same path is detected on the
next launch and refreshes the existing Login Item registration only when Launch at Login remains on.
Each build stamps its exact Git revision into `CodexCommanderSourceRevision` in the bundle's `Info.plist`
Expand Down
14 changes: 11 additions & 3 deletions docs-site/src/content/docs/ja/guides/macos-menu-bar.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,9 +20,15 @@ Intel Mac と Apple シリコンの両方に対応する **v0.1.0 ユニバー

## 起動モード

- **Desktop** — サインイン時にメニューアプリを開き、1 つのサーバーを確認または起動します。
- **Desktop** — サインイン時にメニューアプリが開き、明示的な Start を実行してプロキシを
起動または接続し、管理対象の Codex をそのプロキシ経由にルーティングします。外部のユーザー管理
プロバイダーは保持されます。
- **Headless** — メニューアプリは開かず、別途インストールした `ccx service` だけを起動します。
- **Off** — 自動起動せず、アプリまたは `ccx start` で手動起動します。
- **Off** — 自動起動しません。アプリを手動で新規起動すると明示的な Start が実行され、管理対象の
Codex がプロキシ経由にルーティングされます。`ccx start` でも同じ処理を実行できます。

このガイドで **ネイティブに復元** とは、CodexCommander が所有するルーティングだけを削除することを
意味します。外部のユーザー管理 Codex プロバイダーは変更されません。

設定行から **Launch at Login** を切り替えられます。承認が必要な場合は macOS の Login Items
設定を直接開けます。この切り替えはバックグラウンドサービスをインストール、停止、削除しません。
Expand Down Expand Up @@ -184,7 +190,9 @@ open dist/macos/CodexCommander.app
開発アプリの場所は `dist/macos/CodexCommander.app` です。各ビルドで Bun ランタイムと
CodexCommander サーバーリソースがアプリバンドルに埋め込まれ、実行中のアプリが checkout の `src/` を
直接実行することはありません。ソース変更を反映するには再ビルドしてください。ダブルクリックすると
プロキシの起動を試みますが、オフラインまたは起動失敗でもアプリは閉じず、パネルと **Start**
新しいアプリプロセスをダブルクリックで起動すると、明示的な Start を実行し、プロキシを起動または
接続して管理対象の Codex をそのプロキシ経由にルーティングします。外部のユーザー管理プロバイダーは
保持されます。オフラインまたは起動失敗でもアプリは閉じず、パネルと **Start**
コントロールは利用できます。開発中はこの場所に置き、Application Support へコピーしないでください。
各ビルドは正確な Git リビジョンをバンドルの `Info.plist` の `CodexCommanderSourceRevision` に記録し、
ビルド完了時にも表示します。未コミットのソースには `-dirty` が付くため、最終配布ビルドの前に
Expand Down
13 changes: 10 additions & 3 deletions docs-site/src/content/docs/ko/guides/macos-menu-bar.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,9 +20,14 @@ Intel Mac과 Apple Silicon을 모두 지원하는 **v0.1.0 유니버설 미리

## 시작 모드

- **Desktop** — 로그인할 때 메뉴 앱을 열고 정확히 하나의 서버에 연결하거나 시작합니다.
- **Desktop** — 로그인할 때 메뉴 앱이 열리고 명시적 Start를 실행해 프록시를 시작하거나 연결한 다음
관리되는 Codex를 해당 프록시를 통하도록 라우팅합니다. 외부 사용자 관리 공급자는 그대로 유지됩니다.
- **Headless** — 메뉴 앱 없이 별도로 설치한 `ccx service`만 시작합니다.
- **Off** — 자동으로 시작하지 않으며 앱 또는 `ccx start`로 수동 시작합니다.
- **Off** — 자동으로 시작하지 않습니다. 앱을 수동으로 새로 실행하면 명시적 Start가 실행되어 관리되는
Codex가 프록시를 통하도록 라우팅됩니다. `ccx start`도 같은 전환을 실행합니다.

이 가이드에서 **네이티브 복원**은 CodexCommander가 소유한 라우팅만 제거한다는 뜻입니다. 외부 사용자
관리 Codex 공급자는 변경되지 않습니다.

시작 행에서 **Launch at Login**을 변경할 수 있습니다. 승인이 필요하면 macOS Login Items 설정을
직접 엽니다. 이 스위치는 백그라운드 서비스를 설치, 중지 또는 제거하지 않습니다.
Expand Down Expand Up @@ -178,7 +183,9 @@ open dist/macos/CodexCommander.app
개발 앱의 위치는 정확히 `dist/macos/CodexCommander.app`입니다. 빌드할 때마다 Bun 런타임과
CodexCommander 서버 리소스가 앱 번들에 포함되며, 실행 중인 앱은 체크아웃의 `src/`를 직접 실행하지
않습니다. 소스 변경을 반영하려면 앱을 다시 빌드하세요. 개발 중에는 이 위치에 두고 Application Support로
복사하지 마세요. 더블클릭하면 프록시 시작을 시도하지만 오프라인이거나 시작에 실패해도 앱은 닫히지
복사하지 마세요. 더블클릭으로 새 앱 프로세스를 실행하면 명시적 Start를 실행해 프록시를 시작하거나
연결하고 관리되는 Codex를 해당 프록시를 통하도록 라우팅합니다. 외부 사용자 관리 공급자는 그대로
유지됩니다. 오프라인이거나 시작에 실패해도 앱은 닫히지
않으며 패널과 **Start** 컨트롤을 계속 사용할 수 있습니다.
각 빌드는 정확한 Git 리비전을 번들의 `Info.plist`에 있는 `CodexCommanderSourceRevision`에 기록하고 빌드
마지막에도 출력합니다. 커밋하지 않은 소스에는 `-dirty`가 붙으므로 최종 번들을 만들기 전에
Expand Down
17 changes: 12 additions & 5 deletions docs-site/src/content/docs/ru/guides/macos-menu-bar.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,10 +20,15 @@ description: Установка и использование нативного

## Режимы запуска

- **Desktop** — приложение меню открывается при входе и подключается ровно к одному серверу или
запускает его.
- **Desktop** — приложение меню открывается при входе, выполняет явный Start, запускает прокси или
подключается к нему и направляет управляемый Codex через этот прокси. Внешний провайдер,
настроенный пользователем, сохраняется.
- **Headless** — запускается только отдельно установленная служба `ccx service`, без приложения меню.
- **Off** — автозапуск выключен; используйте приложение или `ccx start` вручную.
- **Off** — автозапуск выключен. При новом ручном запуске приложение выполняет явный Start и
направляет управляемый Codex через прокси; `ccx start` выполняет тот же переход.

В этом руководстве **восстановить нативную маршрутизацию** означает удалить только маршрутизацию,
которой владеет CodexCommander. Внешний провайдер Codex, настроенный пользователем, не изменяется.

Переключатель **Launch at Login** находится в строке запуска. Если требуется разрешение, приложение
открывает настройки Login Items macOS. Переключатель не устанавливает, не останавливает и не удаляет
Expand Down Expand Up @@ -201,8 +206,10 @@ open dist/macos/CodexCommander.app
Приложение для разработки находится ровно в `dist/macos/CodexCommander.app`. Каждая сборка встраивает
среду Bun и ресурсы сервера CodexCommander в пакет приложения; запущенное приложение не выполняет
`src/` непосредственно из checkout. Чтобы применить изменения исходников, пересоберите приложение.
Оставляйте его там во время разработки и не копируйте в Application Support. Двойной щелчок пытается
запустить прокси, но при сбое или работе офлайн не закрывает приложение: панель и кнопка **Start**
Оставляйте его там во время разработки и не копируйте в Application Support. При двойном щелчке,
который запускает новый процесс приложения, оно выполняет явный Start, запускает прокси или
подключается к нему и направляет управляемый Codex через него; внешний провайдер пользователя сохраняется.
При сбое или работе офлайн приложение не закрывается: панель и кнопка **Start**
остаются доступными.
Каждая сборка записывает точную ревизию Git в `CodexCommanderSourceRevision` файла `Info.plist` внутри
пакета и печатает её после сборки. Для незакоммиченного исходного кода добавляется `-dirty`, поэтому
Expand Down
Loading
Loading