Skip to content
Closed
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
113 changes: 111 additions & 2 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -110,20 +110,129 @@ jobs:
if-no-files-found: error
retention-days: 7

package-standalone:
needs: validate-dispatch
strategy:
fail-fast: false
matrix:
include:
- os: ubuntu-latest
target: bun-linux-x64
smoke: true
- os: macos-latest
target: bun-darwin-arm64
smoke: true
- os: macos-latest
target: bun-darwin-x64
smoke: false
- os: windows-latest
target: bun-windows-x64
smoke: true
- os: ubuntu-latest
target: bun-linux-arm64
smoke: false
runs-on: ${{ matrix.os }}
timeout-minutes: 25
permissions:
contents: read
steps:
- name: Checkout
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7
with:
persist-credentials: false

- name: Setup project Bun
uses: ./.github/actions/setup-project-bun

- name: Install dependencies
run: bun install --frozen-lockfile

- name: Build dashboard
run: bun run build:gui

- name: Build standalone binary
run: bun run build:standalone --target ${{ matrix.target }}

- name: Smoke test standalone binary
if: matrix.smoke && runner.os != 'Windows'
shell: bash
run: |
set -euo pipefail
binary="dist/standalone/${{ matrix.target }}/ocx"
"$binary" --version
OPENCODEX_HOME="$RUNNER_TEMP/ocx-home" "$binary" start --port 10177 >"$RUNNER_TEMP/ocx.log" 2>&1 &
pid=$!
trap 'kill "$pid" 2>/dev/null || true' EXIT
for _ in $(seq 1 30); do curl -fsS http://127.0.0.1:10177/healthz && break || sleep 1; done
curl -fsS http://127.0.0.1:10177/healthz
test "$(curl -sS -o /dev/null -w '%{http_code}' http://127.0.0.1:10177/)" = 200

- name: Smoke test standalone binary (Windows)
if: matrix.smoke && runner.os == 'Windows'
shell: pwsh
run: |
$binary = "dist/standalone/${{ matrix.target }}/ocx.exe"
& $binary --version
$env:OPENCODEX_HOME = Join-Path $env:RUNNER_TEMP "ocx-home"
$process = Start-Process -FilePath $binary -ArgumentList "start", "--port", "10177" -PassThru
try {
for ($i = 0; $i -lt 30; $i++) {
try { Invoke-WebRequest -UseBasicParsing http://127.0.0.1:10177/healthz | Out-Null; break } catch { Start-Sleep -Seconds 1 }
}
Invoke-WebRequest -UseBasicParsing http://127.0.0.1:10177/healthz | Select-Object -ExpandProperty Content
Invoke-WebRequest -UseBasicParsing http://127.0.0.1:10177/ | Out-Null
} finally { Stop-Process -Id $process.Id -Force -ErrorAction SilentlyContinue }

- name: Archive standalone release
shell: bash
env:
RELEASE_VERSION: ${{ inputs.version }}
STANDALONE_TARGET: ${{ matrix.target }}
run: |
set -euo pipefail
cd "dist/standalone/$STANDALONE_TARGET"
if [[ "$RUNNER_OS" == "Windows" ]]; then
powershell -NoProfile -Command 'Compress-Archive -Path ocx.exe,gui -DestinationPath ("../../ocx-{0}-{1}.zip" -f $env:RELEASE_VERSION,$env:STANDALONE_TARGET) -Force'
else
tar -czf "../../ocx-${RELEASE_VERSION}-${STANDALONE_TARGET}.tar.gz" ocx gui
fi
cd ../../..
if [[ "$RUNNER_OS" == "Windows" ]]; then sha256sum "dist/ocx-${RELEASE_VERSION}-${STANDALONE_TARGET}.zip" > "dist/ocx-${RELEASE_VERSION}-${STANDALONE_TARGET}.sha256"
else sha256sum "dist/ocx-${RELEASE_VERSION}-${STANDALONE_TARGET}.tar.gz" > "dist/ocx-${RELEASE_VERSION}-${STANDALONE_TARGET}.sha256"
Comment on lines +200 to +201

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Generate checksum entries relative to the download directory

In the package-standalone archive step, passing dist/ocx-... to sha256sum records that full relative path inside every sidecar. The attach job later flattens the artifacts into dist/release, changes into that directory, and runs shasum -a 256 -c, so it looks for dist/release/dist/ocx-... and fails before uploading any release assets. This is consistent with shasum --help, which says -c reads sums from the supplied files and generated checksum lines contain the name of each input file; generate the sidecar while inside dist or otherwise record only the archive basename.

Useful? React with 👍 / 👎.

fi

- name: Upload standalone release
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: standalone-${{ matrix.target }}
path: |
dist/ocx-*.tar.gz
dist/ocx-*.zip
dist/ocx-*.sha256
if-no-files-found: error
retention-days: 7

attach-macos:
runs-on: ubuntu-latest
needs: [publish, package-macos]
needs: [publish, package-macos, package-standalone]
if: ${{ inputs.dry-run != true }}
timeout-minutes: 10
permissions:
contents: write
steps:
- name: Download the packaged asset
- name: Download the macOS packaged asset
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
with:
name: macos-release
path: dist/release

- name: Download standalone packaged assets
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
with:
pattern: standalone-*
merge-multiple: true
path: dist/release

- name: Verify the checksum before uploading
run: |
cd dist/release
Expand Down
13 changes: 13 additions & 0 deletions docs-site/src/content/docs/getting-started/installation.md
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,19 @@ ocx --version
opencodex --version
```

## Standalone binary (no npm)

Release downloads also include a standalone `ocx` binary for supported macOS, Linux, and Windows
targets. It includes the Bun runtime and dashboard, so npm, Node, and a separate Bun installation
are not required. Download the archive for your platform, extract it, and run:

```bash
./ocx --version
./ocx start
```

The extracted `gui/dist` directory must stay beside the binary so `GET /` can serve the dashboard.

### Release channels

The stable `latest` channel already includes GPT-5.6 Sol/Terra/Luna catalog support for ChatGPT,
Expand Down
5 changes: 5 additions & 0 deletions docs-site/src/content/docs/getting-started/quickstart.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,11 @@ description: Configure your first provider and route OpenAI Codex through openco

This guide takes you from a fresh install to running Codex against a non-OpenAI model.

## Standalone binary (no npm)

You can also use a release archive containing the `ocx` binary and Bun runtime without npm.
Extract it with its `gui/dist` directory beside the binary, then run `./ocx start`.

## 1. Run the setup wizard

```bash
Expand Down
13 changes: 13 additions & 0 deletions docs-site/src/content/docs/ja/getting-started/installation.md
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,19 @@ ocx --version
opencodex --version
```

## スタンドアロンバイナリ(npm 不要)

リリースには、対応する macOS、Linux、Windows 向けのスタンドアロン `ocx` バイナリも含まれます。
Bun ランタイムとダッシュボードが含まれるため、npm、Node、別途の Bun インストールは必要ありません。
お使いの環境向けのアーカイブをダウンロードして展開し、次のように実行します。

```bash
./ocx --version
./ocx start
```

ダッシュボードを提供するため、展開した `gui/dist` ディレクトリはバイナリの隣に置いたままにしてください。

### 配布チャネル

安定チャネルの `latest` にも ChatGPT、OpenAI API キー、OpenRouter、実験段階の Cursor 経路のための
Expand Down
5 changes: 5 additions & 0 deletions docs-site/src/content/docs/ja/getting-started/quickstart.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,11 @@ description: 最初のプロバイダーを構成し、3 つのコマンドで O

このガイドでは、新規インストールから非 OpenAI モデルに対して Codex を実行するまでを説明します。

## スタンドアロンバイナリ(npm 不要)

npm を使わず、Bun ランタイムを含むリリースアーカイブの `ocx` バイナリも利用できます。
`gui/dist` ディレクトリをバイナリの隣に置いて展開し、`./ocx start` を実行してください。

## 1. セットアップウィザードを実行します

```bash
Expand Down
13 changes: 13 additions & 0 deletions docs-site/src/content/docs/ko/getting-started/installation.md
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,19 @@ ocx --version
opencodex --version
```

## 독립 실행형 바이너리(npm 없음)

릴리스에는 지원되는 macOS, Linux, Windows용 독립 실행형 `ocx` 바이너리도 포함됩니다.
Bun 런타임과 대시보드가 포함되어 있으므로 npm, Node 또는 별도의 Bun 설치가 필요하지 않습니다.
플랫폼에 맞는 아카이브를 다운로드해 압축을 풀고 다음과 같이 실행하세요.

```bash
./ocx --version
./ocx start
```

대시보드를 제공하려면 압축을 푼 `gui/dist` 디렉터리를 바이너리 옆에 그대로 두어야 합니다.

### 배포 채널

안정화 채널인 `latest`에도 ChatGPT, OpenAI API 키, OpenRouter, 실험 단계의 Cursor 경로를 위한
Expand Down
5 changes: 5 additions & 0 deletions docs-site/src/content/docs/ko/getting-started/quickstart.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,11 @@ description: 첫 프로바이더를 설정하고 명령어 세 개로 OpenAI Cod

이 가이드는 새로 설치한 상태에서 OpenAI가 아닌 모델로 Codex를 실행하기까지의 과정을 안내합니다.

## 독립 실행형 바이너리(npm 없음)

npm 없이 Bun 런타임이 포함된 릴리스 아카이브의 `ocx` 바이너리를 사용할 수도 있습니다.
`gui/dist` 디렉터리를 바이너리 옆에 둔 채 압축을 풀고 `./ocx start`를 실행하세요.

## 1. 설정 마법사 실행

```bash
Expand Down
13 changes: 13 additions & 0 deletions docs-site/src/content/docs/ru/getting-started/installation.md
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,19 @@ ocx --version
opencodex --version
```

## Автономный бинарный файл (без npm)

В релиз входят автономные бинарные файлы `ocx` для поддерживаемых macOS, Linux и Windows.
Они содержат рантайм Bun и дашборд, поэтому npm, Node и отдельная установка Bun не нужны.
Скачайте архив для своей платформы, распакуйте его и выполните:

```bash
./ocx --version
./ocx start
```

Чтобы дашборд был доступен, оставьте распакованный каталог `gui/dist` рядом с бинарным файлом.

### Каналы релизов

Стабильный канал `latest` уже включает поддержку каталога GPT-5.6 Sol/Terra/Luna для маршрутов
Expand Down
5 changes: 5 additions & 0 deletions docs-site/src/content/docs/ru/getting-started/quickstart.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,11 @@ description: Настройте первого провайдера и напр

Это руководство проводит от чистой установки до запуска Codex с моделью не от OpenAI.

## Автономный бинарный файл (без npm)

Можно также использовать архив с бинарным файлом `ocx` и рантаймом Bun без npm.
Распакуйте его, оставив каталог `gui/dist` рядом с бинарным файлом, и выполните `./ocx start`.

## 1. Запустите мастер настройки

```bash
Expand Down
12 changes: 12 additions & 0 deletions docs-site/src/content/docs/zh-cn/getting-started/installation.md
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,18 @@ ocx --version
opencodex --version
```

## 独立二进制文件(无需 npm)

发布包还包含适用于 macOS、Linux 和 Windows 的独立 `ocx` 二进制文件。
它内置 Bun 运行时和仪表盘,因此无需安装 npm、Node 或单独的 Bun。下载适合你平台的压缩包,解压后运行:

```bash
./ocx --version
./ocx start
```

为了让仪表盘可用,请将解压后的 `gui/dist` 目录保留在二进制文件旁边。

### 发布渠道

稳定的 `latest` 渠道已经包含 ChatGPT、OpenAI API key、OpenRouter 以及实验性 Cursor 路由所需的
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,11 @@ description: 配置你的第一个 provider,并在三条命令内让 OpenAI Co

本指南将带你从全新安装,一路走到用一个非 OpenAI 模型运行 Codex。

## 独立二进制文件(无需 npm)

你也可以使用包含 Bun 运行时的发布压缩包中的 `ocx`,无需 npm。
解压时将 `gui/dist` 目录保留在二进制文件旁边,然后运行 `./ocx start`。

## 1. 运行设置向导

```bash
Expand Down
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@
"build:remote-workspace-helper": "cargo build --release --locked --manifest-path native/remote-workspace-helper/Cargo.toml",
"test:remote-workspace-helper": "cargo test --locked --manifest-path native/remote-workspace-helper/Cargo.toml",
"build:macos": "bash scripts/build-macos-app.sh",
"build:standalone": "bun scripts/build-standalone.ts",
"package:macos": "bash scripts/package-macos-release.sh",
"test:macos": "swift run --package-path app MenuBarCoreTests && swift run --package-path app MenuBarUITests",
"prepare:package": "bun scripts/prepare-package.ts",
Expand Down
53 changes: 53 additions & 0 deletions scripts/build-standalone.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
import { createHash } from "node:crypto";
import { cpSync, existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
import { join, resolve } from "node:path";

const targets = new Set([
"bun-darwin-arm64",
"bun-darwin-x64",
"bun-windows-x64",
"bun-linux-x64",
"bun-linux-arm64",
]);

function hostTarget(): string {
const platform = process.platform === "darwin" ? "darwin" : process.platform === "win32" ? "windows" : "linux";
const arch = process.arch === "arm64" ? "arm64" : "x64";
return `bun-${platform}-${arch}`;
}

function argumentValue(name: string): string | undefined {
const index = Bun.argv.indexOf(name);
return index >= 0 ? Bun.argv[index + 1] : undefined;
}

const target = argumentValue("--target") ?? hostTarget();
if (!targets.has(target)) {
throw new Error(`Unsupported standalone target: ${target}`);
}

const repoRoot = resolve(import.meta.dir, "..");
const guiDist = join(repoRoot, "gui", "dist");
if (!existsSync(join(guiDist, "index.html"))) {
throw new Error("gui/dist is missing; run `bun run build:gui` first");
}

const output = resolve(argumentValue("--out") ?? join(repoRoot, "dist", "standalone", target));
mkdirSync(output, { recursive: true });
const executable = join(output, target.startsWith("bun-windows-") ? "ocx.exe" : "ocx");
const result = Bun.spawnSync([
process.execPath,
"build",
"--compile",
"--target",
target,
join(repoRoot, "src", "cli", "index.ts"),
"--outfile",
executable,
], { stdout: "inherit", stderr: "inherit" });
if (result.exitCode !== 0) process.exit(result.exitCode);

cpSync(guiDist, join(output, "gui", "dist"), { recursive: true });

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Package the assets required by the Windows tray

The standalone output copies only gui/dist, while ocx tray install reads src/tray/windows-tray.ps1 and the three src/tray/assets/*.ico files from paths derived from import.meta.dir. bun build --compile does not embed files reached only through these dynamic filesystem reads, so those paths do not exist in the extracted Windows archive and the install command stops with “a required file is missing.” Copy or embed these assets and resolve them from the standalone distribution root.

Useful? React with 👍 / 👎.

const digest = createHash("sha256").update(readFileSync(executable)).digest("hex");
writeFileSync(join(output, "SHA256SUMS"), `${digest} ${executable.split(/[\\/]/).pop()}\n`);
console.log(`Built ${executable}`);
3 changes: 3 additions & 0 deletions scripts/test-layout/layout.json
Original file line number Diff line number Diff line change
Expand Up @@ -168,6 +168,9 @@
},
"explicit": {
"macos-build-script.test.ts": "gui",
"standalone-build-script.test.ts": "gui",
"standalone-service.test.ts": "service",
"standalone.test.ts": "lib",
"server-combo-held-response.test.ts": "server",
"key-attribution.test.ts": "usage",
"provider-send-path-import.test.ts": "server",
Expand Down
10 changes: 2 additions & 8 deletions src/cli/help.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,5 @@
import { readFileSync } from "node:fs";
import { dirname, join } from "node:path";
import { fileURLToPath } from "node:url";
import { findCommand } from "./registry";

const repoRoot = dirname(fileURLToPath(new URL("../../package.json", import.meta.url)));
import { packageVersion as readPackageVersion } from "../lib/package-version";

/**
* Version of the `ocx` bundle this process is running from.
Expand All @@ -13,9 +9,7 @@ const repoRoot = dirname(fileURLToPath(new URL("../../package.json", import.meta
* rather than throwing; callers must treat that as "cannot compare", not as a mismatch.
*/
export function packageVersion(): string {
const raw = readFileSync(join(repoRoot, "package.json"), "utf8");
const parsed = JSON.parse(raw) as { version?: unknown };
return typeof parsed.version === "string" ? parsed.version : "unknown";
return readPackageVersion();
}

export function printVersion(): void {
Expand Down
Loading
Loading