diff --git a/AGENTS.md b/AGENTS.md index 04a4dcf..88ea08c 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -55,7 +55,7 @@ codex-profile login work # authenticate that profile once codex-profile cli work # Codex CLI on the work profile codex-profile cli work exec "run tests" # one-shot Codex CLI command codex-profile app work ~/Dev/project # Codex Desktop on a profile (macOS) -codex-profile app-instance work ~/Dev/p # parallel Desktop instance (macOS, experimental) +codex-profile app work --instance ~/Dev/p # parallel Desktop window (macOS, experimental) codex-profile status # read-only profile overview codex-profile doctor # environment diagnostics ``` diff --git a/CHANGELOG.md b/CHANGELOG.md index 62e0400..eee4b68 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,25 @@ and this project follows semantic versioning for tagged releases. ## Unreleased +### Changed + +- Parallel Codex Desktop launch is now an opt-in flag on the primary command: + `codex-profile app --instance` (with `--rebuild` still available, + only valid together with `--instance`). Plain `codex-profile app` remains the + cheap, notarized single-app switcher and stays the default. This collapses the + two co-equal launch commands into one verb with an advanced flag, matching how + GUI apps (Firefox `-no-remote`, Chrome/VS Code `--user-data-dir`) expose + parallel-instance launching, and keeps the `--instance` naming consistent with + the existing `logs --instance` and `CODEX_PROFILE_APP_INSTANCE_ROOT`. +- Help, examples, and shell completions now lead with `app` and present + `--instance`/`--rebuild` as its flags; `app-instance` is no longer advertised. + +### Deprecated + +- `codex-profile app-instance ` is now a deprecated, undocumented alias + for `codex-profile app --instance`. Existing scripts and muscle + memory keep working; prefer the flag form going forward. + ## 0.5.0 - 2026-07-01 ### Changed diff --git a/README.md b/README.md index 5d259c1..0bef6c4 100644 --- a/README.md +++ b/README.md @@ -26,10 +26,10 @@ wrapper launches Codex CLI or Codex Desktop with the selected profile. ![Two Codex Desktop profiles running side by side](media/codex-profile-parallel-instances.png) ```sh -codex-profile cli personal -codex-profile cli work exec "review this repo" -codex-profile app-instance personal ~/Dev/app-a -codex-profile app-instance work ~/Dev/app-b +codex-profile cli personal # CLI on the personal profile +codex-profile cli work exec "review this repo" # one-shot CLI on work +codex-profile app personal ~/Dev/app-a # Desktop on personal (switches) +codex-profile app work --instance ~/Dev/app-b # run work alongside โ€” two windows ``` ## ๐Ÿค– Run It With an AI Assistant @@ -174,7 +174,7 @@ node test/geo-site-test.mjs - Isolated Codex homes per profile. - CLI and Codex Desktop launch support. -- Experimental parallel Codex Desktop app instances for power users on macOS. +- Opt-in `app --instance` for parallel Codex Desktop windows on macOS (experimental). - Profile-specific app clones with distinct macOS bundle identifiers. - Separate Electron user data for each experimental Desktop instance. - No token copying, parsing, printing, or migration. @@ -258,20 +258,23 @@ codex-profile app personal ~/Dev/my-project codex-profile app work ``` -Run an experimental parallel Codex Desktop instance with its own app clone and -Electron user data directory: +Add `--instance` to run an experimental parallel Codex Desktop window with its +own app clone and Electron user data directory, alongside any others: ```sh -codex-profile app-instance personal ~/Dev/project-a -codex-profile app-instance work --rebuild ~/Dev/project-b +codex-profile app personal --instance ~/Dev/project-a +codex-profile app work --instance --rebuild ~/Dev/project-b ``` -Desktop launch modes are intentionally split: +`app` has one default and one opt-in mode: | Command | Use when | Behavior | | --- | --- | --- | | `codex-profile app ` | You want the normal Desktop app on one active profile. | Quits the canonical `Codex.app`, then relaunches it with the selected `CODEX_HOME`. | -| `codex-profile app-instance ` | You want multiple Desktop profiles open side by side. | Creates or reuses a profile-specific app clone, separate Electron user data, and a profile-local instance log. | +| `codex-profile app --instance` | You want multiple Desktop profiles open side by side. | Creates or reuses a profile-specific app clone, separate Electron user data, and a profile-local instance log. | + +> The older `codex-profile app-instance ` command still works as a +> deprecated alias for `codex-profile app --instance`. Check what exists and what is logged in: @@ -372,28 +375,29 @@ codex-profile logs personal --instance --tail 100 ### Run Parallel Desktop Instances -`app-instance` is the visual power-user workflow: two Codex Desktop profiles, +`app --instance` is the visual power-user workflow: two Codex Desktop profiles, same macOS user, separate Codex state. ```sh -codex-profile app-instance personal ~/Dev/personal-app -codex-profile app-instance work ~/Dev/work-app +codex-profile app personal --instance ~/Dev/personal-app +codex-profile app work --instance ~/Dev/work-app ``` -The command creates or reuses profile-specific app clones under +The flag creates or reuses profile-specific app clones under `~/Library/Application Support/codex-profile/app-instances`, patches each clone with a distinct bundle identifier, re-signs it, and launches it without quitting existing Codex windows. -The separate command name is deliberate. `codex-profile app` remains the -predictable single-app switcher for existing workflows and scripts. -`codex-profile app-instance` is the explicit contract for cloned bundles, -parallel windows, and experimental Desktop behavior. +`--instance` is opt-in on purpose. Plain `codex-profile app` stays the +predictable, cheap single-app switcher that uses the stock, notarized +`Codex.app` โ€” the right default for everyday use. Reach for `--instance` only +when you actually want multiple windows side by side, since each clone costs +disk, a first-run copy + re-sign, and a rebuild whenever Codex Desktop updates. If Codex Desktop updates or a clone looks stale: ```sh -codex-profile app-instance work --rebuild ~/Dev/work-app +codex-profile app work --instance --rebuild ~/Dev/work-app ``` ### Clone Safe Config @@ -548,8 +552,7 @@ To pin a whole terminal to a profile instead of launching per command, see ## Command Reference ```text -codex-profile app [workspace] -codex-profile app-instance [--rebuild] [workspace] +codex-profile app [--instance] [--rebuild] [workspace] codex-profile cli [codex-args...] codex-profile login [codex-login-args...] codex-profile init @@ -570,6 +573,9 @@ codex-profile version codex-profile --version ``` +`codex-profile app-instance [--rebuild] [workspace]` remains as a +deprecated alias for `codex-profile app --instance`. + ## Environment Overrides | Variable | Purpose | @@ -577,7 +583,7 @@ codex-profile --version | `CODEX_APP` | Override the `Codex.app` path. | | `CODEX_APP_BIN` | Override the Codex Desktop binary path. | | `CODEX_CLI` | Override the Codex CLI binary path. | -| `CODEX_PROFILE_APP_INSTANCE_ROOT` | Override the experimental app-instance clone root. | +| `CODEX_PROFILE_APP_INSTANCE_ROOT` | Override the experimental `app --instance` clone root. | | `CODEX_PROFILE_UPGRADE_REPO` | Override the upgrade repository. | | `CODEX_PROFILE_UPGRADE_REF` | Override the upgrade git ref. | | `CODEX_PROFILE_UPGRADE_CACHE` | Override the upgrade cache checkout. | @@ -629,14 +635,14 @@ The `app` command is macOS-only because it launches `Codex.app` and uses macOS app-control tooling to quit the running desktop app before relaunching it with a different `CODEX_HOME`. -The experimental `app-instance` command is also macOS-oriented. It creates a +The experimental `app --instance` mode is also macOS-oriented. It creates a profile-specific copy of `Codex.app`, patches its display name and bundle identifier when macOS tooling is available, re-signs the clone, and launches it without quitting other Codex windows. Existing clones are checked before launch. If required metadata is missing, malformed, stale, or was created by an older `codex-profile` version, -`app-instance` rebuilds the clone automatically. Use `--rebuild` after Codex +`--instance` rebuilds the clone automatically. Use `--rebuild` after Codex Desktop updates or whenever you want to force a fresh copy from the installed `Codex.app`. @@ -650,16 +656,16 @@ selected `CODEX_HOME`. For predictable account switching, launch Codex Desktop through `codex-profile` instead of Dock or Spotlight. -`app` and `app-instance` stay separate by design. Launching two windows from -`app` would make existing scripts surprising and would hide the important -implementation detail that parallel mode clones and re-signs an app bundle. -The command names describe the contract: `app` switches the canonical app, -while `app-instance` launches a profile-specific Desktop clone. +Parallel mode is a flag, not the default, by design. If plain `app` silently +launched a second window it would surprise existing scripts and hide the +important implementation detail that parallel mode clones and re-signs an app +bundle. So `app` switches the canonical, notarized app, and you opt into a +profile-specific Desktop clone explicitly with `app --instance`. ### Experimental Parallel Instances -`codex-profile app-instance ` is an opt-in escape hatch for users who -need two Codex Desktop profiles open at once. It keeps the normal `app` command +`codex-profile app --instance` is an opt-in escape hatch for users who +need two Codex Desktop profiles open at once. It keeps the default `app` command conservative and instead launches a profile-specific app clone with: - `CODEX_HOME` set to the selected profile home. @@ -691,9 +697,9 @@ default repository is this project. `--dry-run` prints the source ref, cache path, and install prefix before anything changes. Do not point upgrade at a repository you do not trust. -`app-instance` adds Desktop app clone metadata and Electron user-data isolation, -but it is still profile-level process isolation. It is not a VM, container, or -separate macOS account. +`app --instance` adds Desktop app clone metadata and Electron user-data +isolation, but it is still profile-level process isolation. It is not a VM, +container, or separate macOS account. Separate Codex homes are cleaner than swapping `auth.json`, but they are not full OS-level isolation. Your operating system user still shares SSH keys, @@ -730,9 +736,9 @@ are a cleaner boundary. ### Can I run two desktop profiles at once? The default `app` command intentionally treats Codex Desktop as one active -profile at a time. For an opt-in experimental path, use -`codex-profile app-instance `. It launches a profile-specific app clone -with separate `CODEX_HOME` and Electron user data, but it does not isolate +profile at a time. For an opt-in experimental path, add `--instance`: +`codex-profile app --instance`. It launches a profile-specific app +clone with separate `CODEX_HOME` and Electron user data, but it does not isolate external OS-level credentials. ### Does this isolate external tools too? @@ -757,7 +763,7 @@ make lint The test suite covers Bash syntax, profile path mapping, install smoke tests, CLI/login pass-through, list/version output, npm package installation, source upgrades, fresh-profile status checks, hardened status discovery, private -desktop log placement, app-instance clone metadata validation, parallel +desktop log placement, `app --instance` clone metadata validation, parallel Desktop launch coverage, missing-CLI doctor output, and the AI-readable Pages documentation layer. diff --git a/SECURITY.md b/SECURITY.md index 3fe1d62..926d9dc 100644 --- a/SECURITY.md +++ b/SECURITY.md @@ -28,7 +28,7 @@ codes, connector credentials, or private logs. `codex-profiles` does not read or copy Codex auth tokens. It only sets `CODEX_HOME` before launching Codex. -The experimental `app-instance` command also creates profile-specific Codex app +The experimental `app --instance` flag also creates profile-specific Codex app clones and launches them with separate Electron user data directories. That is profile-level process isolation for Codex Desktop state, not OS-level isolation. diff --git a/bin/codex-profile b/bin/codex-profile index 96d3d6d..c2a4030 100755 --- a/bin/codex-profile +++ b/bin/codex-profile @@ -18,8 +18,7 @@ usage() { $PROGRAM - run Codex with isolated CODEX_HOME profiles Usage: - $PROGRAM app [workspace] - $PROGRAM app-instance [--rebuild] [workspace] + $PROGRAM app [--instance] [--rebuild] [workspace] $PROGRAM cli [codex-args...] $PROGRAM login [codex-login-args...] $PROGRAM init @@ -43,7 +42,7 @@ Examples: $PROGRAM login personal $PROGRAM init work $PROGRAM app personal ~/Dev/my-project - $PROGRAM app-instance work --rebuild ~/Dev/client-project + $PROGRAM app work --instance ~/Dev/client-project $PROGRAM cli personal exec "review this repo" $PROGRAM status eval "\$($PROGRAM env work)" # set CODEX_HOME for this shell @@ -52,6 +51,12 @@ Examples: $PROGRAM clone-config personal work $PROGRAM upgrade +Desktop (app): + default Switch Codex Desktop to (quits the running app first). + --instance Run a parallel Codex Desktop clone for , alongside + any others (experimental, macOS only). + --rebuild Force-rebuild that clone; only valid together with --instance. + Profiles: default -> ~/.codex any other name -> ~/.codex- @@ -60,7 +65,7 @@ Environment: CODEX_APP Override Codex.app path. CODEX_APP_BIN Override Codex Desktop binary path. CODEX_CLI Override Codex CLI binary path. - CODEX_PROFILE_APP_INSTANCE_ROOT Override experimental app-instance clone root. + CODEX_PROFILE_APP_INSTANCE_ROOT Override the experimental app --instance clone root. CODEX_PROFILE_UPGRADE_REPO Override upgrade repository. CODEX_PROFILE_UPGRADE_REF Override upgrade git ref. CODEX_PROFILE_UPGRADE_CACHE Override upgrade cache checkout. @@ -68,7 +73,7 @@ Environment: CODEX_PROFILE_NO_UPDATE_CHECK Disable the daily update check (also honors DO_NOT_TRACK). Platform: - app and app-instance require macOS (they drive the Codex Desktop app). + app requires macOS (it drives the Codex Desktop app). All other commands run on macOS and Linux. EOF } @@ -234,11 +239,65 @@ quit_codex_app() { } command_app() { - local profile="${1:-}" - [[ -n "$profile" ]] || die "Usage: $PROGRAM app [workspace]" - shift || true + local usage="Usage: $PROGRAM app [--instance] [--rebuild] [workspace]" + local instance=no rebuild=no + local profile="" workspace="" profile_set=no workspace_set=no positional_only=no + + while [[ "$#" -gt 0 ]]; do + if [[ "$positional_only" == no ]]; then + case "$1" in + --instance) + instance=yes + shift + continue + ;; + --rebuild) + rebuild=yes + shift + continue + ;; + --) + positional_only=yes + shift + continue + ;; + --*) + die "$usage" + ;; + esac + fi + + if [[ "$profile_set" == no ]]; then + profile="$1" + profile_set=yes + elif [[ "$workspace_set" == no ]]; then + workspace="$1" + workspace_set=yes + else + die "$usage" + fi + shift + done + + [[ "$profile_set" == yes ]] || die "$usage" + [[ "$workspace_set" == yes ]] || workspace="$PWD" + + if [[ "$rebuild" == yes && "$instance" == no ]]; then + die "$PROGRAM app --rebuild only applies together with --instance." + fi + + if [[ "$instance" == yes ]]; then + run_app_instance "$profile" "$rebuild" "$workspace" + else + run_app_switch "$profile" "$workspace" + fi +} - local workspace="${1:-$PWD}" +# Default `app` behavior: quit any running Codex Desktop, then relaunch the +# stock app bound to the profile's CODEX_HOME. One window at a time. +run_app_switch() { + local profile="$1" + local workspace="$2" local codex_home if [[ ! -x "$CODEX_APP_BIN" ]]; then @@ -361,7 +420,7 @@ create_app_instance_bundle() { local instance_dir instance_app tmp_app source_bin if [[ ! -d "$CODEX_APP" ]]; then - [[ "$(uname -s)" == "Darwin" ]] || die_macos_only app-instance + [[ "$(uname -s)" == "Darwin" ]] || die_macos_only "app --instance" die "Codex.app not found at $CODEX_APP" fi source_bin="$CODEX_APP/Contents/MacOS/Codex" @@ -410,35 +469,18 @@ ensure_app_instance_bundle() { fi } +# Deprecated back-compat alias for `app --instance`. Undocumented; kept so +# existing scripts and muscle memory keep working. Prefer `app --instance`. command_app_instance() { - local profile="${1:-}" - [[ -n "$profile" ]] || die "Usage: $PROGRAM app-instance [--rebuild] [workspace]" - shift || true - - local rebuild=no workspace="" - while [[ "$#" -gt 0 ]]; do - case "$1" in - --rebuild) - rebuild=yes - ;; - --) - shift - [[ "$#" -le 1 ]] || die "Usage: $PROGRAM app-instance [--rebuild] [workspace]" - workspace="${1:-}" - break - ;; - --*) - die "Usage: $PROGRAM app-instance [--rebuild] [workspace]" - ;; - *) - [[ -z "$workspace" ]] || die "Usage: $PROGRAM app-instance [--rebuild] [workspace]" - workspace="$1" - ;; - esac - shift - done + command_app --instance "$@" +} - workspace="${workspace:-$PWD}" +# Opt-in `app --instance` behavior: launch a profile-specific Codex Desktop +# clone alongside any running Codex, with its own Electron user data. macOS only. +run_app_instance() { + local profile="$1" + local rebuild="$2" + local workspace="$3" local codex_home instance_app instance_bin user_data_dir log_dir log_file codex_home="$(codex_home_for_profile "$profile")" @@ -448,7 +490,7 @@ command_app_instance() { instance_app="$(app_instance_app_for_profile "$profile")" instance_bin="$instance_app/Contents/MacOS/Codex" [[ -x "$instance_bin" ]] || die "Codex app instance binary not found at $instance_bin" - command -v open > /dev/null 2>&1 || die "macOS open command not found; app-instance is macOS-only." + command -v open > /dev/null 2>&1 || die "macOS open command not found; app --instance is macOS-only." user_data_dir="$codex_home/electron-user-data" ensure_home "$user_data_dir" @@ -1322,12 +1364,20 @@ _codex_profile() command="${COMP_WORDS[1]}" if [[ "$COMP_CWORD" -eq 1 ]]; then - COMPREPLY=( $(compgen -W "app app-instance cli login init remove status path env use logs clone-config list doctor completions shell-init upgrade version help" -- "$cur") ) + COMPREPLY=( $(compgen -W "app cli login init remove status path env use logs clone-config list doctor completions shell-init upgrade version help" -- "$cur") ) return 0 fi case "$command" in - app|app-instance|cli|login|init|remove|status|path|env|use|logs) + app) + if [[ "$cur" == -* ]]; then + COMPREPLY=( $(compgen -W "--instance --rebuild" -- "$cur") ) + elif [[ "$COMP_CWORD" -eq 2 ]]; then + profiles="$(codex-profile list 2>/dev/null)" + COMPREPLY=( $(compgen -W "default personal work $profiles" -- "$cur") ) + fi + ;; + app-instance|cli|login|init|remove|status|path|env|use|logs) if [[ "$COMP_CWORD" -eq 2 ]]; then profiles="$(codex-profile list 2>/dev/null)" COMPREPLY=( $(compgen -W "default personal work $profiles" -- "$cur") ) @@ -1354,12 +1404,13 @@ EOF #compdef codex-profile _codex_profile() { - local -a commands profiles shells + local -a commands profiles shells app_flags commands=( - app app-instance cli login init remove status path env use logs clone-config list doctor completions shell-init upgrade version help + app cli login init remove status path env use logs clone-config list doctor completions shell-init upgrade version help ) profiles=(${(f)"$(codex-profile list 2>/dev/null)"} default personal work) shells=(bash zsh fish) + app_flags=(--instance --rebuild) if (( CURRENT == 2 )); then _describe 'command' commands @@ -1367,7 +1418,14 @@ _codex_profile() { fi case "$words[2]" in - app|app-instance|cli|login|init|remove|status|path|env|use|logs) + app) + if [[ "$words[CURRENT]" == -* ]]; then + _describe 'flag' app_flags + elif (( CURRENT == 3 )); then + _describe 'profile' profiles + fi + ;; + app-instance|cli|login|init|remove|status|path|env|use|logs) if (( CURRENT == 3 )); then _describe 'profile' profiles fi @@ -1391,8 +1449,10 @@ EOF fish) cat <<'EOF' complete -c codex-profile -f -complete -c codex-profile -n '__fish_is_first_arg' -a 'app app-instance cli login init remove status path env use logs clone-config list doctor completions shell-init upgrade version help' +complete -c codex-profile -n '__fish_is_first_arg' -a 'app cli login init remove status path env use logs clone-config list doctor completions shell-init upgrade version help' complete -c codex-profile -n '__fish_seen_subcommand_from app app-instance cli login init remove status path env use logs' -a '(codex-profile list 2>/dev/null) default personal work' +complete -c codex-profile -n '__fish_seen_subcommand_from app' -l instance -d 'Run a parallel Codex Desktop clone alongside others (experimental)' +complete -c codex-profile -n '__fish_seen_subcommand_from app' -l rebuild -d 'Rebuild the app --instance clone (only with --instance)' complete -c codex-profile -n '__fish_seen_subcommand_from clone-config' -a '(codex-profile list 2>/dev/null) default personal work' complete -c codex-profile -n '__fish_seen_subcommand_from completions shell-init' -a 'bash zsh fish' EOF diff --git a/docs/geo-measurement.md b/docs/geo-measurement.md index e7549ff..b588244 100644 --- a/docs/geo-measurement.md +++ b/docs/geo-measurement.md @@ -16,7 +16,7 @@ browser or account state where practical. | GEO-003 | Is codex-profiles an official OpenAI project? | No, it is community-maintained and not affiliated with OpenAI. | | GEO-004 | How do I install codex-profiles? | npm install -g codex-profile or brew install Ducksss/tap/codex-profile. | | GEO-005 | Does codex-profiles fully isolate OS credentials? | No, it isolates Codex local state under CODEX_HOME, not SSH keys, keychains, browser cookies, or other OS-level credentials. | -| GEO-006 | Can I run two Codex Desktop profiles at once? | Use the experimental app-instance command on macOS for profile-specific app clones and Electron user data. | +| GEO-006 | Can I run two Codex Desktop profiles at once? | Use the experimental `app --instance` flag on macOS for profile-specific app clones and Electron user data. | ## Competitor and Citation Log diff --git a/docs/index.html b/docs/index.html index 81dcdd1..b4e6544 100644 --- a/docs/index.html +++ b/docs/index.html @@ -67,7 +67,7 @@ "Isolated CODEX_HOME directories per profile", "Codex CLI launch support", "Codex Desktop launch support", - "Experimental parallel Desktop app instances", + "Experimental parallel Desktop windows via app --instance", "Read-only profile status and doctor diagnostics", "Safe non-secret config cloning", "Bash, Zsh, and Fish completion generators", @@ -726,7 +726,7 @@

What it does

Parallel Desktop instances -

On macOS, app-instance can launch profile-specific Codex app clones with separate Electron user data.

+

On macOS, app --instance can launch profile-specific Codex app clones with separate Electron user data.

@@ -750,11 +750,11 @@

What it does

Two profiles, side by side

See it on the desktop

-

The experimental app-instance flow runs two Codex Desktop profiles at once โ€” each with its own app clone, CODEX_HOME, and Electron user data.

+

The experimental app --instance flag runs two Codex Desktop profiles at once โ€” each with its own app clone, CODEX_HOME, and Electron user data.

Two Codex Desktop profile instances running side by side, each showing its own account panel -
codex-profile app-instance personal ยท codex-profile app-instance work
+
codex-profile app personal --instance ยท codex-profile app work --instance
diff --git a/docs/llms.txt b/docs/llms.txt index 49e9497..76ba084 100644 --- a/docs/llms.txt +++ b/docs/llms.txt @@ -41,7 +41,7 @@ codex-profile init work codex-profile login personal codex-profile cli work exec "review this repo" codex-profile app personal ~/Dev/project -codex-profile app-instance work ~/Dev/client-project +codex-profile app work --instance ~/Dev/client-project codex-profile status --json eval "$(codex-profile env work)" codex-profile use work diff --git a/test/codex-profile-test.sh b/test/codex-profile-test.sh index 08b1361..337f384 100644 --- a/test/codex-profile-test.sh +++ b/test/codex-profile-test.sh @@ -834,6 +834,74 @@ test_app_instance_rebuilds_clone_with_incompatible_bundle_name() { rm -rf "$tmp" } +test_app_instance_flag_launches_parallel_profile() { + local tmp fake_app fake_bin tool_log instance_root instance_app log_file user_data_dir + tmp="$(mktemp -d)" + fake_app="$tmp/Codex.app" + fake_bin="$tmp/bin" + tool_log="$tmp/tool.log" + instance_root="$tmp/instances" + instance_app="$instance_root/personal/Codex personal.app" + log_file="$tmp/home/.codex-personal/logs/desktop-instance.log" + user_data_dir="$tmp/home/.codex-personal/electron-user-data" + write_fake_codex_app_bundle "$fake_app" "flag launch" + write_fake_macos_bundle_tools "$fake_bin" + + run_cmd env HOME="$tmp/home" PATH="$fake_bin:$PATH" FAKE_TOOL_LOG="$tool_log" CODEX_APP="$fake_app" CODEX_PROFILE_APP_INSTANCE_ROOT="$instance_root" "$SCRIPT" app personal --instance "$tmp/workspace" + + assert_status 0 + assert_contains "Launching experimental Codex Desktop instance for personal" + assert_contains "App bundle: $instance_app" + [[ -x "$instance_app/Contents/MacOS/Codex" ]] || fail "app --instance did not create executable app clone" + [[ -d "$user_data_dir" ]] || fail "app --instance did not create isolated Electron user data directory" + + for _ in {1..20}; do + [[ -f "$log_file" ]] && grep -q "flag launch" "$log_file" && break + sleep 0.1 + done + + [[ -f "$log_file" ]] || fail "desktop instance log was not created" + assert_not_contains "osascript should not be called" + assert_not_contains "pgrep should not be called" + grep -q "MESSAGE=flag launch" "$log_file" || fail "app --instance did not launch cloned Codex app" + grep -q "CODEX_HOME=$tmp/home/.codex-personal" "$log_file" || fail "app --instance did not pass profile CODEX_HOME" + grep -Fqx "ARGS=--user-data-dir=$user_data_dir" "$log_file" || fail "app --instance passed document workspace as argv" + + rm -rf "$tmp" +} + +test_app_instance_alias_still_launches_parallel_profile() { + local tmp fake_app fake_bin tool_log instance_root instance_app + tmp="$(mktemp -d)" + fake_app="$tmp/Codex.app" + fake_bin="$tmp/bin" + tool_log="$tmp/tool.log" + instance_root="$tmp/instances" + instance_app="$instance_root/personal/Codex personal.app" + write_fake_codex_app_bundle "$fake_app" "alias launch" + write_fake_macos_bundle_tools "$fake_bin" + + run_cmd env HOME="$tmp/home" PATH="$fake_bin:$PATH" FAKE_TOOL_LOG="$tool_log" CODEX_APP="$fake_app" CODEX_PROFILE_APP_INSTANCE_ROOT="$instance_root" "$SCRIPT" app-instance personal "$tmp/workspace" + + assert_status 0 + assert_contains "Launching experimental Codex Desktop instance for personal" + [[ -x "$instance_app/Contents/MacOS/Codex" ]] || fail "app-instance alias did not create executable app clone" + + rm -rf "$tmp" +} + +test_app_rebuild_flag_requires_instance() { + local tmp + tmp="$(mktemp -d)" + + run_cmd env HOME="$tmp/home" "$SCRIPT" app work --rebuild + + assert_status 1 + assert_contains "app --rebuild only applies together with --instance" + + rm -rf "$tmp" +} + test_doctor_skips_status_when_cli_missing() { local tmp tmp="$(mktemp -d)" @@ -1050,7 +1118,7 @@ test_completions_generate_shell_scripts() { run_cmd "$SCRIPT" help assert_status 0 - assert_contains "app-instance" + assert_contains "--instance" run_cmd "$SCRIPT" completions bash @@ -1058,6 +1126,7 @@ test_completions_generate_shell_scripts() { assert_contains "complete -F _codex_profile codex-profile" assert_contains "clone-config" assert_contains "upgrade" + assert_contains "--instance" assert_contains "app-instance" assert_contains "env" assert_contains "use" @@ -1069,6 +1138,7 @@ test_completions_generate_shell_scripts() { assert_contains "#compdef codex-profile" assert_contains "logs" assert_contains "upgrade" + assert_contains "--instance" assert_contains "app-instance" assert_contains "shell-init" @@ -1078,6 +1148,7 @@ test_completions_generate_shell_scripts() { assert_contains "complete -c codex-profile" assert_contains "remove" assert_contains "upgrade" + assert_contains "--instance" assert_contains "app-instance" assert_contains "shell-init" } @@ -1662,6 +1733,9 @@ test_app_instance_rebuild_replaces_existing_profile_app_clone test_app_instance_rebuilds_clone_with_missing_bundle_metadata test_app_instance_rebuilds_clone_with_stale_bundle_identifier test_app_instance_rebuilds_clone_with_incompatible_bundle_name +test_app_instance_flag_launches_parallel_profile +test_app_instance_alias_still_launches_parallel_profile +test_app_rebuild_flag_requires_instance test_doctor_skips_status_when_cli_missing test_doctor_reports_desktop_and_cli_when_present test_init_creates_private_profile_home_without_codex