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
3 changes: 2 additions & 1 deletion .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -201,7 +201,8 @@ jobs:
"$intel_artifact" \
-output "$universal_artifact"
chmod 755 "$universal_artifact"
xcrun lipo "$universal_artifact" -verify_arch arm64 x86_64
xcrun lipo "$universal_artifact" -verify_arch arm64
xcrun lipo "$universal_artifact" -verify_arch x86_64

- name: Import Developer ID identity
shell: bash
Expand Down
82 changes: 75 additions & 7 deletions Documentation/ExtensionAPI.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ Pass `--json` before the subcommand or directly to a supported command:
fxcodex --json status
fxcodex workspace list --json
fxcodex open work --json
fxcodex open --workspace-id 00000000-0000-0000-0000-000000000001 --json
fxcodex delete work personal --yes --json
```

Expand Down Expand Up @@ -59,8 +60,12 @@ standard output:
}
```

All JSON object property names use lower snake case, including properties in
nested response data. Enum and outcome strings retain their documented values.
All API-owned JSON object property names use lower snake case, including
properties in nested response data. Integration attribute values and
integration-owned dictionaries are opaque JSON: their keys are preserved
exactly. Integrations are encouraged to use lower snake case for their own keys
to keep the complete payload consistent. Enum and outcome strings retain their
documented values.

The machine interface is available for:

Expand All @@ -77,11 +82,67 @@ The machine interface is available for:
- `workspace open` and its root `open` alias
- `workspace delete` and its root `delete` alias
- `workspace erase` and its root `erase` alias
- `integrations attributes get`
- `integrations attributes set`
- `integrations attributes remove`
- `integrations raycast status`
- `integrations raycast install`

JSON mode never presents an interactive prompt. `use` requires a workspace
name. `delete` and `erase` require at least one workspace name and `--yes`.
`open` without a name opens or focuses the current workspace.
`open` without a name opens or focuses the current workspace. Every integration
attribute command requires an explicit integration identifier, and `set` also
requires an explicit value. `integrations raycast install` requires an explicit
`app` or `script-command` component. Script Command installation also requires
`--directory` unless an installed configuration already records the directory.

## Stable workspace identities

Schema 2.0 assigns every workspace a stable lowercase UUID. Workspace names
remain the human-facing command-line identifiers, while IDs let frontends keep
references that survive a rename.

`status` includes `current_workspace_id`. When workspaces are requested, each
workspace record includes `workspace.id` alongside its name and kind. A
frontend that already has an ID can open that workspace without resolving its
current name:

```sh
fxcodex open \
--workspace-id 00000000-0000-0000-0000-000000000001 \
--json
```

The ID and workspace name are mutually exclusive for `open`. Other workspace
mutation commands continue to accept names in 0.2.0.

Integration attributes expose frontend-owned JSON stored in the shared
configuration:

```sh
fxcodex integrations attributes get raycast --path script_commands --json
fxcodex integrations attributes set raycast '{"enabled":true}' --path settings --json
fxcodex integrations attributes remove raycast --path settings --json
```

`get` and `set` return the selected `CodableValue` as the response data.
Syntactically valid JSON passed to `set` preserves its JSON type; other input is
stored as a string. `remove` returns an object containing `integration` and
`path`. An omitted `--path` addresses the integration root.

Raycast installation returns the selected `component`, an `outcome`, and the
resulting component status:

```sh
fxcodex integrations raycast install app --json
fxcodex integrations raycast install script-command \
--directory "$HOME/.config/raycast/script-commands" \
--json
```

Application outcomes are `already-installed`, `installed`, or
`download-opened`. Script Command installation returns `installed` together
with `script_commands`.

`rename` requests the `Codex.app` name and returns a
`CodexApplicationRenameResult`. `rename --undo` requests `ChatGPT.app`. The
Expand Down Expand Up @@ -116,7 +177,14 @@ Set a scoped environment variable to `-1` to exclude that section even when
all sections are enabled. On the command line, use the corresponding
`--no-list-...` flag.

`cli`, `exec`, `uninstall`, and integration installation commands are
intentionally outside the machine API. A frontend should use workspace/status
commands and refresh its state after mutations. Raycast Script Commands remain
an optional integration.
The following commands are intentionally outside the 0.2.0 machine API:

- `cli` and `exec` replace the fxcodex process with Codex CLI, whose session or
output may be interactive.
- The root `uninstall` command removes the current executable.
- `integrations raycast sync` and `integrations raycast uninstall` are
terminal-only maintenance commands in 0.2.0.

A frontend should use the supported workspace and status commands and refresh
its state after mutations. Raycast Script Commands remain an optional
integration.
6 changes: 6 additions & 0 deletions Documentation/Releases.md
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,12 @@ dist/fxcodex-universal-apple-darwin
dist/fxcodex-universal-apple-darwin.sha256
```

The script uses separate per-architecture scratch directories and SwiftPM's
native build engine. This keeps build-tool and macro-plugin executables on the
host architecture while Xcode 27 cross-compiles the Intel product slice. Xcode
27 ships an arm64-only Swift toolchain, so running the compiler under Rosetta
is neither required nor supported.

Override `OUTPUT_PATH` when invoking the script directly to place the binary
elsewhere, including the Raycast extension's `assets/bin` directory. The
universal artifact is the default choice for manual installation and extension
Expand Down
1 change: 1 addition & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ universal:
CONFIGURATION="release" \
MACOS_DEPLOYMENT_TARGET="$(MACOS_DEPLOYMENT_TARGET)" \
OUTPUT_PATH="$(DISTDIR)/$(UNIVERSAL_RELEASE_ARTIFACT)" \
BUILD_SYSTEM="native" \
SWIFT="$(SWIFT)" \
"$(UNIVERSAL_BUILD_SCRIPT)"

Expand Down
8 changes: 8 additions & 0 deletions Package.swift
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,10 @@ let package = Package(
url: "https://github.com/pointfreeco/swift-case-paths.git",
.upToNextMajor(from: "1.9.0")
),
.package(
url: "https://github.com/pointfreeco/swift-parsing.git",
.upToNextMajor(from: "0.15.0")
),
.package(
url: "https://github.com/onmyway133/Promptberry.git",
.upToNextMajor(from: "1.0.0")
Expand Down Expand Up @@ -81,6 +85,10 @@ let package = Package(
name: "CasePaths",
package: "swift-case-paths"
),
.product(
name: "Parsing",
package: "swift-parsing"
),
],
path: "Sources/fxcodex-client"
),
Expand Down
111 changes: 100 additions & 11 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,31 @@ name.

`fxcodex` is not affiliated with or endorsed by OpenAI or Raycast.

## Table of contents

- [How it works](#how-it-works)
- [Requirements](#requirements)
- [Installation](#installation)
- [Brew](#brew)
- [Download a release](#download-a-release)
- [Build from source](#build-from-source)
- [Quick start](#quick-start)
- [Desktop and terminal usage](#desktop-and-terminal-usage)
- [Managing workspaces](#managing-workspaces)
- [App naming](#app-naming)
- [Updates](#updates)
- [Integration attributes](#integration-attributes)
- [Raycast integration](#raycast-integration)
- [Script Commands](#script-commands)
- [Extension](#extension)
- [Machine-readable output](#machine-readable-output)
- [Uninstalling](#uninstalling)
- [Data and privacy](#data-and-privacy)
- [Upgrading from 0.1.x to 0.2.y](#upgrading-from-01x-to-02y)
- [Development](#development)
- [Publishing a release](#publishing-a-release)
- [License](#license)

## How it works

A **workspace** is an isolated Codex profile, not a source-code project or
Expand Down Expand Up @@ -253,6 +278,22 @@ Automatic updates apply only to an external `fxcodex` executable. Applications
that bundle `fxcodex`, such as the companion Raycast extension, manage their
bundled executable separately.

## Integration attributes

Frontends can store integration-owned JSON values in the shared fxcodex
configuration. Read, replace, or remove either an integration root or a nested
attribute selected with `--path`:

```sh
fxcodex integrations attributes get raycast --path script_commands
fxcodex integrations attributes set raycast '{"enabled":true}' --path settings
fxcodex integrations attributes remove raycast --path settings
```

Omitting the integration identifier or value starts guided input in an
interactive terminal. Values that parse as JSON retain their JSON type;
otherwise, `set` stores the input as a string.

## Raycast integration

### Script Commands
Expand All @@ -276,14 +317,17 @@ fxcodex integrations raycast uninstall script-command
By default, installation creates commands for every workspace and records the
integration so commands remain synchronized when workspaces are created,
renamed, erased, or deleted. Use `--current-only` during installation to create
only the current workspace's command, and `--directory <path>` to select the
Raycast Script Commands directory explicitly.
only the command for the workspace that is current at installation time. That
workspace remains selected by its stable ID if it is renamed; newly created or
later-selected workspaces are not added automatically. Reinstall without
`--current-only` to return to automatic all-workspace management. Use
`--directory <path>` to select the Raycast Script Commands directory explicitly.

### Extension

A companion Raycast extension provides workspace navigation, management,
custom icons, executable selection, and preferences. It is prepared separately
for Raycast Store submission after the first CLI release; the Script Commands
for [Raycast Store submission](https://github.com/raycast/extensions/pull/29538); the Script Commands
above are available without it.

## Machine-readable output
Expand All @@ -294,11 +338,20 @@ integrations:
```sh
fxcodex --json status --all
FXCODEX_JSON=1 fxcodex workspace list
fxcodex integrations attributes get raycast --path script_commands --json
fxcodex integrations raycast install script-command \
--directory "$HOME/.config/raycast/script-commands" \
--json
```

Use `--no-json` to override the environment variable. Interactive selection is
disabled when required input is missing in JSON mode, so automation should pass
workspace names and confirmation flags explicitly.
workspace names and confirmation flags explicitly. Attribute commands require
an integration identifier, and `attributes set` also requires a value. JSON
`get` and `set` responses contain the attribute value; `remove` returns the
integration identifier and removed path. API-owned keys use lower snake case;
opaque integration attribute keys are preserved exactly, and integrations are
encouraged to use lower snake case for them.

## Uninstalling

Expand All @@ -325,18 +378,49 @@ delete it manually after the command completes.
```text
~/Library/Application Support/fxcodex/
├── configuration.json
├── instances.json
├── preferences.json
├── runtime.json
├── storage.lock
├── update-state.json
└── workspaces/
└── <workspace-name>/
├── codex-home/
└── user-data/
└── <workspace-id>/
├── workspace.json
├── codex-home/ # managed workspaces only
└── user-data/ # managed workspaces only
```

The `primary` workspace continues to use Codex's normal locations. Managed
workspace directories contain account-specific Codex state; treat them as
private data and do not commit or share them.
`configuration.json` stores the current workspace ID and integration-owned
attributes. `runtime.json` contains transient application-instance records, and
`workspace.json` maps each stable workspace ID to its display name and kind.
Some files are created only after their corresponding feature is used.

The `primary` workspace has metadata under `workspaces/`, but continues to use
Codex's normal data locations. Managed workspace directories contain
account-specific Codex state; treat the complete support directory as private
data and do not commit or share it.

### Upgrading from 0.1.x to 0.2.y

The first 0.2.x command migrates schema 1.0 storage to schema 2.0. In an
interactive terminal, `fxcodex` explains the migration and requests
confirmation. Non-interactive callers proceed automatically because the
migration does not require additional input.

Close every managed Codex workspace before running the first 0.2.x command.
Migration refuses to move a workspace whose recorded Codex process is still
running. A live Codex process retains its original `CODEX_HOME` and user-data
paths and can otherwise recreate the legacy name-based directory after it is
moved.

The migration assigns stable IDs, moves managed workspace directories from
name-based to ID-based paths, converts runtime records and integration
attributes, and validates the new storage before continuing. An interrupted
migration records its progress in `.migration.json` and resumes on the next
invocation.

Schema 2.0 is not readable by fxcodex 0.1.x. Back up
`~/Library/Application Support/fxcodex` before upgrading if you may need to
return to 0.1.x.

## Development

Expand All @@ -349,6 +433,11 @@ make release
make universal
```

`make universal` cross-builds the Intel slice with SwiftPM's native build
engine so macro plugins remain executable on the Apple Silicon build host. It
works with the Swift 6.3 toolchain used by CI and the Swift 6.4 toolchain in
Xcode 27; Rosetta is not required.

The package targets macOS 14 and uses Swift 6 language mode. Run
`fxcodex help <command>` or `fxcodex <command> --help` for the complete CLI
reference.
Expand Down
17 changes: 12 additions & 5 deletions Scripts/build-universal.sh
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ CONFIGURATION="${CONFIGURATION:-release}"
MACOS_DEPLOYMENT_TARGET="${MACOS_DEPLOYMENT_TARGET:-14.0}"
SCRATCH_PATH="${SCRATCH_PATH:-.build/universal}"
OUTPUT_PATH="${OUTPUT_PATH:-dist/${PRODUCT}-universal-apple-darwin}"
BUILD_SYSTEM="${BUILD_SYSTEM:-native}"
SWIFT="${SWIFT:-swift}"
XCRUN="${XCRUN:-xcrun}"

Expand All @@ -23,6 +24,7 @@ usage() {
" MACOS_DEPLOYMENT_TARGET Minimum macOS version (default: 14.0)" \
" SCRATCH_PATH SwiftPM scratch path (default: .build/universal)" \
" OUTPUT_PATH Output executable path" \
" BUILD_SYSTEM SwiftPM build system (default: native)" \
" SWIFT Swift executable (default: swift)" \
" XCRUN xcrun executable (default: xcrun)"
}
Expand Down Expand Up @@ -56,21 +58,25 @@ esac
build_architecture() {
architecture="$1"
triple="${architecture}-apple-macosx${MACOS_DEPLOYMENT_TARGET}"
architecture_scratch_path="$SCRATCH_PATH/$architecture"

printf 'Building %s for %s...\n' "$PRODUCT" "$triple"
"$SWIFT" build \
--package-path "$REPOSITORY_ROOT" \
--scratch-path "$SCRATCH_PATH" \
--scratch-path "$architecture_scratch_path" \
--configuration "$CONFIGURATION" \
--product "$PRODUCT" \
--triple "$triple"
--triple "$triple" \
--build-system "$BUILD_SYSTEM"
}

rm -f "$OUTPUT_PATH" "$OUTPUT_PATH.sha256"

build_architecture arm64
build_architecture x86_64

ARM64_EXECUTABLE="$SCRATCH_PATH/arm64-apple-macosx/$CONFIGURATION/$PRODUCT"
X86_64_EXECUTABLE="$SCRATCH_PATH/x86_64-apple-macosx/$CONFIGURATION/$PRODUCT"
ARM64_EXECUTABLE="$SCRATCH_PATH/arm64/arm64-apple-macosx/$CONFIGURATION/$PRODUCT"
X86_64_EXECUTABLE="$SCRATCH_PATH/x86_64/x86_64-apple-macosx/$CONFIGURATION/$PRODUCT"
OUTPUT_DIRECTORY=$(dirname -- "$OUTPUT_PATH")
OUTPUT_NAME=$(basename -- "$OUTPUT_PATH")

Expand All @@ -91,7 +97,8 @@ mkdir -p "$OUTPUT_DIRECTORY"
"$X86_64_EXECUTABLE" \
-output "$OUTPUT_PATH"
chmod 755 "$OUTPUT_PATH"
"$XCRUN" lipo "$OUTPUT_PATH" -verify_arch arm64 x86_64
"$XCRUN" lipo "$OUTPUT_PATH" -verify_arch arm64
"$XCRUN" lipo "$OUTPUT_PATH" -verify_arch x86_64

(
cd "$OUTPUT_DIRECTORY"
Expand Down
Loading